Snap for 10453563 from d3f1a023c2ee14ab266f6149aff0275b93169bca to mainline-mediaprovider-release

Change-Id: I6917585004116377f83a9c6c75646ffb8206b593
diff --git a/.gitignore b/.gitignore
index c3548f5..56c4dbf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 out
 .idea
 *.iml
+.vscode
diff --git a/Android.bp b/Android.bp
index 5eb9738..c03f937 100644
--- a/Android.bp
+++ b/Android.bp
@@ -44,15 +44,29 @@
     ],
 }
 
+filegroup {
+    name: "contexthub_generic_aidl_hal_core",
+    srcs: [
+        "host/common/preloaded_nanoapp_loader.cc",
+        "host/common/time_syncer.cc",
+        "host/common/config_util.cc",
+        "host/common/log_message_parser.cc",
+        "host/hal_generic/common/permissions_util.cc",
+        "host/hal_generic/common/hal_client_manager.cc",
+        "host/hal_generic/common/multi_client_context_hub_base.cc",
+    ],
+}
+
 cc_library_static {
     name: "chre_client",
-    vendor: true,
+    vendor_available: true,
     export_include_dirs: [
         "host/common/include",
         "platform/shared/include",
         "util/include",
     ],
     srcs: [
+        "host/common/file_stream.cc",
         "host/common/fragmented_load_transaction.cc",
         "host/common/host_protocol_host.cc",
         "host/common/socket_client.cc",
@@ -65,7 +79,7 @@
         "liblog",
         "libutils",
     ],
-    cflags: ["-Wall", "-Werror"],
+    cflags: ["-Wall", "-Werror"]
 }
 
 cc_binary {
@@ -107,6 +121,33 @@
     static_libs: ["chre_client"],
 }
 
+filegroup {
+    name: "st_hal_lpma_handler",
+    srcs: ["host/common/st_hal_lpma_handler.cc"],
+}
+
+cc_binary {
+    name: "chre_aidl_hal_client",
+    vendor: true,
+    local_include_dirs: [
+        "host/common/include",
+        "chre_api/include",
+    ],
+    srcs: [
+        "host/common/file_stream.cc",
+        "host/common/chre_aidl_hal_client.cc",
+    ],
+    shared_libs: [
+        "android.hardware.contexthub-V2-ndk",
+        "libbase",
+        "libbinder_ndk",
+        "libjsoncpp",
+        "liblog",
+        "libutils",
+    ],
+    cflags: ["-Wall", "-Werror", "-fexceptions"],
+}
+
 cc_test {
     name: "audio_stress_test",
     vendor: true,
@@ -168,20 +209,24 @@
     static_libs: ["chre_client"],
 }
 
-cc_binary {
-    name: "[email protected]",
-    defaults: ["hidl_defaults"],
+cc_library_static {
+    name: "[email protected]",
     vendor: true,
-    relative_install_path: "hw",
     srcs: [
         "host/hal_generic/V1_1/generic_context_hub_v1_1.cc",
-        "host/hal_generic/V1_1/service.cc",
         "host/hal_generic/common/context_hub_settings_util.cc",
         "host/hal_generic/common/hal_chre_socket_connection.cc",
         "host/hal_generic/common/permissions_util.cc",
     ],
-    init_rc: ["host/hal_generic/V1_1/[email protected]"],
     cflags: ["-Wall", "-Werror"],
+    export_header_lib_headers: [
+        "[email protected]",
+        "[email protected]",
+    ],
+    export_static_lib_headers: [
+        "chre_client",
+    ],
+    export_include_dirs: ["host/hal_generic/V1_1/"],
     header_libs: [
         "[email protected]",
         "[email protected]",
@@ -196,6 +241,31 @@
         "[email protected]",
     ],
     static_libs: ["chre_client"],
+}
+
+cc_binary {
+    name: "[email protected]",
+    defaults: ["hidl_defaults"],
+    vendor: true,
+    relative_install_path: "hw",
+    srcs: [
+        "host/hal_generic/V1_1/service.cc",
+    ],
+    init_rc: ["host/hal_generic/V1_1/[email protected]"],
+    cflags: ["-Wall", "-Werror"],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libhidlbase",
+        "libutils",
+        "[email protected]",
+        "[email protected]",
+        "[email protected]",
+    ],
+    static_libs: [
+        "[email protected]",
+        "chre_client",
+    ],
     vintf_fragments: ["host/hal_generic/V1_1/[email protected]"],
 }
 
@@ -242,7 +312,7 @@
         "host/common/include",
     ],
     shared_libs: [
-        "android.hardware.contexthub-V1-ndk",
+        "android.hardware.contexthub-V2-ndk",
         "libcutils",
         "libutils",
     ],
@@ -267,7 +337,7 @@
         "host/common/include",
     ],
     static_libs: [
-        "android.hardware.contexthub-V1-ndk",
+        "android.hardware.contexthub-V2-ndk",
         "event_logger",
         "libgmock",
     ],
@@ -288,48 +358,32 @@
     ],
 }
 
-cc_binary {
-    name: "android.hardware.contexthub-service.generic",
-    defaults: ["hidl_defaults"],
+genrule {
+    name: "chre_atoms_log.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/chre_atoms_log.h --module chre --namespace android,chre,Atoms --vendor-proto hardware/google/pixel/pixelstats/pixelatoms.proto",
+    out: [
+        "chre_atoms_log.h",
+    ],
+}
+
+cc_library {
+    name: "chre_atoms_log",
     vendor: true,
-    relative_install_path: "hw",
+    generated_headers: ["chre_atoms_log.h"],
+    export_generated_headers: ["chre_atoms_log.h"],
+}
+
+cc_library {
+    name: "chremetrics-cpp",
+    vendor: true,
+    proto: {
+        type: "lite",
+        export_proto_headers: true,
+    },
     srcs: [
-        "host/hal_generic/aidl/generic_context_hub_aidl.cc",
-        "host/hal_generic/aidl/service.cc",
-        "host/hal_generic/common/hal_chre_socket_connection.cc",
-        "host/hal_generic/common/permissions_util.cc",
+        "core/chre_metrics.proto",
     ],
-    local_include_dirs: [
-        "host/hal_generic/common/",
-        "util/include",
-    ],
-    init_rc: ["host/hal_generic/aidl/android.hardware.contexthub-service.generic.rc"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-DCHRE_MESSAGE_TO_HOST_MAX_SIZE=4000", // Needed to import CHRE APIs.
-        "-DCHRE_HAL_SOCKET_METRICS_ENABLED",
-        "-DCHRE_IS_HOST_BUILD",
-    ],
-    shared_libs: [
-        "android.frameworks.stats-V1-ndk",
-        "libcutils",
-        "liblog",
-        "libprotobuf-cpp-lite",
-        "libutils",
-        "libbase",
-        "libbinder_ndk",
-        "android.hardware.contexthub-V1-ndk",
-        "//hardware/google/pixel:pixelatoms-cpp",
-    ],
-    header_libs: [
-        "chre_api",
-    ],
-    static_libs: [
-        "chre_client",
-        "event_logger",
-    ],
-    vintf_fragments: ["host/hal_generic/aidl/android.hardware.contexthub-service.generic.xml"],
 }
 
 cc_library_headers {
@@ -344,7 +398,7 @@
 
 cc_library_headers {
     name: "chre_flatbuffers",
-    vendor: true,
+    vendor_available: true,
     host_supported: true,
     export_include_dirs: [
         "external/flatbuffers/include",
@@ -389,6 +443,9 @@
         "platform/linux/pal_wwan.cc",
         "platform/linux/platform_log.cc",
         "platform/linux/system_time.cc",
+        "platform/linux/task_util/task.cc",
+        "platform/linux/task_util/task_manager.cc",
+        "util/dynamic_vector_base.cc",
     ],
     export_include_dirs: [
         "platform/shared/include",
@@ -411,6 +468,8 @@
 
 cc_test_host {
     name: "chre_unit_tests",
+    isolated: true,
+    test_suites: ["general-tests"],
     srcs: [
         "core/tests/**/*.cc",
         "pal/tests/**/*_test.cc",
@@ -418,6 +477,7 @@
         "pal/util/wifi_pal_convert.c",
         "pal/util/wifi_scan_cache.c",
         "platform/tests/**/*.cc",
+        "platform/linux/tests/**/*.cc",
         "util/tests/**/*.cc",
     ],
     exclude_srcs: [
@@ -452,11 +512,101 @@
     },
 }
 
+// pw_rpc rules instantiation
+
+cc_defaults {
+    name: "pw_rpc_cflags_chre",
+    cflags: [
+        "-DPW_RPC_USE_GLOBAL_MUTEX=0",
+        "-DPW_RPC_CLIENT_STREAM_END_CALLBACK",
+        "-DPW_RPC_DYNAMIC_ALLOCATION",
+    ],
+}
+
+cc_library_static {
+    name: "pw_rpc_chre",
+    defaults: [
+        "pw_rpc_cflags_chre",
+        "pw_rpc_defaults",
+    ],
+}
+
+cc_library_static {
+    name: "pw_rpc_nanopb_chre",
+    defaults: [
+        "pw_rpc_cflags_chre",
+        "pw_rpc_nanopb_defaults",
+    ],
+    static_libs: [
+        "pw_rpc_raw_chre",
+        "pw_rpc_chre",
+    ],
+    export_static_lib_headers: [
+        "pw_rpc_raw_chre",
+        "pw_rpc_chre",
+    ],
+}
+
+cc_library_static {
+    name: "pw_rpc_raw_chre",
+    defaults: [
+        "pw_rpc_cflags_chre",
+        "pw_rpc_raw_defaults",
+    ],
+    static_libs: [
+        "pw_rpc_chre",
+    ],
+}
+
+genrule {
+    name: "rpc_test_proto_header",
+    defaults: [
+        "pw_rpc_generate_nanopb_proto",
+    ],
+    srcs: ["test/simulation/rpc/rpc_test.proto"],
+    out: [
+        "rpc_test.pb.h",
+    ],
+}
+
+genrule {
+    name: "rpc_test_proto_source",
+    defaults: [
+        "pw_rpc_generate_nanopb_proto",
+    ],
+    srcs: ["test/simulation/rpc/rpc_test.proto"],
+    out: [
+        "rpc_test.pb.c",
+    ],
+
+}
+
+genrule {
+    name: "rpc_test_rpc_header",
+    defaults: [
+        "pw_rpc_generate_nanopb_rpc_header",
+    ],
+    srcs: ["test/simulation/rpc/rpc_test.proto"],
+    out: [
+        "rpc_test.rpc.pb.h",
+    ],
+}
+
 cc_test_host {
     name: "chre_simulation_tests",
+    // TODO(b/232537107): Evaluate if isolated can be turned on
+    isolated: false,
+    test_suites: ["general-tests"],
     srcs: [
         "test/simulation/*.cc",
     ],
+    generated_sources: [
+        "rpc_test_proto_source",
+    ],
+    generated_headers: [
+        "rpc_test_proto_header",
+        "rpc_test_rpc_header",
+    ],
     local_include_dirs: [
         "test/simulation/inc",
         "platform/shared",
@@ -464,9 +614,17 @@
     static_libs: [
         "chre_linux",
         "chre_pal_linux",
+        "libprotobuf-c-nano",
+        "pw_containers",
+        "pw_protobuf",
+        "pw_rpc_nanopb_chre",
+        "pw_rpc_chre",
+        "pw_stream",
+        "pw_varint",
     ],
     defaults: [
         "chre_linux_cflags",
+        "pw_rpc_cflags_chre",
     ],
     sanitize: {
         address: true,
@@ -488,7 +646,7 @@
         "core/event.cc",
         "core/gnss_manager.cc",
         "core/host_comms_manager.cc",
-        "core/host_notifications.cc",
+        "core/host_endpoint_manager.cc",
         "core/init.cc",
         "core/nanoapp.cc",
         "core/sensor_request_manager.cc",
@@ -498,6 +656,7 @@
         "core/sensor_type.cc",
         "core/sensor.cc",
         "core/settings.cc",
+        "core/system_health_monitor.cc",
         "core/timer_pool.cc",
         "core/wifi_request_manager.cc",
         "core/wifi_scan_request.cc",
@@ -517,12 +676,12 @@
         "platform/linux/platform_log.cc",
         "platform/linux/platform_nanoapp.cc",
         "platform/linux/platform_pal.cc",
-        "platform/linux/platform_sensor_type_helpers.cc",
-        "platform/linux/platform_sensor.cc",
         "platform/linux/power_control_manager.cc",
         "platform/linux/system_time.cc",
         "platform/linux/system_timer.cc",
-        "platform/linux/testing/platform_audio.cc",
+        "platform/linux/task_util/task.cc",
+        "platform/linux/task_util/task_manager.cc",
+        "platform/shared/audio_pal/platform_audio.cc",
         "platform/shared/chre_api_audio.cc",
         "platform/shared/chre_api_ble.cc",
         "platform/shared/chre_api_core.cc",
@@ -536,16 +695,16 @@
         "platform/shared/pal_system_api.cc",
         "platform/shared/platform_ble.cc",
         "platform/shared/platform_gnss.cc",
-        "platform/shared/platform_sensor_manager.cc",
         "platform/shared/platform_wifi.cc",
         "platform/shared/system_time.cc",
+        "platform/shared/tracing.cc",
         "platform/shared/version.cc",
+        "platform/shared/sensor_pal/platform_sensor.cc",
+        "platform/shared/sensor_pal/platform_sensor_type_helpers.cc",
+        "platform/shared/sensor_pal/platform_sensor_manager.cc",
         "util/**/*.cc",
     ],
-    // TODO(b/217952682) Exclude pigweed because it can't be built via
-    // Android.bp
     exclude_srcs: [
-        "util/pigweed/*.cc",
         "util/tests/**/*",
     ],
     export_include_dirs: [
@@ -554,9 +713,10 @@
         "core/include",
         "pal/include",
         "pal/util/include",
+        "platform/shared/audio_pal/include",
         "platform/linux/include",
-        "platform/linux/testing/include",
         "platform/shared/include",
+        "platform/shared/sensor_pal/include",
         "platform/include",
         "util/include",
     ],
@@ -565,10 +725,12 @@
     ],
     defaults: [
         "chre_linux_cflags",
+        "pw_rpc_cflags_chre",
     ],
     static_libs: [
         "libgtest",
         "libgmock",
+        "pw_rpc_chre",
     ],
     host_supported: true,
 }
@@ -588,9 +750,116 @@
         "-DCHRE_SENSORS_SUPPORT_ENABLED",
         "-DCHRE_WIFI_SUPPORT_ENABLED",
         "-DCHRE_WIFI_NAN_SUPPORT_ENABLED",
+        "-DCHRE_TEST_WIFI_SCAN_RESULT_TIMEOUT_NS=3000000000",
+        "-DCHRE_TEST_WIFI_RANGING_RESULT_TIMEOUT_NS=3000000000",
+        "-DCHRE_TEST_ASYNC_RESULT_TIMEOUT_NS=3000000000",
+        "-DCHRE_BLE_READ_RSSI_SUPPORT_ENABLED",
     ],
 }
 
 subdirs = [
     "apps/wifi_offload",
 ]
+
+cc_defaults {
+    name: "chre_daemon_common",
+    local_include_dirs: [
+        "external/flatbuffers/include",
+        "host/common/include",
+        "platform/shared/include",
+        "util/include",
+    ],
+    srcs: [
+        "host/common/daemon_base.cc",
+        "host/common/fbs_daemon_base.cc",
+        "host/common/file_stream.cc",
+        "host/common/fragmented_load_transaction.cc",
+        "host/common/host_protocol_host.cc",
+        "host/common/log_message_parser.cc",
+        "host/common/socket_server.cc",
+        "host/common/st_hal_lpma_handler.cc",
+        "platform/shared/host_protocol_common.cc",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder_ndk",
+        "libcutils",
+        "libjsoncpp",
+        "libutils",
+        "liblog",
+    ],
+    static_libs: [
+        "chre_config_util",
+    ]
+}
+
+cc_library_static {
+    name: "chre_config_util",
+    vendor: true,
+    host_supported: true,
+    export_include_dirs: [
+        "host/common/include",
+    ],
+    srcs: [
+        "host/common/config_util.cc",
+    ],
+    shared_libs: [
+        "libjsoncpp",
+        "liblog",
+    ]
+}
+
+cc_binary {
+    name: "chre_daemon_exynos",
+    cpp_std: "c++20",
+    defaults: ["chre_daemon_common"],
+    soc_specific: true,
+    local_include_dirs: [
+        "host/exynos",
+    ],
+    cflags: [ "-DCHRE_LPMA_REQUEST_START_RECOGNITION" ],
+    srcs: [
+        "host/exynos/exynos_daemon.cc",
+        "host/exynos/main.cc",
+    ],
+    static_libs: [
+        "pw_detokenizer",
+        "pw_varint",
+    ],
+    shared_libs: [
+        "libhidlbase",
+        "libpower",
+        "[email protected]",
+    ],
+    header_libs: [
+        "pw_span_headers",
+        "pw_polyfill_headers",
+    ],
+    init_rc: ["host/exynos/chre_daemon_exynos.rc"],
+}
+
+java_library_static {
+    name: "chre_api_test_proto_java_lite",
+    host_supported: true,
+    proto: {
+        type: "lite",
+    },
+    srcs: ["apps/test/common/chre_api_test/rpc/chre_api_test.proto"],
+    sdk_version: "current",
+}
+
+cc_library_static {
+    name: "chre_host_util",
+    vendor_available: true,
+    host_supported: true,
+    export_include_dirs: [
+        "host/common/include",
+    ],
+    srcs: [
+        "host/common/file_stream.cc",
+    ],
+    shared_libs:[
+        "liblog",
+    ],
+    cflags: ["-Wall", "-Werror"]
+}
diff --git a/Android.mk b/Android.mk
index 7e89141..011206f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -52,30 +52,37 @@
 LOCAL_CFLAGS += -DCHRE_DAEMON_LOAD_INTO_SENSORSPD
 endif
 
+MSM_SRC_FILES := \
+    host/common/fbs_daemon_base.cc \
+    host/msm/daemon/fastrpc_daemon.cc \
+    host/msm/daemon/main.cc \
+    host/msm/daemon/generated/chre_slpi_stub.c
+
+MSM_INCLUDES := \
+    system/chre/host/msm/daemon
+
 LOCAL_SRC_FILES := \
     host/common/daemon_base.cc \
+    host/common/config_util.cc \
+    host/common/file_stream.cc \
     host/common/fragmented_load_transaction.cc \
     host/common/host_protocol_host.cc \
     host/common/log_message_parser.cc \
     host/common/socket_server.cc \
     host/common/st_hal_lpma_handler.cc \
-    host/msm/daemon/fastrpc_daemon.cc \
-    host/msm/daemon/main.cc \
-    host/msm/daemon/generated/chre_slpi_stub.c \
     platform/shared/host_protocol_common.cc
 
 LOCAL_C_INCLUDES := \
     external/fastrpc/inc \
     system/chre/external/flatbuffers/include \
     system/chre/host/common/include \
-    system/chre/host/msm/daemon \
     system/chre/platform/shared/include \
     system/chre/platform/slpi/include \
     system/chre/util/include \
     system/libbase/include \
     system/core/libcutils/include \
     system/logging/liblog/include \
-    system/core/libutils/include \
+    system/core/libutils/include
 
 LOCAL_SHARED_LIBRARIES := \
     libjsoncpp \
@@ -87,16 +94,21 @@
     [email protected] \
     libpower \
     libprotobuf-cpp-lite \
-    pixelatoms-cpp \
+    chremetrics-cpp \
+    chre_atoms_log \
     android.frameworks.stats-V1-ndk \
     libbinder_ndk
 
+LOCAL_SRC_FILES += $(MSM_SRC_FILES)
+LOCAL_C_INCLUDES += $(MSM_INCLUDES)
+
 LOCAL_CPPFLAGS += -std=c++20
 LOCAL_CFLAGS += -Wno-sign-compare
 LOCAL_CFLAGS += -Wno-c++11-narrowing
 LOCAL_CFLAGS += -Wno-deprecated-volatile
 PIGWEED_DIR = external/pigweed
 PIGWEED_DIR_RELPATH = ../../$(PIGWEED_DIR)
+LOCAL_CFLAGS += -I$(PIGWEED_DIR)/pw_bytes/public
 LOCAL_CFLAGS += -I$(PIGWEED_DIR)/pw_polyfill/public
 LOCAL_CFLAGS += -I$(PIGWEED_DIR)/pw_polyfill/public_overrides
 LOCAL_CFLAGS += -I$(PIGWEED_DIR)/pw_polyfill/standard_library_public
@@ -117,5 +129,5 @@
 
 include $(BUILD_EXECUTABLE)
 
-endif
-endif
+endif   # CHRE_DAEMON_ENABLED
+endif   # BUILDING_VENDOR_IMAGE
diff --git a/Makefile b/Makefile
index 809ad9c..604c4ac 100644
--- a/Makefile
+++ b/Makefile
@@ -64,7 +64,7 @@
 
 # Optional tokenized logging support.
 ifeq ($(CHRE_TOKENIZED_LOGGING_ENABLED), true)
-COMMON_CFLAGS += -DCHRE_USE_TOKENIZED_LOGGING
+COMMON_CFLAGS += -DCHRE_TOKENIZED_LOGGING_ENABLED
 include $(CHRE_PREFIX)/external/pigweed/pw_tokenizer.mk
 endif
 
@@ -111,6 +111,8 @@
 ifneq ($(CHRE_TARGET_EXTENSION),)
 include $(CHRE_TARGET_EXTENSION)
 endif
+include $(CHRE_PREFIX)/build/variant/aosp_cm4_exynos-embos.mk
+include $(CHRE_PREFIX)/build/variant/aosp_riscv55e03_tinysys.mk
 include $(CHRE_PREFIX)/build/variant/google_arm64_android.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi-uimg.mk
diff --git a/OWNERS b/OWNERS
index 8c9ef60..77c4fde 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,4 @@
 [email protected]
 [email protected]
[email protected]
[email protected]
 [email protected]
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index e99706b..64a49d5 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -10,3 +10,5 @@
                   --config_xml ${REPO_ROOT}/prebuilts/checkstyle/android-style.xml
 
 todo_checker_hook = ${REPO_ROOT}/system/chre/tools/todo_checker.py
+
+run_sim = ${REPO_ROOT}/system/chre/run_sim.sh -b
\ No newline at end of file
diff --git a/apps/audio_stress_test/Makefile b/apps/audio_stress_test/Makefile
index 7bdf99d..12a5984 100644
--- a/apps/audio_stress_test/Makefile
+++ b/apps/audio_stress_test/Makefile
@@ -26,6 +26,7 @@
 # Defines.
 COMMON_CFLAGS += -DCHRE_NANOAPP_DISABLE_BACKCOMPAT
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/audio_stress_test/audio_stress_test.cc b/apps/audio_stress_test/audio_stress_test.cc
index 3176da9..1aa59b4 100644
--- a/apps/audio_stress_test/audio_stress_test.cc
+++ b/apps/audio_stress_test/audio_stress_test.cc
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/audio.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[AudioStress]"
 
diff --git a/apps/audio_world/Makefile b/apps/audio_world/Makefile
index be9e9ce..47eb446 100644
--- a/apps/audio_world/Makefile
+++ b/apps/audio_world/Makefile
@@ -22,10 +22,6 @@
 
 NANOAPP_NAME_STRING = \"Audio\ World\"
 
-# Enable CHRE Overrides ########################################################
-
-CHRE_STD_OVERRIDES_ALLOWED = true
-
 # Common Compiler Flags ########################################################
 
 # Include paths.
@@ -36,6 +32,7 @@
 # Defines.
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
 COMMON_CFLAGS += -DFIXED_POINT
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/audio_world/audio_world.cc b/apps/audio_world/audio_world.cc
index 820a032..686cdec 100644
--- a/apps/audio_world/audio_world.cc
+++ b/apps/audio_world/audio_world.cc
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 #include <cmath>
 
@@ -22,6 +21,7 @@
 #include "chre/util/nanoapp/audio.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 #include "kiss_fftr.h"
 
 #define LOG_TAG "[AudioWorld]"
diff --git a/apps/ble_world/Makefile b/apps/ble_world/Makefile
index d826a4f..89b1476 100644
--- a/apps/ble_world/Makefile
+++ b/apps/ble_world/Makefile
@@ -31,10 +31,15 @@
 # Defines
 COMMON_CFLAGS += -DLOG_TAG=\"[BleWorld]\"
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
+
+# Uncomment this line to test batching
+# COMMON_CFLAGS += -DBLE_WORLD_ENABLE_BATCHING
 
 # Common Source Files ##########################################################
 
 COMMON_SRCS += ble_world.cc
+COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/ble.cc
 
 # Permission declarations ######################################################
 
diff --git a/apps/ble_world/ble_world.cc b/apps/ble_world/ble_world.cc
index 507b583..3b954c5 100644
--- a/apps/ble_world/ble_world.cc
+++ b/apps/ble_world/ble_world.cc
@@ -13,168 +13,255 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <inttypes.h>
 
-#include <cinttypes>
-
-#include "chre.h"
-#include "chre/util/macros.h"
-#include "chre/util/memory.h"
+#include "chre/util/nanoapp/ble.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
-#include "chre/util/unique_ptr.h"
+#include "chre_api/chre.h"
+
+/**
+ * @file
+ *
+ * This nanoapp is designed to continually start and stop BLE scans and verify
+ * that the expected data is delivered. BLE_WORLD_ENABLE_BATCHING can be defined
+ * to test batching and flushing if the nanoapp has the
+ * CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING capability. This will configure
+ * the BLE scans with a batch window and periodically make flush requests to get
+ * batched BLE scan result events.
+ */
 
 #ifdef CHRE_NANOAPP_INTERNAL
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
 
-bool gAsyncResultReceived = false;
-uint32_t gTimerHandle = 0;
+using chre::ble_constants::kNumScanFilters;
 
-//! A fake/unused cookie to pass into the session async and timer request.
-const uint32_t kBleCookie = 0x1337;
-//! The interval in seconds between updates.
-const chreBleScanMode kScanModes[] = {CHRE_BLE_SCAN_MODE_BACKGROUND,
-                                      CHRE_BLE_SCAN_MODE_FOREGROUND,
-                                      CHRE_BLE_SCAN_MODE_AGGRESSIVE};
+constexpr int8_t kDataTypeServiceData = 0x16;
 
-enum ScanRequestType {
-  NO_FILTER = 0,
-  SERVICE_DATA_16 = 1,
-  STOP_SCAN = 2,
-};
+#ifdef BLE_WORLD_ENABLE_BATCHING
+//! A timer handle to request the BLE flush.
+uint32_t gFlushTimerHandle = 0;
+//! The period to which to make the BLE flush request.
+uint64_t gFlushPeriodNs = 7 * chre::kOneSecondInNanoseconds;
+#endif  // BLE_WORLD_ENABLE_BATCHING
 
-chreBleScanFilter *getBleScanFilter(ScanRequestType &scanRequestType) {
-  chre::UniquePtr<chreBleScanFilter> filter =
-      chre::MakeUniqueZeroFill<chreBleScanFilter>();
-  filter->rssiThreshold = CHRE_BLE_RSSI_THRESHOLD_NONE;
-  filter->scanFilterCount = 1;
-  chre::UniquePtr<chreBleGenericFilter> scanFilter =
-      chre::MakeUniqueZeroFill<chreBleGenericFilter>();
-  switch (scanRequestType) {
-    case NO_FILTER:
-      filter = nullptr;
-      scanRequestType = SERVICE_DATA_16;
-      break;
-    case SERVICE_DATA_16:
-      scanFilter->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16;
-      scanFilter->len = 2;
-      filter->scanFilters = scanFilter.release();
-      scanRequestType = STOP_SCAN;
-      break;
-    case STOP_SCAN:
-      break;
-  }
-  return filter.release();
+//! Report delay for BLE scans.
+uint32_t gBleBatchDurationMs = 0;
+//! A timer handle to toggle enable/disable BLE scans.
+uint32_t gEnableDisableTimerHandle = 0;
+//! The period at which to enable/disable BLE scans.
+uint64_t gEnableDisablePeriodNs = 10 * chre::kOneSecondInNanoseconds;
+//! True if BLE scans are currently enabled
+bool gBleEnabled = false;
+
+//! A timer handle to poll for RSSI.
+uint32_t gReadRssiTimerHandle = CHRE_TIMER_INVALID;
+//! A hardcoded connection handle on which the RSSI will be read
+//! On the Broadcom controllers used by Pixel, if a connection is made
+//! immediately after startup, it will be on this handle.
+uint16_t gReadRssiConnectionHandle = 0x40;
+//! The period at which to read RSSI of kConnectionHandle.
+uint64_t gReadRssiPeriodNs = 3 * chre::kOneSecondInNanoseconds;
+
+bool enableBleScans() {
+  struct chreBleScanFilter filter;
+  chreBleGenericFilter genericFilters[kNumScanFilters];
+  chre::createBleScanFilterForKnownBeacons(filter, genericFilters,
+                                           kNumScanFilters);
+  return chreBleStartScanAsync(CHRE_BLE_SCAN_MODE_BACKGROUND,
+                               gBleBatchDurationMs, &filter);
 }
 
-void makeBleScanRequest() {
-  static uint8_t scanModeIndex = 0;
-  static ScanRequestType scanRequestType = NO_FILTER;
-  if (scanRequestType != STOP_SCAN) {
-    chreBleScanMode mode = kScanModes[scanModeIndex];
-    uint32_t reportDelayMs = 0;
-    chreBleScanFilter *filter = getBleScanFilter(scanRequestType);
-    LOGI("Sending BLE start scan request to PAL with parameters:");
-    LOGI("  mode=%" PRIu8, kScanModes[scanModeIndex]);
-    LOGI("  reportDelayMs=%" PRIu32, reportDelayMs);
-    if (filter != nullptr) {
-      LOGI("  rssiThreshold=%" PRIu32, filter->rssiThreshold);
-      LOGI("  scanFilterType=%" PRIx8, filter->scanFilters[0].type);
-      LOGI("  scanFilterLen=%" PRIu8, filter->scanFilters[0].len);
-      LOGI("  scanFilterData=%s", filter->scanFilters[0].data);
-      LOGI("  scanFilterDataMask=%s", filter->scanFilters[0].dataMask);
+bool disableBleScans() {
+  return chreBleStopScanAsync();
+}
+
+bool nanoappStart() {
+  LOGI("BLE world from version 0x%08" PRIx32, chreGetVersion());
+  uint32_t capabilities = chreBleGetCapabilities();
+  LOGI("Got BLE capabilities 0x%" PRIx32, capabilities);
+#ifdef BLE_WORLD_ENABLE_BATCHING
+  bool batchingAvailable =
+      ((capabilities & CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING) != 0);
+  if (!batchingAvailable) {
+    LOGE("BLE scan result batching is unavailable");
+  } else {
+    gBleBatchDurationMs = 5000;
+    LOGI("BLE batching enabled");
+  }
+#endif  // BLE_WORLD_ENABLE_BATCHING
+  bool success = enableBleScans();
+  if (!success) {
+    LOGE("Failed to send BLE start scan request");
+  } else {
+    gEnableDisableTimerHandle =
+        chreTimerSet(gEnableDisablePeriodNs, &gEnableDisableTimerHandle,
+                     false /* oneShot */);
+    if (gEnableDisableTimerHandle == CHRE_TIMER_INVALID) {
+      LOGE("Could not set enable/disable timer");
     }
-    if (chreBleStartScanAsync(mode, 0, nullptr)) {
-      LOGI("BLE start scan request sent to PAL");
-    } else {
-      LOGE("Error sending BLE start scan request sent to PAL");
-    }
-    if (filter != nullptr) {
-      if (filter->scanFilters != nullptr) {
-        chre::memoryFree(
-            const_cast<chreBleGenericFilter *>(filter->scanFilters));
+
+#ifdef BLE_WORLD_ENABLE_BATCHING
+    if (batchingAvailable) {
+      gFlushTimerHandle =
+          chreTimerSet(gFlushPeriodNs, &gFlushTimerHandle, false /* oneShot */);
+      if (gFlushTimerHandle == CHRE_TIMER_INVALID) {
+        LOGE("Could not set flush timer");
       }
-      chre::memoryFree(filter);
+    }
+#endif  // BLE_WORLD_ENABLE_BATCHING
+  }
+
+  if (capabilities & CHRE_BLE_CAPABILITIES_READ_RSSI) {
+    gReadRssiPeriodNs = chreTimerSet(gReadRssiPeriodNs, &gReadRssiTimerHandle,
+                                     false /* oneShot */);
+    if (gReadRssiTimerHandle == CHRE_TIMER_INVALID) {
+      LOGE("Could not set RSSI timer");
     }
   } else {
-    if (chreBleStopScanAsync()) {
-      LOGI("BLE stop scan request sent to PAL");
-    } else {
-      LOGE("Error sending BLE stop scan request sent to PAL");
-    }
-    scanRequestType = NO_FILTER;
-    scanModeIndex = (scanModeIndex + 1) % ARRAY_SIZE(kScanModes);
+    LOGW(
+        "Skipping RSSI read since CHRE_BLE_CAPABILITIES_READ_RSSI not "
+        "supported");
   }
-  gTimerHandle = chreTimerSet(CHRE_ASYNC_RESULT_TIMEOUT_NS, /* 5 sec */
-                              &kBleCookie, true /* oneShot */);
+
+  return true;
 }
 
-void handleAdvertismentEvent(const chreBleAdvertisementEvent *event) {
-  for (uint8_t i = 0; i < event->numReports; i++) {
-    LOGI("BLE Report %" PRIu8, i + 1);
-    LOGI("Scan data:");
-    const uint8_t *data = event->reports[i].data;
-    for (uint8_t j = 0; j < event->reports[i].dataLength; j++) {
-      LOGI("  %" PRIx8, data[j]);
+void parseAdData(const uint8_t *data, uint16_t size) {
+  for (uint16_t i = 0; i < size;) {
+    // First byte has the dvertisement data length.
+    uint16_t ad_data_length = data[i];
+    // Early termination with zero length advertisement.
+    if (ad_data_length == 0) break;
+    // Second byte has advertisement data type.
+    // Only retrieves service data for Nearby.
+    if (data[++i] == kDataTypeServiceData) {
+      // First two bytes of the service data are service data UUID in little
+      // endian.
+      uint16_t uuid = static_cast<uint16_t>(data[i + 1] + (data[i + 2] << 8));
+      LOGD("Service Data UUID: %" PRIx16, uuid);
     }
+    // Moves to next advertisement.
+    i += ad_data_length;
   }
 }
 
 void handleAsyncResultEvent(const chreAsyncResult *result) {
-  gAsyncResultReceived = true;
   const char *requestType =
       result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN ? "start"
                                                               : "stop";
   if (result->success) {
     LOGI("BLE %s scan success", requestType);
+    gBleEnabled = (result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN);
   } else {
     LOGE("BLE %s scan failure: %" PRIu8, requestType, result->errorCode);
   }
 }
 
-void handleTimerEvent(const void *eventData) {
-  static uint32_t timerCount = 1;
-  if (eventData == &kBleCookie) {
-    LOGI("BLE timer event received, count %" PRIu32, timerCount++);
-    if (!gAsyncResultReceived) {
-      LOGE("BLE async result not received");
-    }
-    gAsyncResultReceived = false;
-    makeBleScanRequest();
-  } else {
-    LOGE("Invalid timer cookie");
+void handleAdvertismentEvent(const chreBleAdvertisementEvent *event) {
+  for (uint8_t i = 0; i < event->numReports; i++) {
+    LOGD("BLE Report %" PRIu32, static_cast<uint32_t>(i + 1));
+    LOGD("Event type and data status: 0x%" PRIx8,
+         event->reports[i].eventTypeAndDataStatus);
+    LOGD("Timestamp: %" PRIu64 " ms",
+         event->reports[i].timestamp / chre::kOneMillisecondInNanoseconds);
+    parseAdData(event->reports[i].data, event->reports[i].dataLength);
   }
 }
 
-bool nanoappStart(void) {
-  LOGI("nanoapp started");
-  makeBleScanRequest();
-  return true;
+void handleTimerEvent(const void *cookie) {
+  if (cookie == &gEnableDisableTimerHandle) {
+    bool success = false;
+    if (!gBleEnabled) {
+      success = enableBleScans();
+    } else {
+      success = disableBleScans();
+    }
+    if (!success) {
+      LOGE("Failed to send BLE %s scan request",
+           !gBleEnabled ? "start" : "stop");
+    }
+#ifdef BLE_WORLD_ENABLE_BATCHING
+  } else if (cookie == &gFlushTimerHandle) {
+    if (gBleEnabled) {
+      if (!chreBleFlushAsync(nullptr /* cookie */)) {
+        LOGE("Could not send flush request");
+      } else {
+        LOGI("Successfully sent flush request at time %" PRIu64 " ms",
+             chreGetTime() / chre::kOneMillisecondInNanoseconds);
+      }
+    }
+#endif  // BLE_WORLD_ENABLE_BATCHING
+  } else if (cookie == &gReadRssiTimerHandle) {
+    bool success = chreBleReadRssiAsync(gReadRssiConnectionHandle, nullptr);
+    LOGI("Reading RSSI for handle 0x%" PRIx16 ", status=%d",
+         gReadRssiConnectionHandle, success);
+  } else {
+    LOGE("Received unknown timer cookie %p", cookie);
+  }
 }
 
-void nanoappEnd(void) {
-  if (!chreBleStopScanAsync()) {
+void handleRssiEvent(const chreBleReadRssiEvent *event) {
+  LOGI("Received RSSI Read with status 0x%" PRIx8 " and rssi %" PRIi8,
+       event->result.errorCode, event->rssi);
+}
+
+void handleBatchCompleteEvent(const chreBatchCompleteEvent *event) {
+  LOGI("Received Batch complete event with event type %" PRIu16,
+       event->eventType);
+}
+
+void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                        const void *eventData) {
+  LOGI("Received event 0x%" PRIx16 " from 0x%" PRIx32 " at time %" PRIu64 " ms",
+       eventType, senderInstanceId,
+       chreGetTime() / chre::kOneMillisecondInNanoseconds);
+  switch (eventType) {
+    case CHRE_EVENT_BLE_ADVERTISEMENT:
+      handleAdvertismentEvent(
+          static_cast<const chreBleAdvertisementEvent *>(eventData));
+      break;
+    case CHRE_EVENT_BLE_ASYNC_RESULT:
+      handleAsyncResultEvent(static_cast<const chreAsyncResult *>(eventData));
+      break;
+    case CHRE_EVENT_TIMER:
+      handleTimerEvent(eventData);
+      break;
+    case CHRE_EVENT_BLE_FLUSH_COMPLETE:
+      LOGI("Received flush complete");
+      break;
+    case CHRE_EVENT_BLE_RSSI_READ:
+      handleRssiEvent(static_cast<const chreBleReadRssiEvent *>(eventData));
+      break;
+    case CHRE_EVENT_BLE_BATCH_COMPLETE:
+      handleBatchCompleteEvent(
+          static_cast<const chreBatchCompleteEvent *>(eventData));
+    default:
+      LOGW("Unhandled event type %" PRIu16, eventType);
+      break;
+  }
+}
+
+void nanoappEnd() {
+  if (gBleEnabled && !chreBleStopScanAsync()) {
     LOGE("Error sending BLE stop scan request sent to PAL");
   }
-  if (!chreTimerCancel(gTimerHandle)) {
-    LOGE("Error canceling timer");
+  if (!chreTimerCancel(gEnableDisableTimerHandle)) {
+    LOGE("Error canceling BLE scan timer");
+  }
+#ifdef BLE_WORLD_ENABLE_BATCHING
+  if (!chreTimerCancel(gFlushTimerHandle)) {
+    LOGE("Error canceling BLE flush timer");
+  }
+#endif
+  if (!chreTimerCancel(gReadRssiTimerHandle)) {
+    LOGE("Error canceling RSSI read timer");
   }
   LOGI("nanoapp stopped");
 }
 
-void nanoappHandleEvent(uint32_t /* sender_instance_id */, uint16_t event_type,
-                        const void *event_data) {
-  if (event_type == CHRE_EVENT_BLE_ADVERTISEMENT) {
-    handleAdvertismentEvent(
-        static_cast<const chreBleAdvertisementEvent *>(event_data));
-  } else if (event_type == CHRE_EVENT_BLE_ASYNC_RESULT) {
-    handleAsyncResultEvent(static_cast<const chreAsyncResult *>(event_data));
-  } else if (event_type == CHRE_EVENT_TIMER) {
-    handleTimerEvent(event_data);
-  }
-}
-
 #ifdef CHRE_NANOAPP_INTERNAL
 }  // anonymous namespace
 }  // namespace chre
diff --git a/apps/debug_dump_world/Makefile b/apps/debug_dump_world/Makefile
index 9f89b1a..1aeea6c 100644
--- a/apps/debug_dump_world/Makefile
+++ b/apps/debug_dump_world/Makefile
@@ -26,6 +26,7 @@
 
 COMMON_CFLAGS += -I.
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/debug_dump_world/debug_dump_world.cc b/apps/debug_dump_world/debug_dump_world.cc
index 7e50e33..ab9a031 100644
--- a/apps/debug_dump_world/debug_dump_world.cc
+++ b/apps/debug_dump_world/debug_dump_world.cc
@@ -16,11 +16,10 @@
 
 #include <cinttypes>
 
-#include <chre.h>
-
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[DebugDumpWorld]"
 
diff --git a/apps/gnss_world/Makefile b/apps/gnss_world/Makefile
index af43b7e..45c309e 100644
--- a/apps/gnss_world/Makefile
+++ b/apps/gnss_world/Makefile
@@ -30,6 +30,7 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/gnss_world/gnss_world.cc b/apps/gnss_world/gnss_world.cc
index 02d65a1..2539230 100644
--- a/apps/gnss_world/gnss_world.cc
+++ b/apps/gnss_world/gnss_world.cc
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[GnssWorld]"
 
diff --git a/apps/hello_world/Makefile b/apps/hello_world/Makefile
index 981d01c..a19732c 100644
--- a/apps/hello_world/Makefile
+++ b/apps/hello_world/Makefile
@@ -25,6 +25,8 @@
 # Common Compiler Flags ########################################################
 
 COMMON_CFLAGS += -I.
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/hello_world/hello_world.cc b/apps/hello_world/hello_world.cc
index c702223..205d612 100644
--- a/apps/hello_world/hello_world.cc
+++ b/apps/hello_world/hello_world.cc
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <inttypes.h>
 
+#include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[HelloWorld]"
+
 /**
  * @file
  * A very basic example nanoapp that just logs activity in its entry points.
@@ -31,20 +35,18 @@
 #endif  // CHRE_NANOAPP_INTERNAL
 
 bool nanoappStart() {
-  chreLog(CHRE_LOG_INFO, "Hello, world from version 0x%08" PRIx32,
-          chreGetVersion());
+  LOGI("Hello, world from version 0x%08" PRIx32, chreGetVersion());
   return true;
 }
 
 void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
                         const void * /*eventData*/) {
-  chreLog(CHRE_LOG_INFO,
-          "Received event 0x%" PRIx16 " from 0x%" PRIx32 " at time %" PRIu64,
-          eventType, senderInstanceId, chreGetTime());
+  LOGI("Received event 0x%" PRIx16 " from 0x%" PRIx32 " at time %" PRIu64,
+       eventType, senderInstanceId, chreGetTime());
 }
 
 void nanoappEnd() {
-  chreLog(CHRE_LOG_INFO, "Goodbye, world!");
+  LOGI("Goodbye, world!");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
diff --git a/apps/host_awake_world/Makefile b/apps/host_awake_world/Makefile
index fae2466..d00faa6 100644
--- a/apps/host_awake_world/Makefile
+++ b/apps/host_awake_world/Makefile
@@ -28,6 +28,7 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/host_awake_world/host_awake_world.cc b/apps/host_awake_world/host_awake_world.cc
index 5977715..4b5a77b 100644
--- a/apps/host_awake_world/host_awake_world.cc
+++ b/apps/host_awake_world/host_awake_world.cc
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-#include <chre.h>
-
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[HostAwakeWorld]"
 
diff --git a/apps/message_world/Makefile b/apps/message_world/Makefile
index 5911044..9c69b00 100644
--- a/apps/message_world/Makefile
+++ b/apps/message_world/Makefile
@@ -5,13 +5,22 @@
 # Environment Checks ###########################################################
 
 ifeq ($(CHRE_PREFIX),)
-$(error "The CHRE_PREFIX environment variable must be set to a path to the \
-         CHRE project root. Example: export CHRE_PREFIX=$$HOME/chre")
+ifneq ($(ANDROID_BUILD_TOP),)
+CHRE_PREFIX = $(ANDROID_BUILD_TOP)/system/chre
+else
+$(error "You must run 'lunch' to setup ANDROID_BUILD_TOP, or explicitly define \
+         the CHRE_PREFIX environment variable to point to the CHRE root \
+         directory.")
+endif
 endif
 
 # Nanoapp Configuration ########################################################
 
 NANOAPP_NAME = message_world
+NANOAPP_ID = 0x0123456789000003
+NANOAPP_VERSION = 0x00000001
+
+NANOAPP_NAME_STRING = \"Message\ World\"
 
 # Common Compiler Flags ########################################################
 
@@ -21,6 +30,10 @@
 
 COMMON_SRCS += message_world.cc
 
+# Defines.
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
+
 # Makefile Includes ############################################################
 
 include $(CHRE_PREFIX)/build/nanoapp/app.mk
diff --git a/apps/message_world/message_world.cc b/apps/message_world/message_world.cc
index f844f1a..17c910e 100644
--- a/apps/message_world/message_world.cc
+++ b/apps/message_world/message_world.cc
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[MsgWorld]"
 
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/common/math/macros.h b/apps/nearby/location/lbs/contexthub/nanoapps/common/math/macros.h
new file mode 100644
index 0000000..43cf738
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/common/math/macros.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+// This file contains frequently used constants and helper macros.
+
+#include <stdint.h>
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_MACROS_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_MACROS_H_
+
+// Constants.
+#define NANO_PI (3.14159265359f)
+#define INVALID_TEMPERATURE_CELSIUS (-274.0f)
+
+// Common math operations.
+#define NANO_ABS(x) ((x) > 0 ? (x) : -(x))
+#define NANO_MAX(a, b) ((a) > (b)) ? (a) : (b)
+#define NANO_MIN(a, b) ((a) < (b)) ? (a) : (b)
+#define SIGMOID(x) (1 / (1 + expf(-x)))
+
+// Floor macro implementation for platforms that do not supply the standard
+// floorf() math function.
+#define NANO_FLOOR(x) ((int)(x) - ((x) < (int)(x)))  // NOLINT
+
+// Timestamp conversion macros.
+#ifdef __cplusplus
+#define MSEC_TO_NANOS(x) (static_cast<uint64_t>((x)*UINT64_C(1000000)))
+#else
+#define MSEC_TO_NANOS(x) ((uint64_t)((x)*UINT64_C(1000000)))  // NOLINT
+#endif
+
+#define SEC_TO_NANOS(x) MSEC_TO_NANOS((x)*UINT64_C(1000))
+#define MIN_TO_NANOS(x) SEC_TO_NANOS((x)*UINT64_C(60))
+#define HRS_TO_NANOS(x) MIN_TO_NANOS((x)*UINT64_C(60))
+#define DAYS_TO_NANOS(x) HRS_TO_NANOS((x)*UINT64_C(24))
+
+// Sample rate to period conversion.
+#ifdef __cplusplus
+#define HZ_TO_PERIOD_NANOS(hz) \
+  (SEC_TO_NANOS(1024) / (static_cast<uint64_t>((hz)*1024)))
+#else
+#define HZ_TO_PERIOD_NANOS(hz) \
+  (SEC_TO_NANOS(1024) / ((uint64_t)((hz)*1024)))  // NOLINT
+#endif
+
+// Unit conversion: nanoseconds to seconds.
+#define NANOS_TO_SEC (1.0e-9f)
+
+// Unit conversion: milli-degrees to radians.
+#define MDEG_TO_RAD (NANO_PI / 180.0e3f)
+
+// Unit conversion: radians to milli-degrees.
+#define RAD_TO_MDEG (180.0e3f / NANO_PI)
+
+// Time check helper macro that returns true if:
+//    i.  't1' is equal to or exceeds 't2' plus 't_delta'.
+//    ii. Or, a negative timestamp delta occurred since,
+//        't1' should always >= 't2'. This prevents potential lockout conditions
+//        if the timer count 't1' rolls over or an erroneously large
+//        timestamp is passed through.
+#define NANO_TIMER_CHECK_T1_GEQUAL_T2_PLUS_DELTA(t1, t2, t_delta) \
+  (((t1) >= (t2) + (t_delta)) || ((t1) < (t2)))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This conversion function may be necessary for embedded hardware that can't
+// cast a uint64_t to a float directly. This conversion function was taken from:
+// [android]//device/google/contexthub/firmware/os/core/floatRt.c
+static inline float floatFromUint64(uint64_t v) {
+  uint32_t hi = v >> 32;
+  uint32_t lo = (uint32_t)v;
+
+  if (!hi) {  // This is very fast for cases where 'v' fits into a uint32_t.
+    return (float)lo;
+  } else {
+    return ((float)hi) * 4294967296.0f + (float)lo;
+  }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_COMMON_MATH_MACROS_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/README b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/README
new file mode 100644
index 0000000..24c72cf
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/README
@@ -0,0 +1,20 @@
+Nearby Offload supports applications to discover and interact with local
+devices. Nearby Offload provides always-on discovery with low-latency and
+minimal power consumption.
+
+Currently, Nearby Offload supports the applications below.
+
+* Fast Pair. Scan pheripherials in the background and helps Fast Pair to pop up
+  the discovery notification as soon as a user turns on the screen.
+
+* Nearby Presence. Always-on scan for Nearby Presence BLE advertisements.
+  Applications can use this feature through Nearby Presence AOSP system API.
+
+* Nearby Offload Extension. Nearby Offload can also be extended by OEMs to
+  define their customized BLE discovery by plugging their implementation into
+  the offload extension library.
+
+Nearby Offload NanoApp is released to production by Google to support all the
+applications above (Fast Pair, Presence, as well as Offload Extension).
+
+
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/adv_report_cache.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/adv_report_cache.cc
new file mode 100644
index 0000000..e766639
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/adv_report_cache.cc
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/adv_report_cache.h"
+
+#include <utility>
+
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][ADV_CACHE]"
+
+namespace nearby {
+
+void AdvReportCache::Clear() {
+  // Release all resources.
+  for (const auto &report : cache_reports_) {
+    if (report.dataLength == 0) {
+      continue;
+    }
+    chreHeapFree(const_cast<uint8_t *>(report.data));
+  }
+  cache_reports_.clear();
+}
+
+void AdvReportCache::Refresh() {
+  if (cache_expire_nanosec_ == kMaxExpireTimeNanoSec) {
+    return;
+  }
+
+  uint64_t current_time = chreGetTime();
+  size_t index = 0;
+  while (index < cache_reports_.size()) {
+    if (current_time - cache_reports_[index].timestamp >
+        cache_expire_nanosec_) {
+      // TODO(b/285043291): Refactor cache element by wrapper struct/class
+      // which deallocates data in its destructor.
+      if (cache_reports_[index].data != nullptr) {
+        chreHeapFree(const_cast<uint8_t *>(cache_reports_[index].data));
+      }
+      // Don't require to increase index because all elements after the indexed
+      // one are moved forward one position.
+      cache_reports_.erase(index);
+    } else {
+      ++index;
+    }
+  }
+}
+
+void AdvReportCache::Push(const chreBleAdvertisingReport &event_report) {
+#ifdef NEARBY_PROFILE
+  ashProfileBegin(&profile_data_);
+#endif
+  bool is_duplicate = false;
+  for (auto &cache_report : cache_reports_) {
+    if (cache_report.addressType == event_report.addressType &&
+        memcmp(cache_report.address, event_report.address,
+               CHRE_BLE_ADDRESS_LEN) == 0 &&
+        cache_report.dataLength == event_report.dataLength &&
+        memcmp(cache_report.data, event_report.data, cache_report.dataLength) ==
+            0) {
+      // Updates RSSI by max value in the duplicated report.
+      if (cache_report.rssi == CHRE_BLE_RSSI_NONE ||
+          (event_report.rssi != CHRE_BLE_RSSI_NONE &&
+           event_report.rssi > cache_report.rssi)) {
+        cache_report.rssi = event_report.rssi;
+      }
+      // Updates timestamp to latest in the duplicated report.
+      if (event_report.timestamp > cache_report.timestamp) {
+        cache_report.timestamp = event_report.timestamp;
+      }
+      is_duplicate = true;
+      break;
+    }
+  }
+  if (!is_duplicate) {
+    LOGD("Adds to advertising reports cache");
+    // Copies advertise report by value.
+    chreBleAdvertisingReport new_report = event_report;
+    // Allocates advertise data and copy it.
+    uint16_t dataLength = event_report.dataLength;
+    uint8_t *data = nullptr;
+    if (dataLength > 0) {
+      data =
+          static_cast<uint8_t *>(chreHeapAlloc(sizeof(uint8_t) * dataLength));
+      if (data == nullptr) {
+        LOGE("Memory allocation failed!");
+        return;
+      }
+      memcpy(data, event_report.data, dataLength);
+      new_report.data = data;
+    }
+    if (!cache_reports_.push_back(std::move(new_report))) {
+      LOGE("Pushes advertise report failed!");
+      if (data != nullptr) {
+        chreHeapFree(const_cast<uint8_t *>(data));
+      }
+    }
+  } else {
+    LOGD("Duplicated report in advertising reports cache");
+  }
+#ifdef NEARBY_PROFILE
+  ashProfileEnd(&profile_data_, nullptr /* output */);
+#endif
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/adv_report_cache.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/adv_report_cache.h
new file mode 100644
index 0000000..5561483
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/adv_report_cache.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_ADV_REPORT_CACHE_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_ADV_REPORT_CACHE_H_
+
+#include "third_party/contexthub/chre/util/include/chre/util/time.h"
+#ifdef NEARBY_PROFILE
+#include <ash/profile.h>
+#endif
+
+#include <limits>
+#include <utility>
+
+#include "third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h"
+
+namespace nearby {
+
+class AdvReportCache {
+ public:
+  // Constructs advertise report cache.
+#ifdef NEARBY_PROFILE
+  AdvReportCache() {
+    ashProfileInit(
+        &profile_data_, "[NEARBY_ADV_CACHE_PERF]", 1000 /* print_interval_ms */,
+        false /* report_total_thread_cycles */, true /* printCsvFormat */);
+  }
+#else
+  AdvReportCache() = default;
+#endif
+
+  // Deconstructs advertise report cache and releases all resources.
+  ~AdvReportCache() {
+    Clear();
+  }
+
+  // Move assignment operator
+  AdvReportCache &operator=(AdvReportCache &&other) {
+    if (&other == this) {
+      return *this;
+    }
+    Clear();
+    cache_reports_ = std::move(other.cache_reports_);
+    cache_expire_nanosec_ = other.cache_expire_nanosec_;
+    return *this;
+  }
+
+  // Releases all resources {cache element, heap memory} in cache.
+  void Clear();
+
+  // Adds advertise report to cache with deduplicating by
+  // unique key which is {advertiser address and data}.
+  // Among advertise report with the same key, latest one will be placed at the
+  // same index of existing report in advertise reports cache.
+  void Push(const chreBleAdvertisingReport &report);
+
+  // Return advertise reports in cache after refreshing the cache elements.
+  chre::DynamicVector<chreBleAdvertisingReport> &GetAdvReports() {
+    Refresh();
+    return cache_reports_;
+  }
+
+  // Computes moving average with previous average (previous) and a current data
+  // point (current). Returns the computed average.
+  int8_t ComputeMovingAverage(int8_t previous, int8_t current) const {
+    return static_cast<int8_t>(current * kMovingAverageWeight +
+                               previous * (1 - kMovingAverageWeight));
+  }
+
+  // Sets current cache timeout value.
+  void SetCacheTimeout(uint64_t cache_expire_millisec) {
+    cache_expire_nanosec_ =
+        cache_expire_millisec * chre::kOneMillisecondInNanoseconds;
+  }
+
+  // Removes cached elements older than the cache timeout.
+  void Refresh();
+
+ private:
+  // Weight for a current data point in moving average.
+  static constexpr float kMovingAverageWeight = 0.3f;
+
+  // Default value for advertise report cache to expire.
+  // Uses large enough value that it won't end soon.
+  static constexpr uint64_t kMaxExpireTimeNanoSec =
+      std::numeric_limits<uint64_t>::max();
+
+  chre::DynamicVector<chreBleAdvertisingReport> cache_reports_;
+  // Current cache timeout value.
+  uint64_t cache_expire_nanosec_ = kMaxExpireTimeNanoSec;
+#ifdef NEARBY_PROFILE
+  ashProfileData profile_data_;
+#endif
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_ADV_REPORT_CACHE_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_main.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_main.cc
new file mode 100644
index 0000000..ff01df1
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_main.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/app_manager.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+// The following functions define the entry points of Nearby nanoapp.
+
+#define LOG_TAG "[NEARBY][APP_MAIN]"
+
+// Only define one of the macros below.
+#if defined(MOCK_PRESENCE_V1) || defined(MOCK_PRESENCE_V0) || \
+    defined(MOCK_SUBSEQUENT_PAIR) || defined(MOCK_FAST_PAIR)
+#define MOCK_BLE_EVENT true
+#include "location/lbs/contexthub/nanoapps/nearby/mock_ble.h"
+extern uint32_t mock_ble_timer_id;
+extern uint32_t mock_ble_flush_complete_timer_id;
+#endif
+
+bool nanoappStart(void) {
+  // Initialize the AppManager singleton.
+  // Must be done before invoking AppManagerSingleton::get().
+  ::nearby::AppManagerSingleton::init();
+  return true;
+}
+
+void nanoappEnd(void) {
+  ::nearby::AppManagerSingleton::deinit();
+}
+
+void nanoappHandleEvent(uint32_t sender_instance_id, uint16_t event_type,
+                        const void *event_data) {
+#ifdef MOCK_BLE_EVENT
+  if (event_type == CHRE_EVENT_BLE_ADVERTISEMENT) {
+    // Throw away real BLE events.
+    return;
+  }
+  if (event_type == CHRE_EVENT_TIMER) {
+    if (event_data == &mock_ble_timer_id) {
+      // Change a timer event to a mock BLE event.
+      LOGI("Mocked BLE event.");
+      event_data = &nearby::MockBle::kBleEvent;
+      event_type = CHRE_EVENT_BLE_ADVERTISEMENT;
+    } else if (event_data == &mock_ble_flush_complete_timer_id) {
+      // Change a timer event to a mock batch complete event.
+      LOGI("Mocked BLE batch complete event.");
+      event_data = &nearby::MockBle::kBleBatchCompleteEvent;
+      event_type = CHRE_EVENT_BLE_BATCH_COMPLETE;
+      ::nearby::AppManagerSingleton::get()->HandleEvent(sender_instance_id,
+                                                        event_type, event_data);
+
+      // Change a timer event to a mock BLE flush complete event.
+      LOGI("Mocked BLE flush complete event.");
+      event_data = &nearby::MockBle::kBleFlushCompleteEvent;
+      event_type = CHRE_EVENT_BLE_FLUSH_COMPLETE;
+      ::nearby::AppManagerSingleton::get()->HandleEvent(sender_instance_id,
+                                                        event_type, event_data);
+      return;
+    }
+  }
+#endif
+  ::nearby::AppManagerSingleton::get()->HandleEvent(sender_instance_id,
+                                                    event_type, event_data);
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_manager.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_manager.cc
new file mode 100644
index 0000000..bbaf9dc
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_manager.cc
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/app_manager.h"
+
+#include <inttypes.h>
+#include <pb_decode.h>
+#include <pb_encode.h>
+
+#include <utility>
+
+#include "location/lbs/contexthub/nanoapps/common/math/macros.h"
+#include "location/lbs/contexthub/nanoapps/proto/filter.nanopb.h"
+#include "third_party/contexthub/chre/util/include/chre/util/macros.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][APP_MANAGER]"
+
+namespace nearby {
+
+using ::chre::Nanoseconds;
+
+AppManager::AppManager() {
+  fp_filter_cache_time_nanosec_ = chreGetTime();
+#ifdef NEARBY_PROFILE
+  ashProfileInit(
+      &profile_data_, "[NEARBY_MATCH_ADV_PERF]", 1000 /* print_interval_ms */,
+      false /* report_total_thread_cycles */, true /* printCsvFormat */);
+#endif
+}
+
+void AppManager::HandleEvent(uint32_t sender_instance_id, uint16_t event_type,
+                             const void *event_data) {
+  Nanoseconds wakeup_start_ns = Nanoseconds(chreGetTime());
+  LOGD("NanoApp wakeup starts by event %" PRIu16, event_type);
+  UNUSED_VAR(sender_instance_id);
+  const chreBleAdvertisementEvent *event;
+  switch (event_type) {
+    case CHRE_EVENT_MESSAGE_FROM_HOST:
+      HandleMessageFromHost(
+          static_cast<const chreMessageFromHostData *>(event_data));
+      break;
+    case CHRE_EVENT_BLE_ADVERTISEMENT:
+      event = static_cast<const chreBleAdvertisementEvent *>(event_data);
+      LOGD("Received %d BLE reports", event->numReports);
+      // Print BLE advertisements for debug only.
+      for (int i = 0; i < event->numReports; i++) {
+        LOGD_SENSITIVE_INFO("Report %d has %d bytes service data", i,
+                            event->reports[i].dataLength);
+        LOGD_SENSITIVE_INFO("timestamp msec: %" PRIu64,
+                            event->reports[i].timestamp / MSEC_TO_NANOS(1));
+        LOGD_SENSITIVE_INFO("service data byte: ");
+        LOGD_SENSITIVE_INFO("Tx power: %d", event->reports[i].txPower);
+        LOGD_SENSITIVE_INFO("RSSI: %d", event->reports[i].rssi);
+        for (int j = 0; j < 6; j++) {
+          LOGD_SENSITIVE_INFO("direct address %d: %d", j,
+                              event->reports[i].directAddress[j]);
+          LOGD_SENSITIVE_INFO("address %d: %d", j,
+                              event->reports[i].address[j]);
+        }
+        for (int j = 0; j < event->reports[i].dataLength; j++) {
+          LOGD_SENSITIVE_INFO("%d", event->reports[i].data[j]);
+        }
+        // Adds advertise report to advertise report cache with deduplicating.
+        adv_reports_cache_.Push(event->reports[i]);
+      }
+
+      // If batch scan is not supported, requests the match process here.
+      // Otherwise, the match process is postponed to the batch complete event.
+      if (!ble_scanner_.IsBatchSupported()) {
+        HandleMatchAdvReports(adv_reports_cache_);
+      }
+      break;
+    case CHRE_EVENT_BLE_FLUSH_COMPLETE:
+      ble_scanner_.HandleEvent(event_type, event_data);
+      break;
+    case CHRE_EVENT_BLE_ASYNC_RESULT:
+      ble_scanner_.HandleEvent(event_type, event_data);
+      break;
+    case CHRE_EVENT_BLE_BATCH_COMPLETE:
+      LOGD("Received batch complete event");
+      HandleMatchAdvReports(adv_reports_cache_);
+      break;
+    default:
+      LOGD("Unknown event type: %d", event_type);
+  }
+  Nanoseconds wakeup_duration_ns = Nanoseconds(chreGetTime()) - wakeup_start_ns;
+  LOGD("NanoApp wakeup ends after %" PRIu64 " ns by event %" PRIu16,
+       wakeup_duration_ns.toRawNanoseconds(), event_type);
+}
+
+void AppManager::HandleMatchAdvReports(AdvReportCache &adv_reports_cache) {
+#ifdef NEARBY_PROFILE
+  ashProfileBegin(&profile_data_);
+#endif
+  chre::DynamicVector<nearby_BleFilterResult> filter_results;
+  chre::DynamicVector<nearby_BleFilterResult> fp_filter_results;
+  for (const auto &report : adv_reports_cache.GetAdvReports()) {
+    if (report.dataLength == 0) {
+      continue;
+    }
+    filter_.MatchBle(report, &filter_results, &fp_filter_results);
+  }
+  if (!filter_results.empty()) {
+    LOGD("Send filter results back");
+    SendBulkFilterResultsToHost(filter_results);
+  }
+  if (!fp_filter_results.empty()) {
+    // FP host requires to receive scan results once during screen on
+    if (screen_on_ && !fp_screen_on_sent_) {
+      LOGD("Send FP filter results back");
+      SendBulkFilterResultsToHost(fp_filter_results);
+      fp_screen_on_sent_ = true;
+    }
+    LOGD("update FP filter cache");
+    fp_filter_cache_results_ = std::move(fp_filter_results);
+    fp_filter_cache_time_nanosec_ = chreGetTime();
+  }
+#ifdef ENABLE_EXTENSION
+  // Matches extended filters.
+  chre::DynamicVector<FilterExtensionResult> filter_extension_results;
+  chre::DynamicVector<FilterExtensionResult> screen_on_filter_extension_results;
+  filter_extension_.Match(adv_reports_cache.GetAdvReports(),
+                          &filter_extension_results,
+                          &screen_on_filter_extension_results);
+  if (!filter_extension_results.empty()) {
+    SendFilterExtensionResultToHost(filter_extension_results);
+  }
+  if (!screen_on_filter_extension_results.empty()) {
+    if (screen_on_) {
+      LOGD("Send screen on filter extension results back");
+      SendFilterExtensionResultToHost(screen_on_filter_extension_results);
+    } else {
+      LOGD("Update filter extension result cache");
+      screen_on_filter_extension_results_.clear();
+      screen_on_filter_extension_results_ =
+          std::move(screen_on_filter_extension_results);
+    }
+  }
+#endif
+  adv_reports_cache.Clear();
+#ifdef NEARBY_PROFILE
+  ashProfileEnd(&profile_data_, nullptr /* output */);
+#endif
+}
+
+void AppManager::HandleMessageFromHost(const chreMessageFromHostData *event) {
+  LOGD("Got message from host with type %" PRIu32 " size %" PRIu32
+       " hostEndpoint 0x%" PRIx16,
+       event->messageType, event->messageSize, event->hostEndpoint);
+  switch (event->messageType) {
+    case lbs_FilterMessageType_MESSAGE_FILTERS:
+      host_endpoint_ = event->hostEndpoint;
+      RespondHostSetFilterRequest(filter_.Update(
+          static_cast<const uint8_t *>(event->message), event->messageSize));
+      fp_screen_on_sent_ = false;
+      if (filter_.IsEmpty()) {
+        ble_scanner_.ClearDefaultFilters();
+      } else {
+        ble_scanner_.SetDefaultFilters();
+      }
+      UpdateBleScanState();
+      break;
+    case lbs_FilterMessageType_MESSAGE_CONFIG:
+      HandleHostConfigRequest(static_cast<const uint8_t *>(event->message),
+                              event->messageSize);
+      break;
+#ifdef ENABLE_EXTENSION
+    case lbs_FilterMessageType_MESSAGE_FILTER_EXTENSIONS:
+      if (UpdateFilterExtension(event)) {
+        UpdateBleScanState();
+      }
+      break;
+#endif
+  }
+}
+
+void AppManager::UpdateBleScanState() {
+  if (!filter_.IsEmpty()
+#ifdef ENABLE_EXTENSION
+      || !filter_extension_.IsEmpty()
+#endif
+  ) {
+    ble_scanner_.Restart();
+  } else {
+    ble_scanner_.Stop();
+  }
+}
+
+void AppManager::RespondHostSetFilterRequest(const bool success) {
+  auto resp_type = (success ? lbs_FilterMessageType_MESSAGE_SUCCESS
+                            : lbs_FilterMessageType_MESSAGE_FAILURE);
+  // TODO(b/238708594): change back to zero size response.
+  void *msg_buf = chreHeapAlloc(3);
+  LOGI("Acknowledge filter config.");
+  if (chreSendMessageWithPermissions(
+          msg_buf, 3, resp_type, host_endpoint_, CHRE_MESSAGE_PERMISSION_BLE,
+          [](void *msg, size_t /*size*/) { chreHeapFree(msg); })) {
+    LOGI("Succeeded to acknowledge Filter update");
+  } else {
+    LOGI("Failed to acknowledge Filter update");
+  }
+}
+
+void AppManager::HandleHostConfigRequest(const uint8_t *message,
+                                         uint32_t message_size) {
+  nearby_BleConfig config = nearby_BleConfig_init_zero;
+  pb_istream_t stream = pb_istream_from_buffer(message, message_size);
+  if (!pb_decode(&stream, nearby_BleConfig_fields, &config)) {
+    LOGE("failed to decode config message");
+    return;
+  }
+  if (config.has_screen_on) {
+    screen_on_ = config.screen_on;
+    LOGD("received screen config %d", screen_on_);
+    if (screen_on_) {
+      fp_screen_on_sent_ = false;
+      ble_scanner_.Flush();
+      // TODO(b/255338604): used the default report delay value only because
+      // FP offload scan doesn't use low latency report delay.
+      // when the flushed packet droping issue is resolved, try to reconfigure
+      // report delay for Nearby Presence.
+      if (!fp_filter_cache_results_.empty()) {
+        LOGD("send FP filter result from cache");
+        uint64_t current_time = chreGetTime();
+        if (current_time - fp_filter_cache_time_nanosec_ <
+            fp_filter_cache_expire_nanosec_) {
+          SendBulkFilterResultsToHost(fp_filter_cache_results_);
+        } else {
+          // nanoapp receives screen_on message for both screen_on and unlock
+          // events. To send FP cache results on both events, keeps FP cache
+          // results until cache timeout.
+          fp_filter_cache_results_.clear();
+        }
+      }
+#ifdef ENABLE_EXTENSION
+      if (!screen_on_filter_extension_results_.empty()) {
+        LOGD("send filter extension result from cache");
+        SendFilterExtensionResultToHost(screen_on_filter_extension_results_);
+        screen_on_filter_extension_results_.clear();
+      }
+#endif
+    }
+  }
+  if (config.has_fast_pair_cache_expire_time_sec) {
+    fp_filter_cache_expire_nanosec_ = config.fast_pair_cache_expire_time_sec;
+  }
+}
+
+void AppManager::SendBulkFilterResultsToHost(
+    const chre::DynamicVector<nearby_BleFilterResult> &filter_results) {
+  size_t encoded_size = 0;
+  if (!GetEncodedSizeFromFilterResults(filter_results, encoded_size)) {
+    return;
+  }
+  if (encoded_size <= kFilterResultsBufSize) {
+    SendFilterResultsToHost(filter_results);
+    return;
+  }
+  LOGD("Encoded size %zu is larger than buffer size %zu. Sends each one",
+       encoded_size, kFilterResultsBufSize);
+  for (const auto &filter_result : filter_results) {
+    SendFilterResultToHost(filter_result);
+  }
+}
+
+void AppManager::SendFilterResultsToHost(
+    const chre::DynamicVector<nearby_BleFilterResult> &filter_results) {
+  void *msg_buf = chreHeapAlloc(kFilterResultsBufSize);
+  if (msg_buf == nullptr) {
+    LOGE("Failed to allocate message buffer of size %zu for dispatch.",
+         kFilterResultsBufSize);
+    return;
+  }
+  auto stream = pb_ostream_from_buffer(static_cast<pb_byte_t *>(msg_buf),
+                                       kFilterResultsBufSize);
+  size_t msg_size = 0;
+  if (!EncodeFilterResults(filter_results, &stream, &msg_size)) {
+    LOGE("Unable to encode protobuf for BleFilterResults, error %s",
+         PB_GET_ERROR(&stream));
+    chreHeapFree(msg_buf);
+    return;
+  }
+  if (!chreSendMessageWithPermissions(
+          msg_buf, msg_size, lbs_FilterMessageType_MESSAGE_FILTER_RESULTS,
+          host_endpoint_, CHRE_MESSAGE_PERMISSION_BLE,
+          [](void *msg, size_t size) {
+            UNUSED_VAR(size);
+            chreHeapFree(msg);
+          })) {
+    LOGE("Failed to send FilterResults");
+  } else {
+    LOGD("Successfully sent the filter result.");
+  }
+}
+
+void AppManager::SendFilterResultToHost(
+    const nearby_BleFilterResult &filter_result) {
+  void *msg_buf = chreHeapAlloc(kFilterResultsBufSize);
+  if (msg_buf == nullptr) {
+    LOGE("Failed to allocate message buffer of size %zu for dispatch.",
+         kFilterResultsBufSize);
+    return;
+  }
+  auto stream = pb_ostream_from_buffer(static_cast<pb_byte_t *>(msg_buf),
+                                       kFilterResultsBufSize);
+  size_t msg_size = 0;
+  if (!EncodeFilterResult(filter_result, &stream, &msg_size)) {
+    LOGE("Unable to encode protobuf for BleFilterResult, error %s",
+         PB_GET_ERROR(&stream));
+    chreHeapFree(msg_buf);
+    return;
+  }
+  if (!chreSendMessageWithPermissions(
+          msg_buf, msg_size, lbs_FilterMessageType_MESSAGE_FILTER_RESULTS,
+          host_endpoint_, CHRE_MESSAGE_PERMISSION_BLE,
+          [](void *msg, size_t size) {
+            UNUSED_VAR(size);
+            chreHeapFree(msg);
+          })) {
+    LOGE("Failed to send FilterResults");
+  } else {
+    LOGD("Successfully sent the filter result.");
+  }
+}
+
+// Struct to pass into EncodeFilterResult as *arg.
+struct EncodeFieldResultsArg {
+  size_t *msg_size;
+  const chre::DynamicVector<nearby_BleFilterResult> *results;
+};
+
+// Callback to encode repeated result in nearby_BleFilterResults.
+static bool EncodeFilterResultCallback(pb_ostream_t *stream,
+                                       const pb_field_t *field,
+                                       void *const *arg) {
+  UNUSED_VAR(field);
+  bool success = true;
+  auto *encode_arg = static_cast<EncodeFieldResultsArg *>(*arg);
+
+  for (const auto &result : *encode_arg->results) {
+    if (!pb_encode_tag_for_field(
+            stream,
+            &nearby_BleFilterResults_fields[nearby_BleFilterResults_result_tag -
+                                            1])) {
+      return false;
+    }
+    success =
+        pb_encode_submessage(stream, nearby_BleFilterResult_fields, &result);
+  }
+  if (success) {
+    *encode_arg->msg_size = stream->bytes_written;
+  }
+  return success;
+}
+
+bool AppManager::GetEncodedSizeFromFilterResults(
+    const chre::DynamicVector<nearby_BleFilterResult> &filter_results,
+    size_t &encoded_size) {
+  size_t total_encoded_size = 0;
+  for (const auto &filter_result : filter_results) {
+    constexpr size_t kHeaderSize = 2;
+    size_t single_encoded_size = 0;
+    if (!pb_get_encoded_size(&single_encoded_size,
+                             nearby_BleFilterResult_fields, &filter_result)) {
+      LOGE("Failed to get encoded size for BleFilterResult");
+      return false;
+    }
+    total_encoded_size += single_encoded_size + kHeaderSize;
+  }
+  encoded_size = total_encoded_size;
+  return true;
+}
+
+bool AppManager::EncodeFilterResults(
+    const chre::DynamicVector<nearby_BleFilterResult> &filter_results,
+    pb_ostream_t *stream, size_t *msg_size) {
+  // Ensure stream is properly initialized before encoding.
+  CHRE_ASSERT(stream->bytes_written == 0);
+  *msg_size = 0;
+
+  EncodeFieldResultsArg arg = {
+      .msg_size = msg_size,
+      .results = &filter_results,
+  };
+  nearby_BleFilterResults pb_results = {
+      .result =
+          {
+              .funcs =
+                  {
+                      .encode = EncodeFilterResultCallback,
+                  },
+              .arg = &arg,
+          },
+  };
+  return pb_encode(stream, nearby_BleFilterResults_fields, &pb_results);
+}
+
+bool AppManager::EncodeFilterResult(const nearby_BleFilterResult &filter_result,
+                                    pb_ostream_t *stream, size_t *msg_size) {
+  // Ensure stream is properly initialized before encoding.
+  CHRE_ASSERT(stream->bytes_written == 0);
+  if (!pb_encode_tag_for_field(
+          stream,
+          &nearby_BleFilterResults_fields[nearby_BleFilterResults_result_tag -
+                                          1])) {
+    return false;
+  }
+  if (!pb_encode_submessage(stream, nearby_BleFilterResult_fields,
+                            &filter_result)) {
+    return false;
+  }
+  *msg_size = stream->bytes_written;
+  return true;
+}
+
+#ifdef ENABLE_EXTENSION
+bool AppManager::UpdateFilterExtension(const chreMessageFromHostData *event) {
+  chreHostEndpointInfo host_info;
+  chre::DynamicVector<chreBleGenericFilter> generic_filters;
+  nearby_extension_FilterConfigResult config_result =
+      nearby_extension_FilterConfigResult_init_zero;
+  if (chreGetHostEndpointInfo(event->hostEndpoint, &host_info)) {
+    if (host_info.isNameValid) {
+      LOGD("host package name %s", host_info.packageName);
+      // TODO(b/283035791) replace "android" with the package name of Nearby
+      // Mainline host.
+      // The event is sent from Nearby Mainline host, not OEM services.
+      if (strcmp(host_info.packageName, "android") == 0) {
+        return false;
+      }
+      filter_extension_.Update(host_info, *event, &generic_filters,
+                               &config_result);
+      if (!ble_scanner_.UpdateFilters(event->hostEndpoint, &generic_filters)) {
+        config_result.result = CHREX_NEARBY_RESULT_INTERNAL_ERROR;
+      }
+    } else {
+      LOGE("host package name invalid.");
+      config_result.result = CHREX_NEARBY_RESULT_UNKNOWN_PACKAGE;
+    }
+  } else {
+    config_result.result = CHREX_NEARBY_RESULT_INTERNAL_ERROR;
+    LOGE("Failed to get host info.");
+  }
+  SendFilterExtensionConfigResultToHost(event->hostEndpoint, config_result);
+  return true;
+}
+
+void AppManager::SendFilterExtensionConfigResultToHost(
+    uint16_t host_end_point,
+    const nearby_extension_FilterConfigResult &config_result) {
+  uint8_t *msg_buf = (uint8_t *)chreHeapAlloc(kFilterResultsBufSize);
+  if (msg_buf == nullptr) {
+    LOGE("Failed to allocate message buffer of size %zu for dispatch.",
+         kFilterResultsBufSize);
+    return;
+  }
+  size_t encoded_size;
+  if (!FilterExtension::EncodeConfigResult(
+          config_result, ByteArray(msg_buf, kFilterResultsBufSize),
+          &encoded_size)) {
+    chreHeapFree(msg_buf);
+    return;
+  }
+  auto resp_type = (config_result.result == CHREX_NEARBY_RESULT_OK
+                        ? lbs_FilterMessageType_MESSAGE_SUCCESS
+                        : lbs_FilterMessageType_MESSAGE_FAILURE);
+
+  if (chreSendMessageWithPermissions(
+          msg_buf, encoded_size, resp_type, host_end_point,
+          CHRE_MESSAGE_PERMISSION_BLE,
+          [](void *msg, size_t /*size*/) { chreHeapFree(msg); })) {
+    LOGD("Successfully sent the filter extension config result.");
+  } else {
+    LOGE("Failed to send filter extension config result.");
+  }
+}
+
+void AppManager::SendFilterExtensionResultToHost(
+    chre::DynamicVector<FilterExtensionResult> &filter_results) {
+  for (auto &result : filter_results) {
+    auto &reports = result.GetAdvReports();
+    if (reports.empty()) {
+      continue;
+    }
+    uint8_t *msg_buf = (uint8_t *)chreHeapAlloc(kFilterResultsBufSize);
+    if (msg_buf == nullptr) {
+      LOGE("Failed to allocate message buffer of size %zu for dispatch.",
+           kFilterResultsBufSize);
+      return;
+    }
+    size_t encoded_size;
+    if (!FilterExtension::Encode(reports,
+                                 ByteArray(msg_buf, kFilterResultsBufSize),
+                                 &encoded_size)) {
+      chreHeapFree(msg_buf);
+      return;
+    }
+    if (chreSendMessageWithPermissions(
+            msg_buf, encoded_size, lbs_FilterMessageType_MESSAGE_FILTER_RESULTS,
+            result.end_point, CHRE_MESSAGE_PERMISSION_BLE,
+            [](void *msg, size_t /*size*/) { chreHeapFree(msg); })) {
+      LOGD("Successfully sent the filter extension result.");
+    } else {
+      LOGE("Failed to send filter extension result.");
+    }
+  }
+}
+#endif
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_manager.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_manager.h
new file mode 100644
index 0000000..bed03e4
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/app_manager.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_APP_MANAGER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_APP_MANAGER_H_
+
+#ifdef NEARBY_PROFILE
+#include <ash/profile.h>
+#endif
+
+#include <chre.h>
+
+#include "location/lbs/contexthub/nanoapps/nearby/adv_report_cache.h"
+#include "location/lbs/contexthub/nanoapps/nearby/ble_scanner.h"
+#include "location/lbs/contexthub/nanoapps/nearby/filter.h"
+#ifdef ENABLE_EXTENSION
+#include "location/lbs/contexthub/nanoapps/nearby/filter_extension.h"
+#endif
+#include "third_party/contexthub/chre/util/include/chre/util/singleton.h"
+#include "third_party/contexthub/chre/util/include/chre/util/time.h"
+
+namespace nearby {
+
+// AppManager handles events from CHRE as well messages with host.
+class AppManager {
+  friend class AppManagerTest;
+
+ public:
+  AppManager();
+  // Handles an event from CHRE.
+  void HandleEvent(uint32_t sender_instance_id, uint16_t event_type,
+                   const void *event_data);
+
+ private:
+  // Handles a message from host.
+  void HandleMessageFromHost(const chreMessageFromHostData *event);
+  // Acknowledge a host's SET_FILTER_REQUEST to indicate success or failure.
+  void RespondHostSetFilterRequest(bool success);
+  // Handles config request from the host.
+  void HandleHostConfigRequest(const uint8_t *message, uint32_t message_size);
+  // Handles advertise reports to match filters.
+  // Advertise reports will be cleared at the end of this function.
+  void HandleMatchAdvReports(AdvReportCache &adv_reports);
+
+  // If encoded byte size for filter results is larger than output buffer, sends
+  // each filter result. Otherwise, sends filter results together.
+  void SendBulkFilterResultsToHost(
+      const chre::DynamicVector<nearby_BleFilterResult> &filter_results);
+
+  // Serializes filter_results into stream after encoding as BleFilterResults.
+  // Returns false if encoding fails.
+  void SendFilterResultsToHost(
+      const chre::DynamicVector<nearby_BleFilterResult> &filter_results);
+
+  // Serializes a filter_result into stream after encoding as BleFilterResults.
+  // Returns false if encoding fails.
+  void SendFilterResultToHost(const nearby_BleFilterResult &filter_result);
+
+  // Updates Filter extension with event. Returns true if event is sent
+  // from an OEM service.
+  bool UpdateFilterExtension(const chreMessageFromHostData *event);
+
+  // Updates BLE scan state to start or stop based on filter configurations.
+  void UpdateBleScanState();
+
+  // Encodes filter results as BleFilterResults format. Returns true if encoding
+  // was successful.
+  static bool EncodeFilterResults(
+      const chre::DynamicVector<nearby_BleFilterResult> &filter_results,
+      pb_ostream_t *stream, size_t *msg_size);
+
+  // Encodes a filter result as BleFilterResults format. Returns true if
+  // encoding was successful.
+  static bool EncodeFilterResult(const nearby_BleFilterResult &filter_result,
+                                 pb_ostream_t *stream, size_t *msg_size);
+
+  // Gets the expected encoded message size of filter results, which is
+  // equivalent to msg_size output of EncodeFilterResults()
+  static bool GetEncodedSizeFromFilterResults(
+      const chre::DynamicVector<nearby_BleFilterResult> &filter_results,
+      size_t &encoded_size);
+
+#ifdef ENABLE_EXTENSION
+  static void SendFilterExtensionConfigResultToHost(
+      uint16_t host_end_point,
+      const nearby_extension_FilterConfigResult &config_result);
+
+  static void SendFilterExtensionResultToHost(
+      chre::DynamicVector<FilterExtensionResult> &filter_results);
+#endif
+  // TODO(b/193756395): Find the optimal size or compute the size in runtime.
+  // Note: the nanopb API pb_get_encoded_size
+  // (https://jpa.kapsi.fi/nanopb/docs/reference.html#pb_get_encoded_size)
+  // can only get the encoded message size if the message does not contained
+  // repeated fields. Otherwise, the repeated fields require Callback field
+  // encoders, which need a pb_ostream_t to work with, while pb_ostream_t is
+  // initialized by a buffer with the size to be determined.
+  // It seems possible to compute a message size with repeated field by
+  // rehearsing the encoding without actually storing in memory. Explore to
+  // enhance nanopb API to extend pb_get_encoded_size for repeated fields.
+  static constexpr size_t kFilterResultsBufSize = 300;
+  // Default value for Fast Pair cache to expire.
+  static constexpr uint64_t kFpFilterResultExpireTimeNanoSec =
+      5 * chre::kOneSecondInNanoseconds;
+
+  Filter filter_;
+#ifdef ENABLE_EXTENSION
+  FilterExtension filter_extension_;
+#endif
+  BleScanner ble_scanner_;
+
+  uint16_t host_endpoint_ = 0;
+  bool screen_on_ = false;
+  bool fp_screen_on_sent_ = false;
+  AdvReportCache adv_reports_cache_;
+  chre::DynamicVector<nearby_BleFilterResult> fp_filter_cache_results_;
+#ifdef ENABLE_EXTENSION
+  chre::DynamicVector<FilterExtensionResult>
+      screen_on_filter_extension_results_;
+#endif
+  uint64_t fp_filter_cache_time_nanosec_;
+  uint64_t fp_filter_cache_expire_nanosec_ = kFpFilterResultExpireTimeNanoSec;
+#ifdef NEARBY_PROFILE
+  ashProfileData profile_data_;
+#endif
+};
+
+// The singleton AppManager that will be initialized safely.
+typedef chre::Singleton<AppManager> AppManagerSingleton;
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_APP_MANAGER_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scan_record.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scan_record.cc
new file mode 100644
index 0000000..c228da8
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scan_record.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 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 implementation below follows BLE Core Spec 5.3 from
+ * https://www.bluetooth.com/specifications/specs/core-specification/
+ * Part C Generic Access Profile,
+ *   Section 11 Advertisement and Scan Response Format ... Page 1357.
+ * Also follows the Java reference implementation in
+ * https://android.googlesource.com/platform/frameworks/base/+/b661bb7/core/java/android/bluetooth/le/ScanRecord.java
+ */
+
+#include "location/lbs/contexthub/nanoapps/nearby/ble_scan_record.h"
+
+#include <chre.h>
+
+namespace nearby {
+
+BleScanRecord BleScanRecord::Parse(const uint8_t data[], const uint16_t size) {
+  BleScanRecord record;
+
+  // Scans through data and parses each advertisement.
+  for (int i = 0; i < size;) {
+    // First byte has the advertisement data length.
+    uint8_t ad_data_length = data[i];
+    // Early termination with zero length advertisement.
+    if (ad_data_length == 0) break;
+    // Terminates when advertisement length passes over the end of data buffer.
+    if (ad_data_length >= size - i) break;
+    // Second byte has advertisement data type.
+    // Only retrieves service data for Nearby NanoApp.
+    // Also, skip advertisement less than 3 bytes since a non-empty service data
+    // include 2 bytes UUID plus at least 1 byte data.
+    if (data[++i] == kDataTypeServiceData && ad_data_length >= 3) {
+      BleServiceData service_data;
+      // First two bytes of the service data are service data UUID in little
+      // endian.
+      service_data.uuid =
+          static_cast<uint16_t>(data[i + 1] + (data[i + 2] << 8));
+      // Third byte starts service data after uuid.
+      service_data.data = &data[i + 3];
+      // service data length is the total data length minus one byte of type
+      // and two bytes of uuid.
+      service_data.length = ad_data_length - 3;
+      record.service_data.push_back(service_data);
+    }
+    // Moves to next advertisement.
+    i += ad_data_length;
+  }
+
+  return record;
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scan_record.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scan_record.h
new file mode 100644
index 0000000..7c0b207
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scan_record.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.
+ *
+ * Simplified BLE Scan Record implementation for Nearby nanoapp only.
+ */
+
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLE_SCAN_RECORD_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLE_SCAN_RECORD_H_
+
+#include <stddef.h>
+
+#include <cstdint>
+
+#include "third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h"
+#include "third_party/contexthub/chre/util/include/chre/util/optional.h"
+
+namespace nearby {
+
+// BLE service data with its UUID.
+struct BleServiceData {
+  // See 16-bit UUIDs in
+  // https://www.bluetooth.com/specifications/assigned-numbers/
+  uint16_t uuid;
+
+  // length of service data, which is always less than 256, the max size of BLE
+  // advertisement.
+  uint8_t length;
+  // byte array of service data, or null if length is 0.
+  const uint8_t *data;
+};
+
+struct BleScanRecord {
+  // Returns a BLE scan record by parsing the data.
+  // Note, the returned BleScanRecord has dependence on the lifetime of data,
+  // and the return will be invalid if data is altered/destructed after parsing.
+  static BleScanRecord Parse(const uint8_t data[], const uint16_t size);
+  chre::DynamicVector<BleServiceData> service_data;
+
+  static constexpr int8_t kDataTypeServiceData = 0x16;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLE_SCAN_RECORD_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scanner.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scanner.cc
new file mode 100644
index 0000000..f77c0cf
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scanner.cc
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/ble_scanner.h"
+
+#include <chre.h>
+
+#include <utility>
+
+#include "third_party/contexthub/chre/util/include/chre/util/macros.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#ifdef MOCK_BLE
+#include "chre/util/time.h"
+#include "location/lbs/contexthub/nanoapps/nearby/mock_ble.h"
+
+uint32_t mock_ble_timer_id = CHRE_TIMER_INVALID;
+uint32_t mock_ble_flush_complete_timer_id = CHRE_TIMER_INVALID;
+#endif
+
+#define LOG_TAG "[NEARBY][BLE_SCANNER]"
+
+namespace nearby {
+#ifdef MOCK_BLE
+BleScanner::BleScanner() {
+  is_ble_scan_supported_ = true;
+  is_batch_supported_ = nearby::MockBle::kBleBatchScanSupported;
+  report_delay_ms_ = kBatchScanReportDelayLowPowerMilliSec;
+}
+
+void BleScanner::Start() {
+  if (is_started_) {
+    LOGD("Mock BLE scan already started.");
+    return;
+  }
+  Restart();
+}
+
+void BleScanner::Restart() {
+  LOGD("Start mock BLE events in scan mode %d.", scan_mode_);
+  if (is_started_) {
+    chreTimerCancel(mock_ble_timer_id);
+  }
+  mock_ble_timer_id =
+      chreTimerSet(chre::Milliseconds(report_delay_ms_).toRawNanoseconds(),
+                   &mock_ble_timer_id, false);
+  is_started_ = true;
+}
+
+void BleScanner::Stop() {
+  if (!is_started_) {
+    LOGD("Mock BLE scan already stopped.");
+    return;
+  }
+  LOGD("Stop mock BLE events.");
+  chreTimerCancel(mock_ble_timer_id);
+  if (mock_ble_flush_complete_timer_id != CHRE_TIMER_INVALID) {
+    chreTimerCancel(mock_ble_flush_complete_timer_id);
+    mock_ble_flush_complete_timer_id = CHRE_TIMER_INVALID;
+  }
+  is_started_ = false;
+}
+
+void BleScanner::UpdateBatchDelay(uint32_t delay_ms) {
+  bool is_updated = false;
+  if (!is_batch_supported_) {
+    LOGD("Batch scan is not supported");
+    return;
+  }
+  // avoids the report delay from being set too small for simulation
+  if (delay_ms < nearby::MockBle::kBleReportDelayMinMs) {
+    LOGE("Requested report delay is too small");
+    return;
+  }
+  if (report_delay_ms_ != delay_ms) {
+    report_delay_ms_ = delay_ms;
+    is_updated = true;
+  }
+  // restart scan with new parameter if scan is already started
+  if (is_updated && is_started_) {
+    Restart();
+  }
+}
+
+bool BleScanner::Flush() {
+  if (!is_batch_supported_) {
+    LOGD("Batch scan is not supported");
+    return false;
+  }
+  if (!is_started_) {
+    LOGD("Mock BLE scan was not started.");
+    return false;
+  }
+  if (IsFlushing()) {
+    LOGD("Flushing BLE scan is already in progress.");
+    return true;
+  }
+  // stops normal BLE scan result timer internally
+  chreTimerCancel(mock_ble_timer_id);
+  // simulates the flushed scan results
+  mock_ble_flush_complete_timer_id = chreTimerSet(
+      chre::Milliseconds(nearby::MockBle::kBleFlushCompleteTimeoutMs)
+          .toRawNanoseconds(),
+      &mock_ble_flush_complete_timer_id, true);
+  mock_ble_timer_id = chreTimerSet(
+      chre::Milliseconds(nearby::MockBle::kBleFlushScanResultIntervalMs)
+          .toRawNanoseconds(),
+      &mock_ble_timer_id, false);
+  is_batch_flushing_ = true;
+  return true;
+}
+
+void BleScanner::HandleEvent(uint16_t event_type, const void *event_data) {
+  const chreAsyncResult *async_result;
+  switch (event_type) {
+    case CHRE_EVENT_BLE_FLUSH_COMPLETE:
+      async_result = static_cast<const chreAsyncResult *>(event_data);
+      LOGD("Received mock flush complete event: return_code(%u) cookie(%p)",
+           async_result->errorCode, async_result->cookie);
+      // stops the flushed scan results timer internally
+      chreTimerCancel(mock_ble_timer_id);
+      mock_ble_flush_complete_timer_id = CHRE_TIMER_INVALID;
+      is_batch_flushing_ = false;
+      if (is_started_) {
+        Restart();
+      }
+      break;
+    default:
+      LOGD("Unknown mock scan control event_type: %d", event_type);
+  }
+}
+#else
+constexpr chreBleGenericFilter kDefaultGenericFilters[] = {
+    {
+        .type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE,
+        .len = 2,
+        // Fast Pair Service UUID in OTA format.
+        .data = {0x2c, 0xfe},
+        .dataMask = {0xff, 0xff},
+    },
+    {
+        .type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE,
+        .len = 2,
+        // Presence Service UUID in OTA format.
+        .data = {0xf1, 0xfc},
+        .dataMask = {0xff, 0xff},
+    }};
+
+BleScanner::BleScanner() {
+  if (!(chreBleGetCapabilities() & CHRE_BLE_CAPABILITIES_SCAN)) {
+    LOGE("BLE scan not supported.");
+    is_ble_scan_supported_ = false;
+  }
+  if (!(chreBleGetFilterCapabilities() &
+        CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA)) {
+    LOGI("BLE filter by service UUID not supported.");
+  }
+  if (chreBleGetCapabilities() & CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING) {
+    is_batch_supported_ = true;
+    report_delay_ms_ = kBatchScanReportDelayLowPowerMilliSec;
+  }
+}
+
+void BleScanner::Start() {
+  if (is_started_) {
+    LOGD("BLE scan already started.");
+    return;
+  }
+  Restart();
+}
+
+static bool ContainsFilter(
+    const chre::DynamicVector<chreBleGenericFilter> &filters,
+    const chreBleGenericFilter &src) {
+  bool contained = false;
+  for (const auto &dst : filters) {
+    if (src.type == dst.type && src.len == dst.len) {
+      for (int i = 0; i < src.len; i++) {
+        if (src.data[i] != dst.data[i]) {
+          continue;
+        }
+        if (src.dataMask[i] != dst.dataMask[i]) {
+          continue;
+        }
+      }
+      contained = true;
+      break;
+    }
+  }
+  return contained;
+}
+
+void BleScanner::Restart() {
+  if (!is_ble_scan_supported_) {
+    LOGE("Failed to start BLE scan on an unsupported device");
+    return;
+  }
+  chre::DynamicVector<chreBleGenericFilter> generic_filters;
+  if (is_default_generic_filter_enabled_) {
+    for (size_t i = 0; i < ARRAY_SIZE(kDefaultGenericFilters); i++) {
+      generic_filters.push_back(kDefaultGenericFilters[i]);
+    }
+  }
+  for (auto &oem_generic_filters : generic_filters_list_) {
+    for (auto &generic_filter : oem_generic_filters.filters) {
+      if (!ContainsFilter(generic_filters, generic_filter)) {
+        generic_filters.push_back(generic_filter);
+      }
+    }
+  }
+  chreBleScanFilter scan_filter;
+  scan_filter.rssiThreshold = CHRE_BLE_RSSI_THRESHOLD_NONE;
+  scan_filter.scanFilters = generic_filters.data();
+  scan_filter.scanFilterCount = static_cast<uint8_t>(generic_filters.size());
+  if (chreBleStartScanAsync(scan_mode_, report_delay_ms_, &scan_filter)) {
+    LOGD("Succeeded to start BLE scan");
+    // is_started_ is set to true here, but it can be set back to false
+    // if CHRE_BLE_REQUEST_TYPE_START_SCAN request is failed in
+    // CHRE_EVENT_BLE_ASYNC_RESULT event.
+    is_started_ = true;
+  } else {
+    LOGE("Failed to start BLE scan");
+  }
+}
+
+void BleScanner::Stop() {
+  if (!is_started_) {
+    LOGD("BLE scan already stopped.");
+    return;
+  }
+  if (chreBleStopScanAsync()) {
+    LOGD("Succeeded Stop BLE scan.");
+    is_started_ = false;
+  } else {
+    LOGE("Failed to stop BLE scan");
+  }
+}
+
+bool BleScanner::UpdateFilters(
+    uint16_t host_end_point,
+    chre::DynamicVector<chreBleGenericFilter> *generic_filters) {
+  size_t index = 0;
+  while (index < generic_filters_list_.size()) {
+    if (generic_filters_list_[index].end_point == host_end_point) {
+      if (generic_filters->empty()) {
+        generic_filters_list_.erase(index);
+      } else {
+        generic_filters_list_[index].filters = std::move(*generic_filters);
+      }
+      return true;
+    }
+    ++index;
+  }
+  if (generic_filters_list_.push_back(GenericFilters(host_end_point))) {
+    generic_filters_list_.back().filters = std::move(*generic_filters);
+  } else {
+    LOGE("Failed to add new hardware filter.");
+    return false;
+  }
+  return true;
+}
+
+void BleScanner::UpdateBatchDelay(uint32_t delay_ms) {
+  bool is_updated = false;
+  if (!is_batch_supported_) {
+    LOGD("Batch scan is not supported");
+    return;
+  }
+  if (report_delay_ms_ != delay_ms) {
+    report_delay_ms_ = delay_ms;
+    is_updated = true;
+  }
+  // restart scan with new parameter if scan is already started
+  if (is_updated && is_started_) {
+    Restart();
+  }
+}
+
+bool BleScanner::Flush() {
+  if (!is_batch_supported_) {
+    LOGD("Batch scan is not supported");
+    return false;
+  }
+  if (!is_started_) {
+    LOGE("BLE scan was not started.");
+    return false;
+  }
+  if (IsFlushing()) {
+    LOGD("Flushing BLE scan is already in progress.");
+    return true;
+  }
+  LOGD("Flush batch scan results");
+  if (!chreBleFlushAsync(nullptr)) {
+    LOGE("Failed to call chreBleFlushAsync()");
+    return false;
+  }
+  is_batch_flushing_ = true;
+  return true;
+}
+
+void BleScanner::HandleEvent(uint16_t event_type, const void *event_data) {
+  const chreAsyncResult *async_result =
+      static_cast<const chreAsyncResult *>(event_data);
+  switch (event_type) {
+    case CHRE_EVENT_BLE_FLUSH_COMPLETE:
+      LOGD("Received flush complete event: return_code(%u) cookie(%p)",
+           async_result->errorCode, async_result->cookie);
+      if (async_result->errorCode != CHRE_ERROR_NONE) {
+        LOGE("Flush failed: %u", async_result->errorCode);
+      }
+      is_batch_flushing_ = false;
+      break;
+    case CHRE_EVENT_BLE_ASYNC_RESULT:
+      if (async_result->errorCode != CHRE_ERROR_NONE) {
+        LOGE(
+            "Failed to complete the async request: "
+            "request type (%u) error code(%u)",
+            async_result->requestType, async_result->errorCode);
+        if (async_result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN) {
+          LOGD("Failed in CHRE_BLE_REQUEST_TYPE_START_SCAN");
+          if (is_started_) {
+            is_started_ = false;
+          }
+        } else if (async_result->requestType ==
+                   CHRE_BLE_REQUEST_TYPE_STOP_SCAN) {
+          LOGD("Failed in CHRE_BLE_REQUEST_TYPE_STOP_SCAN");
+        }
+      }
+      break;
+    default:
+      LOGD("Unknown scan control event_type: %d", event_type);
+  }
+}
+#endif /* end ifdef MOCK_BLE */
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scanner.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scanner.h
new file mode 100644
index 0000000..08c51d1
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/ble_scanner.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLE_SCANNER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLE_SCANNER_H_
+
+#include <chre.h>
+
+#include "third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h"
+
+namespace nearby {
+
+struct GenericFilters {
+  uint16_t end_point;
+  chre::DynamicVector<chreBleGenericFilter> filters;
+
+  explicit GenericFilters(uint16_t end_point) : end_point(end_point) {}
+
+  friend bool operator==(const GenericFilters &c1, const GenericFilters &c2) {
+    return c1.end_point == c2.end_point;
+  }
+
+  friend bool operator!=(const GenericFilters &c1, const GenericFilters &c2) {
+    return c1.end_point != c2.end_point;
+  }
+};
+
+class BleScanner {
+ public:
+  // Default value for report delay of batch scan results in low latency mode.
+  static constexpr uint32_t kBatchScanReportDelayLowLatencyMilliSec = 0;
+
+  // Default value for report delay of batch scan results in low power mode.
+  static constexpr uint32_t kBatchScanReportDelayLowPowerMilliSec = 3000;
+
+  // Constructs BLE Scanner and checks whether BLE batch scan is supported.
+  BleScanner();
+
+  // Starts BLE scan. If scan already started, nothing happens.
+  void Start();
+
+  // Stops BLE scan.
+  void Stop();
+
+  // Flushes the batched scan results.
+  // Returns whether flush operation proceeds.
+  bool Flush();
+
+  // Returns whether BLE batch scan is flushing.
+  bool IsFlushing() {
+    return is_batch_flushing_;
+  }
+
+  // Returns whether BLE batch scan is supported.
+  bool IsBatchSupported() {
+    return is_batch_supported_;
+  }
+
+  // Updates extended generic filters. Caller needs to call Restart() for the
+  // updated filters to be effective. Returns true for successful update.
+  bool UpdateFilters(
+      uint16_t host_end_point,
+      chre::DynamicVector<chreBleGenericFilter> *generic_filters);
+
+  // Updates the report delay of batch scan
+  void UpdateBatchDelay(uint32_t delay_ms);
+
+  // Handles an event from CHRE.
+  void HandleEvent(uint16_t event_type, const void *event_data);
+
+  // Starts BLE scan. If scan already started, replacing the previous scan.
+  void Restart();
+
+  // Sets default generic filters.
+  void SetDefaultFilters() {
+    is_default_generic_filter_enabled_ = true;
+  }
+
+  // Clears default generic filters.
+  void ClearDefaultFilters() {
+    is_default_generic_filter_enabled_ = false;
+  }
+
+ private:
+  // Whether BLE scan is started.
+  bool is_started_ = false;
+
+  // Whether BLE scan is supported.
+  bool is_ble_scan_supported_ = true;
+
+  // Whether BLE batch scan is supported.
+  bool is_batch_supported_ = false;
+
+  // Whether BLE batch scan is flushing.
+  bool is_batch_flushing_ = false;
+
+  // Whether default generic filter is enabled.
+  bool is_default_generic_filter_enabled_ = false;
+
+  // Current report delay for BLE batch scan
+  uint32_t report_delay_ms_ = 0;
+
+  // Current BLE scan mode
+  chreBleScanMode scan_mode_ = CHRE_BLE_SCAN_MODE_BACKGROUND;
+
+  chre::DynamicVector<GenericFilters> generic_filters_list_;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLE_SCANNER_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/bloom_filter.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/bloom_filter.cc
new file mode 100644
index 0000000..5133f8b
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/bloom_filter.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/bloom_filter.h"
+
+#include <cstring>
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/sha2.h"
+
+namespace nearby {
+
+inline static uint32_t BSWAP32(uint32_t value) {
+#if defined(__clang__) || \
+    (defined(__GNUC__) && \
+     ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5))
+  return __builtin_bswap32(value);
+#else
+  uint32_t Byte0 = value & 0x000000FF;
+  uint32_t Byte1 = value & 0x0000FF00;
+  uint32_t Byte2 = value & 0x00FF0000;
+  uint32_t Byte3 = value & 0xFF000000;
+  return (Byte0 << 24) | (Byte1 << 8) | (Byte2 >> 8) | (Byte3 >> 24);
+#endif
+}
+
+BloomFilter::BloomFilter(const uint8_t filter[], size_t size)
+    : filter_bit_size_(Init(filter, size) * 8) {}
+
+size_t BloomFilter::Init(const uint8_t filter[], size_t size) {
+  size_t minFilterSize =
+      (kMaxBloomFilterByteSize > size) ? size : kMaxBloomFilterByteSize;
+  memcpy(filter_, filter, minFilterSize);
+  return minFilterSize;
+}
+
+bool BloomFilter::MayContain(const uint8_t key[], size_t size) {
+  uint32_t hash[SHA2_HASH_WORDS];
+  sha256(key, static_cast<uint32_t>(size), hash, sizeof(hash));
+
+  for (size_t i = 0; i < SHA2_HASH_WORDS; i++) {
+    hash[i] = BSWAP32(hash[i]);
+    uint32_t bitPos = hash[i] % (filter_bit_size_);
+    if (!(filter_[bitPos / 8] & (1 << (bitPos % 8)))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/bloom_filter.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/bloom_filter.h
new file mode 100644
index 0000000..b455499
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/bloom_filter.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLOOM_FILTER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLOOM_FILTER_H_
+
+#include <chre.h>
+#include <inttypes.h>
+
+#include <cstddef>
+
+namespace nearby {
+
+// Bloom filter to test if an account key is included.
+class BloomFilter {
+ public:
+  // Bloom Filter size is capped by the max value of Length-Type header, i.e.
+  // 2^4 = 16.
+  static constexpr size_t kMaxBloomFilterByteSize = 16;
+
+  // Constructs a Bloom Filter from a byte array.
+  BloomFilter(const uint8_t filter[], size_t size);
+
+  // Returns true if the key is set in the Bloom filter.
+  bool MayContain(const uint8_t key[], size_t size);
+
+ private:
+  uint8_t filter_[kMaxBloomFilterByteSize] = {0};
+  size_t filter_bit_size_;
+
+  // Initializes filter uint8_t array
+  // Returns byte size of filter as min(kMaxBloomFilterByteSize, size)
+  size_t Init(const uint8_t filter[], size_t size);
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BLOOM_FILTER_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/byte_array.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/byte_array.h
new file mode 100644
index 0000000..de4131c
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/byte_array.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BYTE_ARRAY_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BYTE_ARRAY_H_
+
+#include <cstddef>
+#include <cstdint>
+
+namespace nearby {
+
+// ByteArray struct includes a byte array together with the length of bytes.
+// This makes it convenient to pass a byte array around with its length.
+// The struct has the same lifetime of input data, i.e. it becomes invalid if
+// data is reclaimed.
+struct ByteArray {
+  ByteArray() = default;
+  ByteArray(uint8_t data[], size_t length) : data(data), length(length) {}
+
+  uint8_t *data = nullptr;
+  size_t length = 0;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_BYTE_ARRAY_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto.h
new file mode 100644
index 0000000..d98e038
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "location/lbs/contexthub/nanoapps/nearby/byte_array.h"
+
+namespace nearby {
+
+// Interface for crypto algorithms. Default to no encryption implementation,
+// i.e. echoing back the input as plain text.
+// This class will be overridden by implementations with different encryption
+// algorithms such as AES.
+class Crypto {
+ public:
+  // Decrypts input with salt and key. Places the decrypted result in output.
+  // Returns true if decryption succeeds.
+  // Caller needs to allocates the output memory and set the buffer size.
+  virtual bool decrypt(const ByteArray &input, const ByteArray &salt,
+                       const ByteArray &key, ByteArray &output) const = 0;
+
+  // Verifies the computed signature is equal to the expected signature.
+  virtual bool verify(const ByteArray &input, const ByteArray &key,
+                      const ByteArray &signature) const = 0;
+
+  virtual ~Crypto() = default;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/aes.c b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/aes.c
new file mode 100644
index 0000000..25c0e6f
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/aes.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2022 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 "location/lbs/contexthub/nanoapps/nearby/crypto/aes.h"
+
+#include <stdalign.h>
+#include <stdbool.h>
+#include <string.h>
+
+#define AES_128_KEY_NUM_ROUNDS 10
+#define AES_192_KEY_NUM_ROUNDS 12
+#define AES_256_KEY_NUM_ROUNDS 14
+
+#define IS_ALIGNED(ptr, type) (((uintptr_t)(ptr) & (alignof(type) - 1)) == 0)
+static const uint8_t FwdSbox[] = {
+    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,
+    0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,
+    0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26,
+    0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
+    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
+    0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,
+    0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED,
+    0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
+    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F,
+    0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
+    0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC,
+    0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
+    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14,
+    0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,
+    0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
+    0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
+    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F,
+    0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,
+    0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,
+    0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
+    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F,
+    0xB0, 0x54, 0xBB, 0x16,
+};
+
+static const uint32_t FwdTab0[] = {
+    0xC66363A5, 0xF87C7C84, 0xEE777799, 0xF67B7B8D, 0xFFF2F20D, 0xD66B6BBD,
+    0xDE6F6FB1, 0x91C5C554, 0x60303050, 0x02010103, 0xCE6767A9, 0x562B2B7D,
+    0xE7FEFE19, 0xB5D7D762, 0x4DABABE6, 0xEC76769A, 0x8FCACA45, 0x1F82829D,
+    0x89C9C940, 0xFA7D7D87, 0xEFFAFA15, 0xB25959EB, 0x8E4747C9, 0xFBF0F00B,
+    0x41ADADEC, 0xB3D4D467, 0x5FA2A2FD, 0x45AFAFEA, 0x239C9CBF, 0x53A4A4F7,
+    0xE4727296, 0x9BC0C05B, 0x75B7B7C2, 0xE1FDFD1C, 0x3D9393AE, 0x4C26266A,
+    0x6C36365A, 0x7E3F3F41, 0xF5F7F702, 0x83CCCC4F, 0x6834345C, 0x51A5A5F4,
+    0xD1E5E534, 0xF9F1F108, 0xE2717193, 0xABD8D873, 0x62313153, 0x2A15153F,
+    0x0804040C, 0x95C7C752, 0x46232365, 0x9DC3C35E, 0x30181828, 0x379696A1,
+    0x0A05050F, 0x2F9A9AB5, 0x0E070709, 0x24121236, 0x1B80809B, 0xDFE2E23D,
+    0xCDEBEB26, 0x4E272769, 0x7FB2B2CD, 0xEA75759F, 0x1209091B, 0x1D83839E,
+    0x582C2C74, 0x341A1A2E, 0x361B1B2D, 0xDC6E6EB2, 0xB45A5AEE, 0x5BA0A0FB,
+    0xA45252F6, 0x763B3B4D, 0xB7D6D661, 0x7DB3B3CE, 0x5229297B, 0xDDE3E33E,
+    0x5E2F2F71, 0x13848497, 0xA65353F5, 0xB9D1D168, 0x00000000, 0xC1EDED2C,
+    0x40202060, 0xE3FCFC1F, 0x79B1B1C8, 0xB65B5BED, 0xD46A6ABE, 0x8DCBCB46,
+    0x67BEBED9, 0x7239394B, 0x944A4ADE, 0x984C4CD4, 0xB05858E8, 0x85CFCF4A,
+    0xBBD0D06B, 0xC5EFEF2A, 0x4FAAAAE5, 0xEDFBFB16, 0x864343C5, 0x9A4D4DD7,
+    0x66333355, 0x11858594, 0x8A4545CF, 0xE9F9F910, 0x04020206, 0xFE7F7F81,
+    0xA05050F0, 0x783C3C44, 0x259F9FBA, 0x4BA8A8E3, 0xA25151F3, 0x5DA3A3FE,
+    0x804040C0, 0x058F8F8A, 0x3F9292AD, 0x219D9DBC, 0x70383848, 0xF1F5F504,
+    0x63BCBCDF, 0x77B6B6C1, 0xAFDADA75, 0x42212163, 0x20101030, 0xE5FFFF1A,
+    0xFDF3F30E, 0xBFD2D26D, 0x81CDCD4C, 0x180C0C14, 0x26131335, 0xC3ECEC2F,
+    0xBE5F5FE1, 0x359797A2, 0x884444CC, 0x2E171739, 0x93C4C457, 0x55A7A7F2,
+    0xFC7E7E82, 0x7A3D3D47, 0xC86464AC, 0xBA5D5DE7, 0x3219192B, 0xE6737395,
+    0xC06060A0, 0x19818198, 0x9E4F4FD1, 0xA3DCDC7F, 0x44222266, 0x542A2A7E,
+    0x3B9090AB, 0x0B888883, 0x8C4646CA, 0xC7EEEE29, 0x6BB8B8D3, 0x2814143C,
+    0xA7DEDE79, 0xBC5E5EE2, 0x160B0B1D, 0xADDBDB76, 0xDBE0E03B, 0x64323256,
+    0x743A3A4E, 0x140A0A1E, 0x924949DB, 0x0C06060A, 0x4824246C, 0xB85C5CE4,
+    0x9FC2C25D, 0xBDD3D36E, 0x43ACACEF, 0xC46262A6, 0x399191A8, 0x319595A4,
+    0xD3E4E437, 0xF279798B, 0xD5E7E732, 0x8BC8C843, 0x6E373759, 0xDA6D6DB7,
+    0x018D8D8C, 0xB1D5D564, 0x9C4E4ED2, 0x49A9A9E0, 0xD86C6CB4, 0xAC5656FA,
+    0xF3F4F407, 0xCFEAEA25, 0xCA6565AF, 0xF47A7A8E, 0x47AEAEE9, 0x10080818,
+    0x6FBABAD5, 0xF0787888, 0x4A25256F, 0x5C2E2E72, 0x381C1C24, 0x57A6A6F1,
+    0x73B4B4C7, 0x97C6C651, 0xCBE8E823, 0xA1DDDD7C, 0xE874749C, 0x3E1F1F21,
+    0x964B4BDD, 0x61BDBDDC, 0x0D8B8B86, 0x0F8A8A85, 0xE0707090, 0x7C3E3E42,
+    0x71B5B5C4, 0xCC6666AA, 0x904848D8, 0x06030305, 0xF7F6F601, 0x1C0E0E12,
+    0xC26161A3, 0x6A35355F, 0xAE5757F9, 0x69B9B9D0, 0x17868691, 0x99C1C158,
+    0x3A1D1D27, 0x279E9EB9, 0xD9E1E138, 0xEBF8F813, 0x2B9898B3, 0x22111133,
+    0xD26969BB, 0xA9D9D970, 0x078E8E89, 0x339494A7, 0x2D9B9BB6, 0x3C1E1E22,
+    0x15878792, 0xC9E9E920, 0x87CECE49, 0xAA5555FF, 0x50282878, 0xA5DFDF7A,
+    0x038C8C8F, 0x59A1A1F8, 0x09898980, 0x1A0D0D17, 0x65BFBFDA, 0xD7E6E631,
+    0x844242C6, 0xD06868B8, 0x824141C3, 0x299999B0, 0x5A2D2D77, 0x1E0F0F11,
+    0x7BB0B0CB, 0xA85454FC, 0x6DBBBBD6, 0x2C16163A,
+};
+
+static const uint32_t rcon[] = {
+    0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
+    0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
+    // for 128-bit blocks, Rijndael never uses more than 10 rcon values
+};
+
+inline static uint32_t BSWAP32(uint32_t value) {
+#if defined(__clang__) || \
+    (defined(__GNUC__) && \
+     ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5))
+  return __builtin_bswap32(value);
+#else
+  uint32_t Byte0 = value & 0x000000FF;
+  uint32_t Byte1 = value & 0x0000FF00;
+  uint32_t Byte2 = value & 0x00FF0000;
+  uint32_t Byte3 = value & 0xFF000000;
+  return (Byte0 << 24) | (Byte1 << 8) | (Byte2 >> 8) | (Byte3 >> 24);
+#endif
+}
+
+#ifdef ARM
+
+#define STRINFIGY2(b) #b
+#define STRINGIFY(b) STRINFIGY2(b)
+#define ror(v, b)                                         \
+  ({                                                      \
+    uint32_t ret;                                         \
+    if (b)                                                \
+      asm("ror %0, #" STRINGIFY(b) : "=r"(ret) : "0"(v)); \
+    else                                                  \
+      ret = v;                                            \
+    ret;                                                  \
+  })
+
+#else
+
+inline static uint32_t ror(uint32_t val, uint32_t by) {
+  if (!by) return val;
+
+  val = (val >> by) | (val << (32 - by));
+
+  return val;
+}
+
+#endif
+
+int aesInitForEncr(struct AesContext *ctx, const uint32_t *k) {
+  uint32_t i, *ks = ctx->round_key;
+
+  for (i = 0; i < ctx->aes_key_words; i++) {
+    ks[i] = BSWAP32(k[i]);
+  }
+
+  // create round keys for encryption
+  if (AES_128_KEY_WORDS == ctx->aes_key_words) {
+    for (i = 0; i < 10; i++, ks += 4) {
+      ks[4] = ks[0] ^ rcon[i] ^
+              (((uint32_t)FwdSbox[(ks[3] >> 16) & 0xff]) << 24) ^
+              (((uint32_t)FwdSbox[(ks[3] >> 8) & 0xff]) << 16) ^
+              (((uint32_t)FwdSbox[(ks[3] >> 0) & 0xff]) << 8) ^
+              (((uint32_t)FwdSbox[(ks[3] >> 24) & 0xff]) << 0);
+      ks[5] = ks[1] ^ ks[4];
+      ks[6] = ks[2] ^ ks[5];
+      ks[7] = ks[3] ^ ks[6];
+    }
+  } else if (AES_256_KEY_WORDS == ctx->aes_key_words) {
+    for (i = 0; i < 7; i++, ks += 8) {
+      ks[8] = ks[0] ^ rcon[i] ^
+              (((uint32_t)FwdSbox[(ks[7] >> 16) & 0xff]) << 24) ^
+              (((uint32_t)FwdSbox[(ks[7] >> 8) & 0xff]) << 16) ^
+              (((uint32_t)FwdSbox[(ks[7] >> 0) & 0xff]) << 8) ^
+              (((uint32_t)FwdSbox[(ks[7] >> 24) & 0xff]) << 0);
+      ks[9] = ks[1] ^ ks[8];
+      ks[10] = ks[2] ^ ks[9];
+      ks[11] = ks[3] ^ ks[10];
+      if (i == 6) break;
+      ks[12] = ks[4] ^ (((uint32_t)FwdSbox[(ks[11] >> 24) & 0xff]) << 24) ^
+               (((uint32_t)FwdSbox[(ks[11] >> 16) & 0xff]) << 16) ^
+               (((uint32_t)FwdSbox[(ks[11] >> 8) & 0xff]) << 8) ^
+               (((uint32_t)FwdSbox[(ks[11] >> 0) & 0xff]) << 0);
+      ks[13] = ks[5] ^ ks[12];
+      ks[14] = ks[6] ^ ks[13];
+      ks[15] = ks[7] ^ ks[14];
+    }
+  } else {
+    return -1;
+  }
+  return 0;
+}
+
+void aesEncr(struct AesContext *ctx, const uint32_t *src, uint32_t *dst) {
+  uint32_t x0, x1, x2, x3;  // we CAN use an array, but then GCC will not use
+                            // registers. so we use separate vars. sigh...
+  uint32_t *k = ctx->round_key, i;
+
+  // setup
+  x0 = BSWAP32(*src++) ^ *k++;
+  x1 = BSWAP32(*src++) ^ *k++;
+  x2 = BSWAP32(*src++) ^ *k++;
+  x3 = BSWAP32(*src++) ^ *k++;
+
+  // all-but-last round
+  for (i = 0; i < ctx->aes_num_rounds - 1; i++) {
+    uint32_t t0, t1, t2;
+
+    t0 = *k++ ^ ror(FwdTab0[(x0 >> 24) & 0xff], 0) ^
+         ror(FwdTab0[(x1 >> 16) & 0xff], 8) ^
+         ror(FwdTab0[(x2 >> 8) & 0xff], 16) ^
+         ror(FwdTab0[(x3 >> 0) & 0xff], 24);
+
+    t1 = *k++ ^ ror(FwdTab0[(x1 >> 24) & 0xff], 0) ^
+         ror(FwdTab0[(x2 >> 16) & 0xff], 8) ^
+         ror(FwdTab0[(x3 >> 8) & 0xff], 16) ^
+         ror(FwdTab0[(x0 >> 0) & 0xff], 24);
+
+    t2 = *k++ ^ ror(FwdTab0[(x2 >> 24) & 0xff], 0) ^
+         ror(FwdTab0[(x3 >> 16) & 0xff], 8) ^
+         ror(FwdTab0[(x0 >> 8) & 0xff], 16) ^
+         ror(FwdTab0[(x1 >> 0) & 0xff], 24);
+
+    x3 = *k++ ^ ror(FwdTab0[(x3 >> 24) & 0xff], 0) ^
+         ror(FwdTab0[(x0 >> 16) & 0xff], 8) ^
+         ror(FwdTab0[(x1 >> 8) & 0xff], 16) ^
+         ror(FwdTab0[(x2 >> 0) & 0xff], 24);
+
+    x0 = t0;
+    x1 = t1;
+    x2 = t2;
+  }
+
+  // last round
+  *dst++ = BSWAP32(*k++ ^ (((uint32_t)(FwdSbox[(x0 >> 24) & 0xff])) << 24) ^
+                   (((uint32_t)(FwdSbox[(x1 >> 16) & 0xff])) << 16) ^
+                   (((uint32_t)(FwdSbox[(x2 >> 8) & 0xff])) << 8) ^
+                   (((uint32_t)(FwdSbox[(x3 >> 0) & 0xff])) << 0));
+
+  *dst++ = BSWAP32(*k++ ^ (((uint32_t)(FwdSbox[(x1 >> 24) & 0xff])) << 24) ^
+                   (((uint32_t)(FwdSbox[(x2 >> 16) & 0xff])) << 16) ^
+                   (((uint32_t)(FwdSbox[(x3 >> 8) & 0xff])) << 8) ^
+                   (((uint32_t)(FwdSbox[(x0 >> 0) & 0xff])) << 0));
+
+  *dst++ = BSWAP32(*k++ ^ (((uint32_t)(FwdSbox[(x2 >> 24) & 0xff])) << 24) ^
+                   (((uint32_t)(FwdSbox[(x3 >> 16) & 0xff])) << 16) ^
+                   (((uint32_t)(FwdSbox[(x0 >> 8) & 0xff])) << 8) ^
+                   (((uint32_t)(FwdSbox[(x1 >> 0) & 0xff])) << 0));
+
+  *dst++ = BSWAP32(*k++ ^ (((uint32_t)(FwdSbox[(x3 >> 24) & 0xff])) << 24) ^
+                   (((uint32_t)(FwdSbox[(x0 >> 16) & 0xff])) << 16) ^
+                   (((uint32_t)(FwdSbox[(x1 >> 8) & 0xff])) << 8) ^
+                   (((uint32_t)(FwdSbox[(x2 >> 0) & 0xff])) << 0));
+}
+
+int aesCtrInit(struct AesCtrContext *ctx, const void *k, const void *iv,
+               enum AesKeyType key_type) {
+  const uint32_t *p_k;
+  uint32_t aligned_k[AES_BLOCK_WORDS];
+
+  if (AES_128_KEY_TYPE == key_type) {
+    ctx->aes.aes_key_words = AES_128_KEY_WORDS;
+    ctx->aes.aes_num_rounds = AES_128_KEY_NUM_ROUNDS;
+  } else if (AES_256_KEY_TYPE == key_type) {
+    ctx->aes.aes_key_words = AES_256_KEY_WORDS;
+    ctx->aes.aes_num_rounds = AES_256_KEY_NUM_ROUNDS;
+  } else {
+    return -1;
+  }
+  // if key is not aligned, copy it to stack
+  if (IS_ALIGNED(k, uint32_t)) {
+    p_k = (const uint32_t *)k;
+  } else {
+    memcpy(aligned_k, k, sizeof(aligned_k));
+    p_k = aligned_k;
+  }
+
+  memcpy(ctx->iv, iv, sizeof(ctx->iv));
+  return aesInitForEncr(&ctx->aes, p_k);
+}
+
+void aesCtr(struct AesCtrContext *ctx, const void *src, void *dst,
+            size_t data_len) {
+  const uint8_t *p_src_pos = (const uint8_t *)src;
+  uint8_t *p_dst_pos = (uint8_t *)dst;
+  const bool is_src_aligned = IS_ALIGNED(src, uint32_t);
+  const bool is_dst_aligned = IS_ALIGNED(dst, uint32_t);
+  const uint32_t *p_src;
+  uint32_t *p_dst;
+  uint32_t aligned_src[AES_BLOCK_WORDS];
+  uint32_t aligned_dst[AES_BLOCK_WORDS];
+  size_t bytes_to_process = data_len;
+
+  while (bytes_to_process > 0) {
+    size_t chunk_bytes_len =
+        (bytes_to_process < AES_BLOCK_SIZE) ? bytes_to_process : AES_BLOCK_SIZE;
+
+    // if source is not aligned or size is not multiple of words,
+    // copy it to stack
+    if (is_src_aligned && (chunk_bytes_len % sizeof(uint32_t) == 0)) {
+      // Cast to "void *" first to prevent the -Wcast-align compiling
+      // error when casting the uint8_t pointer to the uint32_t pointer.
+      // Note, we already verified the alignment above before casting.
+      p_src = (const uint32_t *)(void *)p_src_pos;
+    } else {
+      memcpy(aligned_src, p_src_pos, chunk_bytes_len);
+      p_src = aligned_src;
+    }
+
+    // if destination is not aligned or full block size,
+    // copy results to stack
+    if (is_dst_aligned && chunk_bytes_len == AES_BLOCK_SIZE) {
+      // Cast to "void *" first to prevent the -Wcast-align compiling
+      // error when casting the uint8_t pointer to the uint32_t pointer.
+      // Note, we already verified the alignment above before casting.
+      p_dst = (uint32_t *)(void *)p_dst_pos;
+    } else {
+      p_dst = aligned_dst;
+    }
+
+    // encrypt/decrypt by AES/CTR mode
+    // encryption and decryption are same operation in AES/CTR mode
+    size_t num_words =
+        (chunk_bytes_len + (sizeof(uint32_t) - 1)) / sizeof(uint32_t);
+    aesEncr(&ctx->aes, ctx->iv, p_dst);
+    for (size_t i = 0; i < num_words; i++) {
+      p_dst[i] ^= p_src[i];
+    }
+
+    // if p_dst is aligned_dst, we used stack
+    // then, copy stack to destination by safe way
+    if (p_dst == aligned_dst) {
+      memcpy(p_dst_pos, aligned_dst, chunk_bytes_len);
+    }
+
+    // update position and left bytes
+    p_dst_pos += chunk_bytes_len;
+    p_src_pos += chunk_bytes_len;
+    bytes_to_process -= chunk_bytes_len;
+
+    // increase AES block counter
+    for (int i = AES_BLOCK_SIZE - 1; i >= 0; i--) {
+      ((uint8_t *)ctx->iv)[i]++;
+      if (((uint8_t *)ctx->iv)[i]) break;
+    }
+  }
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/aes.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/aes.h
new file mode 100644
index 0000000..3ef490e
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/aes.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_AES_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_AES_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Supported AES mode:
+ * - AES/CTR with 128/256-bit key
+ *
+ * External APIs:
+ *  - aesCtrInit() for AES/CTR initialization
+ *  - aesCtr() for AES/CTR encryption and decryption
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define AES_128_KEY_WORDS 4
+#define AES_192_KEY_WORDS 6
+#define AES_256_KEY_WORDS 8
+#define AES_KEY_MAX_WORDS AES_256_KEY_WORDS
+#define AES_BLOCK_WORDS 4
+#define AES_BLOCK_SIZE 16  // in bytes
+// AES key type
+enum AesKeyType { AES_128_KEY_TYPE, AES_192_KEY_TYPE, AES_256_KEY_TYPE };
+
+// basic AES context
+struct AesContext {
+  uint32_t round_key[AES_KEY_MAX_WORDS * 4 + 28];
+  uint32_t aes_key_words;
+  uint32_t aes_num_rounds;
+};
+
+// basic AES block ops
+int aesInitForEncr(struct AesContext *ctx, const uint32_t *k);
+void aesEncr(struct AesContext *ctx, const uint32_t *src, uint32_t *dst);
+
+// AES-CTR context
+struct AesCtrContext {
+  struct AesContext aes;
+  uint32_t iv[AES_BLOCK_WORDS];
+};
+
+/**
+ * aesCtrInit:
+ * @ctx: AES/CTR context
+ * @k: AES encryption/decryption key.
+ *     the size must match with the key type.
+ * @iv: AES/CTR 16 byte counter block.
+ *     the size must fit exactly 16 bytes.
+ * @key_type: AES encryption/decryption key type.
+ *     it must be either AES_128_KEY_TYPE or AES_256_KEY_TYPE.
+ *
+ * Initialize AES/CTR by
+ * creating round keys and copying the counter blocks
+ *
+ * Returns: 0 if key_type is valid otherwise -1
+ */
+int aesCtrInit(struct AesCtrContext *ctx, const void *k, const void *iv,
+               enum AesKeyType key_type);
+
+/**
+ * aesCtr:
+ * @ctx: AES/CTR context initialized
+ * @src: source. plain text for encryption or cipher text for decryption.
+ *       the size must be same as the size of destination.
+ * @dst: destination. cipher text for encryption or plain text for decryption.
+ *       the size must be same as the size of source.
+ * @data_len: number of bytes of the source or destination byte array
+ *
+ * Encrypt/Decrypt by AES/CTR mode
+ *
+ * Returns:
+ */
+void aesCtr(struct AesCtrContext *ctx, const void *src, void *dst,
+            size_t data_len);
+
+#ifdef __cplusplus
+}
+#endif
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_AES_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.c b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.c
new file mode 100644
index 0000000..8251cbf
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 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 "location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.h"
+
+#include <string.h>
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/hmac.h"
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/sha2.h"
+
+static void hkdfExpand(const void *inPrk, const size_t prkLen, void *outKm,
+                       const size_t okmLen) {
+  uint8_t buf[SHA2_HASH_SIZE + 1];
+  uint8_t exp_hmac[SHA2_HASH_SIZE];
+  memset(buf, 0, sizeof(buf));
+  memset(exp_hmac, 0, sizeof(exp_hmac));
+
+  // calculates how many sha256 hash blocks are required for the output length
+  const size_t num_blocks = (okmLen + SHA2_HASH_SIZE - 1) / SHA2_HASH_SIZE;
+  if (num_blocks >= 256u) return;
+
+  // initializes hmac context with the input pseudorandom key
+  struct HmacContext hmacCtx;
+  hmacInit(&hmacCtx, inPrk, prkLen);
+
+  /**
+   * https://tools.ietf.org/html/rfc5869#section-2.3
+   * The output OKM is calculated as follows:
+   *    N = ceil(L/HashLen)
+   *    T = T(1) | T(2) | T(3) | ... | T(N)
+   *    OKM = first L octets of T
+   *    where:
+   *    T(0) = empty string (zero length)
+   *    T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
+   *    T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
+   *    T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
+   */
+
+  for (size_t i = 0; i < num_blocks; i++) {
+    size_t block_input_len = 0;
+    size_t block_output_len;
+
+    if (i != 0) {
+      memcpy(buf, exp_hmac, sizeof(exp_hmac));
+      block_input_len = sizeof(exp_hmac);
+    }
+    *(buf + block_input_len++) = (uint8_t)(i + 1);
+    // initializes hash context without refreshing HMAC keys
+    hmacUpdateHashInit(&hmacCtx, buf, block_input_len);
+    hmacFinish(&hmacCtx, exp_hmac, sizeof(exp_hmac));
+
+    if (SHA2_HASH_SIZE < okmLen - i * SHA2_HASH_SIZE) {
+      block_output_len = SHA2_HASH_SIZE;
+    } else {
+      block_output_len = okmLen - i * SHA2_HASH_SIZE;
+    }
+    memcpy((uint8_t *)outKm + i * SHA2_HASH_SIZE, exp_hmac, block_output_len);
+  }
+}
+
+void hkdf(const void *inSalt, const size_t saltLen, const void *inKm,
+          const size_t ikmLen, void *outKm, const size_t okmLen) {
+  // refers to hkdf implementation in key master
+  // https://source.corp.google.com/aosp-android11/system/keymaster/km_openssl/hkdf.cpp
+
+  if (outKm == NULL || okmLen == 0) return;
+
+  // pseudorandom key
+  uint8_t prk_hmac[SHA2_HASH_SIZE];
+
+  /**
+   * Step 1. Extract: PRK = HMAC-SHA256(salt, IKM)
+   * https://tools.ietf.org/html/rfc5869#section-2.2
+   * Generates a pseudorandom key by HMAC-SHA256
+   */
+  hmacSha256(inSalt, saltLen, inKm, ikmLen, prk_hmac, sizeof(prk_hmac));
+
+  /**
+   * Step 2. Expand: OUTPUT = HKDF-Expand(PRK, L)
+   * https://tools.ietf.org/html/rfc5869#section-2.3
+   * Note: the optional info is not used for Nearby
+   */
+  hkdfExpand(prk_hmac, sizeof(prk_hmac), outKm, okmLen);
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.h
new file mode 100644
index 0000000..4f920f3
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_HKDF_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_HKDF_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Supported HKDF mode:
+ * - HKDF-HMAC-SHA256
+ *
+ * External a single API:
+ *  - hkdf() for extracting and expanding keys derivation function
+ */
+
+#include <stddef.h>
+
+/**
+ * hkdf:
+ * @inSalt: input salt byte array
+ * @saltLen: number of bytes of the input salt array
+ * @inKm: input key material byte array
+ * @ikmLen: number of bytes of the input key material byte array
+ * @outKm: output key material byte array
+ * @okmLen: number of bytes of the output key material byte array
+ *          okmLen should be less than 8161
+ *          (= 256 * SHA2_HASH_SIZE - SHA2_HASH_SIZE + 1)
+ *
+ * Initializes hmac keys and hash context internally
+ * Updates input data to the context
+ * Generates 32 bytes keyed-hash and copy to the output byte array as much as
+ * min(SHA2_HASH_SIZE, hashLen)
+ *
+ * Returns:
+ */
+void hkdf(const void *inSalt, size_t saltLen, const void *inKm, size_t ikmLen,
+          void *outKm, size_t okmLen);
+#ifdef __cplusplus
+}
+#endif
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_HKDF_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hmac.c b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hmac.c
new file mode 100644
index 0000000..f8aa126
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hmac.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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 "location/lbs/contexthub/nanoapps/nearby/crypto/hmac.h"
+
+#include <string.h>
+
+static void sha2InitHmacKeyUpdate(struct HmacContext *ctx) {
+  // initialize sha2 context and update hmac keys to the sha2 context
+  sha2init(&ctx->sha2ctx);
+  sha2processBytes(&ctx->sha2ctx, ctx->k_ipad, sizeof(ctx->k_ipad));
+}
+
+void hmacInit(struct HmacContext *ctx, const void *inKey, const size_t keyLen) {
+  // initialize hmac keys
+  memset(ctx->k, 0, sizeof(ctx->k));
+  memset(ctx->k_ipad, 0x36, sizeof(ctx->k_ipad));
+  memset(ctx->k_opad, 0x5c, sizeof(ctx->k_opad));
+  memcpy(ctx->k, inKey, keyLen);
+  ctx->is_hmac_updated = false;
+
+  // XOR key with ipad and opad values
+  for (size_t i = 0; i < SHA2_BLOCK_SIZE; i++) {
+    ctx->k_ipad[i] ^= ctx->k[i];
+    ctx->k_opad[i] ^= ctx->k[i];
+  }
+  // initialize sha2 context and update hmac keys to the sha2 context
+  sha2InitHmacKeyUpdate(ctx);
+}
+
+void hmacUpdate(struct HmacContext *ctx, const void *inData,
+                const size_t dataLen) {
+  // update the input data to the sha2 context
+  sha2processBytes(&ctx->sha2ctx, inData, dataLen);
+
+  if (!ctx->is_hmac_updated) {
+    ctx->is_hmac_updated = true;
+  }
+}
+
+void hmacUpdateHashInit(struct HmacContext *ctx, const void *inData,
+                        const size_t dataLen) {
+  // initialize sha2 context and update hmac keys to the sha2 context if it was
+  // updated
+  if (ctx->is_hmac_updated) {
+    sha2InitHmacKeyUpdate(ctx);
+  }
+  hmacUpdate(ctx, inData, dataLen);
+}
+
+void hmacFinish(struct HmacContext *ctx, void *outHash, const size_t hashLen) {
+  // finish inner sha256
+  sha2finish(&ctx->sha2ctx, ctx->ihash, sizeof(ctx->ihash));
+
+  // perform outer sha256
+  sha2init(&ctx->sha2ctx);
+  sha2processBytes(&ctx->sha2ctx, ctx->k_opad, sizeof(ctx->k_opad));
+  sha2processBytes(&ctx->sha2ctx, ctx->ihash, sizeof(ctx->ihash));
+  sha2finish(&ctx->sha2ctx, outHash, (uint32_t)hashLen);
+}
+
+void hmacSha256(const void *inKey, const size_t keyLen, const void *inData,
+                const size_t dataLen, void *outHash, const size_t hashLen) {
+  struct HmacContext ctx;
+  hmacInit(&ctx, inKey, keyLen);
+  hmacUpdate(&ctx, inData, dataLen);
+  hmacFinish(&ctx, outHash, hashLen);
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hmac.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hmac.h
new file mode 100644
index 0000000..c1dd0ab
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/hmac.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_HMAC_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_HMAC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Supported HMAC mode:
+ * - HMAC-SHA256
+ *
+ * External separated APIs:
+ *  - hmacInit() for initializing hmac keys and hash context
+ *  - hmacUpdate() for updating input data
+ *  - hmacUpdateHashInit() for initializing hash context and updating input data
+ *  - hmacFinish() for generating HMAC-SHA256 keyed-hash output
+ *
+ * External a single API:
+ *  - hmacSha256() for performing the separated three APIs at a time:
+ *    hmacInit(), hmacUpdate(), and hmacFinish()
+ */
+#include <stdbool.h>
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/sha2.h"
+
+struct HmacContext {
+  uint8_t k[SHA2_BLOCK_SIZE];
+  uint8_t k_ipad[SHA2_BLOCK_SIZE];
+  uint8_t k_opad[SHA2_BLOCK_SIZE];
+  uint8_t ihash[SHA2_HASH_SIZE];
+  struct Sha2Context sha2ctx;
+  bool is_hmac_updated;
+};
+
+/**
+ * hmacSha256:
+ * @inKey: input key byte array
+ * @keyLen: number of bytes of the input key array
+ * @inData: input data byte array to hash
+ * @dataLen: number of bytes of the input byte array
+ * @outHash: output keyed-hash byte array
+ * @hashLen: number of bytes of the output byte array
+ *
+ * Initializes hmac keys and hash context internally
+ * Updates input data to the context
+ * Generates 32 bytes keyed-hash and copy to the output byte array as much as
+ * min(SHA2_HASH_SIZE, hashLen)
+ *
+ * Returns:
+ */
+void hmacSha256(const void *inKey, size_t keyLen, const void *inData,
+                size_t dataLen, void *outHash, size_t hashLen);
+
+/**
+ * hmacInit:
+ * @ctx: HMAC context
+ * @inKey: input key byte array
+ * @keyLen: number of bytes of the input key array
+ *
+ * Initializes hmac keys and hash context
+ *
+ * Returns:
+ */
+void hmacInit(struct HmacContext *ctx, const void *inKey, size_t keyLen);
+
+/**
+ * hmacUpdate:
+ * @ctx: HMAC context
+ * @inData: input data byte array to hash
+ * @dataLen: number of bytes of the input byte array
+ *
+ * Updates input data to the context
+ *
+ * Returns:
+ */
+void hmacUpdate(struct HmacContext *ctx, const void *inData, size_t dataLen);
+
+/**
+ * hmacUpdateHashInit:
+ * @ctx: HMAC context
+ * @inData: input data byte array to hash
+ * @dataLen: number of bytes of the input byte array
+ *
+ * Initializes hash context and updates input data to the context
+ * Can be used for initializing hash context without refreshing HMAC keys
+ *
+ * Returns:
+ */
+void hmacUpdateHashInit(struct HmacContext *ctx, const void *inData,
+                        size_t dataLen);
+
+/**
+ * hmacFinish:
+ * @ctx: HMAC context
+ * @outHash: output keyed-hash byte array
+ * @hashLen: number of bytes of the output byte array
+ *
+ * Generates 32 bytes keyed-hash and copy to the output byte array as much as
+ * min(SHA2_HASH_SIZE, hashLen)
+ *
+ * Returns:
+ */
+void hmacFinish(struct HmacContext *ctx, void *outHash, size_t hashLen);
+#ifdef __cplusplus
+}
+#endif
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_HMAC_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/sha2.c b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/sha2.c
new file mode 100644
index 0000000..0b4fc75
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/sha2.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 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 "location/lbs/contexthub/nanoapps/nearby/crypto/sha2.h"
+
+#include <string.h>
+
+inline static uint32_t BSWAP32(uint32_t value) {
+#if defined(__clang__) || \
+    (defined(__GNUC__) && \
+     ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || __GNUC__ >= 5))
+  return __builtin_bswap32(value);
+#else
+  uint32_t Byte0 = value & 0x000000FF;
+  uint32_t Byte1 = value & 0x0000FF00;
+  uint32_t Byte2 = value & 0x00FF0000;
+  uint32_t Byte3 = value & 0xFF000000;
+  return (Byte0 << 24) | (Byte1 << 8) | (Byte2 >> 8) | (Byte3 >> 24);
+#endif
+}
+
+void sha2init(struct Sha2Context *ctx) {
+  ctx->h[0] = 0x6a09e667;
+  ctx->h[1] = 0xbb67ae85;
+  ctx->h[2] = 0x3c6ef372;
+  ctx->h[3] = 0xa54ff53a;
+  ctx->h[4] = 0x510e527f;
+  ctx->h[5] = 0x9b05688c;
+  ctx->h[6] = 0x1f83d9ab;
+  ctx->h[7] = 0x5be0cd19;
+  ctx->msgLen = 0;
+  ctx->bufBytesUsed = 0;
+}
+
+#ifdef ARM
+
+#define STRINFIGY2(b) #b
+#define STRINGIFY(b) STRINFIGY2(b)
+#define ror(v, b)                                         \
+  ({                                                      \
+    uint32_t ret;                                         \
+    if (b)                                                \
+      asm("ror %0, #" STRINGIFY(b) : "=r"(ret) : "0"(v)); \
+    else                                                  \
+      ret = v;                                            \
+    ret;                                                  \
+  })
+
+#else
+
+inline static uint32_t ror(uint32_t val, uint32_t by) {
+  if (!by) return val;
+
+  val = (val >> by) | (val << (32 - by));
+
+  return val;
+}
+
+#endif
+
+static void sha2processBlock(struct Sha2Context *ctx) {
+  static const uint32_t k[] = {
+      0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
+      0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+      0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+      0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+      0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+      0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+      0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+      0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+      0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+      0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+      0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+  };
+  uint32_t i, a, b, c, d, e, f, g, h;
+
+  // input and output streams are little-endian
+  // SHA specification uses big-endian
+  // byteswap the input
+  for (i = 0; i < SHA2_BLOCK_SIZE / sizeof(uint32_t); i++)
+    ctx->w[i] = BSWAP32(ctx->w[i]);
+
+  // expand input
+  for (; i < SHA2_WORDS_CTX_SIZE; i++) {
+    uint32_t s0 = ror(ctx->w[i - 15], 7) ^ ror(ctx->w[i - 15], 18) ^
+                  (ctx->w[i - 15] >> 3);
+    uint32_t s1 =
+        ror(ctx->w[i - 2], 17) ^ ror(ctx->w[i - 2], 19) ^ (ctx->w[i - 2] >> 10);
+    ctx->w[i] = ctx->w[i - 16] + s0 + ctx->w[i - 7] + s1;
+  }
+
+  // init working variables
+  a = ctx->h[0];
+  b = ctx->h[1];
+  c = ctx->h[2];
+  d = ctx->h[3];
+  e = ctx->h[4];
+  f = ctx->h[5];
+  g = ctx->h[6];
+  h = ctx->h[7];
+
+  // 64 rounds
+  for (i = 0; i < 64; i++) {
+    uint32_t s1 = ror(e, 6) ^ ror(e, 11) ^ ror(e, 25);
+    uint32_t ch = (e & f) ^ ((~e) & g);
+    uint32_t temp1 = h + s1 + ch + k[i] + ctx->w[i];
+    uint32_t s0 = ror(a, 2) ^ ror(a, 13) ^ ror(a, 22);
+    uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
+    uint32_t temp2 = s0 + maj;
+
+    h = g;
+    g = f;
+    f = e;
+    e = d + temp1;
+    d = c;
+    c = b;
+    b = a;
+    a = temp1 + temp2;
+  }
+
+  // put result back into context
+  ctx->h[0] += a;
+  ctx->h[1] += b;
+  ctx->h[2] += c;
+  ctx->h[3] += d;
+  ctx->h[4] += e;
+  ctx->h[5] += f;
+  ctx->h[6] += g;
+  ctx->h[7] += h;
+}
+
+void sha2processBytes(struct Sha2Context *ctx, const void *inData,
+                      size_t dataLen) {
+  const uint8_t *inBytes = (const uint8_t *)inData;
+
+  ctx->msgLen += dataLen;
+  while (dataLen) {
+    size_t bytesToCopy;
+
+    // step 1: copy data into context if there is space & there is data
+    bytesToCopy = dataLen;
+    if (bytesToCopy > SHA2_BLOCK_SIZE - ctx->bufBytesUsed)
+      bytesToCopy = SHA2_BLOCK_SIZE - ctx->bufBytesUsed;
+    memcpy(ctx->b + ctx->bufBytesUsed, inBytes, bytesToCopy);
+    inBytes += bytesToCopy;
+    dataLen -= bytesToCopy;
+    ctx->bufBytesUsed += bytesToCopy;
+
+    // step 2: if there is a full block, process it
+    if (ctx->bufBytesUsed == SHA2_BLOCK_SIZE) {
+      sha2processBlock(ctx);
+      ctx->bufBytesUsed = 0;
+    }
+  }
+}
+
+void sha2finish(struct Sha2Context *ctx, void *outHash, uint32_t hashLen) {
+  uint8_t appendend = 0x80;
+  uint64_t dataLenInBits = ctx->msgLen * 8;
+  uint32_t minHashLen;
+
+  // append the one
+  sha2processBytes(ctx, &appendend, 1);
+
+  // append the zeroes
+  appendend = 0;
+  while (ctx->bufBytesUsed != 56) sha2processBytes(ctx, &appendend, 1);
+
+  // append the length in bits (we can safely write into context since we're
+  // sure where to write to (we're definitely 56-bytes into a block)
+  for (uint32_t i = 0; i < 8; i++, dataLenInBits >>= 8)
+    ctx->b[63 - i] = (uint8_t)(dataLenInBits);
+
+  // process last block
+  sha2processBlock(ctx);
+
+  // input and output streams are little-endian
+  // SHA specification uses big-endian
+  // copy the final hash to the output after byteswap
+  for (uint32_t i = 0; i < sizeof(ctx->h) / sizeof(uint32_t); i++)
+    ctx->h[i] = BSWAP32(ctx->h[i]);
+
+  minHashLen = (hashLen > SHA2_HASH_SIZE) ? SHA2_HASH_SIZE : hashLen;
+  memcpy(outHash, ctx->h, minHashLen);
+}
+
+void sha256(const void *inData, const uint32_t dataLen, void *outHash,
+            const uint32_t hashLen) {
+  struct Sha2Context ctx;
+  sha2init(&ctx);
+  sha2processBytes(&ctx, inData, dataLen);
+  sha2finish(&ctx, outHash, hashLen);
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/sha2.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/sha2.h
new file mode 100644
index 0000000..85e8f51
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto/sha2.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_SHA2_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_SHA2_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Supported SHA2 mode:
+ * - SHA256
+ *
+ * External separated APIs:
+ *  - sha2init() for SHA256 initialization
+ *  - sha2processBytes() for updating input data
+ *  - sha2finish() for generating SHA256 hash output
+ *
+ * External a single API:
+ *  - sha256() for performing the separated three APIs at a time
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHA2_BLOCK_SIZE 64U      // in bytes
+#define SHA2_WORDS_CTX_SIZE 64U  // in words
+
+#define SHA2_HASH_SIZE 32U  // in bytes
+#define SHA2_HASH_WORDS 8U  // in words
+
+struct Sha2Context {
+  uint32_t h[SHA2_HASH_WORDS];
+  size_t msgLen;
+  union {
+    uint32_t w[SHA2_WORDS_CTX_SIZE];
+    uint8_t b[SHA2_BLOCK_SIZE];
+  };
+  uint8_t bufBytesUsed;
+};
+
+/**
+ * sha256:
+ * @inData: input data byte array to hash
+ * @dataLen: number of bytes of the input byte array
+ * @outHash: output hash byte array
+ * @hashLen: number of bytes of the output byte array
+ *
+ * Initializes SHA256 context internally
+ * Updates input data to the context
+ * Generates 32 bytes hash and copy to the output byte array as much as
+ * min(SHA2_HASH_SIZE, hash_len)
+ *
+ * Returns:
+ */
+void sha256(const void *inData, uint32_t dataLen, void *outHash,
+            uint32_t hashLen);
+
+/**
+ * sha2init:
+ * @ctx: SHA256 context
+ *
+ * Initializes the SHA256 context
+ *
+ * Returns:
+ */
+void sha2init(struct Sha2Context *ctx);
+
+/**
+ * sha2processBytes:
+ * @ctx: SHA256 context initialized
+ * @inData: input data byte array to hash
+ * @dataLen: number of bytes of the input byte array
+ *
+ * Updates input data to the context
+ *
+ * Returns:
+ */
+void sha2processBytes(struct Sha2Context *ctx, const void *inData,
+                      size_t dataLen);
+
+/**
+ * sha2finish:
+ * @ctx: SHA256 context initialized
+ * @outHash: output hash byte array
+ * @hashLen: number of bytes of the output byte array
+ *
+ * Generates 32 bytes hash and copy to the output byte array as much as
+ * min(SHA2_HASH_SIZE, hash_len)
+ *
+ * Returns:
+ */
+void sha2finish(struct Sha2Context *ctx, void *outHash, uint32_t hashLen);
+#ifdef __cplusplus
+}
+#endif
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_SHA2_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.cc
new file mode 100644
index 0000000..b73d282
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.cc
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.h"
+
+#include <chre/util/macros.h>
+
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][CRYPTO]"
+
+namespace nearby {
+
+bool CryptoNonEncryption::decrypt(const ByteArray &input,
+                                  const ByteArray & /*salt*/,
+                                  const ByteArray & /*key*/,
+                                  ByteArray &output) const {
+  if (output.length < input.length) {
+    LOGE("output length %d  less than input length %d",
+         (unsigned int)output.length, (unsigned int)input.length);
+    return false;
+  }
+  memcpy(output.data, input.data, input.length);
+  output.length = input.length;
+  return true;
+}
+
+bool CryptoNonEncryption::verify(const ByteArray & /*input*/,
+                                 const ByteArray & /*key*/,
+                                 const ByteArray & /*signature*/) const {
+  return true;
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.h
new file mode 100644
index 0000000..5d8f6e1
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_NON_ENCRYPTION_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_NON_ENCRYPTION_H_
+
+#include <cstring>
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto.h"
+
+namespace nearby {
+
+// Implements Crypto interface without encryption, i.e. echoing back the input.
+class CryptoNonEncryption : public Crypto {
+ public:
+  // Echos input back in output. Returns the size of input. Returns 0 if
+  // output_size is less than input_size.
+  bool decrypt(const ByteArray &input, const ByteArray &salt,
+               const ByteArray &key, ByteArray &output) const override;
+
+  bool verify(const ByteArray &input, const ByteArray &key,
+              const ByteArray &signature) const override;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_CRYPTO_NON_ENCRYPTION_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.cc
new file mode 100644
index 0000000..af8c51c
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.cc
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 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 implementation below follows Fast Pair Spec
+ * https://developers.google.com/nearby/fast-pair/specifications/service/provider#provider_advertising_signal
+ * and SASS spec
+ * https://developers.devsite.corp.google.com/nearby/fast-pair/early-access/specifications/extensions/sass#SmartAudioSourceSwitching
+ */
+
+#include "location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.h"
+
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][FAST_PAIR_ACCOUNT_DATA]"
+
+namespace nearby {
+
+namespace {
+constexpr uint8_t kAccountFilterUiType = 0b0000;
+constexpr uint8_t kAccountFilterNoUiType = 0b0010;
+constexpr uint8_t kSaltType = 0b0001;
+constexpr uint8_t kBatteryUiType = 0b0011;
+constexpr uint8_t kBatteryNoUiType = 0b0100;
+constexpr uint8_t kRrdType = 0b0110;
+
+struct Header {
+  Header(uint8_t length, uint8_t type) : length(length), type(type) {}
+
+  static Header Parse(uint8_t byte) {
+    // The byte has 4 bits length and 4 bits type as 0bLLLLTTTT.
+    return Header((byte & 0xF0) >> 4, byte & 0x0F);
+  }
+
+  const uint8_t length;
+  const uint8_t type;
+};
+
+}  // namespace
+
+FastPairAccountData FastPairAccountData::Parse(const ByteArray &service_data) {
+  const FastPairAccountData invalid_data(false, 0, ByteArray(), ByteArray(),
+                                         ByteArray(), ByteArray());
+  if (service_data.length == 0) {
+    return invalid_data;
+  }
+  uint8_t *data = service_data.data;
+  // First byte is Version and flags(0bVVVVFFFF), which splits the byte the same
+  // way as Header.
+  const uint8_t version = Header::Parse(data[0]).length;
+  ByteArray filter;
+  ByteArray salt;
+  ByteArray battery;
+  ByteArray rrd;
+  // Each element has one byte header plus variable-length value.
+  for (size_t i = 1; i < service_data.length;) {
+    const Header header = Header::Parse(data[i]);
+    // Available buffer for field data.
+    size_t available_buf_size = service_data.length - i - 1;
+    if (header.length > available_buf_size) {
+      LOGE(
+          "Invalid Fast Pair service data. Field length %d exceeds service "
+          "data buffer size %zu",
+          header.length, available_buf_size);
+      return invalid_data;
+    }
+    // Element one byte header + value, used by battery and RRD.
+    ByteArray header_element(&data[i], header.length + 1);
+    i++;  // Move data index to the byte next to header.
+    ByteArray element(&data[i], header.length);
+    switch (header.type) {
+      case kAccountFilterNoUiType:
+      case kAccountFilterUiType:
+        filter = element;
+        break;
+      case kSaltType:
+        salt = element;
+        break;
+      case kBatteryUiType:
+      case kBatteryNoUiType:
+        battery = header_element;
+        break;
+      case kRrdType:
+        rrd = header_element;
+        break;
+    }
+    i += header.length;
+  }
+  // filter and salt are required.
+  if (filter.length == 0 || salt.length == 0) {
+    LOGE(
+        "Invalid Fast Pair service data with filter length %zu and salt length "
+        "%zu.",
+        filter.length, salt.length);
+    return invalid_data;
+  } else {
+    return FastPairAccountData(true, version, filter, salt, battery, rrd);
+  }
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.h
new file mode 100644
index 0000000..d02c185
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FAST_PAIR_ACCOUNT_DATA_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FAST_PAIR_ACCOUNT_DATA_H_
+
+#include "location/lbs/contexthub/nanoapps/nearby/byte_array.h"
+
+namespace nearby {
+
+struct FastPairAccountData {
+  // Returns a FastPairAccountData by parsing the BLE service_data.
+  // Note, the returned FastPairAccountData has the lifetime of service_data,
+  // and the return will be invalid if service_data is altered/destructed after
+  // parsing.
+  static FastPairAccountData Parse(const ByteArray &service_data);
+
+  FastPairAccountData(bool is_valid, uint8_t version, ByteArray filter,
+                      ByteArray salt, ByteArray battery, ByteArray rrd)
+      : is_valid(is_valid),
+        version(version),
+        filter(filter),
+        salt(salt),
+        battery(battery),
+        rrd(rrd) {}
+
+  const bool is_valid;
+  const uint8_t version;
+  const ByteArray filter;
+  const ByteArray salt;
+  // battery includes the Length-Type header.
+  // See
+  // https://developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification#BatteryNotification.
+  const ByteArray battery;
+  // RRD includes the Length-Type header.
+  const ByteArray rrd;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FAST_PAIR_ACCOUNT_DATA_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.cc
new file mode 100644
index 0000000..9407495
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.cc
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.h"
+
+#include <cinttypes>
+#include <iterator>
+
+#include "location/lbs/contexthub/nanoapps/nearby/bloom_filter.h"
+#include "location/lbs/contexthub/nanoapps/nearby/fast_pair_account_data.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][FAST_PAIR_FILTER]"
+
+namespace nearby {
+
+constexpr uint16_t kFastPairUuid = 0xFE2C;
+constexpr uint16_t kFastPairUuidFirstByte = 0xFE;
+constexpr uint16_t kFastPairUuidSecondByte = 0x2C;
+constexpr size_t kFpAccountKeyLength = 16;
+constexpr size_t kFastPairModelIdLength = 3;
+constexpr uint8_t kAccountKeyFirstByte[] = {
+    0b00000100,  // Default.
+    0b00000101,  // Recent.
+    0b00000110   // In use.
+};
+// The key fed into Bloom Filter is the concatenation of account key, SALT, and
+// RRD, and the max length is kFpAccountKeyLength + 3 x 2^4. SALT,Battery, or
+// RRD length is less than 2^4 according to the spec.
+constexpr size_t kMaxBloomFilterKeyLength = kFpAccountKeyLength + 48;
+
+// Returns true if data_element is Fast Pair account.
+bool IsAccountDataElement(const nearby_DataElement &data_element) {
+  return (data_element.has_key &&
+          data_element.key ==
+              nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY &&
+          data_element.has_value && data_element.has_value_length &&
+          data_element.value_length == kFpAccountKeyLength);
+}
+
+// Returns true if filter include Fast Pair initial pair.
+// Otherwise, returns false and saves account keys in account_keys.
+bool CheckFastPairFilter(const nearby_BleFilter &filter,
+                         chre::DynamicVector<const uint8_t *> *account_keys) {
+  bool has_initial_pair = false;
+  for (int i = 0; i < filter.data_element_count; i++) {
+    if (IsAccountDataElement(filter.data_element[i])) {
+      uint8_t account_value = 0;
+      for (size_t j = 0; j < kFpAccountKeyLength; j++) {
+        account_value |= filter.data_element[i].value[j];
+      }
+      if (account_value == 0) {
+        // Account value for initial pair are all zeros.
+        LOGD("Find Fast Pair initial pair filter.");
+        has_initial_pair = true;
+      } else {
+        account_keys->push_back(
+            static_cast<const uint8_t *>(filter.data_element[i].value));
+      }
+    }
+  }
+  return has_initial_pair;
+}
+
+// Fills a Fast Pair filtered result with service_data and account_key.
+// Passes account_key as nullptr for initial pair.
+// Returns false when filling failed due to memory overflow.
+bool FillResult(const BleServiceData &service_data, const uint8_t *account_key,
+                nearby_BleFilterResult *result) {
+  if (result->data_element_count >= std::size(result->data_element)) {
+    LOGE("Failed to fill Fast Pair result. Data Elements buffer full");
+    return false;
+  }
+  // Sends the service data, which will be re-parsed by Fast Pair in GmsCore.
+  if (!result->has_ble_service_data) {
+    // the buffer size of 'result->ble_service_data' is defined in
+    // ble_filter.options, which must be large enough to hold one byte service
+    // length, two bytes UUID, and the service data.
+    if (account_key == nullptr) {
+      // Initial Pair service data only has model ID.
+      static_assert(kFastPairModelIdLength + 3 <=
+                    sizeof(result->ble_service_data));
+    } else if (service_data.length + 3 > sizeof(result->ble_service_data)) {
+      LOGE("Fast Pair BLE service data overflows the result buffer.");
+      return false;
+    }
+    result->has_ble_service_data = true;
+    // First byte is the length of service data plus two bytes UUID.
+    result->ble_service_data[0] = service_data.length + 2;
+    // Second and third byte includes the FP 3.2 UUID.
+    result->ble_service_data[1] = kFastPairUuidFirstByte;
+    result->ble_service_data[2] = kFastPairUuidSecondByte;
+    // The rest bytes are service data.
+    memcpy(result->ble_service_data + 3, service_data.data,
+           service_data.length);
+  }
+
+  // Capacity has been checked above.
+  size_t de_index = result->data_element_count;
+  result->data_element[de_index].has_key = true;
+  result->data_element[de_index].key =
+      nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY;
+  result->data_element[de_index].has_value_length = true;
+  result->data_element[de_index].value_length = kFpAccountKeyLength;
+  result->data_element[de_index].has_value = true;
+  CHRE_ASSERT(sizeof(result->data_element[de_index].value) >=
+              kFpAccountKeyLength);
+  if (account_key != nullptr) {
+    memcpy(result->data_element[de_index].value, account_key,
+           kFpAccountKeyLength);
+  }
+  result->data_element_count++;
+
+  result->has_result_type = true;
+  result->result_type = nearby_BleFilterResult_ResultType_RESULT_FAST_PAIR;
+  return true;
+}
+
+bool MatchInitialFastPair(const BleServiceData &ble_service_data,
+                          nearby_BleFilterResult *result) {
+  if (ble_service_data.uuid != kFastPairUuid) {
+    LOGD("Not Fast Pair service data.");
+    return false;
+  }
+  // Service data for initial pair only contains the three-byte model id.
+  if (ble_service_data.length != kFastPairModelIdLength) {
+    LOGD(
+        "Not a initial pair whose BLE service data only includes a model "
+        "id of three bytes.");
+    return false;
+  }
+  return FillResult(ble_service_data, nullptr, result);
+}
+
+bool MatchSubsequentPair(const uint8_t *account_key,
+                         const BleServiceData &service_data,
+                         nearby_BleFilterResult *result) {
+  LOGD("MatchSubsequentPair");
+  if (service_data.uuid != kFastPairUuid) {
+    LOGD("service data uuid %x is not Fast Pair uuid %x", service_data.uuid,
+         kFastPairUuid);
+    return false;
+  }
+  FastPairAccountData account_data = FastPairAccountData::Parse(
+      ByteArray(const_cast<uint8_t *>(service_data.data), service_data.length));
+  if (!account_data.is_valid) {
+    return false;
+  }
+  LOGD_SENSITIVE_INFO("Fast Pair Bloom Filter:");
+  for (size_t i = 0; i < account_data.filter.length; i++) {
+    LOGD_SENSITIVE_INFO("%x", account_data.filter.data[i]);
+  }
+  if (account_data.filter.length > BloomFilter::kMaxBloomFilterByteSize) {
+    LOGE("Subsequent Pair Bloom Filter size %zu exceeds: %zu",
+         account_data.filter.length, BloomFilter::kMaxBloomFilterByteSize);
+    return false;
+  }
+  BloomFilter bloom_filter =
+      BloomFilter(account_data.filter.data, account_data.filter.length);
+  // SALT, BATTERY, and RRD length must be less than 2^4 in FastPairAccountData
+  // implementation based on the spec.
+  CHRE_ASSERT((kFpAccountKeyLength + account_data.salt.length +
+               account_data.battery.length + account_data.rrd.length) <=
+              kMaxBloomFilterKeyLength);
+  uint8_t key[kMaxBloomFilterKeyLength];
+  size_t pos = 0;
+  memcpy(&key[pos], account_key, kFpAccountKeyLength);
+  pos += kFpAccountKeyLength;
+  memcpy(&key[pos], account_data.salt.data, account_data.salt.length);
+  pos += account_data.salt.length;
+  LOGD_SENSITIVE_INFO("Fast Pair subsequent pair SALT");
+  for (size_t i = 0; i < account_data.salt.length; i++) {
+    LOGD_SENSITIVE_INFO("%x", account_data.salt.data[i]);
+  }
+  memcpy(&key[pos], account_data.battery.data, account_data.battery.length);
+  pos += account_data.battery.length;
+  LOGD_SENSITIVE_INFO("Fast Pair subsequent pair battery:");
+  for (size_t i = 0; i < account_data.battery.length; i++) {
+    LOGD_SENSITIVE_INFO("%x", account_data.battery.data[i]);
+  }
+  if (account_data.version == 1) {
+    memcpy(&key[pos], account_data.rrd.data, account_data.rrd.length);
+    pos += account_data.rrd.length;
+    LOGD_SENSITIVE_INFO("Fast Pair subsequent pair RRD");
+    for (size_t i = 0; i < account_data.rrd.length; i++) {
+      LOGD_SENSITIVE_INFO("%x", account_data.rrd.data[i]);
+    }
+  }
+
+  LOGD_SENSITIVE_INFO("Fast Pair subsequent pair combined key:");
+  for (size_t i = 0; i < pos; i++) {
+    LOGD_SENSITIVE_INFO("%x", key[i]);
+  }
+  bool matched = bloom_filter.MayContain(key, pos);
+  if (!matched && account_data.rrd.length > 0) {
+    // Flip the first byte to 4, 5, 6 when RRD is presented.
+    for (uint8_t firstByte : kAccountKeyFirstByte) {
+      key[0] = firstByte;
+      matched = bloom_filter.MayContain(key, pos);
+      if (matched) break;
+    }
+  }
+  if (matched) {
+    LOGD("Subsequent Pair match succeeds.");
+    return FillResult(service_data, account_key, result);
+  } else {
+    return false;
+  }
+}
+
+bool MatchFastPair(const nearby_BleFilter &filter,
+                   const BleScanRecord &scan_record,
+                   nearby_BleFilterResult *result) {
+  LOGD("MatchFastPair");
+  chre::DynamicVector<const uint8_t *> account_keys;
+  if (CheckFastPairFilter(filter, &account_keys)) {
+    LOGD("Fast Pair initial pair filter found.");
+    for (const auto &ble_service_data : scan_record.service_data) {
+      if (MatchInitialFastPair(ble_service_data, result)) {
+        return true;
+      }
+    }
+  } else {
+    for (const auto account_key : account_keys) {
+      for (const auto &ble_service_data : scan_record.service_data) {
+        if (MatchSubsequentPair(account_key, ble_service_data, result)) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.h
new file mode 100644
index 0000000..9869153
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FAST_PAIR_FILTER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FAST_PAIR_FILTER_H_
+
+#include "location/lbs/contexthub/nanoapps/nearby/ble_scan_record.h"
+#include "location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.nanopb.h"
+
+namespace nearby {
+
+bool MatchFastPair(const nearby_BleFilter &filter,
+                   const BleScanRecord &scan_record,
+                   nearby_BleFilterResult *result);
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FAST_PAIR_FILTER_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter.cc
new file mode 100644
index 0000000..7aded38
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter.cc
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/filter.h"
+
+#include <chre/util/macros.h>
+#include <inttypes.h>
+#include <pb_decode.h>
+
+#include <iterator>
+
+#include "location/lbs/contexthub/nanoapps/nearby/ble_scan_record.h"
+#include "location/lbs/contexthub/nanoapps/nearby/fast_pair_filter.h"
+#ifdef ENABLE_PRESENCE
+#include "location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.h"
+#include "location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.h"
+#include "location/lbs/contexthub/nanoapps/nearby/presence_filter.h"
+#endif
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][FILTER]"
+
+namespace nearby {
+
+constexpr nearby_BleFilters kDefaultBleFilters = nearby_BleFilters_init_zero;
+
+bool Filter::Update(const uint8_t *message, uint32_t message_size) {
+  LOGD("Decode a Filters message with size %" PRIu32, message_size);
+  ble_filters_ = kDefaultBleFilters;
+  pb_istream_t stream = pb_istream_from_buffer(message, message_size);
+  if (!pb_decode(&stream, nearby_BleFilters_fields, &ble_filters_)) {
+    LOGE("Failed to decode a Filters message.");
+    return false;
+  }
+  // Print filters for debug.
+  LOGD_SENSITIVE_INFO("BLE filters counter %d", ble_filters_.filter_count);
+  if (ble_filters_.filter_count > 0) {
+    LOGD_SENSITIVE_INFO("BLE filter 0 data element count %d",
+                        ble_filters_.filter[0].data_element_count);
+    if (ble_filters_.filter[0].data_element_count > 0) {
+      LOGD_SENSITIVE_INFO(
+          "Data Element 0, key: %" PRIi32
+          " value[0]: %d,"
+          " has key: %d, has value: %d, has value length %d,"
+          " value length %" PRIu32,
+          ble_filters_.filter[0].data_element[0].key,
+          ble_filters_.filter[0].data_element[0].value[0],
+          ble_filters_.filter[0].data_element[0].has_key,
+          ble_filters_.filter[0].data_element[0].has_value,
+          ble_filters_.filter[0].data_element[0].has_value_length,
+          ble_filters_.filter[0].data_element[0].value_length);
+    }
+  }
+  for (int i = 0; i < ble_filters_.filter_count; i++) {
+    const nearby_BleFilter *filter = &ble_filters_.filter[i];
+    // Sets the scan interval to satisfy the minimal latency requirement.
+    if (filter->has_latency_ms && filter->latency_ms < scan_interval_ms_) {
+      scan_interval_ms_ = filter->latency_ms;
+    }
+  }
+  return true;
+}
+
+void Filter::MatchBle(
+    const chreBleAdvertisingReport &report,
+    chre::DynamicVector<nearby_BleFilterResult> *filter_results,
+    chre::DynamicVector<nearby_BleFilterResult> *fp_filter_results) {
+#ifndef ENABLE_PRESENCE
+  UNUSED_VAR(filter_results);
+#endif
+  LOGD("MatchBle");
+
+  nearby_BleFilterResult result;
+  auto record = BleScanRecord::Parse(report.data, report.dataLength);
+  // Log the service data for debug only.
+  for (const auto &ble_service_data : record.service_data) {
+    LOGD_SENSITIVE_INFO("Receive service data with uuid %" PRIX16,
+                        ble_service_data.uuid);
+    for (int i = 0; i < ble_service_data.length; i++) {
+      LOGD_SENSITIVE_INFO("%" PRIx8, ble_service_data.data[i]);
+    }
+    LOGD_SENSITIVE_INFO("Service data end.");
+  }
+  for (int filter_index = 0; filter_index < ble_filters_.filter_count;
+       filter_index++) {
+    LOGD("MatchPresence advertisements.");
+    // TODO(b/193756395): multiple matched results can share the same BLE
+    // event. Optimize the memory usage by avoiding duplicated BLE events
+    // across multiple results.
+    result = nearby_BleFilterResult_init_zero;
+    result.has_id = true;
+    result.id = static_cast<uint32_t>(filter_index);
+    result.has_tx_power = true;
+    result.tx_power = static_cast<int32_t>(report.txPower);
+    result.has_rssi = true;
+    result.rssi = static_cast<int32_t>(report.rssi);
+    result.has_bluetooth_address = true;
+    // The buffer size has already been checked.
+    static_assert(std::size(result.bluetooth_address) == CHRE_BLE_ADDRESS_LEN);
+    memcpy(result.bluetooth_address, report.address, std::size(report.address));
+    if (MatchFastPair(ble_filters_.filter[filter_index], record, &result)) {
+      LOGD("Add a matched Fast Pair filter result");
+      fp_filter_results->push_back(result);
+      return;
+    }
+#ifdef ENABLE_PRESENCE
+    if (MatchPresenceV0(ble_filters_.filter[filter_index], record, &result) ||
+        MatchPresenceV1(ble_filters_.filter[filter_index], record,
+                        PresenceCryptoV1Impl(), PresenceCryptoIdentityV1Impl(),
+                        &result)) {
+      LOGD("Filter result TX power %" PRId32 ", RSSI %" PRId32, result.tx_power,
+           result.rssi);
+
+      LOGD("Add a matched Presence filter result");
+      filter_results->push_back(result);
+    }
+#endif
+  }
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter.h
new file mode 100644
index 0000000..b635c4c
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FILTER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FILTER_H_
+
+#include "location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.nanopb.h"
+#include "third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h"
+
+namespace nearby {
+
+// Filter monitors BLE events and notifies host when an event matches the host
+// interest.
+class Filter {
+  friend class FilterTest;
+
+ public:
+  // Updates filters with new rules. Returns false if message cannot be decoded
+  // as nearby_Filters.
+  bool Update(const uint8_t *message, uint32_t message_size);
+  // Returns true when filters are cleared.
+  bool IsEmpty() {
+    return ble_filters_.filter_count == 0;
+  }
+
+  // Matches a BLE advertisement report against BLE Filters.
+  // Returns matched result in filter_results, which includes a BleFilterResult
+  // when an advertisement matches a Filter.
+  // Fast Pair filter result is returned separately in fp_filter_results.
+  void MatchBle(const chreBleAdvertisingReport &report,
+                chre::DynamicVector<nearby_BleFilterResult> *filter_results,
+                chre::DynamicVector<nearby_BleFilterResult> *fp_filter_results);
+
+ private:
+  nearby_BleFilters ble_filters_ = nearby_BleFilters_init_zero;
+  // BLE Scan interval. Default to 1 minute.
+  uint64_t scan_interval_ms_ = 60 * 1000;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FILTER_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter_extension.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter_extension.cc
new file mode 100644
index 0000000..29267ff
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter_extension.cc
@@ -0,0 +1,205 @@
+#include "location/lbs/contexthub/nanoapps/nearby/filter_extension.h"
+
+#include <inttypes.h>
+#include <pb_decode.h>
+#include <pb_encode.h>
+
+#include <cstddef>
+#include <utility>
+
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][FILTER_EXTENSION]"
+
+namespace nearby {
+
+const size_t kChreBleGenericFilterDataSize = 29;
+
+constexpr nearby_extension_FilterConfig kEmptyFilterConfig =
+    nearby_extension_FilterConfig_init_zero;
+
+constexpr nearby_extension_FilterResult kEmptyFilterResult =
+    nearby_extension_FilterResult_init_zero;
+
+void FilterExtension::Update(
+    const chreHostEndpointInfo &host_info, const chreMessageFromHostData &event,
+    chre::DynamicVector<chreBleGenericFilter> *generic_filters,
+    nearby_extension_FilterConfigResult *config_result) {
+  LOGD("Update extension filter");
+  nearby_extension_FilterConfig filter_config = kEmptyFilterConfig;
+  pb_istream_t stream = pb_istream_from_buffer(
+      static_cast<const uint8_t *>(event.message), event.messageSize);
+  if (!pb_decode(&stream, nearby_extension_FilterConfig_fields,
+                 &filter_config)) {
+    LOGE("Failed to decode a Filters message.");
+    return;
+  }
+  const int32_t host_index = FindOrCreateHostIndex(host_info);
+  if (host_index < 0) {
+    LOGE("Failed to find or create the host.");
+    return;
+  }
+  const chreHostEndpointInfo &host =
+      host_list_[static_cast<size_t>(host_index)];
+  config_result->has_result = true;
+  config_result->has_vendor_status = true;
+
+  chrexNearbyExtendedFilterConfig config;
+  config.data = filter_config.oem_filter;
+  config.data_length = filter_config.oem_filter_length;
+
+  config_result->result =
+      static_cast<int32_t>(chrexNearbySetExtendedFilterConfig(
+          &host, &config, &config_result->vendor_status));
+  if (config_result->result != CHREX_NEARBY_RESULT_OK) {
+    LOGE("Failed to config filters, result %" PRId32, config_result->result);
+    host_list_.erase(static_cast<size_t>(host_index));
+    return;
+  }
+  // Returns hardware filters.
+  for (int i = 0; i < filter_config.hardware_filter_count; i++) {
+    const nearby_extension_ChreBleGenericFilter &hw_filter =
+        filter_config.hardware_filter[i];
+    chreBleGenericFilter generic_filter;
+    generic_filter.type = hw_filter.type;
+    generic_filter.len = static_cast<uint8_t>(hw_filter.len);
+    memcpy(generic_filter.data, hw_filter.data, kChreBleGenericFilterDataSize);
+    memcpy(generic_filter.dataMask, hw_filter.data_mask,
+           kChreBleGenericFilterDataSize);
+    generic_filters->push_back(generic_filter);
+  }
+  // Removes the host if both hardware and oem filters are empty.
+  if (filter_config.hardware_filter_count == 0 &&
+      filter_config.oem_filter_length == 0) {
+    LOGD("Remove host: id (%d), package name (%s)", host.hostEndpointId,
+         host.isNameValid ? host.packageName : "unknown");
+    host_list_.erase(static_cast<size_t>(host_index));
+  }
+}
+
+int32_t FilterExtension::FindOrCreateHostIndex(
+    const chreHostEndpointInfo &host_info) {
+  for (size_t index = 0; index < host_list_.size(); index++) {
+    if (host_info.hostEndpointId == host_list_[index].hostEndpointId) {
+      return static_cast<int32_t>(index);
+    }
+  }
+  if (!host_list_.push_back(host_info)) {
+    LOGE("Failed to add new host info.");
+    return -1;
+  }
+  return static_cast<int32_t>(host_list_.size() - 1);
+}
+
+/* Adds a FilterExtensionResult (initialized by endpoint_id) to filter_results
+ * if it has not been included in filter_results.
+ * Returns the index of the entry.
+ */
+size_t AddToFilterResults(
+    uint16_t endponit_id,
+    chre::DynamicVector<FilterExtensionResult> *filter_results) {
+  FilterExtensionResult result(endponit_id);
+  size_t idx = filter_results->find(result);
+  if (filter_results->size() == idx) {
+    filter_results->push_back(std::move(result));
+  }
+  return idx;
+}
+
+void FilterExtension::Match(
+    const chre::DynamicVector<chreBleAdvertisingReport> &ble_adv_list,
+    chre::DynamicVector<FilterExtensionResult> *filter_results,
+    chre::DynamicVector<FilterExtensionResult> *screen_on_filter_results) {
+  for (const chreHostEndpointInfo &host_info : host_list_) {
+    size_t idx = AddToFilterResults(host_info.hostEndpointId, filter_results);
+    size_t screen_on_idx =
+        AddToFilterResults(host_info.hostEndpointId, screen_on_filter_results);
+    for (const auto &ble_adv_report : ble_adv_list) {
+      switch (chrexNearbyMatchExtendedFilter(&host_info, &ble_adv_report)) {
+        case CHREX_NEARBY_FILTER_ACTION_IGNORE:
+          continue;
+        case CHREX_NEARBY_FILTER_ACTION_DELIVER_ON_WAKE:
+          LOGD("Include BLE report to screen on list.");
+          (*screen_on_filter_results)[screen_on_idx].reports.Push(
+              ble_adv_report);
+          continue;
+        case CHREX_NEARBY_FILTER_ACTION_DELIVER_IMMEDIATELY:
+          LOGD("Include BLE report to immediate delivery list.");
+          (*filter_results)[idx].reports.Push(ble_adv_report);
+          continue;
+      }
+    }
+  }
+}
+
+bool FilterExtension::EncodeConfigResult(
+    const nearby_extension_FilterConfigResult &config_result,
+    ByteArray data_buf, size_t *encoded_size) {
+  if (!pb_get_encoded_size(encoded_size,
+                           nearby_extension_FilterConfigResult_fields,
+                           &config_result)) {
+    LOGE("Failed to get filter config result size.");
+    return false;
+  }
+  pb_ostream_t ostream = pb_ostream_from_buffer(data_buf.data, data_buf.length);
+
+  if (!pb_encode(&ostream, nearby_extension_FilterConfigResult_fields,
+                 &config_result)) {
+    LOGE("Unable to encode protobuf for FilterConfigResult, error %s",
+         PB_GET_ERROR(&ostream));
+    return false;
+  }
+  return true;
+}
+
+bool FilterExtension::Encode(
+    const chre::DynamicVector<chreBleAdvertisingReport> &reports,
+    ByteArray data_buf, size_t *encoded_size) {
+  nearby_extension_FilterResult filter_result = kEmptyFilterResult;
+  size_t idx = 0;
+  for (const auto &report : reports) {
+    nearby_extension_ChreBleAdvertisingReport &report_proto =
+        filter_result.report[idx];
+    report_proto.has_timestamp = true;
+    report_proto.timestamp = report.timestamp;
+    report_proto.has_event_type_and_data_status = true;
+    report_proto.event_type_and_data_status = report.eventTypeAndDataStatus;
+    report_proto.has_address = true;
+    for (size_t i = 0; i < 6; i++) {
+      report_proto.address[i] = report.address[i];
+    }
+    report_proto.has_tx_power = true;
+    report_proto.tx_power = report.txPower;
+    report_proto.has_rssi = true;
+    report_proto.rssi = report.rssi;
+    report_proto.has_data_length = true;
+    report_proto.data_length = report.dataLength;
+    if (report.dataLength > 0) {
+      report_proto.has_data = true;
+    }
+    for (size_t i = 0; i < report.dataLength; i++) {
+      report_proto.data[i] = report.data[i];
+    }
+    idx++;
+  }
+  filter_result.report_count = static_cast<pb_size_t>(idx);
+  filter_result.has_error_code = true;
+  filter_result.error_code = nearby_extension_FilterResult_ErrorCode_SUCCESS;
+
+  if (!pb_get_encoded_size(encoded_size, nearby_extension_FilterResult_fields,
+                           &filter_result)) {
+    LOGE("Failed to get filter extension result size.");
+    return false;
+  }
+  pb_ostream_t ostream = pb_ostream_from_buffer(data_buf.data, data_buf.length);
+
+  if (!pb_encode(&ostream, nearby_extension_FilterResult_fields,
+                 &filter_result)) {
+    LOGE("Unable to encode protobuf for FilterExtensionResults, error %s",
+         PB_GET_ERROR(&ostream));
+    return false;
+  }
+  return true;
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter_extension.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter_extension.h
new file mode 100644
index 0000000..b788675
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/filter_extension.h
@@ -0,0 +1,106 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FILTER_EXTENSION_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FILTER_EXTENSION_H_
+#include <chre.h>
+
+#include <utility>
+
+#include "location/lbs/contexthub/nanoapps/nearby/adv_report_cache.h"
+#include "location/lbs/contexthub/nanoapps/nearby/byte_array.h"
+#include "location/lbs/contexthub/nanoapps/nearby/nearby_extension.h"
+#include "location/lbs/contexthub/nanoapps/nearby/proto/nearby_extension.nanopb.h"
+#include "third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h"
+#include "third_party/contexthub/chre/util/include/chre/util/time.h"
+
+namespace nearby {
+
+struct FilterExtensionResult {
+  // Default value for filter extension result to expire.
+  static constexpr uint64_t kFilterExtensionReportExpireTimeMilliSec =
+      5 * chre::kOneSecondInMilliseconds;
+
+  const uint16_t end_point;
+  AdvReportCache reports;
+
+  explicit FilterExtensionResult(uint16_t end_point) : end_point(end_point) {
+    reports.SetCacheTimeout(kFilterExtensionReportExpireTimeMilliSec);
+  }
+
+  FilterExtensionResult(FilterExtensionResult &&src)
+      : end_point(src.end_point) {
+    this->reports = std::move(src.reports);
+  }
+
+  // Deconstructs FilterExtensionResult and releases all resources.
+  ~FilterExtensionResult() {
+    Clear();
+  }
+
+  // Releases all resources {cache element, heap memory}.
+  void Clear() {
+    reports.Clear();
+  }
+
+  // Returns advertise reports in cache.
+  chre::DynamicVector<chreBleAdvertisingReport> &GetAdvReports() {
+    return reports.GetAdvReports();
+  }
+
+  // Logic operator to compare host end point.
+  friend bool operator==(const FilterExtensionResult &c1,
+                         const FilterExtensionResult &c2) {
+    return c1.end_point == c2.end_point;
+  }
+
+  // Logic operator to compare host end point.
+  friend bool operator!=(const FilterExtensionResult &c1,
+                         const FilterExtensionResult &c2) {
+    return c1.end_point != c2.end_point;
+  }
+};
+
+class FilterExtension {
+ public:
+  // Updates extended filters (passed in the event) for each end host.
+  // Returns generic_filters, which can be used to restart BLE scan.
+  void Update(const chreHostEndpointInfo &host_info,
+              const chreMessageFromHostData &event,
+              chre::DynamicVector<chreBleGenericFilter> *generic_filters,
+              nearby_extension_FilterConfigResult *config_result);
+
+  // Matches BLE advertisements. Returns matched advertisements in
+  // filter_results. If the results is only delivered when screen is on,
+  // returned in screen_on_filter_results.
+  void Match(
+      const chre::DynamicVector<chreBleAdvertisingReport> &ble_adv_list,
+      chre::DynamicVector<FilterExtensionResult> *filter_results,
+      chre::DynamicVector<FilterExtensionResult> *screen_on_filter_results);
+
+  // Serializes config_result into data_buf. The encoded size is filled in
+  // encoded_size. Returns true for successful encoding.
+  static bool EncodeConfigResult(
+      const nearby_extension_FilterConfigResult &config_result,
+      ByteArray data_buf, size_t *encoded_size);
+
+  // Encodes reports into data_buf. The reports are converted to
+  // nearby_extension_FilterResult before the serialization.
+  static bool Encode(
+      const chre::DynamicVector<chreBleAdvertisingReport> &reports,
+      ByteArray data_buf, size_t *encoded_size);
+
+  // Whether host list is empty. The host which doesn't have filter
+  // configuration or was disconnected should be removed in the host list.
+  bool IsEmpty() const {
+    return host_list_.empty();
+  }
+
+  // Returns the index of the host if exists or could create.
+  // Otherwise, returns -1.
+  int32_t FindOrCreateHostIndex(const chreHostEndpointInfo &host_info);
+
+ private:
+  chre::DynamicVector<chreHostEndpointInfo> host_list_;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_FILTER_EXTENSION_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/mock_ble.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/mock_ble.h
new file mode 100644
index 0000000..10eeaef
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/mock_ble.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_MOCK_BLE_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_MOCK_BLE_H_
+
+#include <chre.h>
+
+namespace nearby {
+
+struct MockBle {
+#if defined(MOCK_FAST_PAIR)
+  // A BLEScanRecord consists of one advertisement of Fast Pair initial pair.
+  static constexpr uint8_t kBleScanRecordData[] = {
+      // Advertisement.
+      6,     // byte length of ad below.
+      0x16,  // type of ad data (service data).
+      0x2C,  // 2 bytes uuid in little-endian (Fast Pair).
+      0xFE,
+      0x1F,  // 3 bytes Fast Pair initial pair ervice data (model ID).
+      0xD7,  // second byte of model ID.
+      0xD0,  // third byte of model ID.
+  };
+#elif defined(MOCK_SUBSEQUENT_PAIR)
+  // A BLEScanRecord consists of one advertisement of Fast Pair subsequent pair.
+  static constexpr uint8_t kBleScanRecordData[] = {
+      // Advertisement.
+      12,    // byte length of ad below.
+      0x16,  // type of ad data (service data).
+      0x2C,  // 2 bytes uuid in little-endian (Fast Pair).
+      0xFE,
+      0x00,  // Version 0 with Flag 0
+      0x40,  // 4 bytes Bloom Filter
+      0x02, 0x0C, 0x80, 0x2A,
+      0x21,  // 2 bytes salt.
+      0xC7, 0xC8,
+  };
+#elif defined(MOCK_PRESENCE_V0)
+  // A BLEScanRecord consists of one advertisement of Presence V0.
+  static constexpr uint8_t kBleScanRecordData[] = {
+      // Advertisement.
+      0x0B,  // byte length of ad below.
+      0x16,  // type of ad data (service data).
+      0xF1,  // uuid in little-endian (Nearby Presence)
+      0xFC,
+      // Presence service data below.
+      0b00100100,  // service data header (format 0bVVVLLLLR) with 2 fields.
+      // Intent field below, 1 byte header plus 2 byte value.
+      0b00100101,  // field header with 0b0101 type
+      1,           // first intent as 1
+      5,           // second intent as 5
+      // Model ID, 3 bytes length with 0b0111 type.
+      0b00110111,
+      0b00000001,
+      0b00000010,
+      0b00000100,
+  };
+#else
+  // A BLEScanRecord consists of one advertisement of Presence V1.
+#define IDENTITY_VALUE 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+#define DE_SIGNATURE 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+  static constexpr uint8_t kBleScanRecordData[] = {
+      // Advertisement.
+      51,    // byte length of ad below.
+      0x16,  // type of ad data (service data).
+      0xF1,  // uuid in little-endian (Nearby Presence)
+      0xFC,
+      // Presence service data below.
+      0b00100000,  // Header with version v1.
+      0b00100000,  // Salt header: length 2, type 0
+      2,
+      3,           // Salt value.
+      0b10010000,  // Identity header: length 16, type 4
+      0b00000100,
+      IDENTITY_VALUE,  // Identity value: 16 bytes.
+      0b00010110,      // Action header: length 1, type 6
+      1,
+      0b00010110,  // Action header: length 1, type 6
+      124,
+      0b00010101,  // TX power header: length 1, type 5
+      20,
+      0b00110111,  // Model ID header: length 3, type 7
+      0, 1, 2,
+      DE_SIGNATURE,  // Data Element signature: 16 bytes
+  };
+#endif
+
+  static constexpr chreBleAdvertisingReport kReport = {
+      .address = {1, 2, 3, 4, 5, 6},
+      .txPower = 20,
+      .rssi = 10,
+      .directAddress = {1, 2, 3, 4, 5, 6},
+      .dataLength = sizeof(kBleScanRecordData),
+      .data = kBleScanRecordData,
+  };
+  static constexpr chreBleAdvertisementEvent kBleEvent = {
+      .numReports = 1,
+      .reports = &kReport,
+  };
+  static constexpr chreAsyncResult kBleFlushCompleteEvent = {
+      .requestType = CHRE_BLE_REQUEST_TYPE_FLUSH,
+      .success = true,
+      .errorCode = CHRE_ERROR_NONE,
+      .reserved = 0,
+      .cookie = nullptr};
+  static constexpr chreBatchCompleteEvent kBleBatchCompleteEvent = {
+      .eventType = CHRE_EVENT_BLE_ADVERTISEMENT};
+#ifdef MOCK_BLE_BATCH_SCAN
+  static constexpr bool kBleBatchScanSupported = true;
+#else
+  static constexpr bool kBleBatchScanSupported = false;
+#endif
+  static constexpr uint32_t kBleFlushCompleteTimeoutMs = 50;
+  static constexpr uint32_t kBleFlushScanResultIntervalMs = 10;
+  static constexpr uint32_t kBleReportDelayMinMs = 10;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_MOCK_BLE_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension.cc
new file mode 100644
index 0000000..dc8fe6e
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension.cc
@@ -0,0 +1,74 @@
+#include "location/lbs/contexthub/nanoapps/nearby/nearby_extension.h"
+
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][FILTER_EXTENSION]"
+
+/**
+ * Example advertisement data format.
+ *
+ * 0x02,  // byte length of flag
+ * 0x01,  // type of ad data (flag)
+ * 0x02,  // ad data (flag)
+ * 0x05,  // byte length of manufacturer specific data
+ * 0xff,  // type of ad data (manufacturer specific data)
+ * 0xe0,  // ad data (manufacturer id[0])
+ * 0x00,  // ad data (manufacturer id[1])
+ * 0x78,  // ad data (manufacturer data for data filter)
+ * 0x02,  // ad data (manufacturer data for delivery mode)
+ */
+
+static const uint16_t EXT_ADV_DATA_LEN = 9;
+static const uint16_t EXT_ADV_DATA_FILTER_INDEX = 7;
+static const uint16_t EXT_ADV_DELIVERY_MODE_INDEX = 8;
+static const uint16_t EXT_FILTER_CONFIG_DATA_INDEX = 0;
+static const uint16_t EXT_FILTER_CONFIG_DATA_MASK_INDEX = 1;
+static uint8_t EXT_FILTER_DATA = 0;
+static uint8_t EXT_FILTER_DATA_MASK = 0;
+
+const char kHostPackageName[] = "com.google.android.nearby.offload.reference";
+
+// TODO(b/284151838): investigate to pass hardware filter.
+uint32_t chrexNearbySetExtendedFilterConfig(
+    const chreHostEndpointInfo *host_info,
+    const struct chrexNearbyExtendedFilterConfig *config,
+    uint32_t *vendorStatusCode) {
+  if (host_info->isNameValid &&
+      strcmp(host_info->packageName, kHostPackageName) == 0) {
+    EXT_FILTER_DATA = config->data[EXT_FILTER_CONFIG_DATA_INDEX];
+    EXT_FILTER_DATA_MASK = config->data[EXT_FILTER_CONFIG_DATA_MASK_INDEX];
+  }
+  *vendorStatusCode = 0;
+  LOGD("Set EXT_FILTER_DATA 0x%02X", EXT_FILTER_DATA);
+  LOGD("Set EXT_FILTER_DATA_MASK 0x%02X", EXT_FILTER_DATA_MASK);
+  return CHREX_NEARBY_RESULT_OK;
+}
+
+uint32_t chrexNearbyMatchExtendedFilter(
+    const chreHostEndpointInfo *host_info,
+    const struct chreBleAdvertisingReport *report) {
+  if (!host_info->isNameValid ||
+      strcmp(host_info->packageName, kHostPackageName) != 0 ||
+      report->dataLength == 0) {
+    return CHREX_NEARBY_FILTER_ACTION_IGNORE;
+  }
+  if (report->dataLength < EXT_ADV_DATA_LEN) {
+    LOGD("data length %d is less than expected", report->dataLength);
+    return CHREX_NEARBY_FILTER_ACTION_IGNORE;
+  }
+  uint8_t extData = report->data[EXT_ADV_DATA_FILTER_INDEX];
+  int8_t deliveryMode =
+      static_cast<int8_t>(report->data[EXT_ADV_DELIVERY_MODE_INDEX]);
+  if ((extData & EXT_FILTER_DATA_MASK) !=
+      (EXT_FILTER_DATA & EXT_FILTER_DATA_MASK)) {
+    return CHREX_NEARBY_FILTER_ACTION_IGNORE;
+  }
+  switch (deliveryMode) {
+    case CHREX_NEARBY_FILTER_ACTION_DELIVER_ON_WAKE:
+      return CHREX_NEARBY_FILTER_ACTION_DELIVER_ON_WAKE;
+    case CHREX_NEARBY_FILTER_ACTION_DELIVER_IMMEDIATELY:
+      return CHREX_NEARBY_FILTER_ACTION_DELIVER_IMMEDIATELY;
+    default:
+      return CHREX_NEARBY_FILTER_ACTION_IGNORE;
+  }
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension.h
new file mode 100644
index 0000000..d918689
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension.h
@@ -0,0 +1,112 @@
+#ifndef LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_NEARBY_EXTENSION_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_NEARBY_EXTENSION_H_
+
+#include <chre.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//! Contains vendor-defined data for configuring vendor library filtering
+struct chrexNearbyExtendedFilterConfig {
+  size_t data_length;   //!< Number of bytes in data
+  const uint8_t *data;  //!< Vendor-defined payload
+};
+
+enum chrexNearbyResult {
+  //! Operation completed successfully
+  CHREX_NEARBY_RESULT_OK = 0,
+
+  //! This device does not support vendor extended filtering
+  CHREX_NEARBY_RESULT_FEATURE_NOT_SUPPORTED = 1,
+
+  //! A general/unknown failure occurred while trying to perform the operation
+  CHREX_NEARBY_RESULT_INTERNAL_ERROR = 2,
+
+  //! No vendor library was found matching the Android package that made the
+  //! request
+  CHREX_NEARBY_RESULT_UNKNOWN_PACKAGE = 3,
+
+  //! The system does not have enough resources available to complete the
+  //! request
+  CHREX_NEARBY_RESULT_OUT_OF_RESOURCES = 4,
+
+  //! The operation failed due to an error in the vendor-specific library. Refer
+  //! to
+  //! the vendor status code for details.
+  CHREX_NEARBY_RESULT_VENDOR_SPECIFIC_ERROR = 128,
+};
+
+#define CHREX_NEARBY_VENDOR_STATUS_UNKNOWN (0)
+
+/**
+ * Configures vendor-defined filtering sent by a vendor/OEM service on the host.
+ * This is called by the Nearby nanoapp when it receives a
+ * ChreNearbyExtendedFilter message, and the result is sent back to the host
+ * endpoint that made the request. Note that extended filters are disabled by
+ * default and automatically disabled if the vendor/OEM service disconnects from
+ * ContextHubService, so the vendor/OEM service must send a configuration
+ * request at initialization time to register the extended filter in the system,
+ * even if there is no configuration payload.
+ *
+ * @param host_info The meta data for a host end point that sent the message,
+ *     obtained from chreHostEndpointInfo. The implementation must ensure that
+ *     messages from a given host end point are only provided to the vendor
+ *     library explicitly associated with that host end point.
+ * @param config Configuration data in a vendor-defined format.
+ * @param[out] vendorStatusCode Optional vendor-defined status code that will be
+ *     returned to the vendor service regardless of the return value of this
+ *     function. The value 0 is reserved to indicate that a vendor status code
+ * was not provided or is not relevant. All other values have a vendor-defined
+ *     meaning.
+ * @return A value from enum chrexNearbyResult
+ */
+uint32_t chrexNearbySetExtendedFilterConfig(
+    const chreHostEndpointInfo *host_info,
+    const struct chrexNearbyExtendedFilterConfig *config,
+    uint32_t *vendorStatusCode);
+
+enum chrexNearbyFilterAction {
+  //! Ignore/drop this advertising report
+  CHREX_NEARBY_FILTER_ACTION_IGNORE = 0,
+
+  //! Deliver to the vendor client when the host processor is awake, either on
+  //! the
+  //! next wakeup, or immediately if it is currently awake.
+  //! If the host is asleep, advertisement data is temporarily stored in a
+  //! buffer.
+  //! If a duplicate advertisement already exists in the buffer (same sending
+  //! address and payload), then it is updated rather than storing another copy.
+  CHREX_NEARBY_FILTER_ACTION_DELIVER_ON_WAKE = 1,
+
+  //! Deliver to the vendor client immediately, waking up the host processor if
+  //! it
+  //! is currently asleep. Triggering a wake has a power impact, so this option
+  //! should be used sparingly, with special care taken to avoid repeated
+  //! wakeups.
+  CHREX_NEARBY_FILTER_ACTION_DELIVER_IMMEDIATELY = 2,
+};
+
+/**
+ * Forwards a BLE advertisement to the extended filter associated with the given
+ * package for matching. The Nearby nanoapp will call this function for each
+ * package that has sent a ChreNearbyExtendedFilterConfig message and maintains
+ * an active connection to ContextHubService (incl. via PendingIntent). In other
+ * words, extended filtering for a given package is activated by sending
+ * ChreNearbyExtendedFilterConfig to the Nearby nanoapp and deactivated when the
+ * Nearby nanoapp is notified that the host endpoint has disconnected.
+ *
+ * @param host_info The meta data for a host end point with an active extended
+ *     filtering configuration, where the result will be sent if it is matched.
+ * @param report Contains data for a BLE advertisement
+ * @return A value from enum chrexNearbyFilterAction
+ */
+uint32_t chrexNearbyMatchExtendedFilter(
+    const chreHostEndpointInfo *host_info,
+    const struct chreBleAdvertisingReport *report);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_NEARBY_EXTENSION_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension_support_lib.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension_support_lib.cc
new file mode 100644
index 0000000..87cf393
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/nearby_extension_support_lib.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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 <dlfcn.h>
+
+#include "location/lbs/contexthub/nanoapps/nearby/nearby_extension.h"
+#include "third_party/contexthub/chre/util/include/chre/util/macros.h"
+
+/**
+ * Lazily calls dlsym to find the function pointer for a given function
+ * (provided without quotes) in another library (i.e. the CHRE platform DSO),
+ * caching and returning the result.
+ */
+#define CHRE_NSL_LAZY_LOOKUP(functionName)               \
+  ({                                                     \
+    static bool lookupPerformed = false;                 \
+    static decltype(functionName) *funcPtr = nullptr;    \
+    if (!lookupPerformed) {                              \
+      funcPtr = reinterpret_cast<decltype(funcPtr)>(     \
+          dlsym(RTLD_DEFAULT, STRINGIFY(functionName))); \
+      lookupPerformed = true;                            \
+    }                                                    \
+    funcPtr;                                             \
+  })
+
+WEAK_SYMBOL
+uint32_t chrexNearbySetExtendedFilterConfig(
+    const chreHostEndpointInfo *host_info,
+    const struct chrexNearbyExtendedFilterConfig *config,
+    uint32_t *vendorStatusCode) {
+  auto *fptr = CHRE_NSL_LAZY_LOOKUP(chrexNearbySetExtendedFilterConfig);
+  return (fptr != nullptr)
+             ? fptr(host_info, config, vendorStatusCode)
+             : chrexNearbyResult::CHREX_NEARBY_RESULT_FEATURE_NOT_SUPPORTED;
+}
+
+WEAK_SYMBOL
+uint32_t chrexNearbyMatchExtendedFilter(
+    const chreHostEndpointInfo *host_info,
+    const struct chreBleAdvertisingReport *report) {
+  auto *fptr = CHRE_NSL_LAZY_LOOKUP(chrexNearbyMatchExtendedFilter);
+  return (fptr != nullptr)
+             ? fptr(host_info, report)
+             : chrexNearbyFilterAction::CHREX_NEARBY_FILTER_ACTION_IGNORE;
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.cc
new file mode 100644
index 0000000..53880a1
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.h"
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/aes.h"
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.h"
+#include "third_party/contexthub/chre/util/include/chre/util/macros.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+#define LOG_TAG "[NEARBY][PRESENCE_CRYPTO_V1]"
+namespace nearby {
+bool PresenceCryptoIdentityV1Impl::decrypt(const ByteArray &input,
+                                           const ByteArray &salt,
+                                           const ByteArray &key,
+                                           ByteArray &output) const {
+  if (key.length != kAuthenticityKeySize) {
+    LOGE("Invalid authenticity key size");
+    return false;
+  }
+  if (salt.length != kSaltSize) {
+    LOGE("Invalid salt size");
+    return false;
+  }
+  if (input.length != output.length) {
+    LOGE("Input and output data length are different");
+    return false;
+  }
+  // Generate a 32 bytes decryption key from authenticity_key
+  uint8_t decryption_key[kEncryptionKeySize] = {0};
+  hkdf(kEkIv, ARRAY_SIZE(kEkIv), key.data, key.length, decryption_key,
+       ARRAY_SIZE(decryption_key));
+  // Decrypt the input cipher text using the decryption key
+  uint8_t e_salt[kAesCtrIvSize] = {0};
+  hkdf(kEsaltIv, ARRAY_SIZE(kEsaltIv), salt.data, salt.length, e_salt,
+       ARRAY_SIZE(e_salt));
+  struct AesCtrContext ctx;
+  if (aesCtrInit(&ctx, decryption_key, e_salt, AES_256_KEY_TYPE) < 0) {
+    LOGE("aesCtrInit() is failed");
+    return false;
+  }
+  aesCtr(&ctx, input.data, output.data, output.length);
+  return true;
+}
+
+bool PresenceCryptoIdentityV1Impl::verify(const ByteArray &input,
+                                          const ByteArray &key,
+                                          const ByteArray &signature) const {
+  UNUSED_VAR(key);
+  if (input.data == nullptr || signature.data == nullptr) {
+    LOGE("Null pointer was found in input parameter");
+    return false;
+  }
+  if (signature.length != kHmacTagSize) {
+    LOGE("Invalid signature size");
+    return false;
+  }
+  // Generates a 8 bytes HMAC tag from the data
+  uint8_t hmac_tag[kHmacTagSize] = {0};
+  hkdf(kKtagIv, ARRAY_SIZE(kKtagIv), input.data, input.length, hmac_tag,
+       ARRAY_SIZE(hmac_tag));
+  // Verifies the generated HMAC tag matching the signature
+  return memcmp(hmac_tag, signature.data, signature.length) == 0;
+}
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.h
new file mode 100644
index 0000000..49a7352
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_CRYPTO_IDENTITY_V1_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_CRYPTO_IDENTITY_V1_H_
+#include "location/lbs/contexthub/nanoapps/nearby/crypto.h"
+namespace nearby {
+// Implements Crypto interface for Identity in Presence v1 specification.
+// Crypto algorithms: AES/CTR, HMAC, HKDF, SHA256.
+class PresenceCryptoIdentityV1Impl : public Crypto {
+ public:
+  // Decrypts input with salt and key. Places the decrypted result in output.
+  bool decrypt(const ByteArray &input, const ByteArray &salt,
+               const ByteArray &key, ByteArray &output) const override;
+  // Verifies the computed HMAC tag is equal to the signature.
+  bool verify(const ByteArray &input, const ByteArray &key,
+              const ByteArray &signature) const override;
+
+ private:
+  static constexpr size_t kAuthenticityKeySize = 16;
+  static constexpr size_t kEncryptionKeySize = 32;
+  static constexpr size_t kAesCtrIvSize = 16;
+  static constexpr size_t kHmacTagSize = 8;
+  static constexpr size_t kSaltSize = 2;
+  static constexpr uint8_t kEkIv[] = {0x0E, 0x85, 0xD9, 0x2A, 0x6D, 0x7F,
+                                      0x53, 0x1B, 0x1B, 0x0B, 0x5B, 0xDA,
+                                      0x5C, 0x11, 0xAC, 0x42};
+  static constexpr uint8_t kEsaltIv[] = {0x2E, 0x53, 0xED, 0x0A, 0x81, 0xE1,
+                                         0xE1, 0x0C, 0x1F, 0x4C, 0x3F, 0xF7,
+                                         0x21, 0xBE, 0x0F, 0xF6};
+  static constexpr uint8_t kKtagIv[] = {0xEA, 0xAD, 0xFA, 0x43, 0x10, 0x9D,
+                                        0xF3, 0xF7, 0x08, 0xFD, 0xF0, 0x25,
+                                        0xB5, 0x2F, 0x01, 0xC8};
+};
+}  // namespace nearby
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_CRYPTO_IDENTITY_V1_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.cc
new file mode 100644
index 0000000..defb53a
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.cc
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.h"
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/aes.h"
+#include "location/lbs/contexthub/nanoapps/nearby/crypto/hkdf.h"
+#include "third_party/contexthub/chre/util/include/chre/util/macros.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+#define LOG_TAG "[NEARBY][PRESENCE_CRYPTO_V1]"
+namespace nearby {
+bool PresenceCryptoV1Impl::decrypt(const ByteArray &input,
+                                   const ByteArray &salt, const ByteArray &key,
+                                   ByteArray &output) const {
+  if (key.length != kAuthenticityKeySize) {
+    LOGE("Invalid authenticity key size");
+    return false;
+  }
+  if (salt.length != kSaltSize) {
+    LOGE("Invalid salt size");
+    return false;
+  }
+  if (input.length != output.length) {
+    LOGE("Output length is not equal to input length.");
+    return false;
+  }
+
+  // Generate a 32 bytes decryption key from authenticity_key
+  uint8_t decryption_key[kEncryptionKeySize] = {0};
+  hkdf(kAkIv, ARRAY_SIZE(kAkIv), key.data, key.length, decryption_key,
+       ARRAY_SIZE(decryption_key));
+  // Decrypt the input cipher text using the decryption key
+  uint8_t a_salt[kAesCtrIvSize] = {0};
+  hkdf(kAsaltIv, ARRAY_SIZE(kAsaltIv), salt.data, salt.length, a_salt,
+       ARRAY_SIZE(a_salt));
+  struct AesCtrContext ctx;
+  if (aesCtrInit(&ctx, decryption_key, a_salt, AES_256_KEY_TYPE) < 0) {
+    LOGE("aesCtrInit() is failed");
+    return false;
+  }
+  aesCtr(&ctx, input.data, output.data, output.length);
+  return true;
+}
+bool PresenceCryptoV1Impl::verify(const ByteArray &input, const ByteArray &key,
+                                  const ByteArray &signature) const {
+  if (input.data == nullptr || key.data == nullptr ||
+      signature.data == nullptr) {
+    LOGE("Null pointer was found in input parameter");
+    return false;
+  }
+  if (key.length != kAuthenticityKeySize) {
+    LOGE("Invalid authenticity key size");
+    return false;
+  }
+  if (signature.length != kHmacTagSize) {
+    LOGE("Invalid signature size");
+    return false;
+  }
+  // Generates a 16 bytes HMAC key from authenticity_key
+  uint8_t hmac_key[kAesCtrIvSize] = {0};
+  hkdf(kHkIv, ARRAY_SIZE(kHkIv), key.data, key.length, hmac_key,
+       ARRAY_SIZE(hmac_key));
+  // Generates a 16 bytes HMAC tag from the data
+  uint8_t hmac_tag[kHmacTagSize] = {0};
+  hkdf(hmac_key, ARRAY_SIZE(hmac_key), input.data, input.length, hmac_tag,
+       ARRAY_SIZE(hmac_tag));
+  // Verify the generated HMAC tag matching the signature
+  return memcmp(hmac_tag, signature.data, signature.length) == 0;
+}
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.h
new file mode 100644
index 0000000..b8b9184
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_CRYPTO_V1_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_CRYPTO_V1_H_
+#include "location/lbs/contexthub/nanoapps/nearby/crypto.h"
+namespace nearby {
+// Implements Crypto interface for Data Elements in Presence v1 specification.
+// Crypto algorithms: AES/CTR, HMAC, HKDF, SHA256.
+class PresenceCryptoV1Impl : public Crypto {
+ public:
+  // Decrypts input with salt and key. Places the decrypted result in output.
+  bool decrypt(const ByteArray &input, const ByteArray &salt,
+               const ByteArray &key, ByteArray &output) const override;
+  // Verifies the computed HMAC tag is equal to the signature.
+  bool verify(const ByteArray &input, const ByteArray &key,
+              const ByteArray &signature) const override;
+
+ private:
+  static constexpr size_t kAuthenticityKeySize = 16;
+  static constexpr size_t kEncryptionKeySize = 32;
+  static constexpr size_t kAesCtrIvSize = 16;
+  static constexpr size_t kHmacTagSize = 16;
+  static constexpr size_t kSaltSize = 2;
+  static constexpr uint8_t kAkIv[] = {0x0C, 0xC5, 0x13, 0x17, 0x60, 0x39,
+                                      0xC5, 0x13, 0x75, 0xE1, 0x8C, 0xC3,
+                                      0x56, 0xE7, 0xDF, 0xB2};
+  static constexpr uint8_t kAsaltIv[] = {0x6F, 0x30, 0xAD, 0xB1, 0xF6, 0x9A,
+                                         0xF0, 0x49, 0x2B, 0x37, 0x66, 0x81,
+                                         0x3A, 0xED, 0x8F, 0x04};
+  static constexpr uint8_t kHkIv[] = {0x0C, 0xC5, 0x13, 0x17, 0x60, 0x39,
+                                      0xC5, 0x13, 0x75, 0xE1, 0x8C, 0xC3,
+                                      0x56, 0xE7, 0xDF, 0xB2};
+};
+}  // namespace nearby
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_CRYPTO_V1_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.cc
new file mode 100644
index 0000000..0799dad
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.cc
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.h"
+
+#include <cinttypes>
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][PRESENCE_DECODER_V1]"
+
+namespace nearby {
+// The Presence v1 advertisement is defined in the format below:
+// Header (1 byte) | salt (1+2 bytes) | Identity + filter (2+16 bytes)
+// | repeated Data Element fields (various bytes)
+// The header contains:
+// version (3 bits) | 5 bit reserved for future use (RFU)
+bool PresenceDecoderV1::Decode(const ByteArray &encoded_data,
+                               const Crypto &crypto,
+                               const Crypto &identity_crypto,
+                               const ByteArray &key,
+                               const ByteArray &metadata_encryption_key_tag) {
+  // 1 + 1 + 2 + 2 + 16
+  constexpr size_t kMinAdvertisementLength = 22;
+  constexpr size_t kHeaderIndex = 0;
+  constexpr size_t kSaltIndex = 1;
+  constexpr size_t kIdentityIndex = 4;
+  constexpr size_t kDataElementIndex = 22;
+  constexpr size_t kIdentityHeaderLength = 2;
+  constexpr size_t kDataElementSignatureLength = 16;
+
+  constexpr uint8_t kVersionMask = 0b11100000;
+  constexpr uint8_t kVersion = 1;
+
+  chre::Optional<DataElementHeaderV1> de_header;
+  uint8_t *const data = encoded_data.data;
+  const size_t data_size = encoded_data.length;
+
+  if (data_size < kMinAdvertisementLength) {
+    LOGE(
+        "Encoded advertisement does not have sufficient bytes to include "
+        "de_header, salt, and identity");
+    return false;
+  }
+  if ((data[kHeaderIndex] & kVersionMask) >> 5 != kVersion) {
+    LOGE("Advertisement version is not v1");
+    return false;
+  }
+
+  // Decodes salt.
+  de_header =
+      DataElementHeaderV1::Decode(&data[kSaltIndex], data_size - kSaltIndex);
+  if (de_header.has_value() &&
+      de_header->type == DataElementHeaderV1::kSaltType &&
+      de_header->length == DataElementHeaderV1::kSaltLength) {
+    salt[0] = data[kSaltIndex + 1];
+    salt[1] = data[kSaltIndex + 2];
+  } else {
+    LOGE("Advertisement has no valid salt.");
+    return false;
+  }
+
+  de_header = DataElementHeaderV1::Decode(&data[kIdentityIndex],
+                                          data_size - kIdentityIndex);
+#ifdef LOG_INCLUDE_SENSITIVE_INFO
+  size_t identity_data_index = kIdentityIndex + kIdentityHeaderLength;
+  LOGD_SENSITIVE_INFO("encrypted identity:");
+  for (size_t i = identity_data_index;
+       i < (identity_data_index + DataElementHeaderV1::kIdentityLength); i++) {
+    LOGD_SENSITIVE_INFO("%" PRIi8, data[i]);
+  }
+  LOGD_SENSITIVE_INFO("metadata encryption key tag:");
+  for (size_t i = 0; i < metadata_encryption_key_tag.length; i++) {
+    LOGD_SENSITIVE_INFO("%" PRIi8, metadata_encryption_key_tag.data[i]);
+  }
+  LOGD_SENSITIVE_INFO("SALT [ %" PRIi8 ", %" PRIi8 "]", salt[0], salt[1]);
+  LOGD_SENSITIVE_INFO("authenticity key:");
+  for (size_t i = 0; i < key.length; i++) {
+    LOGD_SENSITIVE_INFO("%" PRIi8, key.data[i]);
+  }
+#endif
+  ByteArray decrypted_identity_array(identity,
+                                     DataElementHeaderV1::kIdentityLength);
+  if (de_header.has_value() &&
+      de_header->type >= DataElementHeaderV1::kPrivateIdentityType &&
+      de_header->type <= DataElementHeaderV1::kProvisionIdentityType &&
+      de_header->length == DataElementHeaderV1::kIdentityLength) {
+    if (!identity_crypto.decrypt(
+            ByteArray(&data[kIdentityIndex + kIdentityHeaderLength],
+                      DataElementHeaderV1::kIdentityLength),
+            ByteArray(salt, DataElementHeaderV1::kSaltLength), key,
+            decrypted_identity_array)) {
+      LOGE("Fail to decrypt the identity.");
+      return false;
+    }
+#ifdef LOG_INCLUDE_SENSITIVE_INFO
+    LOGD_SENSITIVE_INFO("decrypted identity:");
+    for (size_t i = 0; i < decrypted_identity_array.length; i++) {
+      LOGD_SENSITIVE_INFO("%" PRIi8, decrypted_identity_array.data[i]);
+    }
+#endif
+  } else {
+    LOGE("Advertisement has no identity.");
+    return false;
+  }
+  if (!identity_crypto.verify(decrypted_identity_array, ByteArray(),
+                              metadata_encryption_key_tag)) {
+    LOGD("Authenticity key not matched.");
+    return false;
+  }
+
+  if (data_size == kMinAdvertisementLength) {
+    LOGD("Presence advertisement has no data elements.");
+    return true;
+  }
+
+  size_t signature_length = kDataElementIndex + kDataElementSignatureLength;
+  if (data_size < signature_length) {
+    LOGE(
+        "Presence advertisement data elements signature has less than %zu "
+        "bytes.",
+        kDataElementSignatureLength);
+    return false;
+  }
+  size_t de_length = data_size - signature_length;
+
+  // Decodes Data Elements.
+  ByteArray decrypted_byte_array(decryption_output_buffer, de_length);
+#ifdef LOG_INCLUDE_SENSITIVE_INFO
+  LOGD_SENSITIVE_INFO("Data Elements length %d and encrypted bytes:",
+                      (int)de_length);
+  for (size_t i = kDataElementIndex; i < (kDataElementIndex + de_length); i++) {
+    LOGD_SENSITIVE_INFO("%" PRIi8, data[i]);
+  }
+  LOGD_SENSITIVE_INFO("Salt bytes: %" PRIi8 " %" PRIu8, salt[0], salt[1]);
+  LOGD_SENSITIVE_INFO("authenticity key:");
+  for (size_t i = 0; i < key.length; i++) {
+    LOGD_SENSITIVE_INFO("%" PRIi8, key.data[i]);
+  }
+#endif
+  if (!crypto.decrypt(ByteArray(&data[kDataElementIndex], de_length),
+                      ByteArray(salt, DataElementHeaderV1::kSaltLength), key,
+                      decrypted_byte_array)) {
+    LOGE("Fail to decrypt data elements.");
+    return false;
+  }
+#ifdef LOG_INCLUDE_SENSITIVE_INFO
+  LOGD_SENSITIVE_INFO("decrypted data elements bytes:");
+  for (size_t i = 0; i < decrypted_byte_array.length; i++) {
+    LOGD_SENSITIVE_INFO("%" PRIi8, decrypted_byte_array.data[i]);
+  }
+#endif
+  if (!crypto.verify(decrypted_byte_array, key,
+                     ByteArray(&data[data_size - kDataElementSignatureLength],
+                               kDataElementSignatureLength))) {
+    LOGE("Fail to verify data elements with signature.");
+    return false;
+  }
+  if (!DecodeDataElements(decrypted_byte_array.data,
+                          decrypted_byte_array.length)) {
+    LOGE("Advertisement has invalid data elements.");
+    return false;
+  }
+
+  decoded = true;
+  return true;
+}
+
+bool PresenceDecoderV1::DecodeDataElements(uint8_t data[], size_t data_size) {
+  chre::Optional<DataElementHeaderV1> de_header;
+  num_actions = 0;
+  for (size_t index = 0; index < data_size;) {
+    de_header = DataElementHeaderV1::Decode(&data[index], data_size - index);
+    if (de_header.has_value()) {
+      switch (de_header->type) {
+        case DataElementHeaderV1::kActionType:
+          if (de_header->length != DataElementHeaderV1::kActionLength) {
+            LOGE("Advertisement has incorrect action length");
+            return false;
+          }
+          actions[num_actions] = data[index + de_header->header_length];
+          num_actions++;
+          break;
+        case DataElementHeaderV1::kTxPowerType:
+          if (de_header->length != DataElementHeaderV1::kTxPowerLength) {
+            LOGE("Advertisement has incorrect TX power length");
+            return false;
+          }
+          tx_power = ByteArray(&data[index + de_header->header_length],
+                               de_header->length);
+          break;
+        case DataElementHeaderV1::kModelIdType:
+          if (de_header->length != DataElementHeaderV1::kModelIdLength) {
+            LOGE("Advertisement has incorrect model ID length");
+            return false;
+          }
+          model_id = ByteArray(&data[index + de_header->header_length],
+                               de_header->length);
+          break;
+        case DataElementHeaderV1::kConnectionStatusType:
+          if (de_header->length !=
+              DataElementHeaderV1::kConnectionStatusLength) {
+            LOGE("Advertisement has incorrect connection status length");
+            return false;
+          }
+          connection_status = ByteArray(&data[index + de_header->header_length],
+                                        de_header->length);
+          break;
+        case DataElementHeaderV1::kBatteryStatusType:
+          if (de_header->length != DataElementHeaderV1::kBatteryStatusLength) {
+            LOGE("Advertisement has incorrect battery status length");
+            return false;
+          }
+          battery_status = ByteArray(&data[index + de_header->header_length],
+                                     de_header->length);
+          break;
+        default:
+          if (IsValidExtDataElementsType(de_header->type)) {
+            extended_des.push_back(DataElement(
+                static_cast<nearby_DataElement_ElementType>(de_header->type),
+                ByteArray(&data[index + de_header->header_length],
+                          de_header->length)));
+          } else {
+            LOGD("Invalid DE type(%" PRIi64 ") is included", de_header->type);
+          }
+          break;
+      }
+    }
+    index = index + de_header->header_length + de_header->length;
+  }
+  return true;
+}
+
+bool PresenceDecoderV1::IsValidExtDataElementsType(uint64_t type) {
+#ifdef ENABLE_TEST_DE
+  return (type >= nearby_DataElement_ElementType_DE_TEST_BEGIN &&
+          type <= nearby_DataElement_ElementType_DE_TEST_END);
+#endif
+  return false;
+}
+
+chre::Optional<DataElementHeaderV1> DataElementHeaderV1::Decode(
+    const uint8_t data[], const size_t data_size) {
+  // The bytes to define Data Element type should be less than 8.
+  constexpr size_t kTypeMaxByteLength = 8;
+  constexpr uint8_t kExtendBitMask = 0b10000000;
+  constexpr uint8_t kNoneExtendBitsMask = 0b01111111;
+  constexpr uint8_t kLengthBitsMask = 0b01110000;
+  constexpr uint8_t kTypeBitsMask = 0b00001111;
+
+  DataElementHeaderV1 header;
+
+  if (data_size == 0) {
+    LOGE("Decode Data Element header from zero byte.");
+    return chre::Optional<DataElementHeaderV1>();
+  }
+
+  // Single byte header
+  if ((data[0] & kExtendBitMask) == 0) {
+    header.length = (data[0] & kLengthBitsMask) >> 4;
+    header.type = (data[0] & kTypeBitsMask);
+    header.header_length = 1;
+    LOGD("Return single byte header with length: %" PRIu8 " and type: %" PRIu64,
+         header.length, header.type);
+    return header;
+  }
+
+  // multi-byte header
+  header.length = data[0] & kNoneExtendBitsMask;
+  header.type = 0;
+  size_t i = 1;
+  while (true) {
+    if (i > kTypeMaxByteLength) {
+      LOGE("Type exceeds the maximum byte length: %zu", kTypeMaxByteLength);
+      return chre::Optional<DataElementHeaderV1>();
+    }
+    if (i >= data_size) {
+      LOGE("Extended byte exceeds the data size.");
+      return chre::Optional<DataElementHeaderV1>();
+    }
+    header.type = (header.type << 7) | (data[i] & kNoneExtendBitsMask);
+    if ((data[i] & kExtendBitMask) == 0) {
+      break;
+    } else {
+      i++;
+    }
+  }
+  header.header_length = static_cast<uint8_t>(i + 1);
+  LOGD("Return multi byte header with length: %" PRIu8 " and type: %" PRIu64,
+       header.length, header.type);
+  return header;
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.h
new file mode 100644
index 0000000..cd2d631
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_DECODER_V1_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_DECODER_V1_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "location/lbs/contexthub/nanoapps/nearby/byte_array.h"
+#include "location/lbs/contexthub/nanoapps/nearby/crypto.h"
+#include "location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.nanopb.h"
+#include "third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h"
+#include "third_party/contexthub/chre/util/include/chre/util/optional.h"
+
+namespace nearby {
+
+// Represents a Data Element header.
+struct DataElementHeaderV1 {
+  // Decodes data and returns the first Data Element header.
+  // Returns no value if decoding fails.
+  static chre::Optional<DataElementHeaderV1> Decode(const uint8_t data[],
+                                                    const size_t data_size);
+
+  static constexpr uint64_t kSaltType = 0;
+  static constexpr uint64_t kPrivateIdentityType = 1;
+  static constexpr uint64_t kProvisionIdentityType = 4;
+  static constexpr uint64_t kTxPowerType = 5;
+  static constexpr uint64_t kActionType = 6;
+  static constexpr uint64_t kModelIdType = 7;
+  static constexpr uint64_t kConnectionStatusType = 10;
+  static constexpr uint64_t kBatteryStatusType = 11;
+
+  static constexpr size_t kSaltLength = 2;
+  static constexpr size_t kIdentityLength = 16;
+  static constexpr size_t kTxPowerLength = 1;
+  static constexpr size_t kActionLength = 1;
+  static constexpr size_t kModelIdLength = 3;
+  static constexpr size_t kConnectionStatusLength = 3;
+  static constexpr size_t kBatteryStatusLength = 3;
+
+  // length of data element value.
+  uint8_t length;
+  // length of header itself.
+  uint8_t header_length;
+  // type of data element.
+  uint64_t type;
+};
+
+struct DataElement {
+  DataElement(nearby_DataElement_ElementType key, ByteArray value)
+      : key(key), value(value) {}
+
+  nearby_DataElement_ElementType key;
+  ByteArray value;
+};
+
+// PresenceDecoderV1 contains data fields specified by Presence V1.
+struct PresenceDecoderV1 {
+  static constexpr size_t kMaxNumActions = 5;
+  static constexpr size_t kDecryptionOutputBufSize = 16 * 20;
+
+  PresenceDecoderV1() = default;
+  // Decodes encoded_data which is a byte array encoded by following the
+  // Presence V1 specification. Returns true when decoding succeeds.
+  bool Decode(const ByteArray &encoded_data, const Crypto &crypto,
+              const Crypto &identity_crypto, const ByteArray &key,
+              const ByteArray &metadata_encryption_key_tag);
+  // Helper function to decode Presence data elements from data.
+  // Returns true if decoding succeeds.
+  bool DecodeDataElements(uint8_t data[], size_t data_size);
+  // Returns true if valid Extended DE type.
+  bool IsValidExtDataElementsType(uint64_t type);
+
+  // required fields.
+  uint8_t salt[DataElementHeaderV1::kSaltLength];
+  uint8_t identity[DataElementHeaderV1::kIdentityLength];
+
+  // repeated fields.
+  uint8_t actions[kMaxNumActions];
+  size_t num_actions = 0;
+
+  // optional fields. An empty field is defined as Zero length of ByteArray.
+  ByteArray tx_power;
+  ByteArray model_id;
+  ByteArray connection_status;
+  ByteArray battery_status;
+
+  // Extended DE list.
+  chre::DynamicVector<DataElement> extended_des;
+
+  // Sets to true after successfully decoding.
+  bool decoded = false;
+
+ private:
+  // Decrypted buffer provide the underlying storage for optional fields.
+  uint8_t decryption_output_buffer[kDecryptionOutputBufSize];
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_DECODER_V1_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_filter.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_filter.cc
new file mode 100644
index 0000000..6d474ee
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_filter.cc
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/presence_filter.h"
+
+#include <inttypes.h>
+
+#include <cstddef>
+
+#include "location/lbs/contexthub/nanoapps/nearby/crypto_non_encryption.h"
+#include "location/lbs/contexthub/nanoapps/nearby/presence_crypto_identity_v1.h"
+#include "location/lbs/contexthub/nanoapps/nearby/presence_crypto_v1.h"
+#include "location/lbs/contexthub/nanoapps/nearby/presence_decoder_v1.h"
+#include "location/lbs/contexthub/nanoapps/nearby/presence_service_data.h"
+#include "location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.nanopb.h"
+#include "third_party/contexthub/chre/util/include/chre/util/macros.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][PRESENCE_FILTER]"
+
+namespace nearby {
+
+constexpr int kAuthenticityKeyLength = 16;
+constexpr int kMetaDataEncryptionTagLength = 8;
+
+static bool addDataElementToResult(nearby_DataElement_ElementType de_type,
+                                   const ByteArray &de_value,
+                                   nearby_BleFilterResult *result) {
+  size_t de_index = result->data_element_count;
+  if (de_index >= ARRAY_SIZE(result->data_element)) {
+    LOGE("Data Elements(%u) exceed the maximum count: %zu", de_type, de_index);
+    return false;
+  }
+  if (de_value.length > ARRAY_SIZE(result->data_element[de_index].value)) {
+    LOGE("Data Element(%u) exceeds the maximum length: %zu", de_type,
+         de_value.length);
+    return false;
+  }
+  result->data_element_count++;
+  result->data_element[de_index].has_key = true;
+  result->data_element[de_index].key = de_type;
+  result->data_element[de_index].has_value = true;
+  result->data_element[de_index].has_value_length = true;
+  result->data_element[de_index].value_length =
+      static_cast<uint32_t>(de_value.length);
+  LOGD_SENSITIVE_INFO(
+      "AddDataElementToResult de_index: %zu de_type: %d de_value.length: %zu",
+      de_index, de_type, de_value.length);
+  memcpy(result->data_element[de_index].value, de_value.data, de_value.length);
+  return true;
+}
+
+bool MatchFastPairInitial(const nearby_BleFilter &filter,
+                          const PresenceServiceData &service_data,
+                          nearby_BleFilterResult *result) {
+  if (!service_data.has_fp_model_id) {
+    return false;
+  }
+  bool has_initial_pairing_filter = false;
+  for (int i = 0; i < filter.data_element_count; i++) {
+    if (filter.data_element[i].has_key &&
+        filter.data_element[i].key ==
+            nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY &&
+        filter.data_element[i].has_value &&
+        filter.data_element[i].has_value_length &&
+        filter.data_element[i].value_length == kFpAccountKeyLength) {
+      uint32_t value_byte_summary = 0;
+      for (int j = 0; j < kFpAccountKeyLength; j++) {
+        value_byte_summary += filter.data_element[i].value[j];
+      }
+      if (value_byte_summary == 0) {
+        has_initial_pairing_filter = true;
+        break;
+      }
+    }
+  }
+  if (has_initial_pairing_filter) {
+    size_t de_index = result->data_element_count;
+    result->data_element_count++;
+    result->data_element[de_index].has_key = true;
+    result->data_element[de_index].key =
+        nearby_DataElement_ElementType_DE_FAST_PAIR_ACCOUNT_KEY;
+    // value bytes have already been initialized to zero by default.
+    result->data_element[de_index].has_value = true;
+    result->data_element[de_index].has_value_length = true;
+    result->data_element[de_index].value_length = kFpAccountKeyLength;
+    result->has_result_type = true;
+    result->result_type = nearby_BleFilterResult_ResultType_RESULT_FAST_PAIR;
+    return true;
+  }
+  return false;
+}
+
+bool MatchPresenceV0(const nearby_BleFilter &filter,
+                     const BleScanRecord &scan_record,
+                     nearby_BleFilterResult *result) {
+  chre::Optional<PresenceServiceData> presence_service_data;
+  for (const auto &ble_service_data : scan_record.service_data) {
+    if (ble_service_data.uuid == PresenceServiceData::kUuid) {
+      presence_service_data = PresenceServiceData::Parse(
+          ble_service_data.data, ble_service_data.length);
+      if (ble_service_data.length <= sizeof(result->ble_service_data)) {
+        result->has_ble_service_data = true;
+        memcpy(result->ble_service_data, ble_service_data.data,
+               ble_service_data.length);
+      } else {
+        LOGI("Received the BLE advertisement with length larger than %zu",
+             sizeof(result->ble_service_data));
+      }
+      break;
+    }
+  }
+  if (!presence_service_data.has_value()) {
+    LOGI("[MatchPresenceV0] presence_service_data is empty.");
+    return false;
+  }
+
+  if (MatchFastPairInitial(filter, presence_service_data.value(), result)) {
+    LOGD("MatchFastPairInitial succeeded");
+    return true;
+  } else {
+    LOGD("[MatchPresenceV0] filter Presence");
+    if (filter.has_intent) {
+      if (!presence_service_data.has_value()) {
+        return false;
+      }
+      if (presence_service_data->first_intent.has_value() &&
+          filter.intent == presence_service_data->first_intent.value()) {
+        return true;
+      } else if (presence_service_data->second_intent.has_value() &&
+                 filter.intent ==
+                     presence_service_data->second_intent.value()) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+    return false;
+  }
+}
+
+static bool MatchExtendedDE(
+    const nearby_BleFilter &filter,
+    const chre::DynamicVector<DataElement> &extended_des,
+    nearby_BleFilterResult *result) {
+  for (int i = 0; i < filter.data_element_count; i++) {
+    // If filter is valid, at least one DE should match with this filter.
+    // Otherwise, returns failure
+    if (filter.data_element[i].has_key && filter.data_element[i].has_value &&
+        filter.data_element[i].has_value_length) {
+      bool is_matched = false;
+      for (const auto &ext_de : extended_des) {
+        if (ext_de.key == filter.data_element[i].key &&
+            ext_de.value.length == filter.data_element[i].value_length &&
+            memcmp(ext_de.value.data, filter.data_element[i].value,
+                   filter.data_element[i].value_length) == 0) {
+          is_matched = true;
+          break;
+        }
+      }
+      if (!is_matched) {
+        LOGD("Match Presence V1 Data Element failed with %" PRIi32 " type.",
+             filter.data_element[i].key);
+        return false;
+      }
+    }
+  }
+  // Passed all filters. Adds all DEs into results.
+  for (const auto &ext_de : extended_des) {
+    if (!addDataElementToResult(ext_de.key, ext_de.value, result)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool MatchPresenceV1(const nearby_BleFilter &filter,
+                     const BleScanRecord &scan_record, const Crypto &crypto,
+                     const Crypto &identity_crypto,
+                     nearby_BleFilterResult *result) {
+  LOGD_SENSITIVE_INFO("Filter Presence V1 with %" PRIu16 " certificates",
+                      filter.certificate_count);
+  PresenceDecoderV1 decoder;
+  for (const auto &ble_service_data : scan_record.service_data) {
+    if (ble_service_data.uuid == PresenceServiceData::kUuid) {
+      for (int cert_index = 0; cert_index < filter.certificate_count;
+           cert_index++) {
+        ByteArray authenticity_key(
+            const_cast<uint8_t *>(
+                filter.certificate[cert_index].authenticity_key),
+            kAuthenticityKeyLength);
+        LOGD_SENSITIVE_INFO("certificate metadata encryption key tag:");
+        for (size_t i = 0; i < kMetaDataEncryptionTagLength; i++) {
+          LOGD_SENSITIVE_INFO(
+              "%" PRIi8,
+              filter.certificate[cert_index].metadata_encryption_key_tag[i]);
+        }
+        ByteArray metadata_encryption_key_tag(
+            const_cast<uint8_t *>(
+                filter.certificate[cert_index].metadata_encryption_key_tag),
+            kMetaDataEncryptionTagLength);
+        if (decoder.Decode(
+                ByteArray(const_cast<uint8_t *>(ble_service_data.data),
+                          ble_service_data.length),
+                crypto, identity_crypto, authenticity_key,
+                metadata_encryption_key_tag)) {
+          result->has_public_credential = true;
+          result->public_credential.has_encrypted_metadata_tag = true;
+          for (size_t i = 0; i < kMetaDataEncryptionTagLength; i++) {
+            result->public_credential.encrypted_metadata_tag[i] =
+                metadata_encryption_key_tag.data[i];
+          }
+          result->public_credential.has_authenticity_key = true;
+          for (size_t i = 0; i < kAuthenticityKeyLength; i++) {
+            result->public_credential.authenticity_key[i] =
+                authenticity_key.data[i];
+          }
+          // TODO(b/244786064): remove unused fields.
+          result->public_credential.has_secret_id = true;
+          result->public_credential.has_encrypted_metadata = true;
+          result->public_credential.has_public_key = true;
+          LOGD("Succeeded to decode Presence advertisement v1.");
+          break;
+        }
+      }
+    }
+  }
+
+  if (!decoder.decoded) {
+    LOGD("Decode Presence V1 failed.");
+    return false;
+  }
+
+  if (filter.has_intent) {
+    bool action_matched = false;
+    for (size_t i = 0; i < decoder.num_actions; i++) {
+      LOGD("Match filter action %" PRIu32 " with advertisement action %" PRIu8,
+           filter.intent, decoder.actions[i]);
+      if (filter.intent == decoder.actions[i]) {
+        result->has_intent = true;
+        result->intent = filter.intent;
+        action_matched = true;
+        break;
+      }
+    }
+    if (!action_matched) {
+      return false;
+    }
+  }
+
+  if (decoder.connection_status.data != nullptr) {
+    if (!addDataElementToResult(
+            nearby_DataElement_ElementType_DE_CONNECTION_STATUS,
+            decoder.connection_status, result)) {
+      return false;
+    }
+  }
+
+  if (decoder.battery_status.data != nullptr) {
+    if (!addDataElementToResult(
+            nearby_DataElement_ElementType_DE_BATTERY_STATUS,
+            decoder.battery_status, result)) {
+      return false;
+    }
+  }
+
+  if (!MatchExtendedDE(filter, decoder.extended_des, result)) {
+    return false;
+  }
+
+  result->has_result_type = true;
+  result->result_type = nearby_BleFilterResult_ResultType_RESULT_PRESENCE;
+  return true;
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_filter.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_filter.h
new file mode 100644
index 0000000..e6412c8
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_filter.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_FILTER_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_FILTER_H_
+
+#include "location/lbs/contexthub/nanoapps/nearby/ble_scan_record.h"
+#include "location/lbs/contexthub/nanoapps/nearby/crypto.h"
+#include "location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.nanopb.h"
+
+namespace nearby {
+
+bool MatchPresenceV0(const nearby_BleFilter &filter,
+                     const BleScanRecord &scan_record,
+                     nearby_BleFilterResult *result);
+
+bool MatchPresenceV1(const nearby_BleFilter &filter,
+                     const BleScanRecord &scan_record, const Crypto &crypto,
+                     const Crypto &identity_crypto,
+                     nearby_BleFilterResult *result);
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_FILTER_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_service_data.cc b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_service_data.cc
new file mode 100644
index 0000000..854daad
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_service_data.cc
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 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 "location/lbs/contexthub/nanoapps/nearby/presence_service_data.h"
+
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/assert.h"
+#include "third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h"
+
+#define LOG_TAG "[NEARBY][SERVICE_DATA]"
+
+namespace nearby {
+
+chre::Optional<PresenceServiceData> PresenceServiceData::Parse(
+    const uint8_t data[], const uint16_t size) {
+  const uint8_t service_header = data[0];
+  if (((service_header & 0b11100000) >> 5) != 0) {
+    LOGD("Presence advertisement version is not 0.");
+    return chre::Optional<PresenceServiceData>();
+  }
+  uint8_t num_fields = (service_header & 0b00011110) >> 1;
+  LOGD("Decode a FILTERS message with size %d", num_fields);
+
+  PresenceServiceData presence_service_data;
+  // pointer to scan data, starting from 1.
+  uint16_t p = 1;
+  while (num_fields > 0) {
+    PresenceFieldHeader header(data[p], data[p + 1]);
+    switch (header.type) {
+      case PresenceFieldHeader::kIntentType:
+        presence_service_data.first_intent = data[p + 1];
+        presence_service_data.second_intent = data[p + 2];
+        break;
+      case PresenceFieldHeader::kFpModelIdType:
+        if (header.length == kFpModelIdLength) {
+          presence_service_data.has_fp_model_id = true;
+          memcpy(presence_service_data.fp_model_id, &data[p + 1],
+                 kFpModelIdLength);
+        } else {
+          LOGE("Fast Pair model ID length %d not equal to %d", header.length,
+               kFpModelIdLength);
+        }
+        break;
+      case PresenceFieldHeader::kFpAccountKeyDataType:
+        if (header.length == kFpAccountKeyDataLength) {
+          presence_service_data.has_fp_account_key_data = true;
+          presence_service_data.fp_account_key_salt[0] = data[p + 1];
+          presence_service_data.fp_account_key_salt[1] = data[p + 2];
+          memcpy(presence_service_data.fp_account_key_filter, &data[p + 3],
+                 kFpAccountKeyFilterLength);
+        } else {
+          LOGE("Fast account key filter length %d not equal to %d",
+               header.length, kFpAccountKeyDataLength);
+        }
+        break;
+      case PresenceFieldHeader::kBatteryStatusType:
+        if (header.length == kFpBatteryStatusLength) {
+          presence_service_data.has_battery_status = true;
+          memcpy(presence_service_data.fp_battery_status, &data[p + 1],
+                 kFpBatteryStatusLength);
+        } else {
+          LOGE("Battery status length %d not equal to %d", header.length,
+               kFpBatteryStatusLength);
+        }
+        break;
+      case PresenceFieldHeader::kExtensionType:
+      default:
+        break;
+    }
+    num_fields--;
+    // Moves p to the field value
+    p = header.type == PresenceFieldHeader::kExtensionType ? p + 2 : p + 1;
+    // Further moves p to the next field header
+    p += header.length;
+  }
+  // p should be moved to size for a valid encode. Otherwise, returns an empty
+  // value.
+  return (p == size ? presence_service_data
+                    : chre::Optional<PresenceServiceData>());
+}
+
+}  // namespace nearby
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_service_data.h b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_service_data.h
new file mode 100644
index 0000000..27920a2
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/presence_service_data.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 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 LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_SERVICE_DATA_H_
+#define LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_SERVICE_DATA_H_
+
+#include <stdint.h>
+
+#include "third_party/contexthub/chre/util/include/chre/util/non_copyable.h"
+#include "third_party/contexthub/chre/util/include/chre/util/optional.h"
+
+namespace nearby {
+static constexpr uint8_t kFpAccountKeyLength = 16;
+static constexpr uint8_t kFpAccountKeySaltLength = 2;
+static constexpr uint8_t kFpAccountKeyFilterLength = 9;
+static constexpr uint8_t kFpAccountKeyDataLength =
+    kFpAccountKeySaltLength + kFpAccountKeyFilterLength;
+static constexpr uint8_t kFpModelIdLength = 3;
+static constexpr uint8_t kFpBatteryStatusLength = 3;
+
+// Represents a Nearby service data in BLE advertisement.
+struct PresenceServiceData {
+ public:
+  static constexpr uint16_t kUuid = 0xFCF1;
+
+  // Returns Presence service data by parsing data, which is an encoded byte
+  // following the spec (go/nearby-presence-spec).
+  // Returns no value if parse fails. Callee keeps the ownership of data.
+  static chre::Optional<PresenceServiceData> Parse(const uint8_t data[],
+                                                   const uint16_t size);
+  chre::Optional<uint8_t> first_intent;
+  chre::Optional<uint8_t> second_intent;
+  bool has_fp_model_id = false;
+  uint8_t fp_model_id[kFpModelIdLength];
+  bool has_fp_account_key_data = false;
+  uint8_t fp_account_key_salt[kFpAccountKeySaltLength];
+  uint8_t fp_account_key_filter[kFpAccountKeyFilterLength];
+  bool has_battery_status = false;
+  uint8_t fp_battery_status[kFpBatteryStatusLength];
+};
+
+// Represents a field header inside a Nearby service data.
+struct PresenceFieldHeader {
+ public:
+  // Constructs a Field Header for Presence service data from a header and an
+  // optional extension byte.
+  PresenceFieldHeader(const uint8_t header, const uint8_t extension) {
+    type = header & 0x0F;
+    if (type == kExtensionType) {
+      type = extension & 0x0F;
+      length = ((header & 0xF0) >> 2) + ((extension & 0xC0) >> 6);
+    } else {
+      length = (header & 0xF0) >> 4;
+    }
+  }
+
+  // Constants defining the Presence data element type, sorted by their value.
+  static constexpr uint8_t kIntentType = 0b0101;
+  // Fast Pair model ID.
+  static constexpr uint8_t kFpModelIdType = 0b0111;
+  // Fast Pair Account Key data includes both SALT and Bloom filter.
+  static constexpr uint8_t kFpAccountKeyDataType = 0b1001;
+  static constexpr uint8_t kBatteryStatusType = 0b1011;
+  static constexpr uint8_t kExtensionType = 0b1111;
+
+  // length of data element value.
+  uint8_t length;
+  // type of data element.
+  uint8_t type;
+};
+
+}  // namespace nearby
+
+#endif  // LOCATION_LBS_CONTEXTHUB_NANOAPPS_NEARBY_PRESENCE_SERVICE_DATA_H_
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.options b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.options
new file mode 100644
index 0000000..4501c2e
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.options
@@ -0,0 +1,66 @@
+//  Copyright (C) 2023 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.
+//
+// This file is only for nanopb which in use of nanoapp.
+// GmsCore code is not affected by this file.
+// The binary message on wire is not affected by this file either.
+
+nearby.PublicateCertificate.authenticity_key max_size:16
+nearby.PublicateCertificate.authenticity_key type:FT_INLINE
+
+nearby.PublicateCertificate.metadata_encryption_key_tag max_size:8
+nearby.PublicateCertificate.metadata_encryption_key_tag type:FT_INLINE
+
+nearby.PublicCredential.secret_id max_size:4
+nearby.PublicCredential.secret_id type:FT_INLINE
+
+nearby.PublicCredential.authenticity_key max_size:16
+nearby.PublicCredential.authenticity_key type:FT_INLINE
+
+nearby.PublicCredential.public_key max_size:4
+nearby.PublicCredential.public_key type:FT_INLINE
+
+nearby.PublicCredential.encrypted_metadata max_size:4
+nearby.PublicCredential.encrypted_metadata type:FT_INLINE
+
+nearby.PublicCredential.encrypted_metadata_tag max_size:8
+nearby.PublicCredential.encrypted_metadata_tag type:FT_INLINE
+
+nearby.BleFilter.mac_address max_size:6
+nearby.BleFilter.mac_address type:FT_INLINE
+
+nearby.BleFilter.mac_mask max_size:6
+nearby.BleFilter.mac_mask type:FT_INLINE
+
+// TODO(b/193756395): change to variable size.
+nearby.BleFilter.certificate max_count:3
+
+// TODO(b/193756395): change to variable size.
+nearby.BleFilters.filter max_count:10
+nearby.BleFilters.filter type:FT_STATIC
+
+nearby.BleFilterResult.bluetooth_address max_size:6
+nearby.BleFilterResult.bluetooth_address type:FT_INLINE
+
+// can hold an account key with 128 bits.
+nearby.DataElement.value max_size:16
+nearby.DataElement.value type:FT_INLINE
+
+nearby.BleFilter.data_element max_count:8
+nearby.BleFilter.data_element type:FT_STATIC
+
+nearby.BleFilterResult.data_element max_count:8
+nearby.BleFilterResult.data_element type:FT_STATIC
+
+nearby.BleFilterResult.ble_service_data max_size:32
+nearby.BleFilterResult.ble_service_data type:FT_INLINE
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.proto b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.proto
new file mode 100644
index 0000000..0f938c4
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/ble_filter.proto
@@ -0,0 +1,131 @@
+//  Copyright (C) 2023 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.
+//
+// Proto Messages define the interface between Nearby nanoapp and its host.
+//
+// Host registers its interest in BLE event by configuring nanoapp with Filters.
+// The nanoapp keeps watching BLE events and notifies host once an event matches
+// a Filter.
+//
+// Each Filter is defined by its id (required) with optional fields of rssi,
+// uuid, MAC etc. The host should guarantee the uniqueness of ids. It is
+// convenient to assign id incrementally when adding a Filter such that its id
+// is the same as the index of the repeated field in Filters.
+//
+// The nanoapp compares each BLE event against the list of Filters, and notifies
+// host when the event matches a Filter. The Field's id will be sent back to
+// host in the FilterResult.
+//
+// It is possible for the nanoapp to return multiple ids when an event matches
+// multiple Filters.
+
+syntax = "proto2";
+
+package nearby;
+
+option java_package = "com.google.location.lbs.contexthub";
+option java_outer_classname = "NearbyNano";
+
+// Certificate to verify BLE events from trusted devices.
+// When receiving an advertisement from a remote device, it will
+// be decrypted by authenticity_key and SHA hashed. The device
+// is verified as trusted if the hash result is equal to
+// metadata_encryption_key_tag.
+// See details in go/ns-certificates.
+message PublicateCertificate {
+  optional bytes authenticity_key = 1;
+  optional bytes metadata_encryption_key_tag = 2;
+}
+
+// Public credential returned in BleFilterResult.
+message PublicCredential {
+  optional bytes secret_id = 1;
+  optional bytes authenticity_key = 2;
+  optional bytes public_key = 3;
+  optional bytes encrypted_metadata = 4;
+  optional bytes encrypted_metadata_tag = 5;
+}
+
+message DataElement {
+  enum ElementType {
+    DE_NONE = 0;
+    DE_FAST_PAIR_ACCOUNT_KEY = 9;
+    DE_CONNECTION_STATUS = 10;
+    DE_BATTERY_STATUS = 11;
+    // Reserves 128 Test DEs.
+    DE_TEST_BEGIN = 2147483520;  // INT_MAX - 127
+    DE_TEST_END = 2147483647;    // INT_MAX
+  }
+
+  optional int32 key = 1;
+  optional bytes value = 2;
+  optional uint32 value_length = 3;
+}
+
+// A single filter used to filter BLE events.
+message BleFilter {
+  optional uint32 id = 1;  // Required, unique id of this filter.
+  // Maximum delay to notify the client after an event occurs.
+  optional uint32 latency_ms = 2;
+  optional uint32 uuid = 3;
+  // MAC address of the advertising device.
+  optional bytes mac_address = 4;
+  optional bytes mac_mask = 5;
+  // Represents an action that scanners should take when they receive this
+  // packet. See go/nearby-presence-spec for details.
+  optional uint32 intent = 6;
+  // Notify the client if the advertising device is within the distance.
+  // For moving object, the distance is averaged over data sampled within
+  // the period of latency defined above.
+  optional float distance_m = 7;
+  // Used to verify the list of trusted devices.
+  repeated PublicateCertificate certificate = 8;
+  repeated DataElement data_element = 9;
+}
+
+message BleFilters {
+  repeated BleFilter filter = 1;
+}
+
+// FilterResult is returned to host when a BLE event matches a Filter.
+message BleFilterResult {
+  enum ResultType {
+    RESULT_NONE = 0;
+    RESULT_PRESENCE = 1;
+    RESULT_FAST_PAIR = 2;
+  }
+
+  optional uint32 id = 1;  // id of the matched Filter.
+  optional int32 tx_power = 2;
+  optional int32 rssi = 3;
+  optional uint32 intent = 4;
+  optional bytes bluetooth_address = 5;
+  optional PublicCredential public_credential = 6;
+  repeated DataElement data_element = 7;
+  optional bytes ble_service_data = 8;
+  optional ResultType result_type = 9;
+}
+
+message BleFilterResults {
+  repeated BleFilterResult result = 1;
+}
+
+message BleConfig {
+  // True to start BLE scan. Otherwise, stop BLE scan.
+  optional bool start_scan = 1;
+  // True when screen is turned on. Otherwise, set to false when screen is
+  // turned off.
+  optional bool screen_on = 2;
+  // Fast Pair cache expires after this time period.
+  optional uint64 fast_pair_cache_expire_time_sec = 3;
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/nearby_extension.options b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/nearby_extension.options
new file mode 100644
index 0000000..769841b
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/nearby_extension.options
@@ -0,0 +1,36 @@
+//  Copyright (C) 2023 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.
+
+nearby_extension.FilterConfig.oem_filter max_size:512
+nearby_extension.FilterConfig.oem_filter type:FT_INLINE
+
+nearby_extension.FilterConfig.hardware_filter max_count:10
+nearby_extension.FilterConfig.hardware_filter type:FT_STATIC
+
+nearby_extension.ChreBleGenericFilter.data max_size:29
+nearby_extension.ChreBleGenericFilter.data type:FT_INLINE
+
+nearby_extension.ChreBleGenericFilter.data_mask max_size:29
+nearby_extension.ChreBleGenericFilter.data_mask type:FT_INLINE
+
+nearby_extension.FilterResult.report max_count:20
+nearby_extension.FilterResult.report type:FT_STATIC
+
+nearby_extension.ChreBleAdvertisingReport.address max_size:6
+nearby_extension.ChreBleAdvertisingReport.address type:FT_INLINE
+
+nearby_extension.ChreBleAdvertisingReport.data max_size:29
+nearby_extension.ChreBleAdvertisingReport.data type:FT_INLINE
+
+
+
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/nearby_extension.proto b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/nearby_extension.proto
new file mode 100644
index 0000000..f289c57
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/nearby/proto/nearby_extension.proto
@@ -0,0 +1,165 @@
+//  Copyright (C) 2023 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.
+//
+// Proto Messages define the interface between Nearby nanoapp and its hosts
+// (OEM services) for Nearby offload extension.
+//
+syntax = "proto2";
+
+package nearby_extension;
+
+option java_package = "com.google.location.lbs.contexthub";
+option java_outer_classname = "NearbyOffloadExtension";
+
+message FilterConfig {
+  // Vendor-specific configuration data for extended filter. Byte array opaque
+  // to Nearby nanoapp, which will be forwarded through
+  // chrexNearbySetExtendedFilterConfig().
+  // If the OEM service wishes to send more data than can fit in a single
+  // message, or update previous configuration, it can send additional messages.
+  optional bytes oem_filter = 1;
+  optional uint32 oem_filter_length = 2;
+
+  // List of Hardware filters (follows chreBleScanFilter defined in CHRE BLE
+  // API). Resource for hardware filters is constrained in CHRE, and hardware
+  // filtering is best effort, i.e. advertisements may still be forwarded for
+  // inspection if they do not match the configured hardware filters. It is
+  // expected that an OEM service will include at least one hardware filter in
+  // the first message. Subsequent messages that do not include this field will
+  // not impact previously configured filters. But if this field is populated in
+  // a subsequent message, its contents will replace any previously set filters.
+  // To remove all previously set hardware filters, reset extended filtering by
+  // closing the ContextHubClient connection.
+  repeated ChreBleGenericFilter hardware_filter = 3;
+
+  // Maximum time to batch BLE scan results before processing in the nanoapp, in
+  // milliseconds. For optimal power, specify the longest value that the use
+  // case permits. If not provided, either the last provided value will continue
+  // to be used, or if no previous value was given, defaults defined in the
+  // Nearby nanoapp will be used.
+  optional uint32 report_delay_ms = 4;
+
+  // BLE scan modes identify functional scan levels without specifying or
+  // guaranteeing particular scan parameters (e.g. duty cycle, interval, radio
+  // chain). The actual scan parameters may be platform dependent and may change
+  // without notice in real time based on contextual cues, etc.
+  optional ChreBleScanMode scan_mode = 5;
+}
+
+message ChreBleGenericFilter {
+  enum ChreBleAdType {
+    CHRE_BLE_AD_TYPE_SERVICE_DATA_NONE = 0;
+
+    // Service Data with 16-bit UUID
+    CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 = 0x16;
+
+    // Manufacturer Specific Data
+    CHRE_BLE_AD_TYPE_MANUFACTURER_DATA = 0xff;
+  }
+
+  optional ChreBleAdType type = 1;
+  // Length of data and data_mask below.
+  optional uint32 len = 2;
+
+  // data and dataMask must have the same length and are combined together
+  // to filter an advertisement.
+  optional bytes data = 3;
+  optional bytes data_mask = 4;
+}
+
+enum ChreBleScanMode {
+  CHRE_BLE_SCAN_MODE_UNSPECIFIED = 0;
+  //! A background scan level for always-running ambient applications.
+  //! A representative duty cycle may be between 3 - 10 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_BACKGROUND = 1;
+
+  //! A foreground scan level to be used for short periods.
+  //! A representative duty cycle may be between 10 - 20 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_FOREGROUND = 2;
+
+  //! A very high duty cycle scan level to be used for very short durations.
+  //! A representative duty cycle may be between 50 - 100 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_AGGRESSIVE = 3;
+}
+
+// Sent in response to FilterConfig
+message FilterConfigResult {
+  // Value from enum chrexNearbyResult that was returned by
+  // chrexNearbySetExtendedFilterConfig()
+  optional int32 result = 1;
+  // Vendor-defined status code provided in chrexNearbySetExtendedFilterConfig()
+  optional uint32 vendor_status = 2;
+}
+
+// Sent when one or more advertisements matched an extended filter
+message FilterResult {
+  enum ErrorCode {
+    UNSUPPORTED = 0;
+    SUCCESS = 1;
+  }
+
+  // Error code returned to OEM services.
+  optional ErrorCode error_code = 1;
+
+  // Each report contains multiple advertisements in a batch.
+  repeated ChreBleAdvertisingReport report = 2;
+}
+
+message ChreBleAdvertisingReport {
+  enum AddressType {
+    // Public device address.
+    PUBLIC = 0x00;
+
+    // Random device address.
+    RANDOM = 0x01;
+
+    // Public identity address (corresponds to resolved private address).
+    PUBLIC_IDENTITY = 0x02;
+
+    // Random (static) Identity Address (corresponds to resolved private
+    // address)
+    RANDOM_IDENTITY = 0x03;
+
+    // No address provided (anonymous advertisement).
+    NONE = 0xff;
+  }
+
+  // Timestamp the advertisement was received, in nanoseconds, relative to
+  // Android SystemClock.elapsedRealtimeNanos().
+  optional uint64 timestamp = 1;
+
+  // BLE event type and status. Refer to BT Core Spec v5.2, Vol 4, Part E,
+  // Section 7.7.65.13, LE Extended Advertising Report event, Event_Type for
+  // details on how to interpret this field.
+  optional uint32 event_type_and_data_status = 2;
+
+  // Advertising address type
+  optional AddressType address_type = 3;
+
+  // Advertising device address
+  optional bytes address = 4;
+
+  // Transmit (Tx) power in dBm. Typical values are [-127, 20].
+  optional int32 tx_power = 5;
+
+  // RSSI in dBm. Typical values are [-127, 20].
+  optional int32 rssi = 6;
+
+  // BLE advertisement data payload.
+  optional bytes data = 7;
+
+  optional int32 data_length = 8;
+}
diff --git a/apps/nearby/location/lbs/contexthub/nanoapps/proto/filter.proto b/apps/nearby/location/lbs/contexthub/nanoapps/proto/filter.proto
new file mode 100644
index 0000000..f051f54
--- /dev/null
+++ b/apps/nearby/location/lbs/contexthub/nanoapps/proto/filter.proto
@@ -0,0 +1,25 @@
+syntax = "proto2";
+
+package lbs;
+
+option java_multiple_files = true;
+option java_package = "com.google.location.lbs.contexthub";
+option java_outer_classname = "FilterNano";
+
+// Types of messages between host and CHRE.
+enum FilterMessageType {
+  // 0 is reserved to avoid misinterpreting corrupted data.
+  MESSAGE_UNDEFINED = 0;
+  // Success response to host operation (from CHRE).
+  MESSAGE_SUCCESS = 1;
+  // Failure response to host operation (from CHRE).
+  MESSAGE_FAILURE = 2;
+  // Message from host to CHRE to set Filters.
+  MESSAGE_FILTERS = 3;
+  // Notification from CHRE to host with FilterResults as payload.
+  MESSAGE_FILTER_RESULTS = 4;
+  // Config the filtering, including start/stop filtering.
+  MESSAGE_CONFIG = 5;
+  // Message from host to CHRE to set Filter extensions.
+  MESSAGE_FILTER_EXTENSIONS = 6;
+}
diff --git a/apps/nearby/third_party/contexthub/chre/util/dynamic_vector_base.cc b/apps/nearby/third_party/contexthub/chre/util/dynamic_vector_base.cc
new file mode 100644
index 0000000..ee7570b
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/dynamic_vector_base.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/dynamic_vector_base.h"
+
+#include <cstdint>
+#include <cstring>
+
+#include "chre/util/container_support.h"
+
+namespace chre {
+
+DynamicVectorBase::DynamicVectorBase(DynamicVectorBase &&other)
+    : mData(other.mData), mSize(other.mSize), mCapacity(other.mCapacity) {
+  other.mData = nullptr;
+  other.mSize = 0;
+  other.mCapacity = 0;
+}
+
+bool DynamicVectorBase::doReserve(size_t newCapacity, size_t elementSize) {
+  bool success = (newCapacity <= mCapacity);
+  if (!success) {
+    void *newData = memoryAlloc(newCapacity * elementSize);
+    if (newData != nullptr) {
+      memcpy(newData, mData, mSize * elementSize);
+      memoryFree(mData);
+      mData = newData;
+      mCapacity = newCapacity;
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+bool DynamicVectorBase::doPrepareForPush(size_t elementSize) {
+  return doReserve(getNextGrowthCapacity(), elementSize);
+}
+
+size_t DynamicVectorBase::getNextGrowthCapacity() const {
+  size_t newCapacity;
+  if (mCapacity == 0) {
+    newCapacity = 1;
+  } else if (mSize == mCapacity) {
+    newCapacity = mCapacity * 2;
+  } else {
+    newCapacity = mCapacity;
+  }
+
+  return newCapacity;
+}
+
+void DynamicVectorBase::doErase(size_t index, size_t elementSize) {
+  mSize--;
+  size_t moveAmount = (mSize - index) * elementSize;
+  memmove(static_cast<uint8_t *>(mData) + (index * elementSize),
+          static_cast<uint8_t *>(mData) + ((index + 1) * elementSize),
+          moveAmount);
+}
+
+bool DynamicVectorBase::doPushBack(const void *element, size_t elementSize) {
+  bool spaceAvailable = doPrepareForPush(elementSize);
+  if (spaceAvailable) {
+    memcpy(static_cast<uint8_t *>(mData) + (mSize * elementSize), element,
+           elementSize);
+    mSize++;
+  }
+
+  return spaceAvailable;
+}
+
+}  // namespace chre
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h
new file mode 100644
index 0000000..0b5fe01
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector.h
@@ -0,0 +1,374 @@
+/*
+ * 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_UTIL_DYNAMIC_VECTOR_H_
+#define CHRE_UTIL_DYNAMIC_VECTOR_H_
+
+#include <type_traits>
+
+#include "chre/util/dynamic_vector_base.h"
+
+namespace chre {
+
+/**
+ * A container for storing a sequential array of elements. This container
+ * resizes dynamically using heap allocations.
+ */
+template <typename ElementType>
+class DynamicVector : private DynamicVectorBase {
+ public:
+  /**
+   * Random-access iterator that points to some element in the container.
+   */
+  typedef ElementType *iterator;
+  typedef const ElementType *const_iterator;
+  typedef ElementType value_type;
+  typedef size_t size_type;
+
+  /**
+   * Default-constructs a dynamic vector.
+   */
+  DynamicVector();
+
+  /**
+   * Move-constructs a dynamic vector from another. The other dynamic vector is
+   * left in an empty state.
+   *
+   * @param other The other vector to move from.
+   */
+  DynamicVector(DynamicVector<ElementType> &&other);
+
+  /**
+   * Move-constructs a dynamic vector from another. The other dynamic vector is
+   * left in an empty state.
+   */
+  DynamicVector &operator=(DynamicVector<ElementType> &&other);
+
+  /**
+   * Destructs the objects and releases the memory owned by the vector.
+   */
+  ~DynamicVector();
+
+  /**
+   * Removes all elements from the vector, but does not change the capacity.
+   * All iterators and references are invalidated.
+   */
+  void clear();
+
+  /**
+   * Returns a pointer to the underlying buffer. Note that this should not be
+   * considered to be persistent as the vector will be moved and resized
+   * automatically.
+   *
+   * @return The pointer to the underlying buffer.
+   */
+  ElementType *data();
+
+  /**
+   * Returns a const pointer to the underlying buffer. Note that this should not
+   * be considered to be persistent as the vector will be moved and resized
+   * automatically.
+   *
+   * @return The const pointer to the underlying buffer.
+   */
+  const ElementType *data() const;
+
+  /**
+   * Returns the current number of elements in the vector.
+   *
+   * @return The number of elements in the vector.
+   */
+  size_type size() const;
+
+  /**
+   * Returns the maximum number of elements that can be stored in this vector
+   * without a resize operation.
+   *
+   * @return The capacity of the vector.
+   */
+  size_type capacity() const;
+
+  /**
+   * Determines whether the vector is empty or not.
+   *
+   * @return true if the vector is empty.
+   */
+  bool empty() const;
+
+  /**
+   * Erases the last element in the vector. Invalid to call on an empty vector.
+   *
+   * Invalidates any references to back() and end()/cend().
+   */
+  void pop_back();
+
+  /**
+   * Copy- or move-constructs an element onto the back of the vector. If the
+   * vector requires a resize and that allocation fails this function will
+   * return false. All iterators and references are invalidated if the container
+   * has been resized. Otherwise, only the past-the-end iterator is invalidated.
+   *
+   * @param The element to push onto the vector.
+   * @return true if the element was pushed successfully.
+   */
+  bool push_back(const ElementType &element);
+  bool push_back(ElementType &&element);
+
+  /**
+   * Constructs an element onto the back of the vector. All iterators and
+   * references are invalidated if the container has been resized. Otherwise,
+   * only the past-the-end iterator is invalidated.
+   *
+   * @param The arguments to the constructor
+   * @return true if the element is constructed successfully.
+   */
+  template <typename... Args>
+  bool emplace_back(Args &&...args);
+
+  /**
+   * Obtains an element of the vector given an index. It is illegal to index
+   * this vector out of bounds and the user of the API must check the size()
+   * function prior to indexing this vector to ensure that they will not read
+   * out of bounds.
+   *
+   * @param The index of the element.
+   * @return The element.
+   */
+  ElementType &operator[](size_type index);
+
+  /**
+   * Obtains a const element of the vector given an index. It is illegal to
+   * index this vector out of bounds and the user of the API must check the
+   * size() function prior to indexing this vector to ensure that they will not
+   * read out of bounds.
+   *
+   * @param The index of the element.
+   * @return The element.
+   */
+  const ElementType &operator[](size_type index) const;
+
+  /**
+   * Compares two vectors for equality. It will compare the vector sizes and
+   * return false if those are different; if not, it will compare the contents
+   * of the vectors element-by-element. The operator == should be defined and
+   * meaningful for the vector's element type.
+   *
+   * @param Right-hand side vector to compared with.
+   * @return true if two vectors are equal, false otherwise.
+   */
+  bool operator==(const DynamicVector<ElementType> &other) const;
+
+  /**
+   * Resizes the vector to a new capacity returning true if allocation was
+   * successful. If the new capacity is smaller than the current capacity,
+   * the operation is a no-op and true is returned. If a memory allocation
+   * fails, the contents of the vector are not modified and false is returned.
+   * This is intended to be similar to the reserve function of the std::vector.
+   * All iterators and references are invalidated unless the container did not
+   * resize.
+   *
+   * @param newCapacity The new capacity of the vector.
+   * @return true if the resize operation was successful.
+   */
+  bool reserve(size_type newCapacity);
+
+  /**
+   * Resizes the vector to a new size. If the new capacity is smaller than the
+   * current size, the extraneous objects at the end are destructed. If the new
+   * capacity is larger than the current size, the new objects are
+   * default-constructed.
+   *
+   * @param newSize The new size of the vector.
+   * @return true if the resize operation was successful.
+   */
+  bool resize(size_type newSize);
+
+  /**
+   * Inserts an element into the vector at a given index. If a resize of the
+   * vector is required and the allocation fails, false will be returned. This
+   * will shift all vector elements after the given index one position backward
+   * in the list. The supplied index must be <= the size of the vector. It is
+   * not possible to have a sparse list of items. If the index is > the current
+   * size of the vector, false will be returned. All iterators and references
+   * to and after the indexed element are invalidated. Iterators and references
+   * to before the indexed elements are unaffected if the container did not
+   * resize.
+   *
+   * @param index The index to insert an element at.
+   * @param element The element to insert.
+   * @return Whether or not the insert operation was successful.
+   */
+  bool insert(size_type index, const ElementType &element);
+  bool insert(size_type index, ElementType &&element);
+
+  /**
+   * Removes an element from the vector given an index. All elements after the
+   * indexed one are moved forward one position. The destructor is invoked on
+   * on the invalid item left at the end of the vector. The index passed in
+   * must be less than the size() of the vector. If the index is greater than or
+   * equal to the size no operation is performed. All iterators and references
+   * to and after the indexed element are invalidated.
+   *
+   * @param index The index to remove an element at.
+   */
+  void erase(size_type index);
+
+  /**
+   * Searches the vector for an element.
+   *
+   * @param element The element to comare against.
+   * @return The index of the element found. If the return is equal to size()
+   *         then the element was not found.
+   */
+  size_type find(const ElementType &element) const;
+
+  /**
+   * Swaps the location of two elements stored in the vector. The indices
+   * passed in must be less than the size() of the vector. If the index is
+   * greater than or equal to the size, no operation is performed. All
+   * iterators and references to these two indexed elements are invalidated.
+   *
+   * @param index0 The index of the first element
+   * @param index1 The index of the second element
+   */
+  void swap(size_type index0, size_type index1);
+
+  /**
+   * Returns a reference to the first element in the vector. It is illegal to
+   * call this on an empty vector.
+   *
+   * @return The first element in the vector.
+   */
+  ElementType &front();
+
+  /**
+   * Returns a const reference to the first element in the vector. It is illegal
+   * to call this on an empty vector.
+   *
+   * @return The first element in the vector.
+   */
+  const ElementType &front() const;
+
+  /**
+   * Returns a reference to the last element in the vector. It is illegal to
+   * call this on an empty vector.
+   *
+   * @return The last element in the vector.
+   */
+  ElementType &back();
+
+  /**
+   * Returns a const reference to the last element in the vector. It is illegal
+   * to call this on an empty vector.
+   *
+   * @return The last element in the vector.
+   */
+  const ElementType &back() const;
+
+  /**
+   * Prepares a vector to push a minimum of one element onto the back. The
+   * vector may be resized if required. The capacity of the vector increases
+   * with the growth policy of this vector (doubles for each resize for now).
+   *
+   * @return Whether or not the resize was successful.
+   */
+  bool prepareForPush();
+
+  /**
+   * @return A random-access iterator to the beginning.
+   */
+  typename DynamicVector<ElementType>::iterator begin();
+  typename DynamicVector<ElementType>::const_iterator begin() const;
+  typename DynamicVector<ElementType>::const_iterator cbegin() const;
+
+  /**
+   * @return A random-access iterator to the end.
+   */
+  typename DynamicVector<ElementType>::iterator end();
+  typename DynamicVector<ElementType>::const_iterator end() const;
+  typename DynamicVector<ElementType>::const_iterator cend() const;
+
+ private:
+  /**
+   * Prepares the vector for insertion - upon successful return, the memory at
+   * the given index will be allocated but uninitialized
+   *
+   * @param index
+   * @return true
+   */
+  bool prepareInsert(size_t index);
+
+  /**
+   * Performs the reserve operation for DynamicVector when ElementType is a
+   * trivial type. See {@link DynamicVector::reserve} for the rest of the
+   * details.
+   */
+  bool doReserve(size_type newCapacity, std::true_type);
+
+  /**
+   * Performs the reserve operation for DynamicVector when ElementType is a
+   * non-trivial type. See {@link DynamicVector::reserve} for the rest of the
+   * details.
+   */
+  bool doReserve(size_type newCapacity, std::false_type);
+
+  /**
+   * Performs the prepare for push operation for DynamicVector when ElementType
+   * is a trivial type. See {@link DynamicVector::prepareForPush} for the rest
+   * of the details.
+   */
+  bool doPrepareForPush(std::true_type);
+
+  /**
+   * Performs the prepare for push operation for DynamicVector when ElementType
+   * is a non-trivial type. See {@link DynamicVector::prepareForPush} for the
+   * rest of the details.
+   */
+  bool doPrepareForPush(std::false_type);
+
+  /**
+   * Performs the erase operation for DynamicVector when ElementType is a
+   * trivial type. See {@link DynamicVector::erase} for the rest of the details.
+   */
+  void doErase(size_type index, std::true_type);
+
+  /**
+   * Performs the erase operation for DynamicVector when ElementType is a
+   * non-trivial type. See {@link DynamicVector::erase} for the rest of the
+   * details.
+   */
+  void doErase(size_type index, std::false_type);
+
+  /**
+   * Performs the push back operation for DynamicVector when ElementType is a
+   * trivial type. See {@link DynamicVector::push_back} for the rest of the
+   * details.
+   */
+  bool doPushBack(const ElementType &element, std::true_type);
+
+  /**
+   * Performs the push back operation for DynamicVector when ElementType is a
+   * non-trivial type. See {@link DynamicVector::push_back} for the rest of the
+   * details.
+   */
+  bool doPushBack(const ElementType &element, std::false_type);
+};
+
+}  // namespace chre
+
+#include "chre/util/dynamic_vector_impl.h"
+
+#endif  // CHRE_UTIL_DYNAMIC_VECTOR_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector_base.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector_base.h
new file mode 100644
index 0000000..6e62e1c
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector_base.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_UTIL_DYNAMIC_VECTOR_BASE_H_
+#define CHRE_UTIL_DYNAMIC_VECTOR_BASE_H_
+
+#include <cstddef>
+
+#include "chre/util/non_copyable.h"
+
+namespace chre {
+
+class DynamicVectorBase : public NonCopyable {
+ protected:
+  DynamicVectorBase() = default;
+
+  /**
+   * Move-constructs a dynamic vector from another. The other dynamic vector is
+   * left in an empty state.
+   *
+   * @param other The other vector to move from.
+   */
+  DynamicVectorBase(DynamicVectorBase &&other);
+
+  /**
+   * Performs a reserve operation for DynamicVector when the underlying type is
+   * trivial. See {@link DynamicVector::reserve} for further details.
+   *
+   * @param elementSize The size of the element used to determine the effective
+   *        size of the underlying data.
+   */
+  bool doReserve(size_t newCapacity, size_t elementSize);
+
+  /**
+   * Performs a prepare for push operation for DynamicVector when the underlying
+   * type is trivial. See {@link DynamicVector::prepareForPush} for further
+   * details.
+   *
+   * @param elementSize The size of the element used to determine the effective
+   *        size of the underlying data.
+   */
+  bool doPrepareForPush(size_t elementSize);
+
+  /**
+   * @return the next size of allocation to perform when growing the size of
+   *         this vector. If no growth is required (mSize is less than
+   *         mCapacity), the current capacity is returned.
+   */
+  size_t getNextGrowthCapacity() const;
+
+  /*
+   * Performs an erase operation for DynamicVector when the underlying type is
+   * trivial. See {@link DynamicVector::erase} for further details.
+   *
+   * @param elementSize The size of the element used to determine the effective
+   *        size of the underlying data.
+   */
+  void doErase(size_t index, size_t elementSize);
+
+  /**
+   * Performs a push back operation for DynamicVector when the underlying type
+   * is trivial. See {@link DynamicVector::push_back} for further details.
+   *
+   * @param elementSize The size of the element used to determine the effective
+   *        size of the underlying data.
+   */
+  bool doPushBack(const void *element, size_t elementSize);
+
+  //! A pointer to the underlying data buffer.
+  void *mData = nullptr;
+
+  //! The current size of the vector, as in the number of elements stored.
+  size_t mSize = 0;
+
+  //! The current capacity of the vector, as in the maximum number of elements
+  //! that can be stored.
+  size_t mCapacity = 0;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_DYNAMIC_VECTOR_BASE_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector_impl.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector_impl.h
new file mode 100644
index 0000000..f658b5e
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/dynamic_vector_impl.h
@@ -0,0 +1,394 @@
+/*
+ * 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_UTIL_DYNAMIC_VECTOR_IMPL_H_
+#define CHRE_UTIL_DYNAMIC_VECTOR_IMPL_H_
+
+#include "chre/util/dynamic_vector.h"
+
+#include <memory>
+#include <new>
+#include <utility>
+
+#include "chre/util/container_support.h"
+#include "chre/util/memory.h"
+
+namespace chre {
+
+template <typename ElementType>
+DynamicVector<ElementType>::DynamicVector() {}
+
+template <typename ElementType>
+DynamicVector<ElementType>::DynamicVector(DynamicVector<ElementType> &&other)
+    : DynamicVectorBase(std::move(other)) {}
+
+template <typename ElementType>
+DynamicVector<ElementType>::~DynamicVector() {
+  clear();
+  memoryFree(data());
+}
+
+template <typename ElementType>
+DynamicVector<ElementType> &DynamicVector<ElementType>::operator=(
+    DynamicVector<ElementType> &&other) {
+  if (this != &other) {
+    this->~DynamicVector();
+    mData = other.mData;
+    mSize = other.mSize;
+    mCapacity = other.mCapacity;
+
+    other.mData = nullptr;
+    other.mSize = 0;
+    other.mCapacity = 0;
+  }
+
+  return *this;
+}
+
+template <typename ElementType>
+void DynamicVector<ElementType>::clear() {
+  destroy(data(), mSize);
+  mSize = 0;
+}
+
+template <typename ElementType>
+ElementType *DynamicVector<ElementType>::data() {
+  return static_cast<ElementType *>(mData);
+}
+
+template <typename ElementType>
+const ElementType *DynamicVector<ElementType>::data() const {
+  return static_cast<const ElementType *>(mData);
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::size_type
+DynamicVector<ElementType>::size() const {
+  return mSize;
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::size_type
+DynamicVector<ElementType>::capacity() const {
+  return mCapacity;
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::empty() const {
+  return (mSize == 0);
+}
+
+template <typename ElementType>
+void DynamicVector<ElementType>::pop_back() {
+  CHRE_ASSERT(!empty());
+  erase(mSize - 1);
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::push_back(const ElementType &element) {
+  return doPushBack(element, typename std::is_trivial<ElementType>::type());
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::doPushBack(const ElementType &element,
+                                            std::true_type) {
+  return DynamicVectorBase::doPushBack(static_cast<const void *>(&element),
+                                       sizeof(ElementType));
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::doPushBack(const ElementType &element,
+                                            std::false_type) {
+  bool spaceAvailable = prepareForPush();
+  if (spaceAvailable) {
+    new (&data()[mSize++]) ElementType(element);
+  }
+
+  return spaceAvailable;
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::push_back(ElementType &&element) {
+  bool spaceAvailable = prepareForPush();
+  if (spaceAvailable) {
+    new (&data()[mSize++]) ElementType(std::move(element));
+  }
+
+  return spaceAvailable;
+}
+
+template <typename ElementType>
+template <typename... Args>
+bool DynamicVector<ElementType>::emplace_back(Args &&...args) {
+  bool spaceAvailable = prepareForPush();
+  if (spaceAvailable) {
+    new (&data()[mSize++]) ElementType(std::forward<Args>(args)...);
+  }
+
+  return spaceAvailable;
+}
+
+template <typename ElementType>
+ElementType &DynamicVector<ElementType>::operator[](size_type index) {
+  CHRE_ASSERT(index < mSize);
+  return data()[index];
+}
+
+template <typename ElementType>
+const ElementType &DynamicVector<ElementType>::operator[](
+    size_type index) const {
+  CHRE_ASSERT(index < mSize);
+  return data()[index];
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::operator==(
+    const DynamicVector<ElementType> &other) const {
+  bool vectorsAreEqual = (mSize == other.mSize);
+  if (vectorsAreEqual) {
+    for (size_type i = 0; i < mSize; i++) {
+      if (!(data()[i] == other.data()[i])) {
+        vectorsAreEqual = false;
+        break;
+      }
+    }
+  }
+
+  return vectorsAreEqual;
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::reserve(size_type newCapacity) {
+  return doReserve(newCapacity, typename std::is_trivial<ElementType>::type());
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::doReserve(size_type newCapacity,
+                                           std::true_type) {
+  return DynamicVectorBase::doReserve(newCapacity, sizeof(ElementType));
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::doReserve(size_type newCapacity,
+                                           std::false_type) {
+  bool success = (newCapacity <= mCapacity);
+  if (!success) {
+    ElementType *newData = static_cast<ElementType *>(
+        memoryAlloc(newCapacity * sizeof(ElementType)));
+    if (newData != nullptr) {
+      uninitializedMoveOrCopy(data(), mSize, newData);
+      destroy(data(), mSize);
+      memoryFree(data());
+      mData = newData;
+      mCapacity = newCapacity;
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::resize(size_type newSize) {
+  // Remove elements from the back to minimize move operations.
+  while (mSize > newSize) {
+    pop_back();
+  }
+
+  bool success = reserve(newSize);
+  if (success) {
+    while (mSize < newSize) {
+      new (&data()[mSize++]) ElementType();
+    }
+  }
+
+  return success;
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::insert(size_type index,
+                                        const ElementType &element) {
+  bool inserted = prepareInsert(index);
+  if (inserted) {
+    new (&data()[index]) ElementType(element);
+  }
+  return inserted;
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::insert(size_type index,
+                                        ElementType &&element) {
+  bool inserted = prepareInsert(index);
+  if (inserted) {
+    new (&data()[index]) ElementType(std::move(element));
+  }
+  return inserted;
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::prepareInsert(size_type index) {
+  // Insertions are not allowed to create a sparse array.
+  CHRE_ASSERT(index <= mSize);
+
+  // This can be optimized in the case where we need to grow the vector to
+  // do the shift when transferring the values from the old array to the new.
+  bool readyForInsert = (index <= mSize && prepareForPush());
+  if (readyForInsert) {
+    // If we aren't simply appending the new object, create an opening where
+    // we'll insert it
+    if (index < mSize) {
+      // Make a duplicate of the last item in the slot where we're growing
+      uninitializedMoveOrCopy(&data()[mSize - 1], 1, &data()[mSize]);
+      // Shift all elements starting at index towards the end
+      for (size_type i = mSize - 1; i > index; i--) {
+        moveOrCopyAssign(data()[i], data()[i - 1]);
+      }
+
+      data()[index].~ElementType();
+    }
+
+    mSize++;
+  }
+
+  return readyForInsert;
+}
+
+template <typename ElementType>
+void DynamicVector<ElementType>::erase(size_type index) {
+  CHRE_ASSERT(index < mSize);
+  doErase(index, typename std::is_trivial<ElementType>::type());
+}
+
+template <typename ElementType>
+void DynamicVector<ElementType>::doErase(size_type index, std::true_type) {
+  DynamicVectorBase::doErase(index, sizeof(ElementType));
+}
+
+template <typename ElementType>
+void DynamicVector<ElementType>::doErase(size_type index, std::false_type) {
+  mSize--;
+  for (size_type i = index; i < mSize; i++) {
+    moveOrCopyAssign(data()[i], data()[i + 1]);
+  }
+
+  data()[mSize].~ElementType();
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::size_type DynamicVector<ElementType>::find(
+    const ElementType &element) const {
+  // Consider adding iterator support and making this a free function.
+  size_type i;
+  for (i = 0; i < size(); i++) {
+    if (data()[i] == element) {
+      break;
+    }
+  }
+
+  return i;
+}
+
+template <typename ElementType>
+void DynamicVector<ElementType>::swap(size_type index0, size_type index1) {
+  CHRE_ASSERT(index0 < mSize && index1 < mSize);
+  if (index0 != index1) {
+    typename std::aligned_storage<sizeof(ElementType),
+                                  alignof(ElementType)>::type tempStorage;
+    ElementType &temp = *reinterpret_cast<ElementType *>(&tempStorage);
+    uninitializedMoveOrCopy(&data()[index0], 1, &temp);
+    moveOrCopyAssign(data()[index0], data()[index1]);
+    moveOrCopyAssign(data()[index1], temp);
+  }
+}
+
+template <typename ElementType>
+ElementType &DynamicVector<ElementType>::front() {
+  CHRE_ASSERT(mSize > 0);
+  return data()[0];
+}
+
+template <typename ElementType>
+const ElementType &DynamicVector<ElementType>::front() const {
+  CHRE_ASSERT(mSize > 0);
+  return data()[0];
+}
+
+template <typename ElementType>
+ElementType &DynamicVector<ElementType>::back() {
+  CHRE_ASSERT(mSize > 0);
+  return data()[mSize - 1];
+}
+
+template <typename ElementType>
+const ElementType &DynamicVector<ElementType>::back() const {
+  CHRE_ASSERT(mSize > 0);
+  return data()[mSize - 1];
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::prepareForPush() {
+  return doPrepareForPush(typename std::is_trivial<ElementType>::type());
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::doPrepareForPush(std::true_type) {
+  return DynamicVectorBase::doPrepareForPush(sizeof(ElementType));
+}
+
+template <typename ElementType>
+bool DynamicVector<ElementType>::doPrepareForPush(std::false_type) {
+  return reserve(getNextGrowthCapacity());
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::iterator
+DynamicVector<ElementType>::begin() {
+  return data();
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::iterator
+DynamicVector<ElementType>::end() {
+  return (data() + mSize);
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::const_iterator
+DynamicVector<ElementType>::begin() const {
+  return cbegin();
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::const_iterator
+DynamicVector<ElementType>::end() const {
+  return cend();
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::const_iterator
+DynamicVector<ElementType>::cbegin() const {
+  return data();
+}
+
+template <typename ElementType>
+typename DynamicVector<ElementType>::const_iterator
+DynamicVector<ElementType>::cend() const {
+  return (data() + mSize);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_DYNAMIC_VECTOR_IMPL_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/macros.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/macros.h
new file mode 100644
index 0000000..475cd45
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/macros.h
@@ -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.
+ */
+
+#ifndef CHRE_UTIL_MACROS_H_
+#define CHRE_UTIL_MACROS_H_
+
+#ifndef UNUSED_VAR
+#define UNUSED_VAR(var) ((void)(var))
+#endif
+
+/**
+ * Obtains the number of elements in a C-style array.
+ */
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#endif
+
+#ifndef ARRAY_END
+#define ARRAY_END(array) (array + ARRAY_SIZE(array))
+#endif
+
+/** Determines if the provided bit is set in the provided value. */
+#ifndef IS_BIT_SET
+#define IS_BIT_SET(value, bit) (((value) & (bit)) == (bit))
+#endif
+
+/**
+ * Performs macro expansion then converts the value into a string literal
+ */
+#ifndef STRINGIFY
+#define STRINGIFY(x) STRINGIFY2(x)
+#define STRINGIFY2(x) #x
+#endif
+
+/**
+ * Checks if a bitmask contains the specified value
+ */
+#ifndef BITMASK_HAS_VALUE
+#define BITMASK_HAS_VALUE(mask, value) ((mask & value) == value)
+#endif
+
+/**
+ * Min/max macros.
+ */
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+// Compiler-specific functionality
+#if defined(__clang__) || defined(__GNUC__)
+
+//! Exports a symbol so it is accessible outside the .so (symbols are hidden by
+//! default)
+#define DLL_EXPORT __attribute__((visibility("default")))
+
+//! Marks a symbol as weak, so that it may be overridden at link time
+#define WEAK_SYMBOL __attribute__((weak))
+
+#else
+
+#warning "Missing compiler-specific macros"
+
+#endif
+
+#endif  // CHRE_UTIL_MACROS_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/assert.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/assert.h
new file mode 100644
index 0000000..ddc7afe
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/assert.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_UTIL_NANOAPP_ASSERT_H_
+#define CHRE_UTIL_NANOAPP_ASSERT_H_
+
+/**
+ * @file
+ *
+ * Suppplies a CHRE_ASSERT macro for Nanoapps to use.
+ */
+
+#ifdef CHRE_IS_NANOAPP_BUILD
+
+#include "chre_api/chre.h"
+
+/**
+ * Provides the CHRE_ASSERT macro that uses chreAbort to abort the nanoapp upon
+ * failure.
+ *
+ * @param the condition to check for non-zero.
+ */
+#ifdef CHRE_ASSERTIONS_ENABLED
+#define CHRE_ASSERT(condition)                                       \
+  do {                                                               \
+    if (!(condition)) {                                              \
+      chreLog(CHRE_LOG_ERROR, "CHRE_ASSERT at %s:%d", CHRE_FILENAME, \
+              __LINE__);                                             \
+      chreAbort(UINT32_MAX);                                         \
+    }                                                                \
+  } while (0)
+#else
+#define CHRE_ASSERT(condition) ((void)(condition))
+#endif  // CHRE_ASSERTIONS_ENABLED
+
+#ifdef __cplusplus
+#define CHRE_ASSERT_NOT_NULL(ptr) CHRE_ASSERT((ptr) != nullptr)
+#else
+#define CHRE_ASSERT_NOT_NULL(ptr) CHRE_ASSERT((ptr) != NULL)
+#endif
+
+#ifdef GTEST
+// Mocks are not supported in standalone mode. Just skip the statement entirely.
+#define EXPECT_CHRE_ASSERT(statement)
+#endif  // GTEST
+
+#else
+// When compiling as a static nanoapp, use the platform implementation of
+// CHRE_ASSERT.
+#include "chre/platform/assert.h"
+#endif  // CHRE_IS_NANOAPP_BUILD
+
+#endif  // CHRE_UTIL_NANOAPP_ASSERT_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/callbacks.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/callbacks.h
new file mode 100644
index 0000000..a49eb08
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/callbacks.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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_CALLBACKS_H_
+#define CHRE_UTIL_CALLBACKS_H_
+
+#include <cstddef>
+
+namespace chre {
+
+/**
+ * Implementation of a chreMessageFreeFunction that frees the given message
+ * using the appropriate free function depending on whether this is included by
+ * a nanoapp or the CHRE framework. This should be used when no other work needs
+ * to be done after a nanoapp sends a message to the host.
+ *
+ * @see chreMessageFreeFunction
+ */
+void heapFreeMessageCallback(void *message, size_t messageSize);
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_CALLBACKS_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h
new file mode 100644
index 0000000..96dcb17
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/nanoapp/log.h
@@ -0,0 +1,139 @@
+/*
+ * 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_NANOAPP_LOG_H_
+#define CHRE_UTIL_NANOAPP_LOG_H_
+
+/**
+ * @file
+ * Logging macros for nanoapps. These macros allow injecting a LOG_TAG and
+ * compiling nanoapps with a minimum logging level (that is different than CHRE
+ * itself).
+ *
+ * The typical format for the LOG_TAG macro is: "[AppName]"
+ */
+#ifdef CHRE_IS_NANOAPP_BUILD
+
+#include "chre/util/log_common.h"
+#include "chre_api/chre.h"
+
+#ifndef NANOAPP_MINIMUM_LOG_LEVEL
+#error "NANOAPP_MINIMUM_LOG_LEVEL must be defined"
+#endif  // NANOAPP_MINIMUM_LOG_LEVEL
+
+/*
+ * Supply a stub implementation of the LOGx macros when the build is
+ * configured with a minimum logging level that is above the requested level.
+ * Otherwise just map into the chreLog function with the appropriate level.
+ */
+
+#define CHRE_LOG_TAG(level, tag, fmt, ...)         \
+  do {                                             \
+    CHRE_LOG_PREAMBLE                              \
+    chreLog(level, "%s " fmt, tag, ##__VA_ARGS__); \
+    CHRE_LOG_EPILOGUE                              \
+  } while (0)
+
+#if NANOAPP_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_ERROR
+#define LOGE_TAG(tag, fmt, ...) \
+  CHRE_LOG_TAG(CHRE_LOG_ERROR, tag, fmt, ##__VA_ARGS__)
+#else
+#define LOGE_TAG(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#endif
+#define LOGE(fmt, ...) LOGE_TAG(LOG_TAG, fmt, ##__VA_ARGS__)
+
+#if NANOAPP_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_WARN
+#define LOGW_TAG(tag, fmt, ...) \
+  CHRE_LOG_TAG(CHRE_LOG_WARN, tag, fmt, ##__VA_ARGS__)
+#else
+#define LOGW_TAG(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#endif
+#define LOGW(fmt, ...) LOGW_TAG(LOG_TAG, fmt, ##__VA_ARGS__)
+
+#if NANOAPP_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_INFO
+#define LOGI_TAG(tag, fmt, ...) \
+  CHRE_LOG_TAG(CHRE_LOG_INFO, tag, fmt, ##__VA_ARGS__)
+#else
+#define LOGI_TAG(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#endif
+#define LOGI(fmt, ...) LOGI_TAG(LOG_TAG, fmt, ##__VA_ARGS__)
+
+#if NANOAPP_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_DEBUG
+#define LOGD_TAG(tag, fmt, ...) \
+  CHRE_LOG_TAG(CHRE_LOG_DEBUG, tag, fmt, ##__VA_ARGS__)
+#else
+#define LOGD_TAG(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#endif
+#define LOGD(fmt, ...) LOGD_TAG(LOG_TAG, fmt, ##__VA_ARGS__)
+
+// Map LOGV to LOGD as CHRE doesn't support it yet.
+#if NANOAPP_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_VERBOSE
+#define LOGV_TAG(tag, fmt, ...) \
+  CHRE_LOG_TAG(CHRE_LOG_DEBUG, tag, fmt, ##__VA_ARGS__)
+#else
+#define LOGV_TAG(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#endif
+#define LOGV(fmt, ...) LOGV_TAG(LOG_TAG, fmt, ##__VA_ARGS__)
+
+#else
+
+// For static nanoapps, reroute to the internal framework logging macro so that
+// things are consistent across all the source code statically linked into the
+// binary that contains the framework.
+// This loses out on LOG_TAG prepending, and follows CHRE_MINIMUM_LOG_LEVEL
+// rather than NANOAPP_MINIMUM_LOG_LEVEL, but means that anything using the
+// container support library will have a consistent definition regardless of
+// whether it's used in framework code or static nanoapp code.
+#include "chre/platform/log.h"
+
+#endif  // CHRE_IS_NANOAPP_BUILD
+
+// Use this macro when including privacy-sensitive information like the user's
+// location.
+#ifdef LOG_INCLUDE_SENSITIVE_INFO
+#define LOGE_SENSITIVE_INFO LOGE
+#define LOGE_TAG_SENSITIVE_INFO LOGE_TAG
+#define LOGW_SENSITIVE_INFO LOGW
+#define LOGW_TAG_SENSITIVE_INFO LOGW_TAG
+#define LOGI_SENSITIVE_INFO LOGI
+#define LOGI_TAG_SENSITIVE_INFO LOGI_TAG
+#define LOGD_SENSITIVE_INFO LOGD
+#define LOGD_TAG_SENSITIVE_INFO LOGD_TAG
+#define LOGV_SENSITIVE_INFO LOGV
+#define LOGV_TAG_SENSITIVE_INFO LOGV_TAG
+#else
+#define LOGE_SENSITIVE_INFO(fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGE_TAG_SENSITIVE_INFO(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGW_SENSITIVE_INFO(fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGW_TAG_SENSITIVE_INFO(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGI_SENSITIVE_INFO(fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGI_TAG_SENSITIVE_INFO(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGD_SENSITIVE_INFO(fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGD_TAG_SENSITIVE_INFO(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGV_SENSITIVE_INFO(fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#define LOGV_TAG_SENSITIVE_INFO(tag, fmt, ...) CHRE_LOG_NULL(fmt, ##__VA_ARGS__)
+#endif
+
+// Convenience macro that helps with suppressing double promotion warnings when
+// passing a float to chreDebugDumpLog().
+#define CHRE_DEBUG_DUMP_LOG(fmt, ...)     \
+  do {                                    \
+    CHRE_LOG_PREAMBLE                     \
+    chreDebugDumpLog(fmt, ##__VA_ARGS__); \
+    CHRE_LOG_EPILOGUE                     \
+  } while (0)
+
+#endif  // CHRE_UTIL_NANOAPP_LOG_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/non_copyable.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/non_copyable.h
new file mode 100644
index 0000000..27419ba
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/non_copyable.h
@@ -0,0 +1,35 @@
+/*
+ * 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_UTIL_NON_COPYABLE_H_
+#define CHRE_UTIL_NON_COPYABLE_H_
+
+namespace chre {
+
+/**
+ * Marks a class as NonCopyable by deleting the copy constructor and
+ * copy operator.
+ */
+class NonCopyable {
+ public:
+  NonCopyable() = default;
+  NonCopyable(const NonCopyable &) = delete;
+  NonCopyable &operator=(const NonCopyable &) = delete;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_NON_COPYABLE_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/optional.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/optional.h
new file mode 100644
index 0000000..4437b73
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/optional.h
@@ -0,0 +1,185 @@
+/*
+ * 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 UTIL_CHRE_OPTIONAL_H_
+#define UTIL_CHRE_OPTIONAL_H_
+
+#include <type_traits>
+
+namespace chre {
+
+/**
+ * This container keeps track of an optional object. The container is similar to
+ * std::optional introduced in C++17.
+ */
+template <typename ObjectType>
+class Optional {
+ public:
+  // Per the standard, a program that instantiates template optional for a
+  // reference type is ill-formed
+  static_assert(!std::is_reference<ObjectType>::value,
+                "Optional references are not allowed");
+
+  /**
+   * Default constructs the optional object with no initial value.
+   */
+  Optional() = default;
+
+  /**
+   * Default copy constructor.
+   *
+   * @param object The object to copy construct from.
+   */
+  Optional(const Optional<ObjectType> &object) = default;
+
+  /**
+   * Default copy constructor.
+   *
+   * @param object The object to copy construct from.
+   */
+  Optional(Optional<ObjectType> &object) = default;
+
+  /**
+   * Constructs an optional instance with an initial value.
+   *
+   * @param object The initial value of the object.
+   */
+  Optional(const ObjectType &object);
+
+  /**
+   * Constructs an optional instance with an initial value by moving it.
+   *
+   * @param object The instance of the initial object to take ownership of.
+   */
+  Optional(ObjectType &&object);
+
+  /**
+   * Destructs the object. Calls through reset() to destroy the contained
+   * object before destructing this container.
+   */
+  ~Optional();
+
+  /**
+   * @return Returns true if this container holds an object
+   */
+  bool has_value() const;
+
+  /**
+   * Destroys any contained object, and marks this Optional as empty (i.e.
+   * has_value() will return false after this function returns)
+   */
+  void reset();
+
+  /**
+   * Gets a reference to the contained object. Does not check that this optional
+   * contains a value, so this object will be uninitialized if has_value() is
+   * false.
+   */
+  ObjectType &value();
+  const ObjectType &value() const;
+
+  /**
+   * Performs a move assignment operation to the underlying object managed by
+   * this container.
+   *
+   * @param other The other object to move from.
+   * @return Returns a reference to this object.
+   */
+  Optional<ObjectType> &operator=(ObjectType &&other);
+
+  /**
+   * Performs a move assignment from one optional to another. Note that the
+   * other object still holds a value, but it is left in the moved-from state
+   * (as is the case in std::optional).
+   *
+   * @param other The other object to move.
+   * @return Returns a reference to this object.
+   */
+  Optional<ObjectType> &operator=(Optional<ObjectType> &&other);
+
+  /**
+   * Performs a copy assignment operation to the underlying object managed by
+   * this container.
+   *
+   * @param other The other object to copy from.
+   * @return Returns a reference to this object.
+   */
+  Optional<ObjectType> &operator=(const ObjectType &other);
+
+  /**
+   * Performs a copy assignment from one optional to another.
+   *
+   * @param other The other object to copy.
+   * @return Returns a reference to this object.
+   */
+  Optional<ObjectType> &operator=(const Optional<ObjectType> &other);
+
+  /**
+   * Obtains a reference to the underlying object managed by this container.
+   * The behavior of this is undefined if has_value() returns false.
+   *
+   * @return Returns a reference to the underlying object tracked by this
+   *         container.
+   */
+  ObjectType &operator*();
+
+  /**
+   * Obtains a const reference to the underlying object managed by this
+   * container. The behavior of this is undefined if has_value() returns false.
+   *
+   * @return Returns a const reference to the underlying object tracked by this
+   *         container.
+   */
+  const ObjectType &operator*() const;
+
+  /**
+   * Obtains a pointer to the underlying object managed by this container. The
+   * object may not be well-formed if has_value() returns false.
+   *
+   * @return Returns a pointer to the underlying object tracked by this
+   *         container.
+   */
+  ObjectType *operator->();
+
+  /**
+   * Obtains a const pointer to the underlying object managed by this container.
+   * The object may not be well-formed if has_value() returns false.
+   *
+   * @return Returns a const pointer to the underlying object tracked by this
+   *         container.
+   */
+  const ObjectType *operator->() const;
+
+ private:
+  //! The optional object being tracked by this container.
+  typename std::aligned_storage<sizeof(ObjectType), alignof(ObjectType)>::type
+      mObject;
+
+  //! Whether or not the object is set.
+  bool mHasValue = false;
+
+  ObjectType &object();
+  const ObjectType &object() const;
+
+  ObjectType *objectAddr();
+  const ObjectType *objectAddr() const;
+};
+
+}  // namespace chre
+
+#include "chre/util/optional_impl.h"
+
+#endif  // UTIL_CHRE_OPTIONAL_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/optional_impl.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/optional_impl.h
new file mode 100644
index 0000000..cc9834d
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/optional_impl.h
@@ -0,0 +1,167 @@
+/*
+ * 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 UTIL_CHRE_OPTIONAL_IMPL_H_
+#define UTIL_CHRE_OPTIONAL_IMPL_H_
+
+#include <new>
+#include <utility>
+
+#include "chre/util/optional.h"
+
+namespace chre {
+
+template <typename ObjectType>
+Optional<ObjectType>::Optional(const ObjectType &object) {
+  new (objectAddr()) ObjectType(object);
+  mHasValue = true;
+}
+
+template <typename ObjectType>
+Optional<ObjectType>::Optional(ObjectType &&object) {
+  new (objectAddr()) ObjectType(std::move(object));
+  mHasValue = true;
+}
+
+template <typename ObjectType>
+Optional<ObjectType>::~Optional() {
+  reset();
+}
+
+template <typename ObjectType>
+bool Optional<ObjectType>::has_value() const {
+  return mHasValue;
+}
+
+template <typename ObjectType>
+void Optional<ObjectType>::reset() {
+  if (mHasValue) {
+    object().~ObjectType();
+    mHasValue = false;
+  }
+}
+
+template <typename ObjectType>
+ObjectType &Optional<ObjectType>::value() {
+  return object();
+}
+
+template <typename ObjectType>
+const ObjectType &Optional<ObjectType>::value() const {
+  return object();
+}
+
+template <typename ObjectType>
+Optional<ObjectType> &Optional<ObjectType>::operator=(ObjectType &&other) {
+  if (mHasValue) {
+    object() = std::move(other);
+  } else {
+    new (objectAddr()) ObjectType(std::move(other));
+  }
+
+  mHasValue = true;
+  return *this;
+}
+
+template <typename ObjectType>
+Optional<ObjectType> &Optional<ObjectType>::operator=(
+    Optional<ObjectType> &&other) {
+  if (mHasValue) {
+    if (other.mHasValue) {
+      object() = std::move(other.object());
+    } else {
+      reset();
+    }
+  } else if (other.mHasValue) {
+    new (objectAddr()) ObjectType(std::move(other.object()));
+  }
+
+  mHasValue = other.mHasValue;
+  return *this;
+}
+
+template <typename ObjectType>
+Optional<ObjectType> &Optional<ObjectType>::operator=(const ObjectType &other) {
+  if (mHasValue) {
+    object() = std::move(other);
+  } else {
+    new (objectAddr()) ObjectType(other);
+  }
+
+  mHasValue = true;
+  return *this;
+}
+
+template <typename ObjectType>
+Optional<ObjectType> &Optional<ObjectType>::operator=(
+    const Optional<ObjectType> &other) {
+  if (mHasValue) {
+    if (other.mHasValue) {
+      object() = other.object();
+    } else {
+      reset();
+    }
+  } else if (other.mHasValue) {
+    new (objectAddr()) ObjectType(other.object());
+  }
+
+  mHasValue = other.mHasValue;
+  return *this;
+}
+
+template <typename ObjectType>
+ObjectType &Optional<ObjectType>::operator*() {
+  return object();
+}
+
+template <typename ObjectType>
+const ObjectType &Optional<ObjectType>::operator*() const {
+  return object();
+}
+
+template <typename ObjectType>
+ObjectType *Optional<ObjectType>::operator->() {
+  return objectAddr();
+}
+
+template <typename ObjectType>
+const ObjectType *Optional<ObjectType>::operator->() const {
+  return objectAddr();
+}
+
+template <typename ObjectType>
+ObjectType &Optional<ObjectType>::object() {
+  return *objectAddr();
+}
+
+template <typename ObjectType>
+const ObjectType &Optional<ObjectType>::object() const {
+  return *objectAddr();
+}
+
+template <typename ObjectType>
+ObjectType *Optional<ObjectType>::objectAddr() {
+  return std::launder(reinterpret_cast<ObjectType *>(&mObject));
+}
+
+template <typename ObjectType>
+const ObjectType *Optional<ObjectType>::objectAddr() const {
+  return std::launder(reinterpret_cast<const ObjectType *>(&mObject));
+}
+
+}  // namespace chre
+
+#endif  // UTIL_CHRE_OPTIONAL_IMPL_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/singleton.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/singleton.h
new file mode 100644
index 0000000..62fd034
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/singleton.h
@@ -0,0 +1,95 @@
+/*
+ * 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_UTIL_SINGLETON_H_
+#define CHRE_UTIL_SINGLETON_H_
+
+#include <type_traits>
+
+#include "chre/util/non_copyable.h"
+
+namespace chre {
+
+/**
+ * The Singleton template provides static storage for one instance of the
+ * provided type. Initialization does not happen automatically which allows
+ * users of this API to control the order of initialization.
+ *
+ * Caution is recommended when using this class to avoid Singleton hell. In
+ * many cases there is a better solution than using a Singleton in your design.
+ * One good use of this class is for a class that owns the state of your
+ * application (the "root" of a tree of object ownership).
+ */
+template <typename ObjectType>
+class Singleton : public NonCopyable {
+ public:
+  /**
+   * Constructs the object in the space provided by this container. If the
+   * object is already constructed, no operation is performed. Use the
+   * isInitialized method to determine if construction is required.
+   *
+   * @param args The constructor arguments to pass to the singleton instance.
+   */
+  template <typename... Args>
+  static void init(Args &&...args);
+
+  /**
+   * Invokes the destructor on the underlying object if it has been constructed
+   * already.
+   */
+  static void deinit();
+
+  /**
+   * Returns whether or not this singleton instance has been constructed.
+   *
+   * @return Returns true if the object has already been constructed.
+   */
+  static bool isInitialized();
+
+  /**
+   * Returns a pointer to the underlying object. The singleton object must be
+   * constructed prior to using get to interact with it. If the object is not
+   * initialized, the behavior is undefined and the returned pointer is not
+   * initialized.
+   *
+   * @return A pointer to the singleton instance.
+   */
+  static ObjectType *get();
+
+  /**
+   * Returns a pointer to the underlying object. The singleton object must be
+   * constructed prior to using get to interact with it. If the object is not
+   * initialized, nullptr is returned.
+   *
+   * @return A pointer to the singleton instance or nullptr if it is not
+   * initialized.
+   */
+  static ObjectType *safeGet();
+
+ private:
+  //! Static storage for the type of this singleton.
+  static typename std::aligned_storage<sizeof(ObjectType),
+                                       alignof(ObjectType)>::type sObject;
+
+  //! Static storage for the initialized state of this singleton.
+  static bool sIsInitialized;
+};
+
+}  // namespace chre
+
+#include "chre/util/singleton_impl.h"
+
+#endif  // CHRE_UTIL_SINGLETON_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/singleton_impl.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/singleton_impl.h
new file mode 100644
index 0000000..b0ec5e7
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/singleton_impl.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 CHRE_UTIL_SINGLETON_IMPL_H_
+#define CHRE_UTIL_SINGLETON_IMPL_H_
+
+#include <new>
+#include <utility>
+
+#include "chre/util/singleton.h"
+
+namespace chre {
+
+template <typename ObjectType>
+typename std::aligned_storage<sizeof(ObjectType), alignof(ObjectType)>::type
+    Singleton<ObjectType>::sObject;
+
+template <typename ObjectType>
+bool Singleton<ObjectType>::sIsInitialized = false;
+
+template <typename ObjectType>
+template <typename... Args>
+void Singleton<ObjectType>::init(Args &&...args) {
+  if (!sIsInitialized) {
+    sIsInitialized = true;
+    new (get()) ObjectType(std::forward<Args>(args)...);
+  }
+}
+
+template <typename ObjectType>
+void Singleton<ObjectType>::deinit() {
+  if (sIsInitialized) {
+    get()->~ObjectType();
+    sIsInitialized = false;
+  }
+}
+
+template <typename ObjectType>
+bool Singleton<ObjectType>::isInitialized() {
+  return sIsInitialized;
+}
+
+template <typename ObjectType>
+ObjectType *Singleton<ObjectType>::get() {
+  return std::launder(reinterpret_cast<ObjectType *>(&sObject));
+}
+
+template <typename ObjectType>
+ObjectType *Singleton<ObjectType>::safeGet() {
+  if (sIsInitialized) {
+    return std::launder(reinterpret_cast<ObjectType *>(&sObject));
+  } else {
+    return nullptr;
+  }
+}
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_SINGLETON_IMPL_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/time.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/time.h
new file mode 100644
index 0000000..b3fef77
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/time.h
@@ -0,0 +1,301 @@
+/*
+ * 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_UTIL_TIME_H_
+#define CHRE_UTIL_TIME_H_
+
+#include <cstdint>
+
+namespace chre {
+
+//! The number of milliseconds in one min.
+constexpr uint64_t kOneMinuteInMilliseconds(60000);
+
+constexpr uint64_t kOneMinuteInNanoseconds(60000000000);
+
+//! The number of milliseconds in one second.
+constexpr uint64_t kOneSecondInMilliseconds(1000);
+
+//! The number of nanoseconds in one second.
+constexpr uint64_t kOneSecondInNanoseconds(1000000000);
+
+//! The number of nanoseconds in one millisecond.
+constexpr uint64_t kOneMillisecondInNanoseconds(1000000);
+
+//! The number of nanoseconds in one microsecond.
+constexpr uint64_t kOneMicrosecondInNanoseconds(1000);
+
+//! The number of microseconds in one millisecond.
+constexpr uint64_t kOneMillisecondInMicroseconds(1000);
+
+// Forward declare classes for unit-conversion constructors.
+class Milliseconds;
+class Microseconds;
+class Nanoseconds;
+
+class Seconds {
+ public:
+  /**
+   * Construct a Seconds time duration given a value.
+   */
+  constexpr explicit Seconds(uint64_t seconds);
+
+  /**
+   * Converts the underlying seconds to a raw uint64_t representation of
+   * nanoseconds. Handles overflyw by returning UINT64_MAX.
+   *
+   * @return the value of seconds converted to nanoseconds
+   */
+  constexpr uint64_t toRawNanoseconds() const;
+
+  /**
+   * Obtains the number of Milliseconds stored by this time duration.
+   *
+   * @return the value of milliseconds.
+   */
+  constexpr uint64_t getMilliseconds() const;
+
+ private:
+  uint64_t mSeconds;
+};
+
+/**
+ * Represents a duration of time in milliseconds.
+ */
+class Milliseconds {
+ public:
+  /**
+   * Default constructs a milliseconds time duration to zero.
+   */
+  constexpr Milliseconds();
+
+  /**
+   * Construct a Milliseconds time duration given a value.
+   */
+  constexpr explicit Milliseconds(uint64_t milliseconds);
+
+  /**
+   * Constructs a Microseconds time duration given nanoseconds.
+   */
+  constexpr Milliseconds(Nanoseconds nanoseconds);
+
+  /**
+   * Converts the underlying milliseconds to a raw uint64_t representation of
+   * nanoseconds. Handles overflow by returning UINT64_MAX.
+   *
+   * @return the value of milliseconds converted to nanoseconds
+   */
+  constexpr uint64_t toRawNanoseconds() const;
+
+  /**
+   * Obtains the number of Microseconds stored by this time duration.
+   *
+   * @return the value of microseconds.
+   */
+  constexpr uint64_t getMicroseconds() const;
+
+  /**
+   * Obtains the number of Milliseconds stored by this time duration.
+   *
+   * @return the value of milliseconds.
+   */
+  constexpr uint64_t getMilliseconds() const;
+
+  /**
+   * Performs an equality comparison to another Milliseconds value.
+   *
+   * @return Returns true if this milliseconds object is equal to another.
+   */
+  constexpr bool operator==(const Milliseconds &millis) const;
+
+ private:
+  //! Store the time duration.
+  uint64_t mMilliseconds;
+};
+
+/**
+ * Represents a duration of time in microseconds.
+ */
+class Microseconds {
+ public:
+  /**
+   * Construct a Microseconds time duration given a value.
+   */
+  constexpr explicit Microseconds(uint64_t microseconds);
+
+  /**
+   * Constructs a Microseconds time duration given nanoseconds.
+   */
+  constexpr Microseconds(Nanoseconds nanoseconds);
+
+  /**
+   * Converts the underlying microseconds to a raw uint64_t representation of
+   * nanoseconds. Handles overflow by returning UINT64_MAX.
+   *
+   * @return the value of microseconds converted to nanoseconds.
+   */
+  constexpr uint64_t toRawNanoseconds() const;
+
+  /**
+   * Obtains the number of Microseconds stored by this time duration.
+   *
+   * @return the value of microseconds.
+   */
+  constexpr uint64_t getMicroseconds() const;
+
+  /**
+   * Obtains the rounded-down number of Milliseconds stored by this time
+   * duration.
+   *
+   * @return the value of milliseconds.
+   */
+  constexpr uint64_t getMilliseconds() const;
+
+ private:
+  //! Store the time duration.
+  uint64_t mMicroseconds;
+};
+
+/**
+ * Represents a duration of time in nanoseconds.
+ */
+class Nanoseconds {
+ public:
+  /**
+   * Default constructs a Nanoseconds time duration to zero.
+   */
+  constexpr Nanoseconds();
+
+  /**
+   * Constructs a Nanoseconds time duration given a value.
+   */
+  constexpr explicit Nanoseconds(uint64_t nanoseconds);
+
+  /**
+   * Converts a seconds value to nanoseconds.
+   */
+  constexpr Nanoseconds(Seconds seconds);
+
+  /**
+   * Converts a milliseconds value to nanoseconds.
+   */
+  constexpr Nanoseconds(Milliseconds milliseconds);
+
+  /**
+   * Constructs a Nanoseconds time duration given microseconds.
+   */
+  constexpr Nanoseconds(Microseconds microseconds);
+
+  /**
+   * Converts the underlying nanoseconds to a raw uint64_t representation of
+   * nanoseconds.
+   *
+   * @return the value of nanoseconds
+   */
+  constexpr uint64_t toRawNanoseconds() const;
+
+  /**
+   * Performs an equality comparison to another Nanoseconds value.
+   *
+   * @return Returns true if this nanoseconds object is equal to another.
+   */
+  constexpr bool operator==(const Nanoseconds &nanos) const;
+
+  /**
+   * Performs an inequality comparison to another Nanoseconds value.
+   *
+   * @return Returns true if this nanoseconds object is not equal to another.
+   */
+  constexpr bool operator!=(const Nanoseconds &nanos) const;
+
+ private:
+  uint64_t mNanoseconds;
+};
+
+/**
+ * Add seconds to nanoseconds.
+ *
+ * @param seconds the seconds duration
+ * @param nanoseconds the nanoseconds duration
+ * @return the added time quantity expressed in nanoseconds
+ */
+constexpr Nanoseconds operator+(const Seconds &secs, const Nanoseconds &nanos);
+
+/**
+ * Add nanoseconds to nanoseconds.
+ *
+ * @param nanos_a The first nanoseconds duration
+ * @param nanos_b The second nanoseconds duration
+ * @return The added time quantity expressed in nanoseconds
+ */
+constexpr Nanoseconds operator+(const Nanoseconds &nanos_a,
+                                const Nanoseconds &nanos_b);
+
+/**
+ * Subtract two nanosecond durations.
+ *
+ * @param nanos_a the first nanoseconds duration
+ * @param nanos_b the second nanoseconds duration
+ * @return the difference between the two durations
+ */
+constexpr Nanoseconds operator-(const Nanoseconds &nanos_a,
+                                const Nanoseconds &nanos_b);
+
+/**
+ * Performs a greater than or equal to comparison on two nanoseconds values.
+ *
+ * @param nanos_a the first nanoseconds duration
+ * @param nanos_b the second nanoseconds duration
+ * @return Whether nanos_a is greater than or equal to nanos_b.
+ */
+constexpr bool operator>=(const Nanoseconds &nanos_a,
+                          const Nanoseconds &nanos_b);
+
+/**
+ * Performs a less than or equal to comparison on two nanoseconds values.
+ *
+ * @param nanos_a the first nanoseconds duration
+ * @param nanos_b the second nanoseconds duration
+ * @return Whether nanos_a is less than or equal to nanos_b.
+ */
+constexpr bool operator<=(const Nanoseconds &nanos_a,
+                          const Nanoseconds &nanos_b);
+
+/**
+ * Performs a less than comparison on two nanoseconds values.
+ *
+ * @param nanos_a the first nanoseconds duration
+ * @param nanos_b the second nanoseconds duration
+ * @return Whether nanos_a is less than nanos_b.
+ */
+constexpr bool operator<(const Nanoseconds &nanos_a,
+                         const Nanoseconds &nanos_b);
+
+/**
+ * Performs a greater than comparison on two nanoseconds values.
+ *
+ * @param nanos_a the first nanoseconds duration
+ * @param nanos_b the second nanoseconds duration
+ * @return Whether nanos_a is less than nanos_b.
+ */
+constexpr bool operator>(const Nanoseconds &nanos_a,
+                         const Nanoseconds &nanos_b);
+
+}  // namespace chre
+
+#include "chre/util/time_impl.h"
+
+#endif  // CHRE_UTIL_TIME_H_
diff --git a/apps/nearby/third_party/contexthub/chre/util/include/chre/util/time_impl.h b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/time_impl.h
new file mode 100644
index 0000000..c207190
--- /dev/null
+++ b/apps/nearby/third_party/contexthub/chre/util/include/chre/util/time_impl.h
@@ -0,0 +1,165 @@
+/*
+ * 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_TIME_IMPL_H_
+#define CHRE_UTIL_TIME_IMPL_H_
+
+#include "chre/util/time.h"
+
+namespace chre {
+
+constexpr Seconds::Seconds(uint64_t seconds) : mSeconds(seconds) {}
+
+constexpr uint64_t Seconds::toRawNanoseconds() const {
+  // Perform the simple unit conversion. Warning: overflow is caught and
+  // handled by returning UINT64_MAX. A ternary expression is used because
+  // constexpr requires it.
+  return (mSeconds > (UINT64_MAX / kOneSecondInNanoseconds))
+             ? UINT64_MAX
+             : mSeconds * kOneSecondInNanoseconds;
+}
+
+constexpr uint64_t Seconds::getMilliseconds() const {
+  // Perform the simple unit conversion. Warning: overflow is caught and
+  // handled by returning UINT64_MAX. A ternary expression is used because
+  // constexpr requires it.
+  return (mSeconds > (UINT64_MAX / kOneSecondInMilliseconds))
+             ? UINT64_MAX
+             : mSeconds * kOneSecondInMilliseconds;
+}
+
+constexpr Milliseconds::Milliseconds() : mMilliseconds(0) {}
+
+constexpr Milliseconds::Milliseconds(uint64_t milliseconds)
+    : mMilliseconds(milliseconds) {}
+
+constexpr Milliseconds::Milliseconds(Nanoseconds nanoseconds)
+    : mMilliseconds(nanoseconds.toRawNanoseconds() /
+                    kOneMillisecondInNanoseconds) {}
+
+constexpr uint64_t Milliseconds::toRawNanoseconds() const {
+  // Perform the simple unit conversion. Warning: overflow is caught and
+  // handled by returning UINT64_MAX. A ternary expression is used because
+  // constexpr requires it.
+  return (mMilliseconds > (UINT64_MAX / kOneMillisecondInNanoseconds))
+             ? UINT64_MAX
+             : mMilliseconds * kOneMillisecondInNanoseconds;
+}
+
+constexpr uint64_t Milliseconds::getMicroseconds() const {
+  // Perform the simple unit conversion. Warning: overflow is caught and
+  // handled by returning UINT64_MAX. A ternary expression is used because
+  // constexpr requires it.
+  return (mMilliseconds > (UINT64_MAX / kOneMillisecondInMicroseconds))
+             ? UINT64_MAX
+             : mMilliseconds * kOneMillisecondInMicroseconds;
+}
+
+constexpr uint64_t Milliseconds::getMilliseconds() const {
+  return mMilliseconds;
+}
+
+constexpr bool Milliseconds::operator==(const Milliseconds &millis) const {
+  return (mMilliseconds == millis.mMilliseconds);
+}
+
+constexpr Microseconds::Microseconds(uint64_t microseconds)
+    : mMicroseconds(microseconds) {}
+
+constexpr Microseconds::Microseconds(Nanoseconds nanoseconds)
+    : mMicroseconds(nanoseconds.toRawNanoseconds() /
+                    kOneMicrosecondInNanoseconds) {}
+
+constexpr uint64_t Microseconds::toRawNanoseconds() const {
+  // Perform the simple unit conversion. Warning: overflow is caught and
+  // handled by returning UINT64_MAX. A ternary expression is used because
+  // constexpr requires it.
+  return (mMicroseconds > (UINT64_MAX / kOneMicrosecondInNanoseconds))
+             ? UINT64_MAX
+             : mMicroseconds * kOneMicrosecondInNanoseconds;
+}
+
+constexpr uint64_t Microseconds::getMicroseconds() const {
+  return mMicroseconds;
+}
+
+constexpr uint64_t Microseconds::getMilliseconds() const {
+  return (mMicroseconds / kOneMillisecondInMicroseconds);
+}
+
+constexpr Nanoseconds::Nanoseconds() : mNanoseconds(0) {}
+
+constexpr Nanoseconds::Nanoseconds(uint64_t nanoseconds)
+    : mNanoseconds(nanoseconds) {}
+
+constexpr Nanoseconds::Nanoseconds(Seconds seconds)
+    : mNanoseconds(seconds.toRawNanoseconds()) {}
+
+constexpr Nanoseconds::Nanoseconds(Milliseconds milliseconds)
+    : mNanoseconds(milliseconds.toRawNanoseconds()) {}
+
+constexpr Nanoseconds::Nanoseconds(Microseconds microseconds)
+    : mNanoseconds(microseconds.toRawNanoseconds()) {}
+
+constexpr uint64_t Nanoseconds::toRawNanoseconds() const {
+  return mNanoseconds;
+}
+
+constexpr bool Nanoseconds::operator==(const Nanoseconds &nanos) const {
+  return (mNanoseconds == nanos.mNanoseconds);
+}
+
+constexpr bool Nanoseconds::operator!=(const Nanoseconds &nanos) const {
+  return !(mNanoseconds == nanos.mNanoseconds);
+}
+
+constexpr Nanoseconds operator+(const Seconds &secs, const Nanoseconds &nanos) {
+  return Nanoseconds(secs.toRawNanoseconds() + nanos.toRawNanoseconds());
+}
+
+constexpr Nanoseconds operator+(const Nanoseconds &nanos_a,
+                                const Nanoseconds &nanos_b) {
+  return Nanoseconds(nanos_a.toRawNanoseconds() + nanos_b.toRawNanoseconds());
+}
+
+constexpr Nanoseconds operator-(const Nanoseconds &nanos_a,
+                                const Nanoseconds &nanos_b) {
+  return Nanoseconds(nanos_a.toRawNanoseconds() - nanos_b.toRawNanoseconds());
+}
+
+constexpr bool operator>=(const Nanoseconds &nanos_a,
+                          const Nanoseconds &nanos_b) {
+  return nanos_a.toRawNanoseconds() >= nanos_b.toRawNanoseconds();
+}
+
+constexpr bool operator<=(const Nanoseconds &nanos_a,
+                          const Nanoseconds &nanos_b) {
+  return nanos_a.toRawNanoseconds() <= nanos_b.toRawNanoseconds();
+}
+
+constexpr bool operator<(const Nanoseconds &nanos_a,
+                         const Nanoseconds &nanos_b) {
+  return nanos_a.toRawNanoseconds() < nanos_b.toRawNanoseconds();
+}
+
+constexpr bool operator>(const Nanoseconds &nanos_a,
+                         const Nanoseconds &nanos_b) {
+  return nanos_a.toRawNanoseconds() > nanos_b.toRawNanoseconds();
+}
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_TIME_IMPL_H_
diff --git a/platform/freertos/include/chre/target_platform/atomic_base.h b/apps/nearby/third_party/contexthub/chre/util/nanoapp/callbacks.cc
similarity index 64%
copy from platform/freertos/include/chre/target_platform/atomic_base.h
copy to apps/nearby/third_party/contexthub/chre/util/nanoapp/callbacks.cc
index 582d0ae..8dc4734 100644
--- a/platform/freertos/include/chre/target_platform/atomic_base.h
+++ b/apps/nearby/third_party/contexthub/chre/util/nanoapp/callbacks.cc
@@ -14,22 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
-#define CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
+#include "chre/util/nanoapp/callbacks.h"
 
-#include <atomic>
+#include "chre/util/container_support.h"
 
 namespace chre {
 
-template <typename AtomicType>
-class AtomicBase {
- protected:
-  std::atomic<AtomicType> mAtomic;
-};
-
-typedef AtomicBase<bool> AtomicBoolBase;
-typedef AtomicBase<uint32_t> AtomicUint32Base;
+void heapFreeMessageCallback(void *message, size_t /* messageSize */) {
+  // container_support.h will use chreHeapFree when building for nanoapps
+  // and keeps as memoryFree if used within the framework itself.
+  memoryFree(message);
+}
 
 }  // namespace chre
-
-#endif  // CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
diff --git a/apps/power_test/common/idl/update.sh b/apps/power_test/common/idl/update.sh
index 14f742a..c207f14 100755
--- a/apps/power_test/common/idl/update.sh
+++ b/apps/power_test/common/idl/update.sh
@@ -1,6 +1,10 @@
 #!/bin/bash
 
 # NOTE: Ensure you use flatc version 1.6.0 here!
+if [[ $(flatc --version | grep -Po "(?<=flatc version )([0-9]|\.)*(?=\s|$)") != "1.6.0" ]]; then
+echo "[ERROR] flatc version must be 1.6.0"
+exit
+fi
 
 # Generate the CHRE-side header file
 flatc --cpp -o ../include/generated/ --scoped-enums \
diff --git a/apps/power_test/common/include/request_manager.h b/apps/power_test/common/include/request_manager.h
index e7e17b3..c6b1d77 100644
--- a/apps/power_test/common/include/request_manager.h
+++ b/apps/power_test/common/include/request_manager.h
@@ -17,11 +17,11 @@
 #ifndef CHRE_POWER_TEST_REQUEST_MANAGER_H_
 #define CHRE_POWER_TEST_REQUEST_MANAGER_H_
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/singleton.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 #include "common.h"
 #include "generated/chre_power_test_generated.h"
 
diff --git a/apps/power_test/common/power_test.cc b/apps/power_test/common/power_test.cc
index b56c4d9..009f681 100644
--- a/apps/power_test/common/power_test.cc
+++ b/apps/power_test/common/power_test.cc
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/macros.h"
+#include "chre_api/chre.h"
 #include "common.h"
 #include "generated/chre_power_test_generated.h"
 #include "include/request_manager.h"
diff --git a/apps/power_test/common/power_test.mk b/apps/power_test/common/power_test.mk
index a648f1f..e287c66 100644
--- a/apps/power_test/common/power_test.mk
+++ b/apps/power_test/common/power_test.mk
@@ -27,6 +27,7 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Flatbuffers configuration
 include $(CHRE_PREFIX)/external/flatbuffers/flatbuffers.mk
diff --git a/apps/rpc_world/Makefile b/apps/rpc_world/Makefile
new file mode 100644
index 0000000..37bf9b2
--- /dev/null
+++ b/apps/rpc_world/Makefile
@@ -0,0 +1,45 @@
+#
+# RPC Service Nanoapp Makefile
+#
+# Environment Checks ###########################################################
+ifeq ($(CHRE_PREFIX),)
+  ifneq ($(ANDROID_BUILD_TOP),)
+    CHRE_PREFIX = $(ANDROID_BUILD_TOP)/system/chre
+  else
+    $(error "You must run 'lunch' to setup ANDROID_BUILD_TOP, or explicitly \
+    define the CHRE_PREFIX environment variable to point to the CHRE root \
+    directory.")
+  endif
+endif
+
+# Nanoapp Configuration ########################################################
+
+NANOAPP_NAME = rpc_world
+NANOAPP_ID = 0x0123456789000013
+NANOAPP_NAME_STRING = \"RPC\ Service\ World\"
+NANOAPP_VERSION = 0x00000001
+
+NANOAPP_PATH = $(CHRE_PREFIX)/apps/rpc_world
+
+
+# Source Code ##################################################################
+
+COMMON_SRCS += $(NANOAPP_PATH)/src/rpc_world_manager.cc
+COMMON_SRCS += $(NANOAPP_PATH)/src/rpc_world.cc
+
+# Compiler Flags ###############################################################
+
+# Defines
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
+
+# Includes
+COMMON_CFLAGS += -I$(NANOAPP_PATH)/inc
+
+# PW RPC protos ################################################################
+
+PW_RPC_SRCS = $(NANOAPP_PATH)/rpc/rpc_world.proto
+
+# Makefile Includes ############################################################
+
+include $(CHRE_PREFIX)/build/nanoapp/app.mk
diff --git a/apps/rpc_world/inc/rpc_world_manager.h b/apps/rpc_world/inc/rpc_world_manager.h
new file mode 100644
index 0000000..076dafd
--- /dev/null
+++ b/apps/rpc_world/inc/rpc_world_manager.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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_RPC_WORLD_MANAGER_H_
+#define CHRE_RPC_WORLD_MANAGER_H_
+
+#include <cinttypes>
+#include <cstdint>
+
+#include "chre/re.h"
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/util/pigweed/rpc_client.h"
+#include "chre/util/pigweed/rpc_server.h"
+#include "chre/util/singleton.h"
+#include "chre_api/chre.h"
+#include "rpc_world.rpc.pb.h"
+
+class RpcWorldService final
+    : public chre::rpc::pw_rpc::nanopb::RpcWorldService::Service<
+          RpcWorldService> {
+ public:
+  // Increment RPC unary service definition.
+  // See generated IncrementService::Service for more details.
+  pw::Status Increment(const chre_rpc_NumberMessage &request,
+                       chre_rpc_NumberMessage &response);
+
+  // Timer RPC server streaming service definition.
+  // See generated TimerService::Service for more details.
+  void Timer(const chre_rpc_TimerRequest &request,
+             ServerWriter<chre_rpc_TimerResponse> &writer);
+
+  // Add RPC client streaming service definition.
+  // See generated AddService::Service for more details.
+  void Add(
+      ServerReader<chre_rpc_NumberMessage, chre_rpc_NumberMessage> &reader);
+};
+
+/**
+ * Acts both as a RPC server and a RPC client.
+ * The client calls the RPCWorld service provided by the server.
+ */
+class RpcWorldManager {
+ public:
+  /**
+   * Allows the manager to do any init necessary as part of nanoappStart.
+   */
+  bool start();
+
+  /**
+   * Handle a CHRE event.
+   *
+   * @param senderInstanceId The instand ID that sent the event.
+   * @param eventType The type of the event.
+   * @param eventData The data for the event.
+   */
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData);
+
+  /**
+   * Allows the manager to do any cleanup necessary as part of nanoappEnd.
+   */
+  void end();
+
+  /**
+   * Starts the tick timer.
+   *
+   * @param numTicks Number of ticks to stream.
+   * @param writer Used to stream the responses.
+   */
+  void timerStart(
+      uint32_t numTicks,
+      RpcWorldService::ServerWriter<chre_rpc_TimerResponse> &writer);
+
+  /**
+   * Starts a client streamed add.
+   *
+   * @param reader Used to read the streamed requests.
+   */
+  void addStart(RpcWorldService::ServerReader<chre_rpc_NumberMessage,
+                                              chre_rpc_NumberMessage> &reader);
+
+  /**
+   * Sets the permission for the next server message.
+   *
+   * @params permission Bitmasked CHRE_MESSAGE_PERMISSION_.
+   */
+  void setPermissionForNextMessage(uint32_t permission);
+
+  uint32_t mSum = 0;
+
+ private:
+  chre::RpcServer mServer;
+  chre::RpcClient mClient{chre::kRpcWorldAppId};
+  // pw_rpc service used to process the RPCs.
+  RpcWorldService mRpcWorldService;
+  RpcWorldService::ServerWriter<chre_rpc_TimerResponse> mTimerWriter;
+  RpcWorldService::ServerReader<chre_rpc_NumberMessage, chre_rpc_NumberMessage>
+      mAddReader;
+  uint32_t mTimerId = CHRE_TIMER_INVALID;
+  uint32_t mTimerCurrentTick;
+  uint32_t mTimerTotalTicks;
+  pw::rpc::NanopbUnaryReceiver<chre_rpc_NumberMessage> mIncrementCall;
+  pw::rpc::NanopbClientReader<chre_rpc_TimerResponse> mTimerCall;
+  pw::rpc::NanopbClientWriter<chre_rpc_NumberMessage, chre_rpc_NumberMessage>
+      mAddCall;
+};
+
+typedef chre::Singleton<RpcWorldManager> RpcWorldManagerSingleton;
+
+#endif  // CHRE_RPC_WORLD_MANAGER_H_
diff --git a/apps/rpc_world/rpc/rpc_world.proto b/apps/rpc_world/rpc/rpc_world.proto
new file mode 100644
index 0000000..29bd92b
--- /dev/null
+++ b/apps/rpc_world/rpc/rpc_world.proto
@@ -0,0 +1,29 @@
+syntax = "proto3";
+
+package chre.rpc;
+
+service RpcWorldService {
+  // Increment a number.
+  rpc Increment(NumberMessage) returns (NumberMessage) {}
+
+  // Request a stream of ticks.
+  rpc Timer(TimerRequest) returns (stream TimerResponse) {}
+
+  // Add multiple numbers.
+  rpc Add(stream NumberMessage) returns (NumberMessage) {}
+}
+
+// Request and response for the Increment & Add services wrapping a number.
+message NumberMessage {
+  uint32 number = 1;
+}
+
+message TimerRequest {
+  // Number of ticks that the server should stream.
+  uint32 num_ticks = 1;
+}
+
+message TimerResponse {
+  // Tick number, starting at 1 and incremented on each tick.
+  uint32 tick_number = 1;
+}
diff --git a/apps/rpc_world/rpc_world.mk b/apps/rpc_world/rpc_world.mk
new file mode 100644
index 0000000..3317c9f
--- /dev/null
+++ b/apps/rpc_world/rpc_world.mk
@@ -0,0 +1,13 @@
+#
+# Rpc World Makefile
+#
+
+# Common Compiler Flags ########################################################
+
+# Include paths.
+COMMON_CFLAGS += -Iapps/rpc_world/inc
+
+# Common Source Files ##########################################################
+
+COMMON_SRCS += apps/rpc_world/rpc_world.cc
+COMMON_SRCS += apps/rpc_world/rpc_world_manager.cc
\ No newline at end of file
diff --git a/apps/rpc_world/src/rpc_world.cc b/apps/rpc_world/src/rpc_world.cc
new file mode 100644
index 0000000..f2e5d68
--- /dev/null
+++ b/apps/rpc_world/src/rpc_world.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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 "rpc_world_manager.h"
+
+#ifdef CHRE_NANOAPP_INTERNAL
+namespace chre {
+namespace {
+#endif  // CHRE_NANOAPP_INTERNAL
+
+void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                        const void *eventData) {
+  RpcWorldManagerSingleton::get()->handleEvent(senderInstanceId, eventType,
+                                               eventData);
+}
+
+bool nanoappStart(void) {
+  RpcWorldManagerSingleton::init();
+  return RpcWorldManagerSingleton::get()->start();
+}
+
+void nanoappEnd(void) {
+  RpcWorldManagerSingleton::get()->end();
+  RpcWorldManagerSingleton::deinit();
+}
+
+#ifdef CHRE_NANOAPP_INTERNAL
+}  // namespace
+}  // namespace chre
+
+#include "chre/platform/static_nanoapp_init.h"
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/util/system/napp_permissions.h"
+
+CHRE_STATIC_NANOAPP_INIT(RpcWorld, chre::kRpcWorldAppId, 0,
+                         chre::NanoappPermissions::CHRE_PERMS_NONE);
+#endif  // CHRE_NANOAPP_INTERNAL
\ No newline at end of file
diff --git a/apps/rpc_world/src/rpc_world_manager.cc b/apps/rpc_world/src/rpc_world_manager.cc
new file mode 100644
index 0000000..d658678
--- /dev/null
+++ b/apps/rpc_world/src/rpc_world_manager.cc
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 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 "rpc_world_manager.h"
+
+#include "chre/util/macros.h"
+#include "chre/util/nanoapp/log.h"
+#include "chre/util/time.h"
+
+#ifndef LOG_TAG
+#define LOG_TAG "[RpcWorld]"
+#endif  // LOG_TAG
+
+// [Server] Service implementations.
+pw::Status RpcWorldService::Increment(const chre_rpc_NumberMessage &request,
+                                      chre_rpc_NumberMessage &response) {
+  RpcWorldManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  response.number = request.number + 1;
+  return pw::OkStatus();
+}
+
+void RpcWorldService::Timer(
+    const chre_rpc_TimerRequest &request,
+    RpcWorldService::ServerWriter<chre_rpc_TimerResponse> &writer) {
+  RpcWorldManagerSingleton::get()->timerStart(request.num_ticks, writer);
+}
+
+void RpcWorldService::Add(
+    RpcWorldService::ServerReader<chre_rpc_NumberMessage,
+                                  chre_rpc_NumberMessage> &reader) {
+  RpcWorldManagerSingleton::get()->addStart(reader);
+}
+
+// [Client] callbacks.
+void incrementResponse(const chre_rpc_NumberMessage &response,
+                       pw::Status status) {
+  if (status.ok()) {
+    LOGI("Increment response: %d", response.number);
+  } else {
+    LOGE("Increment failed with status %d", static_cast<int>(status.code()));
+  }
+}
+
+void timerResponse(const chre_rpc_TimerResponse &response) {
+  LOGI("Tick response: %d", response.tick_number);
+}
+
+void timerEnd(pw::Status status) {
+  LOGI("Tick stream end: %d", static_cast<int>(status.code()));
+}
+
+void addResponse(const chre_rpc_NumberMessage &response, pw::Status status) {
+  if (status.ok()) {
+    LOGI("Add response: %d", response.number);
+  } else {
+    LOGE("Add failed with status %d", static_cast<int>(status.code()));
+  }
+}
+
+bool RpcWorldManager::start() {
+  chre::RpcServer::Service service = {.service = mRpcWorldService,
+                                      .id = 0xca8f7150a3f05847,
+                                      .version = 0x01020034};
+  if (!mServer.registerServices(1 /*numServices*/, &service)) {
+    LOGE("Error while registering the service");
+  }
+
+  auto client =
+      mClient.get<chre::rpc::pw_rpc::nanopb::RpcWorldService::Client>();
+
+  if (client.has_value()) {
+    // [Client] Invoking a unary RPC.
+    chre_rpc_NumberMessage incrementRequest;
+    incrementRequest.number = 101;
+    mIncrementCall = client->Increment(incrementRequest, incrementResponse);
+    CHRE_ASSERT(mIncrementCall.active());
+
+    // [Client] Invoking a server streaming RPC.
+    chre_rpc_TimerRequest timerRequest;
+    timerRequest.num_ticks = 5;
+    mTimerCall = client->Timer(timerRequest, timerResponse, timerEnd);
+    CHRE_ASSERT(mTimerCall.active());
+
+    // [Client] Invoking a client streaming RPC.
+    chre_rpc_NumberMessage addRequest;
+    addRequest.number = 1;
+    mAddCall = client->Add(addResponse);
+    CHRE_ASSERT(mAddCall.active());
+    mAddCall.Write(addRequest);
+    mAddCall.Write(addRequest);
+    mAddCall.Write(addRequest);
+    mAddCall.CloseClientStream();
+  } else {
+    LOGE("Error while retrieving the client");
+  }
+
+  return true;
+}
+
+void RpcWorldManager::setPermissionForNextMessage(uint32_t permission) {
+  mServer.setPermissionForNextMessage(permission);
+}
+
+void RpcWorldManager::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                                  const void *eventData) {
+  if (!mServer.handleEvent(senderInstanceId, eventType, eventData)) {
+    LOGE("[Server] An RPC error occurred");
+  }
+
+  if (!mClient.handleEvent(senderInstanceId, eventType, eventData)) {
+    LOGE("[Client] An RPC error occurred");
+  }
+
+  switch (eventType) {
+    case CHRE_EVENT_TIMER:
+      // [Server] stream responses.
+      chre_rpc_TimerResponse response;
+      response.tick_number = mTimerCurrentTick;
+      setPermissionForNextMessage(CHRE_MESSAGE_PERMISSION_NONE);
+      mTimerWriter.Write(response);
+      if (mTimerCurrentTick == mTimerTotalTicks) {
+        setPermissionForNextMessage(CHRE_MESSAGE_PERMISSION_NONE);
+        mTimerWriter.Finish(pw::OkStatus());
+        if (chreTimerCancel(mTimerId)) {
+          mTimerId = CHRE_TIMER_INVALID;
+        } else {
+          LOGE("Error while cancelling the timer");
+        }
+      }
+      mTimerCurrentTick++;
+  }
+}
+
+void RpcWorldManager::end() {
+  if (mTimerId != CHRE_TIMER_INVALID) {
+    chreTimerCancel(mTimerId);
+  }
+}
+
+void RpcWorldManager::timerStart(
+    uint32_t numTicks,
+    RpcWorldService::ServerWriter<chre_rpc_TimerResponse> &writer) {
+  mTimerCurrentTick = 1;
+  mTimerTotalTicks = numTicks;
+  mTimerWriter = std::move(writer);
+  mTimerId = chreTimerSet(chre::kOneSecondInNanoseconds, nullptr /*cookie*/,
+                          false /*oneShot*/);
+}
+
+void RpcWorldManager::addStart(
+    RpcWorldService::ServerReader<chre_rpc_NumberMessage,
+                                  chre_rpc_NumberMessage> &reader) {
+  mSum = 0;
+  reader.set_on_next([](const chre_rpc_NumberMessage &request) {
+    RpcWorldManagerSingleton::get()->mSum += request.number;
+  });
+  reader.set_on_client_stream_end([]() {
+    chre_rpc_NumberMessage response;
+    response.number = RpcWorldManagerSingleton::get()->mSum;
+    RpcWorldManagerSingleton::get()->setPermissionForNextMessage(
+        CHRE_MESSAGE_PERMISSION_NONE);
+    RpcWorldManagerSingleton::get()->mAddReader.Finish(response);
+  });
+  mAddReader = std::move(reader);
+}
\ No newline at end of file
diff --git a/apps/sensor_world/Makefile b/apps/sensor_world/Makefile
index 38f9697..553abd4 100644
--- a/apps/sensor_world/Makefile
+++ b/apps/sensor_world/Makefile
@@ -30,6 +30,7 @@
 
 # Defines.
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/sensor_world/sensor_world.cc b/apps/sensor_world/sensor_world.cc
index 9e8624b..9c77045 100644
--- a/apps/sensor_world/sensor_world.cc
+++ b/apps/sensor_world/sensor_world.cc
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[SensorWorld]"
 
@@ -39,6 +39,22 @@
 // enable/disable each sensor.
 constexpr bool kBreakIt = false;
 constexpr Milliseconds kBreakItPeriod = Milliseconds(2000);
+uint32_t gBreakItTimerHandle;
+
+//! Enable chreSensorFlushAsync test
+// When enabled, SensorWorld will set a timer to invoke
+// chreSensorFlushAsync(sensors[kFlushSensorIndex].handle)
+// halfway through sensors[kFlushSensorIndex].latency
+//
+// If CHRE_EVENT_SENSOR_FLUSH_COMPLETE is not received before
+// kFlushItTimeout expires, an error message will be logged.
+constexpr bool kFlushIt = true;
+constexpr uint32_t kFlushCookie = 0xdeadbeef;
+constexpr uint32_t kFlushSensorIndex = 0;  // CHRE_SENSOR_TYPE_ACCELEROMETER
+uint32_t gFlushItTimerHandle;
+
+constexpr Milliseconds kFlushItTimeout = Milliseconds(5000);
+uint32_t gFlushItTimeoutTimerHandle;
 
 //! Whether to enable sensor event logging or not.
 constexpr bool kEnableSensorEventLogging = true;
@@ -223,8 +239,6 @@
     },
 };
 
-uint32_t gBreakItTimerHandle;
-
 // Conditional logging macro
 #define CLOGI(fmt, ...)              \
   do {                               \
@@ -273,35 +287,136 @@
   return nullptr;
 }
 
-void handleTimerEvent(const void *eventData) {
-  UNUSED_VAR(eventData);
-
-  for (size_t i = 0; i < ARRAY_SIZE(sensors); i++) {
-    SensorState &sensor = sensors[i];
-
-    bool enable = getNextLfsrState() & 0x1;
-    if (sensor.isInitialized && sensor.enable != enable) {
-      sensor.enable = enable;
-
-      bool status;
-      if (!enable) {
-        status = chreSensorConfigureModeOnly(sensor.handle,
-                                             CHRE_SENSOR_CONFIGURE_MODE_DONE);
-      } else {
-        enum chreSensorConfigureMode mode =
-            sensor.info.isOneShot ? CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT
-                                  : CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS;
-        status = chreSensorConfigure(sensor.handle, mode, sensor.interval,
-                                     sensor.latency);
-      }
-
-      LOGI("Configure [enable %d, status %d]: %s", enable, status,
-           sensor.info.sensorName);
+void handleTimerEvent(const uint32_t *ev) {
+  if (*ev == gFlushItTimerHandle) {
+    LOGI("FlushIt Timer Fired");
+    if (chreSensorFlushAsync(sensors[kFlushSensorIndex].handle,
+                             &kFlushCookie)) {
+      gFlushItTimeoutTimerHandle =
+          chreTimerSet(kFlushItTimeout.toRawNanoseconds(),
+                       &gFlushItTimeoutTimerHandle, true /* oneShot */);
+    } else {
+      LOGE("chreSensorFlushAsync failed");
     }
+
+  } else if (*ev == gFlushItTimeoutTimerHandle) {
+    LOGE("chreSensorFlushAsync Timeout");
+
+  } else if (*ev == gBreakItTimerHandle) {
+    for (size_t i = 0; i < ARRAY_SIZE(sensors); i++) {
+      SensorState &sensor = sensors[i];
+
+      bool enable = getNextLfsrState() & 0x1;
+      if (sensor.isInitialized && sensor.enable != enable) {
+        sensor.enable = enable;
+
+        bool status;
+        if (!enable) {
+          status = chreSensorConfigureModeOnly(sensor.handle,
+                                               CHRE_SENSOR_CONFIGURE_MODE_DONE);
+        } else {
+          enum chreSensorConfigureMode mode =
+              sensor.info.isOneShot ? CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT
+                                    : CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS;
+          status = chreSensorConfigure(sensor.handle, mode, sensor.interval,
+                                       sensor.latency);
+        }
+
+        LOGI("Configure [enable %d, status %d]: %s", enable, status,
+             sensor.info.sensorName);
+      }
+    }
+
+    gBreakItTimerHandle =
+        chreTimerSet(kBreakItPeriod.toRawNanoseconds(), &gBreakItTimerHandle,
+                     true /* oneShot */);
+  }
+}
+
+void handleThreeAxisEvent(const chreSensorThreeAxisData *ev,
+                          uint16_t eventType) {
+  const auto header = ev->header;
+  const auto *data = ev->readings;
+  const auto accuracy = header.accuracy;
+  uint64_t sampleTime = header.baseTimestamp;
+  uint64_t chreTime = chreGetTime();
+
+  float x = 0, y = 0, z = 0;
+  for (size_t i = 0; i < header.readingCount; i++) {
+    x += data[i].v[0];
+    y += data[i].v[1];
+    z += data[i].v[2];
+    sampleTime += data[i].timestampDelta;
+  }
+  x /= header.readingCount;
+  y /= header.readingCount;
+  z /= header.readingCount;
+
+  CLOGI("%s, %d samples: %f %f %f, accuracy: %u, t=%" PRIu64 " ms",
+        getSensorName(header.sensorHandle), header.readingCount,
+        static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
+        accuracy, header.baseTimestamp / kOneMillisecondInNanoseconds);
+
+  if (eventType == CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA) {
+    CLOGI("UncalGyro time: first %" PRIu64 " last %" PRIu64 " chre %" PRIu64
+          " delta [%" PRId64 ", %" PRId64 "]ms",
+          header.baseTimestamp, sampleTime, chreTime,
+          static_cast<int64_t>(header.baseTimestamp - chreTime) /
+              static_cast<int64_t>(kOneMillisecondInNanoseconds),
+          static_cast<int64_t>(sampleTime - chreTime) /
+              static_cast<int64_t>(kOneMillisecondInNanoseconds));
+  }
+}
+
+void handleFloatEvent(const chreSensorFloatData *ev) {
+  const auto header = ev->header;
+
+  float v = 0;
+  for (size_t i = 0; i < header.readingCount; i++) {
+    v += ev->readings[i].value;
+  }
+  v /= header.readingCount;
+
+  CLOGI("%s, %d samples: %f, accuracy = %u, t=%" PRIu64 " ms",
+        getSensorName(header.sensorHandle), header.readingCount,
+        static_cast<double>(v), header.accuracy,
+        header.baseTimestamp / kOneMillisecondInNanoseconds);
+}
+
+void handleProximityEvent(const chreSensorByteData *ev) {
+  const auto header = ev->header;
+  const auto reading = ev->readings[0];
+  uint64_t sampleTime = header.baseTimestamp;
+  uint64_t chreTime = chreGetTime();
+
+  CLOGI("%s, %d samples: isNear %d, invalid %d, accuracy: %u",
+        getSensorName(header.sensorHandle), header.readingCount, reading.isNear,
+        reading.invalid, header.accuracy);
+
+  CLOGI("Prox time: sample %" PRIu64 " chre %" PRIu64 " delta %" PRId64 "ms",
+        header.baseTimestamp, chreTime,
+        static_cast<int64_t>(sampleTime - chreTime) / 1000000);
+
+  // Enable InstantMotion and StationaryDetect alternatively on near->far.
+  if (reading.isNear == 0 && !kBreakIt) {
+    size_t motionSensorIndex = getMotionSensorIndex();
+    bool status = chreSensorConfigure(
+        sensors[motionSensorIndex].handle, CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT,
+        CHRE_SENSOR_INTERVAL_DEFAULT, CHRE_SENSOR_LATENCY_DEFAULT);
+    LOGI("Requested %s: %s", sensors[motionSensorIndex].info.sensorName,
+         status ? "success" : "failure");
   }
 
-  gBreakItTimerHandle = chreTimerSet(kBreakItPeriod.toRawNanoseconds(),
-                                     nullptr /* data */, true /* oneShot */);
+  // Exercise chreGetSensorSamplingStatus on one sensor on near->far.
+  if (sensors[statusIndex].isInitialized && reading.isNear == 0) {
+    struct chreSensorSamplingStatus status;
+    bool success =
+        chreGetSensorSamplingStatus(sensors[statusIndex].handle, &status);
+    LOGI("%s success %d: enabled %d interval %" PRIu64 " latency %" PRIu64,
+         sensors[statusIndex].info.sensorName, success, status.enabled,
+         status.interval, status.latency);
+  }
+  statusIndex = (statusIndex + 1) % ARRAY_SIZE(sensors);
 }
 
 }  // namespace
@@ -343,7 +458,8 @@
         bool status = chreSensorConfigure(sensor.handle,
                                           CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
                                           sensor.interval, sensor.latency);
-        LOGI("Requested data: odr %f Hz, latency %f sec, %s", odrHz, latencySec,
+        LOGI("Requested data: odr %f Hz, latency %f sec, %s",
+             static_cast<double>(odrHz), static_cast<double>(latencySec),
              status ? "success" : "failure");
       }
     }
@@ -351,8 +467,16 @@
 
   // Set timer for BreakIt test.
   if (kBreakIt) {
-    gBreakItTimerHandle = chreTimerSet(kBreakItPeriod.toRawNanoseconds(),
-                                       nullptr /* data */, true /* oneShot */);
+    gBreakItTimerHandle =
+        chreTimerSet(kBreakItPeriod.toRawNanoseconds(), &gBreakItTimerHandle,
+                     true /* oneShot */);
+  }
+
+  if (kFlushIt) {
+    // Triger a flush half way through the target sensor latency
+    gFlushItTimerHandle =
+        chreTimerSet(sensors[kFlushSensorIndex].latency / 2,
+                     &gFlushItTimerHandle, true /* oneShot */);
   }
 
   return true;
@@ -362,107 +486,28 @@
                         const void *eventData) {
   UNUSED_VAR(senderInstanceId);
 
-  uint64_t chreTime = chreGetTime();
-  uint64_t sampleTime;
   switch (eventType) {
     case CHRE_EVENT_SENSOR_ACCELEROMETER_DATA:
     case CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA:
     case CHRE_EVENT_SENSOR_GYROSCOPE_DATA:
     case CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA:
     case CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA:
-    case CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA: {
-      const auto *ev = static_cast<const chreSensorThreeAxisData *>(eventData);
-      const auto header = ev->header;
-      const auto *data = ev->readings;
-      const auto accuracy = header.accuracy;
-      sampleTime = header.baseTimestamp;
-
-      float x = 0, y = 0, z = 0;
-      for (size_t i = 0; i < header.readingCount; i++) {
-        x += data[i].v[0];
-        y += data[i].v[1];
-        z += data[i].v[2];
-        sampleTime += data[i].timestampDelta;
-      }
-      x /= header.readingCount;
-      y /= header.readingCount;
-      z /= header.readingCount;
-
-      CLOGI("%s, %d samples: %f %f %f, accuracy: %u, t=%" PRIu64 " ms",
-            getSensorName(header.sensorHandle), header.readingCount, x, y, z,
-            accuracy, header.baseTimestamp / kOneMillisecondInNanoseconds);
-
-      if (eventType == CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA) {
-        CLOGI("UncalGyro time: first %" PRIu64 " last %" PRIu64 " chre %" PRIu64
-              " delta [%" PRId64 ", %" PRId64 "]ms",
-              header.baseTimestamp, sampleTime, chreTime,
-              static_cast<int64_t>(header.baseTimestamp - chreTime) /
-                  static_cast<int64_t>(kOneMillisecondInNanoseconds),
-              static_cast<int64_t>(sampleTime - chreTime) /
-                  static_cast<int64_t>(kOneMillisecondInNanoseconds));
-      }
+    case CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA:
+      handleThreeAxisEvent(
+          static_cast<const chreSensorThreeAxisData *>(eventData), eventType);
       break;
-    }
 
     case CHRE_EVENT_SENSOR_PRESSURE_DATA:
     case CHRE_EVENT_SENSOR_LIGHT_DATA:
     case CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA:
     case CHRE_EVENT_SENSOR_GYROSCOPE_TEMPERATURE_DATA:
-    case CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA: {
-      const auto *ev = static_cast<const chreSensorFloatData *>(eventData);
-      const auto header = ev->header;
-
-      float v = 0;
-      for (size_t i = 0; i < header.readingCount; i++) {
-        v += ev->readings[i].value;
-      }
-      v /= header.readingCount;
-
-      CLOGI("%s, %d samples: %f, accuracy = %u, t=%" PRIu64 " ms",
-            getSensorName(header.sensorHandle), header.readingCount, v,
-            header.accuracy,
-            header.baseTimestamp / kOneMillisecondInNanoseconds);
+    case CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA:
+      handleFloatEvent(static_cast<const chreSensorFloatData *>(eventData));
       break;
-    }
 
-    case CHRE_EVENT_SENSOR_PROXIMITY_DATA: {
-      const auto *ev = static_cast<const chreSensorByteData *>(eventData);
-      const auto header = ev->header;
-      const auto reading = ev->readings[0];
-      sampleTime = header.baseTimestamp;
-
-      CLOGI("%s, %d samples: isNear %d, invalid %d, accuracy: %u",
-            getSensorName(header.sensorHandle), header.readingCount,
-            reading.isNear, reading.invalid, header.accuracy);
-
-      CLOGI("Prox time: sample %" PRIu64 " chre %" PRIu64 " delta %" PRId64
-            "ms",
-            header.baseTimestamp, chreTime,
-            static_cast<int64_t>(sampleTime - chreTime) / 1000000);
-
-      // Enable InstantMotion and StationaryDetect alternatively on near->far.
-      if (reading.isNear == 0 && !kBreakIt) {
-        size_t motionSensorIndex = getMotionSensorIndex();
-        bool status = chreSensorConfigure(sensors[motionSensorIndex].handle,
-                                          CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT,
-                                          CHRE_SENSOR_INTERVAL_DEFAULT,
-                                          CHRE_SENSOR_LATENCY_DEFAULT);
-        LOGI("Requested %s: %s", sensors[motionSensorIndex].info.sensorName,
-             status ? "success" : "failure");
-      }
-
-      // Exercise chreGetSensorSamplingStatus on one sensor on near->far.
-      if (sensors[statusIndex].isInitialized && reading.isNear == 0) {
-        struct chreSensorSamplingStatus status;
-        bool success =
-            chreGetSensorSamplingStatus(sensors[statusIndex].handle, &status);
-        LOGI("%s success %d: enabled %d interval %" PRIu64 " latency %" PRIu64,
-             sensors[statusIndex].info.sensorName, success, status.enabled,
-             status.interval, status.latency);
-      }
-      statusIndex = (statusIndex + 1) % ARRAY_SIZE(sensors);
+    case CHRE_EVENT_SENSOR_PROXIMITY_DATA:
+      handleProximityEvent(static_cast<const chreSensorByteData *>(eventData));
       break;
-    }
 
     case CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA:
     case CHRE_EVENT_SENSOR_STATIONARY_DETECT_DATA:
@@ -497,13 +542,23 @@
     }
 
     case CHRE_EVENT_TIMER:
-      if (!kBreakIt) {
-        LOGE("Timer event received with gBreakIt is disabled");
+      if (kBreakIt || kFlushIt) {
+        handleTimerEvent(static_cast<const uint32_t *>(eventData));
       } else {
-        handleTimerEvent(eventData);
+        LOGE("Timer event received with kBreakIt and kFlushIt disabled");
       }
       break;
 
+    case CHRE_EVENT_SENSOR_FLUSH_COMPLETE: {
+      const auto *ev =
+          static_cast<const chreSensorFlushCompleteEvent *>(eventData);
+      chreTimerCancel(gFlushItTimeoutTimerHandle);
+
+      LOGI("Flush Complete: handle %" PRIu32 ", errorCode: %d",
+           ev->sensorHandle, ev->errorCode);
+      break;
+    }
+
     default:
       LOGW("Unhandled event %d", eventType);
       break;
diff --git a/apps/test/chqts/build/busy_startup/Makefile b/apps/test/chqts/build/busy_startup/Makefile
index a765998..5914c1a 100644
--- a/apps/test/chqts/build/busy_startup/Makefile
+++ b/apps/test/chqts/build/busy_startup/Makefile
@@ -5,4 +5,6 @@
 NANOAPP_VERSION := 0x00000000
 NANOAPP_NAME_STRING := \"CHQTS\ Busy\ Startup\"
 
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+
 include ../shared_make.mk
diff --git a/apps/test/chqts/build/do_nothing/Makefile b/apps/test/chqts/build/do_nothing/Makefile
index 196caf5..55deb96 100644
--- a/apps/test/chqts/build/do_nothing/Makefile
+++ b/apps/test/chqts/build/do_nothing/Makefile
@@ -5,4 +5,6 @@
 NANOAPP_VERSION := 0x00000001
 NANOAPP_NAME_STRING := \"CHQTS\ Do\ Nothing\"
 
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+
 include ../shared_make.mk
diff --git a/apps/test/chqts/build/echo_message/Makefile b/apps/test/chqts/build/echo_message/Makefile
index f0613a0..6498944 100644
--- a/apps/test/chqts/build/echo_message/Makefile
+++ b/apps/test/chqts/build/echo_message/Makefile
@@ -5,4 +5,6 @@
 NANOAPP_VERSION := 0x00000000
 NANOAPP_NAME_STRING := \"CHQTS\ Echo\ Message\"
 
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+
 include ../shared_make.mk
diff --git a/apps/test/chqts/build/fail_startup/Makefile b/apps/test/chqts/build/fail_startup/Makefile
index 79715e5..5bd43c1 100644
--- a/apps/test/chqts/build/fail_startup/Makefile
+++ b/apps/test/chqts/build/fail_startup/Makefile
@@ -5,4 +5,6 @@
 NANOAPP_VERSION := 0x00000000
 NANOAPP_NAME_STRING := \"CHQTS\ Fail\ Startup\"
 
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+
 include ../shared_make.mk
diff --git a/apps/test/chqts/build/general_test_sources.mk b/apps/test/chqts/build/general_test_sources.mk
index b7ce978..3cb82c3 100644
--- a/apps/test/chqts/build/general_test_sources.mk
+++ b/apps/test/chqts/build/general_test_sources.mk
@@ -4,6 +4,7 @@
 GENERAL_TEST_SRC_FILES = \
     app.cc \
     basic_audio_test.cc \
+    basic_ble_test.cc \
     basic_flush_async_test.cc \
     basic_gnss_test.cc \
     basic_sensor_test_base.cc \
diff --git a/apps/test/chqts/build/shared_make.mk b/apps/test/chqts/build/shared_make.mk
index f02f4aa..fa4fe17 100644
--- a/apps/test/chqts/build/shared_make.mk
+++ b/apps/test/chqts/build/shared_make.mk
@@ -14,6 +14,7 @@
 endif
 endif
 
+TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
 NANOAPP_DIR_NAME ?= $(NANOAPP_NAME)
 NANOAPP_SRC_PATH = $(CHRE_PREFIX)/apps/test/chqts/src
 
@@ -29,16 +30,20 @@
   $(addprefix $(NANOAPP_SRC_PATH)/shared/, $(SHARED_LIB_FILES))
 
 # Add util srcs since they may be included by the tests
+COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/ble.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/buffer_base.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/dynamic_vector_base.cc
+COMMON_SRCS += $(TEST_SHARED_PATH)/src/audio_validation.cc
 
 COMMON_CFLAGS += -DCHRE_NO_ENDIAN_H \
   -D__LITTLE_ENDIAN=1 \
   -D__BYTE_ORDER=1 \
-  -D__BIG_ENDIAN=2
+  -D__BIG_ENDIAN=2 \
+  -DCHRE_ASSERTIONS_ENABLED
 
 COMMON_CFLAGS += -I$(NANOAPP_SRC_PATH)
 COMMON_CFLAGS += -I$(CHRE_PREFIX)/util/include
+COMMON_CFLAGS += -I$(TEST_SHARED_PATH)/inc
 
 OPT_LEVEL=2
 
@@ -48,5 +53,7 @@
 CHRE_NANOAPP_USES_GNSS = true
 CHRE_NANOAPP_USES_WIFI = true
 CHRE_NANOAPP_USES_WWAN = true
+CHRE_NANOAPP_USES_BLE = true
+
 
 include $(CHRE_PREFIX)/build/nanoapp/app.mk
diff --git a/apps/test/chqts/build/who_am_i/Makefile b/apps/test/chqts/build/who_am_i/Makefile
index 1b11883..9fb1eee 100644
--- a/apps/test/chqts/build/who_am_i/Makefile
+++ b/apps/test/chqts/build/who_am_i/Makefile
@@ -5,4 +5,6 @@
 NANOAPP_VERSION := 0x00000000
 NANOAPP_NAME_STRING := \"CHQTS\ Who\ Am\ I\"
 
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+
 include ../shared_make.mk
diff --git a/apps/test/chqts/src/busy_startup/busy_startup.cc b/apps/test/chqts/src/busy_startup/busy_startup.cc
index 2b80b79..79443f9 100644
--- a/apps/test/chqts/src/busy_startup/busy_startup.cc
+++ b/apps/test/chqts/src/busy_startup/busy_startup.cc
@@ -44,11 +44,14 @@
 
 #include <cinttypes>
 
-#include <chre.h>
+#include <chre/util/nanoapp/log.h>
 
 #include <shared/send_message.h>
 #include <shared/test_success_marker.h>
 #include <shared/time_util.h>
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[BusyStartup]"
 
 using nanoapp_testing::MessageType;
 using nanoapp_testing::sendFatalFailureToHost;
@@ -165,27 +168,27 @@
   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
+    //     nanoappStart(), so we just use LOGE() here, and make
     //     the user look through the logs to determine why this failed.
-    chreLog(CHRE_LOG_ERROR, "Unable to malloc in start");
+    LOGE("Unable to malloc in start");
     return false;
   }
   gInstanceId = chreGetInstanceId();
   if (gInstanceId == CHRE_INSTANCE_ID) {
-    chreLog(CHRE_LOG_ERROR, "Got bad instance ID in start");
+    LOGE("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");
+    LOGE("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");
+    LOGE("Failed chreTimerSet in start");
     return false;
   }
 
@@ -197,7 +200,7 @@
 
   // Confirm we can find and configure a sensor.
   if (!chreSensorFindDefault(CHRE_SENSOR_TYPE_ACCELEROMETER, &gSensorHandle)) {
-    chreLog(CHRE_LOG_ERROR, "Failed sensorFindDefault in start");
+    LOGE("Failed sensorFindDefault in start");
     return false;
   }
 
@@ -206,7 +209,7 @@
   if (!chreSensorConfigure(gSensorHandle, CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
                            20 * nanoapp_testing::kOneMillisecondInNanoseconds,
                            CHRE_SENSOR_LATENCY_ASAP)) {
-    chreLog(CHRE_LOG_ERROR, "Failed sensorConfigure in start");
+    LOGE("Failed sensorConfigure in start");
     return false;
   }
 
diff --git a/apps/test/chqts/src/do_nothing/do_nothing.cc b/apps/test/chqts/src/do_nothing/do_nothing.cc
index 9f7f781..1170db5 100644
--- a/apps/test/chqts/src/do_nothing/do_nothing.cc
+++ b/apps/test/chqts/src/do_nothing/do_nothing.cc
@@ -23,7 +23,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 extern "C" void nanoappHandleEvent(uint32_t /* senderInstanceId */,
                                    uint16_t /* eventType */,
diff --git a/apps/test/chqts/src/echo_message/echo_message.cc b/apps/test/chqts/src/echo_message/echo_message.cc
index 341350d..f591138 100644
--- a/apps/test/chqts/src/echo_message/echo_message.cc
+++ b/apps/test/chqts/src/echo_message/echo_message.cc
@@ -25,11 +25,11 @@
 #include <cstdint>
 #include <cstring>
 
-#include <chre.h>
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
 #include "chre/util/macros.h"
+#include "chre_api/chre.h"
 
 namespace chre {
 namespace {
diff --git a/apps/test/chqts/src/fail_startup/fail_startup.cc b/apps/test/chqts/src/fail_startup/fail_startup.cc
index 27a5a65..3721e7d 100644
--- a/apps/test/chqts/src/fail_startup/fail_startup.cc
+++ b/apps/test/chqts/src/fail_startup/fail_startup.cc
@@ -25,9 +25,8 @@
 
 #include <cstdint>
 
-#include <chre.h>
-
 #include <shared/abort.h>
+#include "chre_api/chre.h"
 
 extern "C" void nanoappHandleEvent(uint32_t /* senderInstanceId */,
                                    uint16_t /* eventType */,
diff --git a/apps/test/chqts/src/general_test/app.cc b/apps/test/chqts/src/general_test/app.cc
index 7881199..a309548 100644
--- a/apps/test/chqts/src/general_test/app.cc
+++ b/apps/test/chqts/src/general_test/app.cc
@@ -18,9 +18,8 @@
 #include <cstdint>
 #include <new>  // placement new
 
-#include <chre.h>
-
 #include <general_test/basic_audio_test.h>
+#include <general_test/basic_ble_test.h>
 #include <general_test/basic_flush_async_test.h>
 #include <general_test/basic_gnss_test.h>
 #include <general_test/basic_sensor_tests.h>
@@ -57,6 +56,8 @@
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
+#include "chre_api/chre.h"
+
 using nanoapp_testing::AbortBlame;
 using nanoapp_testing::MessageType;
 using nanoapp_testing::sendFatalFailureToHost;
@@ -232,6 +233,7 @@
     CASE(kBasicGnssTest, BasicGnssTest);
     CASE(kBasicWifiTest, BasicWifiTest);
     CASE(kBasicSensorFlushAsyncTest, BasicSensorFlushAsyncTest);
+    CASE(kBasicBleTest, BasicBleTest);
 
 #undef CASE
 
diff --git a/apps/test/chqts/src/general_test/basic_audio_test.cc b/apps/test/chqts/src/general_test/basic_audio_test.cc
index 6cd1d63..8076598 100644
--- a/apps/test/chqts/src/general_test/basic_audio_test.cc
+++ b/apps/test/chqts/src/general_test/basic_audio_test.cc
@@ -15,6 +15,7 @@
  */
 #include <cinttypes>
 
+#include "audio_validation.h"
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/log.h"
 
@@ -25,6 +26,9 @@
 
 #define LOG_TAG "[ChreBasicAudioTest]"
 
+using chre::test_shared::checkAudioSamplesAllSame;
+using chre::test_shared::checkAudioSamplesAllZeros;
+
 using nanoapp_testing::kOneSecondInNanoseconds;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
@@ -196,37 +200,6 @@
   }
 }
 
-/**
- * Check if the audio samples are all zeros
- *
- * @return true on check passing
- */
-bool checkSamplesAllZeros(const int16_t *data, const size_t dataLen) {
-  for (size_t i = 0; i < dataLen; ++i) {
-    if (data[i] != 0) {
-      return true;
-    }
-  }
-  return false;
-}
-
-/**
- * Check if adjacent audio samples are unique
- *
- * @return true on check pass
- */
-bool checkSamplesAllSame(const int16_t *data, const size_t dataLen) {
-  if (dataLen > 0) {
-    const int16_t controlValue = data[0];
-    for (size_t i = 1; i < dataLen; ++i) {
-      if (data[i] != controlValue) {
-        return true;
-      }
-    }
-  }
-  return false;
-}
-
 void handleAudioDataEvent(const chreAudioDataEvent *dataEvent) {
   constexpr uint32_t kAudioHandle = 0;
 
@@ -263,10 +236,11 @@
     }
   }
 
-  if (!checkSamplesAllZeros(dataEvent->samplesS16, dataEvent->sampleCount)) {
+  if (!checkAudioSamplesAllZeros(dataEvent->samplesS16,
+                                 dataEvent->sampleCount)) {
     sendFatalFailureToHost("All audio samples were zeros");
-  } else if (!checkSamplesAllSame(dataEvent->samplesS16,
-                                  dataEvent->sampleCount)) {
+  } else if (!checkAudioSamplesAllSame(dataEvent->samplesS16,
+                                       dataEvent->sampleCount)) {
     sendFatalFailureToHost("All audio samples were identical");
   }
 
diff --git a/apps/test/chqts/src/general_test/basic_ble_test.cc b/apps/test/chqts/src/general_test/basic_ble_test.cc
new file mode 100644
index 0000000..74ce4d8
--- /dev/null
+++ b/apps/test/chqts/src/general_test/basic_ble_test.cc
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 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_ble_test.h>
+
+#include <shared/send_message.h>
+
+#include "chre/util/nanoapp/ble.h"
+#include "chre/util/time.h"
+#include "chre_api/chre.h"
+
+/*
+ * Test to check expected functionality of the CHRE BLE APIs.
+ */
+namespace general_test {
+
+using chre::createBleScanFilterForKnownBeacons;
+using chre::ble_constants::kNumScanFilters;
+using nanoapp_testing::sendFatalFailureToHost;
+
+namespace {
+const uint32_t gFlushCookie = 0;
+constexpr uint32_t kGoodReservedValue = 0;
+constexpr uint8_t kMaxReportAdvertisingSid = 0x0f;
+}  // namespace
+
+void testScanSessionAsync(bool supportsBatching, bool supportsFiltering) {
+  uint32_t reportDelayMs = supportsBatching ? 1000 : 0;
+
+  struct chreBleScanFilter filter;
+  chreBleGenericFilter uuidFilters[kNumScanFilters];
+  if (supportsFiltering) {
+    createBleScanFilterForKnownBeacons(filter, uuidFilters, kNumScanFilters);
+  }
+
+  if (!chreBleStartScanAsync(CHRE_BLE_SCAN_MODE_FOREGROUND /* mode */,
+                             reportDelayMs,
+                             supportsFiltering ? &filter : nullptr)) {
+    sendFatalFailureToHost("Failed to start a BLE scan in the foreground");
+  }
+}
+
+BasicBleTest::BasicBleTest()
+    : Test(CHRE_API_VERSION_1_7),
+      mFlushWasCalled(false),
+      mSupportsBatching(false) {}
+
+void BasicBleTest::setUp(uint32_t messageSize, const void * /* message */) {
+  if (messageSize != 0) {
+    sendFatalFailureToHost("Expected 0 byte message, got more bytes:",
+                           &messageSize);
+  }
+
+  mSupportsBatching =
+      isCapabilitySet(CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING);
+  mSupportsFiltering =
+      isFilterCapabilitySet(CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA) &&
+      isFilterCapabilitySet(CHRE_BLE_FILTER_CAPABILITIES_RSSI);
+
+  if (!isCapabilitySet(CHRE_BLE_CAPABILITIES_SCAN)) {
+    mTestSuccessMarker.markStageAndSuccessOnFinish(BASIC_BLE_TEST_STAGE_SCAN);
+    mTestSuccessMarker.markStageAndSuccessOnFinish(BASIC_BLE_TEST_STAGE_FLUSH);
+    return;
+  }
+
+  testScanSessionAsync(mSupportsBatching, mSupportsFiltering);
+  if (!mSupportsBatching) {
+    mTestSuccessMarker.markStageAndSuccessOnFinish(BASIC_BLE_TEST_STAGE_FLUSH);
+  }
+}
+
+void BasicBleTest::handleBleAsyncResult(const chreAsyncResult *result) {
+  if (result == nullptr || !result->success) {
+    sendFatalFailureToHost("Received unsuccessful BLE async result");
+  }
+
+  switch (result->requestType) {
+    case CHRE_BLE_REQUEST_TYPE_START_SCAN:
+      // Wait one second to allow any advertisement events to propagate
+      // and be verified by handleAdvertisementEvent.
+      if (chreTimerSet(chre::kOneSecondInNanoseconds, nullptr, true) ==
+          CHRE_TIMER_INVALID) {
+        sendFatalFailureToHost(
+            "Failed to start a timer after BLE started scanning");
+      }
+      break;
+    case CHRE_BLE_REQUEST_TYPE_FLUSH:
+      if (result->cookie != &gFlushCookie) {
+        sendFatalFailureToHost("Cookie values do not match");
+      }
+      break;
+    case CHRE_BLE_REQUEST_TYPE_STOP_SCAN:
+      mTestSuccessMarker.markStageAndSuccessOnFinish(BASIC_BLE_TEST_STAGE_SCAN);
+      break;
+    default:
+      sendFatalFailureToHost("Unexpected request type");
+      break;
+  }
+}
+
+void BasicBleTest::handleAdvertisementEvent(
+    const chreBleAdvertisementEvent *event) {
+  if (event == nullptr) {
+    sendFatalFailureToHost("Invalid chreBleAdvertisementEvent");
+  } else if (event->reserved != kGoodReservedValue) {
+    sendFatalFailureToHost("chreBleAdvertisementEvent: reserved != 0");
+  } else {
+    for (uint16_t i = 0; i < event->numReports; ++i) {
+      const struct chreBleAdvertisingReport &report = event->reports[i];
+      if (report.advertisingSid != CHRE_BLE_ADI_NONE &&
+          report.advertisingSid > kMaxReportAdvertisingSid) {
+        sendFatalFailureToHost(
+            "chreBleAdvertisingReport: advertisingSid is invalid");
+      } else if (report.reserved != kGoodReservedValue) {
+        sendFatalFailureToHost("chreBleAdvertisingReport: reserved is invalid");
+      }
+    }
+  }
+}
+
+void BasicBleTest::handleTimerEvent() {
+  if (mSupportsBatching) {
+    if (!chreBleFlushAsync(&gFlushCookie)) {
+      sendFatalFailureToHost("Failed to BLE flush");
+    }
+    mFlushWasCalled = true;
+  } else {
+    if (!chreBleStopScanAsync()) {
+      sendFatalFailureToHost("Failed to stop a BLE scan session");
+    }
+  }
+}
+
+void BasicBleTest::handleEvent(uint32_t /* senderInstanceId */,
+                               uint16_t eventType, const void *eventData) {
+  switch (eventType) {
+    case CHRE_EVENT_BLE_ASYNC_RESULT:
+      handleBleAsyncResult(static_cast<const chreAsyncResult *>(eventData));
+      break;
+    case CHRE_EVENT_BLE_FLUSH_COMPLETE:
+      if (!mFlushWasCalled) {
+        sendFatalFailureToHost(
+            "Received CHRE_EVENT_BLE_FLUSH_COMPLETE event when "
+            "chreBleFlushAsync was not called");
+      }
+      if (!chreBleStopScanAsync()) {
+        sendFatalFailureToHost("Failed to stop a BLE scan session");
+      }
+      mTestSuccessMarker.markStageAndSuccessOnFinish(
+          BASIC_BLE_TEST_STAGE_FLUSH);
+      break;
+    case CHRE_EVENT_BLE_ADVERTISEMENT:
+      handleAdvertisementEvent(
+          static_cast<const chreBleAdvertisementEvent *>(eventData));
+      break;
+    case CHRE_EVENT_TIMER:
+      handleTimerEvent();
+      break;
+    default:
+      unexpectedEvent(eventType);
+      break;
+  }
+}
+
+}  // namespace general_test
diff --git a/apps/test/chqts/src/general_test/basic_ble_test.h b/apps/test/chqts/src/general_test/basic_ble_test.h
new file mode 100644
index 0000000..6dc26f2
--- /dev/null
+++ b/apps/test/chqts/src/general_test/basic_ble_test.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 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_BLE_TEST_H_
+#define _GTS_NANOAPPS_GENERAL_TEST_BASIC_BLE_TEST_H_
+
+#include <general_test/test.h>
+
+#include <cstdint>
+
+#include <shared/test_success_marker.h>
+
+#include "chre_api/chre.h"
+
+namespace general_test {
+
+class BasicBleTest : public Test {
+ public:
+  BasicBleTest();
+
+ protected:
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData) override;
+  void setUp(uint32_t messageSize, const void *message) override;
+
+ private:
+  /**
+   * If true, chreBleFlushAsync(...) was called. If false, it was not called.
+   */
+  bool mFlushWasCalled;
+
+  /**
+   * If true, the device supports batching, and we can call
+   * chreBleFlushAsync(...).
+   */
+  bool mSupportsBatching;
+
+  /**
+   * If true, the device supports all filtering available.
+   */
+  bool mSupportsFiltering;
+
+  enum BasicBleTestStage {
+    BASIC_BLE_TEST_STAGE_SCAN =
+        0,  // stage: BLE scanning start and stop was successful
+    BASIC_BLE_TEST_STAGE_FLUSH,  // stage: the flush API was successful
+    BASIC_BLE_TEST_STAGE_COUNT,  // total number of stages
+  };
+
+  nanoapp_testing::TestSuccessMarker mTestSuccessMarker =
+      nanoapp_testing::TestSuccessMarker(BASIC_BLE_TEST_STAGE_COUNT);
+
+  bool isCapabilitySet(uint32_t capability) {
+    return (chreBleGetCapabilities() & capability);
+  };
+
+  bool isFilterCapabilitySet(uint32_t capability) {
+    return (chreBleGetFilterCapabilities() & capability);
+  };
+
+  void handleBleAsyncResult(const chreAsyncResult *result);
+
+  void handleAdvertisementEvent(const chreBleAdvertisementEvent *event);
+
+  void handleTimerEvent();
+};
+
+}  // namespace general_test
+
+#endif  // _GTS_NANOAPPS_GENERAL_TEST_BASIC_BLE_TEST_H_
diff --git a/apps/test/chqts/src/general_test/basic_flush_async_test.cc b/apps/test/chqts/src/general_test/basic_flush_async_test.cc
index 15da8bf..5e44df5 100644
--- a/apps/test/chqts/src/general_test/basic_flush_async_test.cc
+++ b/apps/test/chqts/src/general_test/basic_flush_async_test.cc
@@ -22,8 +22,11 @@
 #include <shared/send_message.h>
 #include <shared/time_util.h>
 
+#include <chre/util/nanoapp/log.h>
 #include "chre/util/macros.h"
 
+#define LOG_TAG "[BasicFlushAsyncTest]"
+
 using nanoapp_testing::kOneMillisecondInNanoseconds;
 using nanoapp_testing::kOneSecondInNanoseconds;
 using nanoapp_testing::sendFatalFailureToHost;
@@ -165,9 +168,8 @@
     ASSERT_GE(mLatestSensorDataTimestamp, oldestValidTimestamp,
               "Received very old data");
 
-    chreLog(CHRE_LOG_INFO,
-            "Flush test: flush request to complete time: %" PRIu64 " ms",
-            (chreGetTime() - mFlushRequestTime) / kOneMillisecondInNanoseconds);
+    LOGI("Flush test: flush request to complete time: %" PRIu64 " ms",
+         (chreGetTime() - mFlushRequestTime) / kOneMillisecondInNanoseconds);
 
     // verify event data
     ASSERT_NE(eventData, nullptr, "null event data");
diff --git a/apps/test/chqts/src/general_test/basic_flush_async_test.h b/apps/test/chqts/src/general_test/basic_flush_async_test.h
index cfc96a1..cf73436 100644
--- a/apps/test/chqts/src/general_test/basic_flush_async_test.h
+++ b/apps/test/chqts/src/general_test/basic_flush_async_test.h
@@ -19,7 +19,7 @@
 
 #include <general_test/test.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/basic_gnss_test.cc b/apps/test/chqts/src/general_test/basic_gnss_test.cc
index 6f77211..6e4be8d 100644
--- a/apps/test/chqts/src/general_test/basic_gnss_test.cc
+++ b/apps/test/chqts/src/general_test/basic_gnss_test.cc
@@ -16,9 +16,10 @@
 
 #include <general_test/basic_gnss_test.h>
 
-#include <chre.h>
 #include <shared/send_message.h>
 
+#include "chre_api/chre.h"
+
 /*
  * Test to check expected functionality of the CHRE GNSS APIs.
  */
diff --git a/apps/test/chqts/src/general_test/basic_gnss_test.h b/apps/test/chqts/src/general_test/basic_gnss_test.h
index c0daa5e..be21b8f 100644
--- a/apps/test/chqts/src/general_test/basic_gnss_test.h
+++ b/apps/test/chqts/src/general_test/basic_gnss_test.h
@@ -22,7 +22,7 @@
 
 #include <shared/test_success_marker.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/basic_sensor_test_base.cc b/apps/test/chqts/src/general_test/basic_sensor_test_base.cc
index 060316d..9028493 100644
--- a/apps/test/chqts/src/general_test/basic_sensor_test_base.cc
+++ b/apps/test/chqts/src/general_test/basic_sensor_test_base.cc
@@ -23,8 +23,7 @@
 #include <shared/time_util.h>
 
 #include "chre/util/nanoapp/log.h"
-
-#include <chre.h>
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[BasicSensorTest]"
 
@@ -181,16 +180,19 @@
   mState = State::kPreConfigure;
 
   bool found = false;
-  if (mApiVersion >= CHRE_API_VERSION_1_5) {
-    found =
-        chreSensorFind(getSensorType(), mCurrentSensorIndex, &mSensorHandle);
-    if (!found && chreSensorFind(getSensorType(), mCurrentSensorIndex + 1,
-                                 &mSensorHandle)) {
+  uint8_t mSensorType = getSensorType();
+  // TODO(b/286604767): CHRE should only expose the default light sensor to
+  // nanoapps.
+  if (mApiVersion >= CHRE_API_VERSION_1_5 &&
+      mSensorType != CHRE_SENSOR_TYPE_LIGHT) {
+    found = chreSensorFind(mSensorType, mCurrentSensorIndex, &mSensorHandle);
+    if (!found &&
+        chreSensorFind(mSensorType, mCurrentSensorIndex + 1, &mSensorHandle)) {
       sendFatalFailureToHostUint8("Missing sensor index ", mCurrentSensorIndex);
       return;
     }
   } else {
-    found = chreSensorFindDefault(getSensorType(), &mSensorHandle);
+    found = chreSensorFindDefault(mSensorType, &mSensorHandle);
   }
 
   if (!found) {
@@ -208,7 +210,7 @@
   if (info.sensorName == nullptr) {
     sendFatalFailureToHost("chreSensorInfo::sensorName is NULL");
   }
-  if (info.sensorType != getSensorType()) {
+  if (info.sensorType != mSensorType) {
     uint32_t type = info.sensorType;
     sendFatalFailureToHost(
         "chreSensorInfo::sensorType is not expected value, is:", &type);
@@ -323,8 +325,12 @@
   bool finished = true;
   if (mApiVersion >= CHRE_API_VERSION_1_5) {
     mCurrentSensorIndex++;
+    // TODO(b/286604767): CHRE should only expose the default light sensor to
+    // nanoapps.
     uint32_t sensorHandle;
-    if (chreSensorFind(getSensorType(), mCurrentSensorIndex, &sensorHandle)) {
+    uint8_t mSensorType = getSensorType();
+    if (mSensorType != CHRE_SENSOR_TYPE_LIGHT &&
+        chreSensorFind(getSensorType(), mCurrentSensorIndex, &sensorHandle)) {
       finished = false;
       mPrevSensorHandle = mSensorHandle;
       sendStartTestMessage();
@@ -377,12 +383,11 @@
             ? (*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);
+      LOGE("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) &&
diff --git a/apps/test/chqts/src/general_test/basic_sensor_test_base.h b/apps/test/chqts/src/general_test/basic_sensor_test_base.h
index afdb9a8..9443f5f 100644
--- a/apps/test/chqts/src/general_test/basic_sensor_test_base.h
+++ b/apps/test/chqts/src/general_test/basic_sensor_test_base.h
@@ -20,8 +20,7 @@
 #include <general_test/test.h>
 
 #include "chre/util/optional.h"
-
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/basic_sensor_tests.h b/apps/test/chqts/src/general_test/basic_sensor_tests.h
index 8fd8257..d95453b 100644
--- a/apps/test/chqts/src/general_test/basic_sensor_tests.h
+++ b/apps/test/chqts/src/general_test/basic_sensor_tests.h
@@ -19,7 +19,7 @@
 
 #include <general_test/basic_sensor_test_base.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/basic_wifi_test.cc b/apps/test/chqts/src/general_test/basic_wifi_test.cc
index af648c9..4b127e8 100644
--- a/apps/test/chqts/src/general_test/basic_wifi_test.cc
+++ b/apps/test/chqts/src/general_test/basic_wifi_test.cc
@@ -17,9 +17,9 @@
 #include <general_test/basic_wifi_test.h>
 
 #include <algorithm>
+#include <cinttypes>
 #include <cmath>
 
-#include <chre.h>
 #include <shared/macros.h>
 #include <shared/send_message.h>
 #include <shared/time_util.h>
@@ -27,6 +27,7 @@
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
 #include "chre/util/unique_ptr.h"
+#include "chre_api/chre.h"
 
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendFatalFailureToHostUint8;
@@ -37,14 +38,19 @@
 /*
  * Test to check expected functionality of the CHRE WiFi APIs.
  *
- * 1. If scan monitor is not supported, skip to 5;
+ * 1. If scan monitor is not supported, skips to 3;
  *    otherwise enables scan monitor.
  * 2. Checks async result of enabling scan monitor.
- * 3. Disables scan monitor.
- * 4. Checks async result of disabling scan monitor.
- * 5. If on demand WiFi scan is not supported, skip to end;
+ * 3. If on demand WiFi scan is not supported, skips to 5;
  *    otherwise sends default scan request.
- * 6. Checks the result of on demand WiFi scan.
+ * 4. Checks the result of on demand WiFi scan.
+ * 5. If scan monitor is supported then disables scan monitor;
+ *    otherwise go to step 7.
+ * 6. Checks async result of disabling scan monitor.
+ * 7. If a scan has ever happened runs the ranging test; otherwise
+ *    go to the end.
+ * 8. Checks the ranging test result.
+ * 9. end
  */
 namespace general_test {
 
@@ -77,6 +83,9 @@
 //! being deliverd to the test.
 constexpr uint32_t kTimeoutWiggleRoomNs = 2 * chre::kOneSecondInNanoseconds;
 
+// Number of seconds waited before retrying when an on demand wifi scan fails.
+constexpr uint64_t kOnDemandScanTimeoutNs = 7 * chre::kOneSecondInNanoseconds;
+
 /**
  * Calls API testConfigureScanMonitorAsync. Sends fatal failure to host
  * if API call fails.
@@ -87,6 +96,7 @@
  *        sent in relation to this request.
  */
 void testConfigureScanMonitorAsync(bool enable, const void *cookie) {
+  LOGI("Starts scan monitor configure test: %s", enable ? "enable" : "disable");
   if (!chreWifiConfigureScanMonitorAsync(enable, cookie)) {
     if (enable) {
       sendFatalFailureToHost("Failed to request to enable scan monitor.");
@@ -101,6 +111,7 @@
  * if API call fails.
  */
 void testRequestScanAsync() {
+  LOGI("Starts on demand scan test");
   // Request a fresh scan to ensure the correct scan type is performed.
   constexpr struct chreWifiScanParams kParams = {
       /*.scanType=*/CHRE_WIFI_SCAN_TYPE_ACTIVE,
@@ -111,7 +122,6 @@
       /*.ssidList=*/NULL,
       /*.radioChainPref=*/CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT,
       /*.channelSet=*/CHRE_WIFI_CHANNEL_SET_NON_DFS};
-
   if (!chreWifiRequestScanAsync(&kParams, &kOnDemandScanCookie)) {
     sendFatalFailureToHost("Failed to request for on-demand WiFi scan.");
   }
@@ -123,6 +133,7 @@
  */
 void testRequestRangingAsync(const struct chreWifiScanResult *aps,
                              uint8_t length) {
+  LOGI("Starts ranging test");
   // Sending an array larger than CHRE_WIFI_RANGING_LIST_MAX_LEN will cause
   // an immediate failure.
   uint8_t targetLength =
@@ -173,20 +184,18 @@
 void validatePrimaryChannel(uint32_t primaryChannel, uint32_t startFrequency,
                             uint8_t maxChannelNumber) {
   if ((primaryChannel - startFrequency) % 5 != 0) {
-    chreLog(CHRE_LOG_ERROR,
-            "primaryChannel - %" PRIu32
-            " must be a multiple of 5,"
-            "got primaryChannel: %" PRIu32,
-            startFrequency, primaryChannel);
+    LOGE("primaryChannel - %" PRIu32
+         " must be a multiple of 5,"
+         "got primaryChannel: %" PRIu32,
+         startFrequency, primaryChannel);
   }
 
   uint32_t primaryChannelNumber = (primaryChannel - startFrequency) / 5;
   if (primaryChannelNumber < 1 || primaryChannelNumber > maxChannelNumber) {
-    chreLog(CHRE_LOG_ERROR,
-            "primaryChannelNumber must be between 1 and %" PRIu8
-            ","
-            "got primaryChannel: %" PRIu32,
-            maxChannelNumber, primaryChannel);
+    LOGE("primaryChannelNumber must be between 1 and %" PRIu8
+         ","
+         "got primaryChannel: %" PRIu32,
+         maxChannelNumber, primaryChannel);
   }
 }
 
@@ -338,6 +347,7 @@
 void BasicWifiTest::handleEvent(uint32_t /* senderInstanceId */,
                                 uint16_t eventType, const void *eventData) {
   ASSERT_NE(eventData, nullptr, "Received null eventData");
+  LOGI("Received event type %" PRIu16, eventType);
   switch (eventType) {
     case CHRE_EVENT_WIFI_ASYNC_RESULT:
       handleChreWifiAsyncEvent(static_cast<const chreAsyncResult *>(eventData));
@@ -347,6 +357,8 @@
         sendFatalFailureToHost("WiFi scan event received when not requested");
       }
       const auto *result = static_cast<const chreWifiScanEvent *>(eventData);
+      LOGI("Received wifi scan result, result count: %" PRIu8,
+           result->resultCount);
 
       if (!isActiveWifiScanType(result)) {
         LOGW("Received unexpected scan type %" PRIu8, result->scanType);
@@ -390,6 +402,15 @@
           BASIC_WIFI_TEST_STAGE_SCAN_RTT);
       break;
     }
+    case CHRE_EVENT_TIMER: {
+      const uint32_t *timerHandle = static_cast<const uint32_t *>(eventData);
+      if (mScanTimeoutTimerHandle != CHRE_TIMER_INVALID &&
+          timerHandle == &mScanTimeoutTimerHandle) {
+        mScanTimeoutTimerHandle = CHRE_TIMER_INVALID;
+        startScanAsyncTestStage();
+      }
+      break;
+    }
     default:
       unexpectedEvent(eventType);
       break;
@@ -400,28 +421,44 @@
   if (!mCurrentWifiRequest.has_value()) {
     nanoapp_testing::sendFailureToHost("Unexpected async result");
   }
-
+  LOGI("Received a wifi async event. request type: %" PRIu8
+       " error code: %" PRIu8,
+       result->requestType, result->errorCode);
+  if (result->requestType == CHRE_WIFI_REQUEST_TYPE_REQUEST_SCAN &&
+      !result->success && mNumScanRetriesRemaining > 0) {
+    LOGI("Wait for %" PRIu64 " seconds and try again",
+         kOnDemandScanTimeoutNs / chre::kOneSecondInNanoseconds);
+    mNumScanRetriesRemaining--;
+    mScanTimeoutTimerHandle = chreTimerSet(
+        /* duration= */ kOnDemandScanTimeoutNs, &mScanTimeoutTimerHandle,
+        /* oneShot= */ true);
+    return;
+  }
   validateChreAsyncResult(result, mCurrentWifiRequest.value());
+  processChreWifiAsyncResult(result);
+}
 
+void BasicWifiTest::processChreWifiAsyncResult(const chreAsyncResult *result) {
   switch (result->requestType) {
     case CHRE_WIFI_REQUEST_TYPE_RANGING:
       // Reuse same start timestamp as the scan request since ranging fields
       // may be retrieved automatically as part of that scan.
       break;
     case CHRE_WIFI_REQUEST_TYPE_REQUEST_SCAN:
-      mStartTimestampNs = chreGetTime();
+      LOGI("Wifi scan result validated");
+      if (mScanTimeoutTimerHandle != CHRE_TIMER_INVALID) {
+        chreTimerCancel(mScanTimeoutTimerHandle);
+        mScanTimeoutTimerHandle = CHRE_TIMER_INVALID;
+      }
       break;
     case CHRE_WIFI_REQUEST_TYPE_CONFIGURE_SCAN_MONITOR:
       if (mCurrentWifiRequest->cookie == &kDisableScanMonitoringCookie) {
         mTestSuccessMarker.markStageAndSuccessOnFinish(
             BASIC_WIFI_TEST_STAGE_SCAN_MONITOR);
-        startScanAsyncTestStage();
+        mStartTimestampNs = chreGetTime();
+        startRangingAsyncTestStage();
       } else {
-        testConfigureScanMonitorAsync(false /* enable */,
-                                      &kDisableScanMonitoringCookie);
-        resetCurrentWifiRequest(&kDisableScanMonitoringCookie,
-                                CHRE_WIFI_REQUEST_TYPE_CONFIGURE_SCAN_MONITOR,
-                                CHRE_ASYNC_RESULT_TIMEOUT_NS);
+        startScanAsyncTestStage();
       }
       break;
     default:
@@ -436,6 +473,8 @@
 }
 
 void BasicWifiTest::startScanMonitorTestStage() {
+  LOGI("startScanMonitorTestStage - Wifi capabilities: %" PRIu32,
+       mWifiCapabilities);
   if (mWifiCapabilities & CHRE_WIFI_CAPABILITIES_SCAN_MONITORING) {
     testConfigureScanMonitorAsync(true /* enable */,
                                   &kEnableScanMonitoringCookie);
@@ -455,9 +494,18 @@
     resetCurrentWifiRequest(&kOnDemandScanCookie,
                             CHRE_WIFI_REQUEST_TYPE_REQUEST_SCAN,
                             CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS);
+  } else if (mWifiCapabilities & CHRE_WIFI_CAPABILITIES_SCAN_MONITORING) {
+    mTestSuccessMarker.markStageAndSuccessOnFinish(
+        BASIC_WIFI_TEST_STAGE_SCAN_ASYNC);
+    testConfigureScanMonitorAsync(false /* enable */,
+                                  &kDisableScanMonitoringCookie);
+    resetCurrentWifiRequest(&kDisableScanMonitoringCookie,
+                            CHRE_WIFI_REQUEST_TYPE_CONFIGURE_SCAN_MONITOR,
+                            CHRE_ASYNC_RESULT_TIMEOUT_NS);
   } else {
     mTestSuccessMarker.markStageAndSuccessOnFinish(
         BASIC_WIFI_TEST_STAGE_SCAN_ASYNC);
+    mStartTimestampNs = chreGetTime();
     startRangingAsyncTestStage();
   }
 }
@@ -495,9 +543,8 @@
   }
 
   if (mNextExpectedIndex != eventData->eventIndex) {
-    chreLog(CHRE_LOG_ERROR,
-            "Expected index: %" PRIu32 ", received index: %" PRIu8,
-            mNextExpectedIndex, eventData->eventIndex);
+    LOGE("Expected index: %" PRIu32 ", received index: %" PRIu8,
+         mNextExpectedIndex, eventData->eventIndex);
     sendFatalFailureToHost("Received out-of-order events");
   }
   mNextExpectedIndex++;
@@ -506,9 +553,8 @@
     mWiFiScanResultRemaining = eventData->resultTotal;
   }
   if (mWiFiScanResultRemaining < eventData->resultCount) {
-    chreLog(CHRE_LOG_ERROR,
-            "Remaining scan results %" PRIu32 ", received %" PRIu8,
-            mWiFiScanResultRemaining, eventData->resultCount);
+    LOGE("Remaining scan results %" PRIu32 ", received %" PRIu8,
+         mWiFiScanResultRemaining, eventData->resultCount);
     sendFatalFailureToHost("Received too many WiFi scan results");
   }
   mWiFiScanResultRemaining -= eventData->resultCount;
@@ -522,11 +568,22 @@
                                       eventData->resultCount);
   }
 
+  LOGI("Remaining scan result is %" PRIu32, mWiFiScanResultRemaining);
+
   if (mWiFiScanResultRemaining == 0) {
     mNextExpectedIndex = 0;
     mTestSuccessMarker.markStageAndSuccessOnFinish(
         BASIC_WIFI_TEST_STAGE_SCAN_ASYNC);
-    startRangingAsyncTestStage();
+    if (mWifiCapabilities & CHRE_WIFI_CAPABILITIES_SCAN_MONITORING) {
+      testConfigureScanMonitorAsync(false /* enable */,
+                                    &kDisableScanMonitoringCookie);
+      resetCurrentWifiRequest(&kDisableScanMonitoringCookie,
+                              CHRE_WIFI_REQUEST_TYPE_CONFIGURE_SCAN_MONITOR,
+                              CHRE_ASYNC_RESULT_TIMEOUT_NS);
+    } else {
+      mStartTimestampNs = chreGetTime();
+      startRangingAsyncTestStage();
+    }
   }
 }
 
@@ -542,7 +599,7 @@
     //       validations when proper error waiver is implemented in CHQTS.
     if (results[i].band != CHRE_WIFI_BAND_2_4_GHZ &&
         results[i].band != CHRE_WIFI_BAND_5_GHZ) {
-      chreLog(CHRE_LOG_ERROR, "Got unexpected band %d", results[i].band);
+      LOGE("Got unexpected band %d", results[i].band);
     }
 
     validateRssi(results[i].rssi);
@@ -564,9 +621,14 @@
 
   for (uint8_t i = 0; i < eventData->resultCount; i++) {
     auto &result = eventData->results[i];
-    ASSERT_IN_RANGE(result.timestamp, mStartTimestampNs, chreGetTime(),
-                    "Ranging result timestamp isn't between the ranging "
-                    "request start time and the current time");
+    auto currentTime = chreGetTime();
+    if (result.timestamp < mStartTimestampNs ||
+        result.timestamp > currentTime) {
+      LOGE("Invalid Ranging result timestamp = %" PRIu64 " (%" PRIu64
+           ", %" PRIu64 "). Status = %" PRIu8,
+           result.timestamp, mStartTimestampNs, currentTime, result.status);
+      sendFatalFailureToHost("Invalid ranging result timestamp");
+    }
 
     if (result.status != CHRE_WIFI_RANGING_STATUS_SUCCESS) {
       if (result.rssi != 0 || result.distance != 0 ||
@@ -604,8 +666,15 @@
 }
 
 bool BasicWifiTest::scanEventExpected() {
-  return mTestSuccessMarker.isStageMarked(BASIC_WIFI_TEST_STAGE_SCAN_MONITOR) &&
-         !mTestSuccessMarker.isStageMarked(BASIC_WIFI_TEST_STAGE_SCAN_ASYNC);
+  bool scanMonitoringFinished =
+      mTestSuccessMarker.isStageMarked(BASIC_WIFI_TEST_STAGE_SCAN_MONITOR);
+  bool onDemandScanFinished =
+      mTestSuccessMarker.isStageMarked(BASIC_WIFI_TEST_STAGE_SCAN_ASYNC);
+  if (mWifiCapabilities & CHRE_WIFI_CAPABILITIES_SCAN_MONITORING) {
+    return !scanMonitoringFinished && !onDemandScanFinished;
+  } else {
+    return scanMonitoringFinished && !onDemandScanFinished;
+  }
 }
 
 }  // namespace general_test
diff --git a/apps/test/chqts/src/general_test/basic_wifi_test.h b/apps/test/chqts/src/general_test/basic_wifi_test.h
index 5410da0..181bfee 100644
--- a/apps/test/chqts/src/general_test/basic_wifi_test.h
+++ b/apps/test/chqts/src/general_test/basic_wifi_test.h
@@ -76,6 +76,13 @@
   void handleChreWifiAsyncEvent(const chreAsyncResult *result);
 
   /**
+   * Processes the result and move to the next action accordingly.
+   *
+   * @param result chreAsyncResult of an async request.
+   */
+  void processChreWifiAsyncResult(const chreAsyncResult *result);
+
+  /**
    * @param eventData received WiFi scan event data.
    * @return true if scanType is CHRE_WIFI_SCAN_TYPE_ACTIVE, false otherwise.
    */
@@ -168,6 +175,12 @@
   //! The remaining results of WiFi scan.
   //! Used to identify when all events have been received.
   uint32_t mWiFiScanResultRemaining = 0;
+
+  // Number of retries remained when an on demand wifi scan fails.
+  uint8_t mNumScanRetriesRemaining = 1;
+
+  // The handle to identify the timer event for restarting a wifi scan.
+  uint32_t mScanTimeoutTimerHandle = CHRE_TIMER_INVALID;
 };
 
 }  // namespace general_test
diff --git a/apps/test/chqts/src/general_test/cell_info_cdma.h b/apps/test/chqts/src/general_test/cell_info_cdma.h
index c72d229..c9e5bbe 100644
--- a/apps/test/chqts/src/general_test/cell_info_cdma.h
+++ b/apps/test/chqts/src/general_test/cell_info_cdma.h
@@ -18,7 +18,7 @@
 
 #include <general_test/cell_info_base.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/cell_info_gsm.h b/apps/test/chqts/src/general_test/cell_info_gsm.h
index f42ecd9..e9ac22f 100644
--- a/apps/test/chqts/src/general_test/cell_info_gsm.h
+++ b/apps/test/chqts/src/general_test/cell_info_gsm.h
@@ -18,7 +18,7 @@
 
 #include <general_test/cell_info_base.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/cell_info_lte.h b/apps/test/chqts/src/general_test/cell_info_lte.h
index 020578c..db3e8d3 100644
--- a/apps/test/chqts/src/general_test/cell_info_lte.h
+++ b/apps/test/chqts/src/general_test/cell_info_lte.h
@@ -18,7 +18,7 @@
 
 #include <general_test/cell_info_base.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/cell_info_nr.cc b/apps/test/chqts/src/general_test/cell_info_nr.cc
index f776679..e0431db 100644
--- a/apps/test/chqts/src/general_test/cell_info_nr.cc
+++ b/apps/test/chqts/src/general_test/cell_info_nr.cc
@@ -17,6 +17,10 @@
 
 #include <cinttypes>
 
+#include <chre/util/nanoapp/log.h>
+
+#define LOG_TAG "[CellInfoNr]"
+
 namespace general_test {
 namespace {
 constexpr int32_t kInvalid = INT32_MAX;
@@ -32,8 +36,7 @@
     sendFatalFailureInt32("Invalid NR Mobile Network Code: %d", identity.mnc);
   } else if (!isBoundedInt64(chreWwanUnpackNrNci(&identity), 0, 68719476735,
                              INT64_MAX)) {
-    chreLog(CHRE_LOG_ERROR, "Invalid NR Cell Identity: %" PRId64,
-            chreWwanUnpackNrNci(&identity));
+    LOGE("Invalid NR Cell Identity: %" PRId64, chreWwanUnpackNrNci(&identity));
     sendFatalFailure("Invalid NR Cell Identity");
   } else if (!isBoundedInt32(identity.pci, 0, 1007, kInvalid,
                              false /* invalidAllowed*/)) {
diff --git a/apps/test/chqts/src/general_test/cell_info_nr.h b/apps/test/chqts/src/general_test/cell_info_nr.h
index e2eaf98..7ae3884 100644
--- a/apps/test/chqts/src/general_test/cell_info_nr.h
+++ b/apps/test/chqts/src/general_test/cell_info_nr.h
@@ -18,7 +18,7 @@
 
 #include <general_test/cell_info_base.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/cell_info_tdscdma.h b/apps/test/chqts/src/general_test/cell_info_tdscdma.h
index 7c874bf..baae1be 100644
--- a/apps/test/chqts/src/general_test/cell_info_tdscdma.h
+++ b/apps/test/chqts/src/general_test/cell_info_tdscdma.h
@@ -18,7 +18,7 @@
 
 #include <general_test/cell_info_base.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/cell_info_wcdma.h b/apps/test/chqts/src/general_test/cell_info_wcdma.h
index 2648318..6e0d18d 100644
--- a/apps/test/chqts/src/general_test/cell_info_wcdma.h
+++ b/apps/test/chqts/src/general_test/cell_info_wcdma.h
@@ -18,7 +18,7 @@
 
 #include <general_test/cell_info_base.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/estimated_host_time_test.cc b/apps/test/chqts/src/general_test/estimated_host_time_test.cc
index 7d01ed8..0e70532 100644
--- a/apps/test/chqts/src/general_test/estimated_host_time_test.cc
+++ b/apps/test/chqts/src/general_test/estimated_host_time_test.cc
@@ -20,7 +20,7 @@
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
@@ -44,39 +44,17 @@
   }
 }
 
-void EstimatedHostTimeTest::handleEvent(uint32_t senderInstanceId,
+void EstimatedHostTimeTest::handleEvent(uint32_t /* senderInstanceId */,
                                         uint16_t eventType,
-                                        const void *eventData) {
+                                        const void * /* eventData */) {
   if (eventType == CHRE_EVENT_TIMER) {
     verifyIncreasingTime();
   } else {
-    // Verify application processor time is within reason
+    // Send the estimated host time to the AP for validation.
     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));
-    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");
-    }
+    nanoapp_testing::sendMessageToHost(nanoapp_testing::MessageType::kContinue,
+                                       &currentHostTime,
+                                       sizeof(currentHostTime));
   }
 }
 
diff --git a/apps/test/chqts/src/general_test/estimated_host_time_test.h b/apps/test/chqts/src/general_test/estimated_host_time_test.h
index 675d98c..5999845 100644
--- a/apps/test/chqts/src/general_test/estimated_host_time_test.h
+++ b/apps/test/chqts/src/general_test/estimated_host_time_test.h
@@ -28,13 +28,11 @@
  *
  * 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
+ *   2) Help verify that AP time is "close" to what nanoapp can get.
+ *      Timestamps are sent to the host when a message is received by
+ *      the test. The host then passes or fails the test depending on
+ *      how close the timestamps are after accounting for the round
+ *      trip time.
  */
 class EstimatedHostTimeTest : public Test {
  public:
diff --git a/apps/test/chqts/src/general_test/event_between_apps_test.cc b/apps/test/chqts/src/general_test/event_between_apps_test.cc
index 848e34d..2dda072 100644
--- a/apps/test/chqts/src/general_test/event_between_apps_test.cc
+++ b/apps/test/chqts/src/general_test/event_between_apps_test.cc
@@ -25,7 +25,7 @@
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 using nanoapp_testing::MessageType;
 using nanoapp_testing::sendFatalFailureToHost;
diff --git a/apps/test/chqts/src/general_test/get_time_test.cc b/apps/test/chqts/src/general_test/get_time_test.cc
index 76aeaa6..ea780f6 100644
--- a/apps/test/chqts/src/general_test/get_time_test.cc
+++ b/apps/test/chqts/src/general_test/get_time_test.cc
@@ -22,7 +22,7 @@
 #include <shared/nano_endian.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 using nanoapp_testing::MessageType;
 using nanoapp_testing::sendFatalFailureToHost;
diff --git a/apps/test/chqts/src/general_test/gnss_capabilities_test.cc b/apps/test/chqts/src/general_test/gnss_capabilities_test.cc
index ad45b5b..635b82e 100644
--- a/apps/test/chqts/src/general_test/gnss_capabilities_test.cc
+++ b/apps/test/chqts/src/general_test/gnss_capabilities_test.cc
@@ -15,10 +15,10 @@
  */
 #include <general_test/gnss_capabilities_test.h>
 
-#include <chre.h>
-
 #include <shared/send_message.h>
 
+#include "chre_api/chre.h"
+
 namespace general_test {
 
 GnssCapabilitiesTest::GnssCapabilitiesTest() : Test(CHRE_API_VERSION_1_1) {}
diff --git a/apps/test/chqts/src/general_test/heap_alloc_stress_test.cc b/apps/test/chqts/src/general_test/heap_alloc_stress_test.cc
index c6aff93..cd5b595 100644
--- a/apps/test/chqts/src/general_test/heap_alloc_stress_test.cc
+++ b/apps/test/chqts/src/general_test/heap_alloc_stress_test.cc
@@ -22,7 +22,7 @@
 #include <shared/abort.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 using nanoapp_testing::sendFailureToHost;
 using nanoapp_testing::sendFatalFailureToHost;
diff --git a/apps/test/chqts/src/general_test/heap_exhaustion_stability_test.cc b/apps/test/chqts/src/general_test/heap_exhaustion_stability_test.cc
index 04b9c3e..5bc7cd9 100644
--- a/apps/test/chqts/src/general_test/heap_exhaustion_stability_test.cc
+++ b/apps/test/chqts/src/general_test/heap_exhaustion_stability_test.cc
@@ -22,7 +22,10 @@
 #include <shared/send_message.h>
 #include <shared/time_util.h>
 
-#include <chre.h>
+#include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[HeapExhaustionStabilityTest]"
 
 using nanoapp_testing::kOneMillisecondInNanoseconds;
 using nanoapp_testing::kOneSecondInNanoseconds;
@@ -136,7 +139,7 @@
 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);
+  LOGI("Test log %s, zero: %" PRId32, "message", zero);
 }
 
 void HeapExhaustionStabilityTest::testSetTimer() {
@@ -262,7 +265,7 @@
 }
 
 void HeapExhaustionStabilityTest::markSuccess(uint32_t stage) {
-  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  LOGD("Stage %" PRIu32 " succeeded", stage);
   uint32_t finishedBit = (1 << stage);
   if ((kAllFinished & finishedBit) == 0) {
     sendFatalFailureToHost("markSuccess bad stage", &stage);
diff --git a/apps/test/chqts/src/general_test/host_awake_suspend_test.cc b/apps/test/chqts/src/general_test/host_awake_suspend_test.cc
index 129483c..2a7ad93 100644
--- a/apps/test/chqts/src/general_test/host_awake_suspend_test.cc
+++ b/apps/test/chqts/src/general_test/host_awake_suspend_test.cc
@@ -16,9 +16,10 @@
 
 #include <general_test/host_awake_suspend_test.h>
 
-#include <chre.h>
 #include <shared/send_message.h>
 
+#include "chre_api/chre.h"
+
 /*
  * Test to check expected functionality of host awake/suspend APIs.
  */
diff --git a/apps/test/chqts/src/general_test/host_awake_suspend_test.h b/apps/test/chqts/src/general_test/host_awake_suspend_test.h
index f415e52..23f4c53 100644
--- a/apps/test/chqts/src/general_test/host_awake_suspend_test.h
+++ b/apps/test/chqts/src/general_test/host_awake_suspend_test.h
@@ -20,7 +20,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/logging_consistency_test.cc b/apps/test/chqts/src/general_test/logging_consistency_test.cc
index 8d0c91d..3b99ab1 100644
--- a/apps/test/chqts/src/general_test/logging_consistency_test.cc
+++ b/apps/test/chqts/src/general_test/logging_consistency_test.cc
@@ -21,10 +21,13 @@
 
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include <chre/util/nanoapp/log.h>
 
 #include "chre/util/macros.h"
 #include "chre/util/toolchain.h"
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[LoggingConsistencyTest]"
 
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
@@ -42,47 +45,42 @@
   }
 
   // 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;
+  LOGE("Level: Error");
+  LOGW("Level: Warn");
+  LOGI("Level: Info");
+  LOGD("Level: Debug");
 
   // Empty string
   char emptyString[1] = {'\0'};
-  chreLog(kInfo, "%s", emptyString);
+  LOGI("%s", emptyString);
 
   // 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);
+  LOGI("%d", 1);
+  LOGI("%d %d", 1, 2);
+  LOGI("%d %d %d", 1, 2, 3);
+  LOGI("%d %d %d %d", 1, 2, 3, 4);
+  LOGI("%d %d %d %d %d", 1, 2, 3, 4, 5);
+  LOGI("%d %d %d %d %d %d", 1, 2, 3, 4, 5, 6);
+  LOGI("%d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7);
+  LOGI("%d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8);
+  LOGI("%d %d %d %d %d %d %d %d %d", 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  LOGI("%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);
+  LOGI("%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.
+  // as the LOGI() 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);                      \
+#define INT_TYPES(kPrefix, type)                                  \
+  {                                                               \
+    type value = std::numeric_limits<type>::min();                \
+    LOGI("%" kPrefix "d %" kPrefix "u 0%" kPrefix "o 0x%" kPrefix \
+         "x 0x%" kPrefix "X",                                     \
+         value, value, value, value, value);                      \
   }
 
   INT_TYPES("hh", char);
@@ -97,27 +95,27 @@
 
   float f = 12.34f;
   // Other required formats, including escaping the '%'.
-  chreLog(kInfo, "%% %f %c %s %p", f, '?', "str", &f);
+  LOGI("%% %f %c %s %p", f, '?', "str", &f);
 
-  // OPTIONAL specifiers.  See chreLog() API documentation for extensive
+  // OPTIONAL specifiers.  See LOGI() API documentation for extensive
   // discussion of what OPTIONAL means.
   // <width> and '-'
-  chreLog(kInfo, "(%5s) (%-5s) (%5d) (%-5d)", "str", "str", 10, 10);
+  LOGI("(%5s) (%-5s) (%5d) (%-5d)", "str", "str", 10, 10);
   // '+'
-  chreLog(kInfo, "(%+d) (%+d) (%+f) (%+f)", -5, 5, -5.f, 5.f);
+  LOGI("(%+d) (%+d) (%+f) (%+f)", -5, 5, -5.f, 5.f);
   // ' '
-  chreLog(kInfo, "(% d) (% d) (% f) (% f)", -5, 5, -5.f, 5.f);
+  LOGI("(% d) (% d) (% f) (% f)", -5, 5, -5.f, 5.f);
   // '#'
-  chreLog(kInfo, "%#o %#x %#X %#f", 8, 15, 15, 1.f);
+  LOGI("%#o %#x %#X %#f", 8, 15, 15, 1.f);
   // '0' padding
-  chreLog(kInfo, "%08d 0x%04x", 123, 0xF);
+  LOGI("%08d 0x%04x", 123, 0xF);
   // '.'<precision>
-  chreLog(kInfo, "%.3d %.3d %.3f %.3f %.3s", 12, 1234, 1.5, 1.0625, "abcdef");
+  LOGI("%.3d %.3d %.3f %.3f %.3s", 12, 1234, 1.5, 1.0625, "abcdef");
 
   // Re-enable logging-related double warnings
   CHRE_LOG_EPILOGUE
 
-  // TODO: In some future Android release, when chreLog() is required to
+  // TODO: In some future Android release, when LOGI() 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.
diff --git a/apps/test/chqts/src/general_test/logging_consistency_test.h b/apps/test/chqts/src/general_test/logging_consistency_test.h
index 8689345..234ca5b 100644
--- a/apps/test/chqts/src/general_test/logging_consistency_test.h
+++ b/apps/test/chqts/src/general_test/logging_consistency_test.h
@@ -22,12 +22,12 @@
 namespace general_test {
 
 /**
- * Invokes chreLog() in a variety of ways.
+ * Invokes LOGI() 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
+ * LOGI() messages go for this platform to confirm the contents look
  * correct.
  *
  * Simple protocol.
diff --git a/apps/test/chqts/src/general_test/nanoapp_info.cc b/apps/test/chqts/src/general_test/nanoapp_info.cc
index 8bde6a3..bd3972e 100644
--- a/apps/test/chqts/src/general_test/nanoapp_info.cc
+++ b/apps/test/chqts/src/general_test/nanoapp_info.cc
@@ -19,7 +19,7 @@
 #include <shared/nano_endian.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/nanoapp_info.h b/apps/test/chqts/src/general_test/nanoapp_info.h
index 056bf53..e95a57c 100644
--- a/apps/test/chqts/src/general_test/nanoapp_info.h
+++ b/apps/test/chqts/src/general_test/nanoapp_info.h
@@ -19,7 +19,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/nanoapp_info_by_app_id_test.cc b/apps/test/chqts/src/general_test/nanoapp_info_by_app_id_test.cc
index 77ac5d2..bc115de 100644
--- a/apps/test/chqts/src/general_test/nanoapp_info_by_app_id_test.cc
+++ b/apps/test/chqts/src/general_test/nanoapp_info_by_app_id_test.cc
@@ -22,7 +22,7 @@
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/nanoapp_info_by_instance_id_test.cc b/apps/test/chqts/src/general_test/nanoapp_info_by_instance_id_test.cc
index e381561..d505b68 100644
--- a/apps/test/chqts/src/general_test/nanoapp_info_by_instance_id_test.cc
+++ b/apps/test/chqts/src/general_test/nanoapp_info_by_instance_id_test.cc
@@ -22,7 +22,7 @@
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/nanoapp_info_events_test_observer.h b/apps/test/chqts/src/general_test/nanoapp_info_events_test_observer.h
index 21705f1..672b298 100644
--- a/apps/test/chqts/src/general_test/nanoapp_info_events_test_observer.h
+++ b/apps/test/chqts/src/general_test/nanoapp_info_events_test_observer.h
@@ -21,7 +21,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/running_info.h b/apps/test/chqts/src/general_test/running_info.h
index f6b6e80..96f8115 100644
--- a/apps/test/chqts/src/general_test/running_info.h
+++ b/apps/test/chqts/src/general_test/running_info.h
@@ -21,7 +21,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/send_event_stress_test.cc b/apps/test/chqts/src/general_test/send_event_stress_test.cc
index 02e5321..6bc7e63 100644
--- a/apps/test/chqts/src/general_test/send_event_stress_test.cc
+++ b/apps/test/chqts/src/general_test/send_event_stress_test.cc
@@ -20,7 +20,7 @@
 
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
diff --git a/apps/test/chqts/src/general_test/send_event_test.cc b/apps/test/chqts/src/general_test/send_event_test.cc
index 4c3cd14..132f2f5 100644
--- a/apps/test/chqts/src/general_test/send_event_test.cc
+++ b/apps/test/chqts/src/general_test/send_event_test.cc
@@ -22,7 +22,7 @@
 #include <shared/array_length.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
diff --git a/apps/test/chqts/src/general_test/send_message_to_host_test.cc b/apps/test/chqts/src/general_test/send_message_to_host_test.cc
index d2fe799..e54d1b5 100644
--- a/apps/test/chqts/src/general_test/send_message_to_host_test.cc
+++ b/apps/test/chqts/src/general_test/send_message_to_host_test.cc
@@ -23,10 +23,13 @@
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
-
+#include <chre/util/nanoapp/log.h>
 #include "chre/util/toolchain.h"
 
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[SendMessageToHostTest]"
+
 using nanoapp_testing::MessageType;
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendInternalFailureToHost;
@@ -195,7 +198,7 @@
 }
 
 void SendMessageToHostTest::markSuccess(uint32_t stage) {
-  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  LOGD("Stage %" PRIu32 " succeeded", stage);
   uint32_t finishedBit = (1 << stage);
   if (sFinishedBitmask & finishedBit) {
     sendFatalFailureToHost("callback called multiple times for stage:", &stage);
diff --git a/apps/test/chqts/src/general_test/send_message_to_host_test.h b/apps/test/chqts/src/general_test/send_message_to_host_test.h
index 73d22b8..117c9da 100644
--- a/apps/test/chqts/src/general_test/send_message_to_host_test.h
+++ b/apps/test/chqts/src/general_test/send_message_to_host_test.h
@@ -19,7 +19,7 @@
 
 #include <general_test/test.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/sensor_info_test.h b/apps/test/chqts/src/general_test/sensor_info_test.h
index 452801b..942ac3e 100644
--- a/apps/test/chqts/src/general_test/sensor_info_test.h
+++ b/apps/test/chqts/src/general_test/sensor_info_test.h
@@ -20,7 +20,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/simple_heap_alloc_test.cc b/apps/test/chqts/src/general_test/simple_heap_alloc_test.cc
index 235657b..76e4d9e 100644
--- a/apps/test/chqts/src/general_test/simple_heap_alloc_test.cc
+++ b/apps/test/chqts/src/general_test/simple_heap_alloc_test.cc
@@ -24,7 +24,7 @@
 #include <shared/nano_string.h>
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 using nanoapp_testing::MessageType;
 using nanoapp_testing::sendFatalFailureToHost;
@@ -145,7 +145,7 @@
     // 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
+    // TODO: If we convince ourselves that LOGI() 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]);
diff --git a/apps/test/chqts/src/general_test/test.cc b/apps/test/chqts/src/general_test/test.cc
index 3d27254..761d700 100644
--- a/apps/test/chqts/src/general_test/test.cc
+++ b/apps/test/chqts/src/general_test/test.cc
@@ -20,7 +20,11 @@
 #include <shared/send_message.h>
 #include <shared/time_util.h>
 
-#include <chre.h>
+#include <chre/util/nanoapp/log.h>
+
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[Test]"
 
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendFatalFailureToHostUint8;
@@ -67,13 +71,12 @@
                                 result->reserved);
   }
   if (result->cookie != request.cookie) {
-    chreLog(CHRE_LOG_ERROR, "Request cookie is %p, got %p", request.cookie,
-            result->cookie);
+    LOGE("Request cookie is %p, got %p", request.cookie, result->cookie);
     sendFatalFailureToHost("Request cookie mismatch");
   }
   if (result->requestType != request.requestType) {
-    chreLog(CHRE_LOG_ERROR, "Request requestType is %d, got %d",
-            request.requestType, result->requestType);
+    LOGE("Request requestType is %d, got %d", request.requestType,
+         result->requestType);
     sendFatalFailureToHost("Request requestType mismatch");
   }
   if (chreGetTime() - request.requestTimeNs > request.timeoutNs) {
diff --git a/apps/test/chqts/src/general_test/test.h b/apps/test/chqts/src/general_test/test.h
index ce021c6..88bdc5c 100644
--- a/apps/test/chqts/src/general_test/test.h
+++ b/apps/test/chqts/src/general_test/test.h
@@ -19,7 +19,7 @@
 
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/general_test/test_names.h b/apps/test/chqts/src/general_test/test_names.h
index 4f6d228..23828fe 100644
--- a/apps/test/chqts/src/general_test/test_names.h
+++ b/apps/test/chqts/src/general_test/test_names.h
@@ -227,6 +227,11 @@
    * Test: SensorFlushAsyncTest
    */
   kBasicSensorFlushAsyncTest = 0x0426,
+
+  /**
+   * Test: BasicBleTest
+   */
+  kBasicBleTest = 0x0427,
 };
 
 }  // namespace general_test
diff --git a/apps/test/chqts/src/general_test/timer_cancel_test.cc b/apps/test/chqts/src/general_test/timer_cancel_test.cc
index 29af9dc..846fdaf 100644
--- a/apps/test/chqts/src/general_test/timer_cancel_test.cc
+++ b/apps/test/chqts/src/general_test/timer_cancel_test.cc
@@ -22,7 +22,11 @@
 #include <shared/send_message.h>
 #include <shared/time_util.h>
 
-#include <chre.h>
+#include <chre/util/nanoapp/log.h>
+
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[TimerCancelTest]"
 
 using nanoapp_testing::kOneMillisecondInNanoseconds;
 using nanoapp_testing::sendFatalFailureToHost;
@@ -151,7 +155,7 @@
 }
 
 void TimerCancelTest::markSuccess(uint32_t stage) {
-  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  LOGD("Stage %" PRIu32 " succeeded", stage);
   uint32_t finishedBit = (1 << stage);
   if ((kAllFinished & finishedBit) == 0) {
     sendFatalFailureToHost("markSuccess bad stage:", &stage);
diff --git a/apps/test/chqts/src/general_test/timer_cancel_test.h b/apps/test/chqts/src/general_test/timer_cancel_test.h
index f1ab01c..c76d332 100644
--- a/apps/test/chqts/src/general_test/timer_cancel_test.h
+++ b/apps/test/chqts/src/general_test/timer_cancel_test.h
@@ -17,9 +17,10 @@
 #ifndef _GTS_NANOAPPS_GENERAL_TEST_TIMER_CANCEL_TEST_H_
 #define _GTS_NANOAPPS_GENERAL_TEST_TIMER_CANCEL_TEST_H_
 
-#include <chre.h>
 #include <general_test/test.h>
 
+#include "chre_api/chre.h"
+
 namespace general_test {
 
 /**
diff --git a/apps/test/chqts/src/general_test/timer_set_test.cc b/apps/test/chqts/src/general_test/timer_set_test.cc
index 7e3a799..720ab69 100644
--- a/apps/test/chqts/src/general_test/timer_set_test.cc
+++ b/apps/test/chqts/src/general_test/timer_set_test.cc
@@ -23,7 +23,11 @@
 #include <shared/send_message.h>
 #include <shared/time_util.h>
 
-#include <chre.h>
+#include <chre/util/nanoapp/log.h>
+
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[TimerSetTest]"
 
 using nanoapp_testing::kOneMillisecondInNanoseconds;
 using nanoapp_testing::kOneSecondInNanoseconds;
@@ -179,7 +183,7 @@
 }
 
 void TimerSetTest::markSuccess(uint32_t stage) {
-  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  LOGD("Stage %" PRIu32 " succeeded", stage);
   uint32_t finishedBit = (1 << stage);
   if ((kAllFinished & finishedBit) == 0) {
     sendFatalFailureToHost("markSuccess bad stage", &stage);
diff --git a/apps/test/chqts/src/general_test/timer_stress_test.cc b/apps/test/chqts/src/general_test/timer_stress_test.cc
index 34650ad..4d65c4d 100644
--- a/apps/test/chqts/src/general_test/timer_stress_test.cc
+++ b/apps/test/chqts/src/general_test/timer_stress_test.cc
@@ -21,7 +21,11 @@
 
 #include <shared/send_message.h>
 
-#include <chre.h>
+#include <chre/util/nanoapp/log.h>
+
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[TimerStressTest]"
 
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendInternalFailureToHost;
@@ -154,7 +158,7 @@
 }
 
 void TimerStressTest::markSuccess(uint32_t stage) {
-  chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+  LOGD("Stage %" PRIu32 " succeeded", stage);
   uint32_t finishedBit = (1 << stage);
   if ((kAllFinished & finishedBit) == 0) {
     sendFatalFailureToHost("markSuccess bad stage:", &stage);
diff --git a/apps/test/chqts/src/general_test/version_consistency_test.cc b/apps/test/chqts/src/general_test/version_consistency_test.cc
index 3ce45df..2a367ec 100644
--- a/apps/test/chqts/src/general_test/version_consistency_test.cc
+++ b/apps/test/chqts/src/general_test/version_consistency_test.cc
@@ -20,9 +20,8 @@
 
 #include <shared/send_message.h>
 
-#include <chre.h>
-
 #include "chre/util/macros.h"
+#include "chre_api/chre.h"
 
 using nanoapp_testing::sendFatalFailureToHost;
 using nanoapp_testing::sendSuccessToHost;
diff --git a/apps/test/chqts/src/general_test/wifi_capabilities_test.cc b/apps/test/chqts/src/general_test/wifi_capabilities_test.cc
index 9bfd741..208641a 100644
--- a/apps/test/chqts/src/general_test/wifi_capabilities_test.cc
+++ b/apps/test/chqts/src/general_test/wifi_capabilities_test.cc
@@ -15,9 +15,8 @@
  */
 #include <general_test/wifi_capabilities_test.h>
 
-#include <chre.h>
-
 #include <shared/send_message.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
@@ -39,6 +38,9 @@
       allCapabilities |= CHRE_WIFI_CAPABILITIES_RADIO_CHAIN_PREF |
                          CHRE_WIFI_CAPABILITIES_RTT_RANGING;
     }
+    if (mApiVersion >= CHRE_API_VERSION_1_6) {
+      allCapabilities |= CHRE_WIFI_CAPABILITIES_NAN_SUB;
+    }
 
     // Call the new API
     uint32_t capabilities = chreWifiGetCapabilities();
diff --git a/apps/test/chqts/src/general_test/wwan_capabilities_test.cc b/apps/test/chqts/src/general_test/wwan_capabilities_test.cc
index 58e9767..b0557eb 100644
--- a/apps/test/chqts/src/general_test/wwan_capabilities_test.cc
+++ b/apps/test/chqts/src/general_test/wwan_capabilities_test.cc
@@ -15,10 +15,10 @@
  */
 #include <general_test/wwan_capabilities_test.h>
 
-#include <chre.h>
-
 #include <shared/send_message.h>
 
+#include "chre_api/chre.h"
+
 namespace general_test {
 
 WwanCapabilitiesTest::WwanCapabilitiesTest() : Test(CHRE_API_VERSION_1_1) {}
diff --git a/apps/test/chqts/src/general_test/wwan_cell_info_test.h b/apps/test/chqts/src/general_test/wwan_cell_info_test.h
index 5604120..25b3e57 100644
--- a/apps/test/chqts/src/general_test/wwan_cell_info_test.h
+++ b/apps/test/chqts/src/general_test/wwan_cell_info_test.h
@@ -20,7 +20,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace general_test {
 
diff --git a/apps/test/chqts/src/shared/abort.cc b/apps/test/chqts/src/shared/abort.cc
index fc142a1..6efb913 100644
--- a/apps/test/chqts/src/shared/abort.cc
+++ b/apps/test/chqts/src/shared/abort.cc
@@ -18,7 +18,7 @@
 
 #include <cstdint>
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace nanoapp_testing {
 
diff --git a/apps/test/chqts/src/shared/nano_endian_be_test.cc b/apps/test/chqts/src/shared/nano_endian_be_test.cc
index bbee22c..ea39fe5 100644
--- a/apps/test/chqts/src/shared/nano_endian_be_test.cc
+++ b/apps/test/chqts/src/shared/nano_endian_be_test.cc
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+
 // For this test, we pretend we're a big endian platform, even if we don't
 // happen to be.
+#undef __LITTLE_ENDIAN
+#undef __BIG_ENDIAN
+#undef __BYTE_ORDER
 #define CHRE_NO_ENDIAN_H 1
 #define __LITTLE_ENDIAN 0
 #define __BIG_ENDIAN 1
@@ -23,9 +30,6 @@
 
 #include <shared/nano_endian.h>
 
-#include <cstdint>
-#include <cstring>
-
 #include <gtest/gtest.h>
 #include <shared/array_length.h>
 
diff --git a/apps/test/chqts/src/shared/send_message.cc b/apps/test/chqts/src/shared/send_message.cc
index 56ec3b7..7597236 100644
--- a/apps/test/chqts/src/shared/send_message.cc
+++ b/apps/test/chqts/src/shared/send_message.cc
@@ -23,7 +23,11 @@
 #include <shared/nano_endian.h>
 #include <shared/nano_string.h>
 
-#include <chre.h>
+#include <chre/util/nanoapp/log.h>
+
+#include "chre_api/chre.h"
+
+#define LOG_TAG "[SendMessage]"
 
 namespace nanoapp_testing {
 
@@ -162,15 +166,15 @@
   internalSendMessage(messageType, fullMessage, fullMessageLen, ChunkAlloc);
 }
 
-// Before we abort the nanoapp, we also put this message in the chreLog().
+// Before we abort the nanoapp, we also put this message in the LOGI().
 // 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);
+    LOGE("TEST ABORT: %s0x%08" PRIX32, message, *value);
   } else {
-    chreLog(CHRE_LOG_ERROR, "TEST ABORT: %s", message);
+    LOGE("TEST ABORT: %s", message);
   }
 }
 
diff --git a/apps/test/chqts/src/shared/test_success_marker.cc b/apps/test/chqts/src/shared/test_success_marker.cc
index 30165c2..2719fcb 100644
--- a/apps/test/chqts/src/shared/test_success_marker.cc
+++ b/apps/test/chqts/src/shared/test_success_marker.cc
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
+#include <chre/util/nanoapp/log.h>
+#include <shared/send_message.h>
 #include <shared/test_success_marker.h>
 
-#include <chre/re.h>
-#include <shared/send_message.h>
+#define LOG_TAG "[SuccessMarkerTest]"
 
 namespace nanoapp_testing {
 
@@ -35,7 +36,7 @@
     sendFatalFailureToHost("markSuccess invalid stage", &stage);
   }
   if ((mFinishedBitmask & finishedBit) == 0) {
-    chreLog(CHRE_LOG_DEBUG, "Stage %" PRIu32 " succeeded", stage);
+    LOGD("Stage %" PRIu32 " succeeded", stage);
     mFinishedBitmask |= finishedBit;
   }
 }
diff --git a/apps/test/chqts/src/who_am_i/who_am_i.cc b/apps/test/chqts/src/who_am_i/who_am_i.cc
index 0ff127b..00c4e6c 100644
--- a/apps/test/chqts/src/who_am_i/who_am_i.cc
+++ b/apps/test/chqts/src/who_am_i/who_am_i.cc
@@ -25,9 +25,10 @@
 #include <cstdint>
 #include <cstring>
 
-#include <chre.h>
 #include <shared/send_message.h>
 
+#include "chre_api/chre.h"
+
 using nanoapp_testing::sendFatalFailureToHost;
 
 namespace chre {
diff --git a/apps/test/common/chre_api_test/Makefile b/apps/test/common/chre_api_test/Makefile
new file mode 100644
index 0000000..6c0a4b1
--- /dev/null
+++ b/apps/test/common/chre_api_test/Makefile
@@ -0,0 +1,23 @@
+#
+# CHRE API Test Nanoapp Makefile
+#
+
+# Makefile Includes ############################################################
+
+include chre_api_test.mk
+
+# Nanoapp Configuration ########################################################
+
+NANOAPP_NAME = chre_api_test
+NANOAPP_ID = 0x476f6f675400000d
+NANOAPP_NAME_STRING = \"CHRE\ API\ Test\"
+NANOAPP_VERSION = 0x00000001
+
+# Compiler Flags ###############################################################
+
+# Defines
+COMMON_CFLAGS += -DLOG_TAG=\"[ChreApiTest]\"
+
+# Makefile Includes ############################################################
+
+include $(CHRE_PREFIX)/build/nanoapp/app.mk
diff --git a/apps/test/common/chre_api_test/chre_api_test.mk b/apps/test/common/chre_api_test/chre_api_test.mk
new file mode 100644
index 0000000..5f8559f
--- /dev/null
+++ b/apps/test/common/chre_api_test/chre_api_test.mk
@@ -0,0 +1,46 @@
+#
+# CHRE API Test Nanoapp Makefile
+#
+# Environment Checks ###########################################################
+ifeq ($(CHRE_PREFIX),)
+  ifneq ($(ANDROID_BUILD_TOP),)
+    CHRE_PREFIX = $(ANDROID_BUILD_TOP)/system/chre
+  else
+    $(error "You must run 'lunch' to setup ANDROID_BUILD_TOP, or explicitly \
+    define the CHRE_PREFIX environment variable to point to the CHRE root \
+    directory.")
+  endif
+endif
+
+# Nanoapp Configuration ########################################################
+
+NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/chre_api_test
+
+# Source Code ##################################################################
+
+COMMON_SRCS += $(NANOAPP_PATH)/src/chre_api_test_manager.cc
+COMMON_SRCS += $(NANOAPP_PATH)/src/chre_api_test_service.cc
+COMMON_SRCS += $(NANOAPP_PATH)/src/chre_api_test.cc
+
+# Utilities ####################################################################
+
+COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/ble.cc
+
+# Compiler Flags ###############################################################
+
+# Defines
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
+COMMON_CFLAGS += -DPB_FIELD_16BIT
+COMMON_CFLAGS += -fno-threadsafe-statics
+
+# Includes
+COMMON_CFLAGS += -I$(NANOAPP_PATH)/inc
+
+# Permission declarations ######################################################
+
+CHRE_NANOAPP_USES_BLE = true
+
+# PW RPC protos ################################################################
+
+PW_RPC_SRCS = $(NANOAPP_PATH)/rpc/chre_api_test.proto
diff --git a/apps/test/common/chre_api_test/inc/chre_api_test_manager.h b/apps/test/common/chre_api_test/inc/chre_api_test_manager.h
new file mode 100644
index 0000000..8380e8a
--- /dev/null
+++ b/apps/test/common/chre_api_test/inc/chre_api_test_manager.h
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2022 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_API_TEST_MANAGER_H_
+#define CHRE_API_TEST_MANAGER_H_
+
+#include <cinttypes>
+#include <cstdint>
+
+#include "chre/re.h"
+#include "chre/util/pigweed/rpc_server.h"
+#include "chre/util/singleton.h"
+#include "chre_api/chre.h"
+#include "chre_api_test.rpc.pb.h"
+
+using ::chre::Optional;
+
+/**
+ * Contains signature-generated RPC functions for the ChreApiTest service.
+ */
+class ChreApiTestService final
+    : public chre::rpc::pw_rpc::nanopb::ChreApiTestService::Service<
+          ChreApiTestService> {
+ public:
+  /**
+   * Returns the BLE capabilities.
+   */
+  pw::Status ChreBleGetCapabilities(const chre_rpc_Void &request,
+                                    chre_rpc_Capabilities &response);
+
+  /**
+   * Returns the BLE filter capabilities.
+   */
+  pw::Status ChreBleGetFilterCapabilities(const chre_rpc_Void &request,
+                                          chre_rpc_Capabilities &response);
+
+  /**
+   * Starts a BLE scan.
+   */
+  pw::Status ChreBleStartScanAsync(
+      const chre_rpc_ChreBleStartScanAsyncInput &request,
+      chre_rpc_Status &response);
+
+  /**
+   * Stops a BLE scan.
+   */
+  pw::Status ChreBleStopScanAsync(const chre_rpc_Void &request,
+                                  chre_rpc_Status &response);
+
+  /**
+   * Finds the default sensor and returns the handle in the output.
+   */
+  pw::Status ChreSensorFindDefault(
+      const chre_rpc_ChreSensorFindDefaultInput &request,
+      chre_rpc_ChreSensorFindDefaultOutput &response);
+
+  /**
+   * Gets the sensor information.
+   */
+  pw::Status ChreGetSensorInfo(const chre_rpc_ChreHandleInput &request,
+                               chre_rpc_ChreGetSensorInfoOutput &response);
+
+  /**
+   * Gets the sensor sampling status for a given sensor.
+   */
+  pw::Status ChreGetSensorSamplingStatus(
+      const chre_rpc_ChreHandleInput &request,
+      chre_rpc_ChreGetSensorSamplingStatusOutput &response);
+
+  /**
+   * Configures a given sensor.
+   */
+  pw::Status ChreSensorConfigure(
+      const chre_rpc_ChreSensorConfigureInput &request,
+      chre_rpc_Status &response);
+
+  /**
+   * Configures the mode for a sensor.
+   */
+  pw::Status ChreSensorConfigureModeOnly(
+      const chre_rpc_ChreSensorConfigureModeOnlyInput &request,
+      chre_rpc_Status &response);
+
+  /**
+   * Gets the audio source information.
+   */
+  pw::Status ChreAudioGetSource(const chre_rpc_ChreHandleInput &request,
+                                chre_rpc_ChreAudioGetSourceOutput &response);
+
+  /**
+   * Configures host endpoint notification.
+   */
+  pw::Status ChreConfigureHostEndpointNotifications(
+      const chre_rpc_ChreConfigureHostEndpointNotificationsInput &request,
+      chre_rpc_Status &response);
+
+  /**
+   * Retrieve the last host endpoint notification.
+   */
+  pw::Status RetrieveLatestDisconnectedHostEndpointEvent(
+      const chre_rpc_Void &request,
+      chre_rpc_RetrieveLatestDisconnectedHostEndpointEventOutput &response);
+
+  /**
+   * Gets the host endpoint info for a given host endpoint id.
+   */
+  pw::Status ChreGetHostEndpointInfo(
+      const chre_rpc_ChreGetHostEndpointInfoInput &request,
+      chre_rpc_ChreGetHostEndpointInfoOutput &response);
+
+  /**
+   * Starts a BLE scan synchronously. Waits for the CHRE_EVENT_BLE_ASYNC_RESULT
+   * event.
+   */
+  void ChreBleStartScanSync(const chre_rpc_ChreBleStartScanAsyncInput &request,
+                            ServerWriter<chre_rpc_GeneralSyncMessage> &writer);
+
+  /**
+   * Stops a BLE scan synchronously. Waits for the CHRE_EVENT_BLE_ASYNC_RESULT
+   * event.
+   */
+  void ChreBleStopScanSync(const chre_rpc_Void &request,
+                           ServerWriter<chre_rpc_GeneralSyncMessage> &writer);
+
+  /**
+   * Gathers events that match the input filter before the timeout in ns or
+   * the max event count.
+   */
+  void GatherEvents(const chre_rpc_GatherEventsInput &request,
+                    ServerWriter<chre_rpc_GeneralEventsMessage> &writer);
+
+  /**
+   * Handles a BLE event from CHRE.
+   *
+   * @param result              the event result.
+   */
+  void handleBleAsyncResult(const chreAsyncResult *result);
+
+  /**
+   * Gathers the event if there is an existing event writer.
+   *
+   * @param eventType           the event type.
+   * @param eventData           the event data.
+   */
+  void handleGatheringEvent(uint16_t eventType, const void *eventData);
+
+  /**
+   * Handles a timer event from CHRE.
+   *
+   * @param cookie              the cookie from the event.
+   */
+  void handleTimerEvent(const void *cookie);
+
+  /**
+   * Handles host endpoint notification event from CHRE.
+   *
+   * @param data                the data from event.
+   */
+  void handleHostEndpointNotificationEvent(
+      const chreHostEndpointNotification *data);
+
+ private:
+  /**
+   * Copies a string from source to destination up to the length of the source
+   * or the max value. Pads with null characters.
+   *
+   * @param destination         the destination string.
+   * @param source              the source string.
+   * @param maxChars            the maximum number of chars.
+   */
+  void copyString(char *destination, const char *source, size_t maxChars);
+
+  /**
+   * Sets the synchronous timeout timer for the active sync message.
+   *
+   * @return                     if the operation was successful.
+   */
+  bool startSyncTimer();
+
+  /**
+   * The following functions validate the RPC input: request, calls the
+   * underlying function, and sets the return value in response.
+   *
+   * @param request              the request.
+   * @param response             the response.
+   * @return                     true if the input was validated correctly;
+   *                             false otherwise.
+   */
+  bool validateInputAndCallChreBleGetCapabilities(
+      const chre_rpc_Void &request, chre_rpc_Capabilities &response);
+
+  bool validateInputAndCallChreBleGetFilterCapabilities(
+      const chre_rpc_Void &request, chre_rpc_Capabilities &response);
+
+  bool validateInputAndCallChreBleStartScanAsync(
+      const chre_rpc_ChreBleStartScanAsyncInput &request,
+      chre_rpc_Status &response);
+
+  bool validateInputAndCallChreBleStopScanAsync(const chre_rpc_Void &request,
+                                                chre_rpc_Status &response);
+
+  bool validateInputAndCallChreSensorFindDefault(
+      const chre_rpc_ChreSensorFindDefaultInput &request,
+      chre_rpc_ChreSensorFindDefaultOutput &response);
+
+  bool validateInputAndCallChreGetSensorInfo(
+      const chre_rpc_ChreHandleInput &request,
+      chre_rpc_ChreGetSensorInfoOutput &response);
+
+  bool validateInputAndCallChreGetSensorSamplingStatus(
+      const chre_rpc_ChreHandleInput &request,
+      chre_rpc_ChreGetSensorSamplingStatusOutput &response);
+
+  bool validateInputAndCallChreSensorConfigure(
+      const chre_rpc_ChreSensorConfigureInput &request,
+      chre_rpc_Status &response);
+
+  bool validateInputAndCallChreSensorConfigureModeOnly(
+      const chre_rpc_ChreSensorConfigureModeOnlyInput &request,
+      chre_rpc_Status &response);
+
+  bool validateInputAndCallChreAudioGetSource(
+      const chre_rpc_ChreHandleInput &request,
+      chre_rpc_ChreAudioGetSourceOutput &response);
+
+  bool validateInputAndCallChreConfigureHostEndpointNotifications(
+      const chre_rpc_ChreConfigureHostEndpointNotificationsInput &request,
+      chre_rpc_Status &response);
+
+  bool validateInputAndRetrieveLatestDisconnectedHostEndpointEvent(
+      const chre_rpc_Void &request,
+      chre_rpc_RetrieveLatestDisconnectedHostEndpointEventOutput &response);
+
+  bool validateInputAndCallChreGetHostEndpointInfo(
+      const chre_rpc_ChreGetHostEndpointInfoInput &request,
+      chre_rpc_ChreGetHostEndpointInfoOutput &response);
+
+  constexpr static uint32_t kMaxNumEventTypes =
+      10;  // declared in chre_api_test.options
+
+  /**
+   * Variables to control synchronization for sync API calls.
+   * Only one sync API call may be made at a time.
+   */
+  Optional<ServerWriter<chre_rpc_GeneralSyncMessage>> mWriter;
+  uint32_t mSyncTimerHandle = CHRE_TIMER_INVALID;
+  uint8_t mRequestType;
+
+  /**
+   * Variables to store disconnected host endpoint notification.
+   */
+  uint32_t mReceivedHostEndpointDisconnectedNum = 0;
+  chreHostEndpointNotification mLatestHostEndpointNotification;
+
+  /*
+   * Variables to control synchronization for sync events calls.
+   * Only one sync event call may be made at a time.
+   */
+  Optional<ServerWriter<chre_rpc_GeneralEventsMessage>> mEventWriter;
+  uint32_t mEventTimerHandle = CHRE_TIMER_INVALID;
+  uint16_t mEventTypes[kMaxNumEventTypes];
+  uint32_t mEventTypeCount;
+  uint32_t mEventExpectedCount;
+  uint32_t mEventSentCount;
+};
+
+/**
+ * Handles RPC requests for the CHRE API Test nanoapp.
+ */
+class ChreApiTestManager {
+ public:
+  /**
+   * Allows the manager to do any init necessary as part of nanoappStart.
+   */
+  bool start();
+
+  /**
+   * Allows the manager to do any cleanup necessary as part of nanoappEnd.
+   */
+  void end();
+
+  /**
+   * Handle a CHRE event.
+   *
+   * @param senderInstanceId    the instand ID that sent the event.
+   * @param eventType           the type of the event.
+   * @param eventData           the data for the event.
+   */
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData);
+
+  /**
+   * Sets the permission for the next server message.
+   *
+   * @params permission Bitmasked CHRE_MESSAGE_PERMISSION_.
+   */
+  void setPermissionForNextMessage(uint32_t permission);
+
+ private:
+  // RPC server.
+  chre::RpcServer mServer;
+
+  // pw_rpc service used to process the RPCs.
+  ChreApiTestService mChreApiTestService;
+};
+
+typedef chre::Singleton<ChreApiTestManager> ChreApiTestManagerSingleton;
+
+#endif  // CHRE_API_TEST_MANAGER_H_
diff --git a/apps/test/common/chre_api_test/rpc/chre_api_test.options b/apps/test/common/chre_api_test/rpc/chre_api_test.options
new file mode 100644
index 0000000..6ec7e7c
--- /dev/null
+++ b/apps/test/common/chre_api_test/rpc/chre_api_test.options
@@ -0,0 +1,9 @@
+chre.rpc.ChreGetSensorInfoOutput.sensorName max_size:100
+chre.rpc.ChreAudioGetSourceOutput.name max_size:100
+chre.rpc.ChreBleGenericFilter.data max_size:29 # CHRE_BLE_DATA_LEN_MAX
+chre.rpc.ChreBleGenericFilter.mask max_size:29 # CHRE_BLE_DATA_LEN_MAX
+chre.rpc.ChreBleScanFilter.scanFilters max_count:10
+chre.rpc.ChreGetHostEndpointInfoOutput.endpointName max_size:51 # CHRE_MAX_ENDPOINT_NAME_LEN
+chre.rpc.ChreGetHostEndpointInfoOutput.endpointTag max_size:51 # CHRE_MAX_ENDPOINT_TAG_LEN
+chre.rpc.ChreSensorThreeAxisData.readings max_count:10
+chre.rpc.GatherEventsInput.eventTypes max_count:10
diff --git a/apps/test/common/chre_api_test/rpc/chre_api_test.proto b/apps/test/common/chre_api_test/rpc/chre_api_test.proto
new file mode 100644
index 0000000..57b7dbc
--- /dev/null
+++ b/apps/test/common/chre_api_test/rpc/chre_api_test.proto
@@ -0,0 +1,284 @@
+syntax = "proto3";
+
+package chre.rpc;
+
+option java_package = "dev.chre.rpc.proto";
+
+service ChreApiTestService {
+  // Returns the BLE capabilities.
+  rpc ChreBleGetCapabilities(Void) returns (Capabilities) {}
+
+  // Returns the BLE filter capabilities.
+  rpc ChreBleGetFilterCapabilities(Void) returns (Capabilities) {}
+
+  /* Starts a BLE scan asynchronously. This will not wait for the
+   * event but will return success if the chreBleStartScanAsync
+   * function returns success.
+   */
+  rpc ChreBleStartScanAsync(ChreBleStartScanAsyncInput) returns (Status) {}
+
+  /* Stops a BLE scan asynchronously. This will not wait for the
+   * event but will return success if the chreBleStopScanAsync
+   * function returns success.
+   */
+  rpc ChreBleStopScanAsync(Void) returns (Status) {}
+
+  // Finds the default sensor for the given sensor type.
+  rpc ChreSensorFindDefault(ChreSensorFindDefaultInput)
+      returns (ChreSensorFindDefaultOutput) {}
+
+  // Gets the information about a sensor.
+  rpc ChreGetSensorInfo(ChreHandleInput) returns (ChreGetSensorInfoOutput) {}
+
+  // Gets the sampling status for the sensor.
+  rpc ChreGetSensorSamplingStatus(ChreHandleInput)
+      returns (ChreGetSensorSamplingStatusOutput) {}
+
+  // Configures a sensor.
+  rpc ChreSensorConfigure(ChreSensorConfigureInput) returns (Status) {}
+
+  // Configures a sensor's mode.
+  rpc ChreSensorConfigureModeOnly(ChreSensorConfigureModeOnlyInput)
+      returns (Status) {}
+
+  // Gets the audio source's information.
+  rpc ChreAudioGetSource(ChreHandleInput) returns (ChreAudioGetSourceOutput) {}
+
+  // Configures the nanoapp to receive host endpoint information for a host
+  // endpoint id.
+  rpc ChreConfigureHostEndpointNotifications(
+      ChreConfigureHostEndpointNotificationsInput) returns (Status) {}
+
+  // TODO(b/274791978): Deprecate this once we can capture event
+  // Retrieves the latest disconnected host endpoint event and the number of
+  // disconnect event received.
+  rpc RetrieveLatestDisconnectedHostEndpointEvent(Void)
+      returns (RetrieveLatestDisconnectedHostEndpointEventOutput) {}
+
+  // Gets the host endpoint info for a given host endpoint id.
+  rpc ChreGetHostEndpointInfo(ChreGetHostEndpointInfoInput)
+      returns (ChreGetHostEndpointInfoOutput) {}
+
+  // Start synchronous functions
+
+  /* Starts a BLE scan synchronously. This will wait for the
+   * event and will return success if the chreBleStartScanAsync
+   * function returns success and the event is seen. This will
+   * return the event's success field.
+   */
+  rpc ChreBleStartScanSync(ChreBleStartScanAsyncInput)
+      returns (stream GeneralSyncMessage) {}
+
+  /* Stops a BLE scan synchronously. This will wait for the
+   * event and will return success if the chreBleStopScanAsync
+   * function returns success and the event is seen. This will
+   * return the event's success field.
+   */
+  rpc ChreBleStopScanSync(Void) returns (stream GeneralSyncMessage) {}
+
+  // Returns events that match the eventType filter
+  rpc GatherEvents(GatherEventsInput) returns (stream GeneralEventsMessage) {}
+}
+
+// General messages
+
+// Empty message (void)
+message Void {}
+
+// Contains a capabilities uint32
+message Capabilities {
+  uint32 capabilities = 1;
+}
+
+// Status message
+message Status {
+  bool status = 1;
+}
+
+// Input with a handle
+message ChreHandleInput {
+  uint32 handle = 1;
+}
+
+// Message for sync function output
+message GeneralSyncMessage {
+  bool status = 1;
+}
+
+// Event capturing messages
+
+// Contains event filters for gathering events
+message GatherEventsInput {
+  repeated uint32 eventTypes = 1;
+  uint32 eventTypeCount = 2;
+  uint32 eventCount = 3;
+  uint64 timeoutInNs = 4;
+}
+
+// Contains events that were gathered
+// To gather a new type of event, add its message to the oneof data
+message GeneralEventsMessage {
+  bool status = 1;
+  oneof data {
+    ChreSensorThreeAxisData chreSensorThreeAxisData = 2;
+    ChreSensorSamplingStatusEvent chreSensorSamplingStatusEvent = 3;
+  }
+}
+
+// A sensor sampling status update event
+message ChreSensorSamplingStatusEvent {
+  uint32 sensorHandle = 1;
+  ChreSensorSamplingStatus status = 2;
+}
+
+// The sampling status of a sensor
+message ChreSensorSamplingStatus {
+  uint64 interval = 1;
+  uint64 latency = 2;
+  bool enabled = 3;
+}
+
+// Contains three axis data for use with the accelerometer and other sensors
+message ChreSensorThreeAxisData {
+  ChreSensorDataHeader header = 1;
+  repeated ChreSensorThreeAxisSampleData readings = 2;
+}
+
+// Header for sensor data
+message ChreSensorDataHeader {
+  uint64 baseTimestamp = 1;
+  uint32 sensorHandle = 2;
+  uint32 readingCount = 3;
+  uint32 accuracy = 4;
+  uint32 reserved = 5;
+}
+
+// Individual sample data for a three-axis sensor.
+message ChreSensorThreeAxisSampleData {
+  uint32 timestampDelta = 1;
+  float x = 2;
+  float y = 3;
+  float z = 4;
+}
+
+// Function specific messages
+
+// Input value for ChreSensorFindDefault
+message ChreSensorFindDefaultInput {
+  uint32 sensorType = 1;
+}
+
+// Input value for ChreConfigureHostEndpointNotifications
+message ChreConfigureHostEndpointNotificationsInput {
+  uint32 hostEndpointId = 1;
+  bool enable = 2;
+}
+
+// Retrieving subscribed disconnected host endpoint notification
+message RetrieveLatestDisconnectedHostEndpointEventOutput {
+  // Records how many disconnected event received by this nanoapp.
+  uint32 disconnectedCount = 1;
+  uint32 hostEndpointId = 2;
+}
+
+// Input value for ChreGetHostEndpointInfo
+message ChreGetHostEndpointInfoInput {
+  uint32 hostEndpointId = 1;
+}
+
+// Return value for ChreGetHostEndpointInfo.
+// Bool + chreHostEndpointInfo
+// @see chreHostEndpointInfo for description of each field.
+message ChreGetHostEndpointInfoOutput {
+  bool status = 1;
+  uint32 hostEndpointId = 2;
+  uint32 hostEndpointType = 3;
+  bool isNameValid = 4;
+  bool isTagValid = 5;
+  string endpointName = 6;
+  string endpointTag = 7;
+}
+
+// Return value for ChreSensorFindDefault. sensorHandle is only valid if
+// foundSensor is true.
+message ChreSensorFindDefaultOutput {
+  bool foundSensor = 1;
+  uint32 sensorHandle = 2;
+}
+
+// Return value for ChreGetSensorInfo
+message ChreGetSensorInfoOutput {
+  bool status = 1;
+  string sensorName = 2;
+  uint32 sensorType = 3;
+  uint32 isOnChange = 4;
+  uint32 isOneShot = 5;
+  uint32 reportsBiasEvents = 6;
+  uint32 supportsPassiveMode = 7;
+  uint32 unusedFlags = 8;
+  uint64 minInterval = 9;
+  uint32 sensorIndex = 10;
+}
+
+// Return value for ChreGetSensorSamplingStatus
+message ChreGetSensorSamplingStatusOutput {
+  bool status = 1;
+  uint64 interval = 2;
+  uint64 latency = 3;
+  bool enabled = 4;
+}
+
+// Input value for ChreSensorConfigureModeOnly
+message ChreSensorConfigureInput {
+  uint32 sensorHandle = 1;
+  uint32 mode = 2;
+  uint64 interval = 3;
+  uint64 latency = 4;
+}
+
+// Input value for ChreSensorConfigureModeOnly
+message ChreSensorConfigureModeOnlyInput {
+  uint32 sensorHandle = 1;
+  uint32 mode = 2;
+}
+
+// Return value for ChreAudioGetSource
+message ChreAudioGetSourceOutput {
+  bool status = 1;
+  string name = 2;
+  uint32 sampleRate = 3;
+  uint64 minBufferDuration = 4;
+  uint64 maxBufferDuration = 5;
+  uint32 format = 6;
+}
+
+// Enumeration for BLE scan mode
+enum ChreBleScanMode {
+  INVALID = 0;
+  CHRE_BLE_SCAN_MODE_BACKGROUND = 1;
+  CHRE_BLE_SCAN_MODE_FOREGROUND = 2;
+  CHRE_BLE_SCAN_MODE_AGGRESSIVE = 3;
+}
+
+// BLE scan filters
+message ChreBleGenericFilter {
+  uint32 type = 1;
+  uint32 length = 2;
+  bytes data = 3;
+  bytes mask = 4;
+}
+
+// Scan filter for BLE scanning
+message ChreBleScanFilter {
+  int32 rssiThreshold = 1;
+  uint32 scanFilterCount = 2;
+  repeated ChreBleGenericFilter scanFilters = 3;
+}
+
+// Input value for ChreBleStartScanAsync
+message ChreBleStartScanAsyncInput {
+  ChreBleScanMode mode = 1;
+  uint32 reportDelayMs = 2;
+  bool hasFilter = 3;
+  ChreBleScanFilter filter = 4;
+}
diff --git a/apps/test/common/chre_api_test/src/chre_api_test.cc b/apps/test/common/chre_api_test/src/chre_api_test.cc
new file mode 100644
index 0000000..4c39c6a
--- /dev/null
+++ b/apps/test/common/chre_api_test/src/chre_api_test.cc
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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 <cinttypes>
+
+#include "chre_api/chre.h"
+#include "chre_api_test_manager.h"
+
+namespace chre {
+
+extern "C" void nanoappHandleEvent(uint32_t senderInstanceId,
+                                   uint16_t eventType, const void *eventData) {
+  ChreApiTestManagerSingleton::get()->handleEvent(senderInstanceId, eventType,
+                                                  eventData);
+}
+
+extern "C" bool nanoappStart(void) {
+  ChreApiTestManagerSingleton::init();
+  return ChreApiTestManagerSingleton::get()->start();
+}
+
+extern "C" void nanoappEnd(void) {
+  ChreApiTestManagerSingleton::get()->end();
+  ChreApiTestManagerSingleton::deinit();
+}
+
+}  // namespace chre
diff --git a/apps/test/common/chre_api_test/src/chre_api_test_manager.cc b/apps/test/common/chre_api_test/src/chre_api_test_manager.cc
new file mode 100644
index 0000000..3e4a38f
--- /dev/null
+++ b/apps/test/common/chre_api_test/src/chre_api_test_manager.cc
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2022 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_api_test_manager.h"
+
+#include "chre.h"
+#include "chre/util/nanoapp/log.h"
+#include "chre/util/time.h"
+
+namespace {
+constexpr uint64_t kSyncFunctionTimeout = 2 * chre::kOneSecondInNanoseconds;
+
+/**
+ * The following constants are defined in chre_api_test.options.
+ */
+constexpr uint32_t kThreeAxisDataReadingsMaxCount = 10;
+
+/**
+ * Closes the writer and invalidates the writer.
+ *
+ * @param T                   the RPC message type.
+ * @param writer              the RPC ServerWriter.
+ */
+template <typename T>
+void finishAndCloseWriter(
+    Optional<ChreApiTestService::ServerWriter<T>> &writer) {
+  CHRE_ASSERT(writer.has_value());
+
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  writer->Finish();
+  writer.reset();
+}
+
+/**
+ * Writes a message to the writer, then closes the writer and invalidates the
+ * writer.
+ *
+ * @param T                   the RPC message type.
+ * @param writer              the RPC ServerWriter.
+ * @param message             the message to write.
+ */
+template <typename T>
+void sendFinishAndCloseWriter(
+    Optional<ChreApiTestService::ServerWriter<T>> &writer, const T &message) {
+  CHRE_ASSERT(writer.has_value());
+
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  pw::Status status = writer->Write(message);
+  CHRE_ASSERT(status.ok());
+  finishAndCloseWriter(writer);
+}
+
+/**
+ * Sends a failure message. If there is not a valid writer, this returns
+ * without doing anything.
+ *
+ * @param T                   the RPC message type.
+ * @param writer              the RPC ServerWriter.
+ */
+template <typename T>
+void sendFailureAndFinishCloseWriter(
+    Optional<ChreApiTestService::ServerWriter<T>> &writer) {
+  CHRE_ASSERT(writer.has_value());
+
+  T message;
+  message.status = false;
+  sendFinishAndCloseWriter(writer, message);
+}
+}  // namespace
+
+// Start ChreApiTestService RPC generated functions
+
+pw::Status ChreApiTestService::ChreBleGetCapabilities(
+    const chre_rpc_Void &request, chre_rpc_Capabilities &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreBleGetCapabilities(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreBleGetFilterCapabilities(
+    const chre_rpc_Void &request, chre_rpc_Capabilities &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreBleGetFilterCapabilities(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreBleStartScanAsync(
+    const chre_rpc_ChreBleStartScanAsyncInput &request,
+    chre_rpc_Status &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreBleStartScanAsync(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreBleStopScanAsync(
+    const chre_rpc_Void &request, chre_rpc_Status &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreBleStopScanAsync(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreSensorFindDefault(
+    const chre_rpc_ChreSensorFindDefaultInput &request,
+    chre_rpc_ChreSensorFindDefaultOutput &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreSensorFindDefault(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreGetSensorInfo(
+    const chre_rpc_ChreHandleInput &request,
+    chre_rpc_ChreGetSensorInfoOutput &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreGetSensorInfo(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreGetSensorSamplingStatus(
+    const chre_rpc_ChreHandleInput &request,
+    chre_rpc_ChreGetSensorSamplingStatusOutput &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreGetSensorSamplingStatus(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreSensorConfigure(
+    const chre_rpc_ChreSensorConfigureInput &request,
+    chre_rpc_Status &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreSensorConfigure(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreSensorConfigureModeOnly(
+    const chre_rpc_ChreSensorConfigureModeOnlyInput &request,
+    chre_rpc_Status &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreSensorConfigureModeOnly(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreAudioGetSource(
+    const chre_rpc_ChreHandleInput &request,
+    chre_rpc_ChreAudioGetSourceOutput &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreAudioGetSource(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreConfigureHostEndpointNotifications(
+    const chre_rpc_ChreConfigureHostEndpointNotificationsInput &request,
+    chre_rpc_Status &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreConfigureHostEndpointNotifications(request,
+                                                                    response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::RetrieveLatestDisconnectedHostEndpointEvent(
+    const chre_rpc_Void &request,
+    chre_rpc_RetrieveLatestDisconnectedHostEndpointEventOutput &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndRetrieveLatestDisconnectedHostEndpointEvent(request,
+                                                                     response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+pw::Status ChreApiTestService::ChreGetHostEndpointInfo(
+    const chre_rpc_ChreGetHostEndpointInfoInput &request,
+    chre_rpc_ChreGetHostEndpointInfoOutput &response) {
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  return validateInputAndCallChreGetHostEndpointInfo(request, response)
+             ? pw::OkStatus()
+             : pw::Status::InvalidArgument();
+}
+
+// End ChreApiTestService RPC generated functions
+
+// Start ChreApiTestService RPC sync functions
+
+void ChreApiTestService::ChreBleStartScanSync(
+    const chre_rpc_ChreBleStartScanAsyncInput &request,
+    ServerWriter<chre_rpc_GeneralSyncMessage> &writer) {
+  if (mWriter.has_value()) {
+    ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+        CHRE_MESSAGE_PERMISSION_NONE);
+    writer.Finish();
+    LOGE("ChreBleStartScanSync: a sync message already exists");
+    return;
+  }
+
+  mWriter = std::move(writer);
+  CHRE_ASSERT(mSyncTimerHandle == CHRE_TIMER_INVALID);
+  mRequestType = CHRE_BLE_REQUEST_TYPE_START_SCAN;
+
+  chre_rpc_Status status;
+  if (!validateInputAndCallChreBleStartScanAsync(request, status) ||
+      !status.status || !startSyncTimer()) {
+    sendFailureAndFinishCloseWriter(mWriter);
+    mSyncTimerHandle = CHRE_TIMER_INVALID;
+    LOGD("ChreBleStartScanSync: status: false (error)");
+  }
+}
+
+void ChreApiTestService::ChreBleStopScanSync(
+    const chre_rpc_Void &request,
+    ServerWriter<chre_rpc_GeneralSyncMessage> &writer) {
+  if (mWriter.has_value()) {
+    ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+        CHRE_MESSAGE_PERMISSION_NONE);
+    writer.Finish();
+    LOGE("ChreBleStopScanSync: a sync message already exists");
+    return;
+  }
+
+  mWriter = std::move(writer);
+  CHRE_ASSERT(mSyncTimerHandle == CHRE_TIMER_INVALID);
+  mRequestType = CHRE_BLE_REQUEST_TYPE_STOP_SCAN;
+
+  chre_rpc_Status status;
+  if (!validateInputAndCallChreBleStopScanAsync(request, status) ||
+      !status.status || !startSyncTimer()) {
+    sendFailureAndFinishCloseWriter(mWriter);
+    mSyncTimerHandle = CHRE_TIMER_INVALID;
+    LOGD("ChreBleStopScanSync: status: false (error)");
+  }
+}
+
+// End ChreApiTestService RPC sync functions
+
+// Start ChreApiTestService event functions
+
+void ChreApiTestService::GatherEvents(
+    const chre_rpc_GatherEventsInput &request,
+    ServerWriter<chre_rpc_GeneralEventsMessage> &writer) {
+  if (mEventWriter.has_value()) {
+    LOGE("GatherEvents: an event gathering call already exists");
+    ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+        CHRE_MESSAGE_PERMISSION_NONE);
+    writer.Finish();
+    return;
+  }
+
+  if (request.eventTypeCount > kMaxNumEventTypes) {
+    LOGE("GatherEvents: request.eventTypeCount is out of bounds");
+    ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+        CHRE_MESSAGE_PERMISSION_NONE);
+    writer.Finish();
+    return;
+  }
+
+  if (request.eventTypeCount == 0) {
+    LOGE("GatherEvents: request.eventTypeCount == 0");
+    ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+        CHRE_MESSAGE_PERMISSION_NONE);
+    writer.Finish();
+    return;
+  }
+
+  for (uint32_t i = 0; i < request.eventTypeCount; ++i) {
+    if (request.eventTypes[i] < std::numeric_limits<uint16_t>::min() ||
+        request.eventTypes[i] > std::numeric_limits<uint16_t>::max()) {
+      LOGE("GatherEvents: invalid request.eventTypes: i: %" PRIu32, i);
+      ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+          CHRE_MESSAGE_PERMISSION_NONE);
+      writer.Finish();
+      return;
+    }
+
+    mEventTypes[i] = static_cast<uint16_t>(request.eventTypes[i]);
+    LOGD("GatherEvents: Watching for events with type: %" PRIu16,
+         mEventTypes[i]);
+  }
+
+  mEventWriter = std::move(writer);
+  CHRE_ASSERT(mEventTimerHandle == CHRE_TIMER_INVALID);
+  mEventTimerHandle = chreTimerSet(
+      request.timeoutInNs, &mEventTimerHandle /* cookie */, true /* oneShot */);
+  if (mEventTimerHandle == CHRE_TIMER_INVALID) {
+    LOGE("GatherEvents: Cannot set the event timer");
+    sendFailureAndFinishCloseWriter(mEventWriter);
+    mEventTimerHandle = CHRE_TIMER_INVALID;
+  } else {
+    mEventTypeCount = request.eventTypeCount;
+    mEventExpectedCount = request.eventCount;
+    mEventSentCount = 0;
+    LOGD("GatherEvents: mEventTypeCount: %" PRIu32
+         " mEventExpectedCount: %" PRIu32,
+         mEventTypeCount, mEventExpectedCount);
+  }
+}
+
+// End ChreApiTestService event functions
+
+void ChreApiTestService::handleBleAsyncResult(const chreAsyncResult *result) {
+  if (result == nullptr || !mWriter.has_value()) {
+    return;
+  }
+
+  if (result->requestType == mRequestType) {
+    chreTimerCancel(mSyncTimerHandle);
+    mSyncTimerHandle = CHRE_TIMER_INVALID;
+
+    chre_rpc_GeneralSyncMessage generalSyncMessage;
+    generalSyncMessage.status = result->success;
+    sendFinishAndCloseWriter(mWriter, generalSyncMessage);
+    LOGD("Active BLE sync function: status: %s",
+         generalSyncMessage.status ? "true" : "false");
+  }
+}
+
+void ChreApiTestService::handleGatheringEvent(uint16_t eventType,
+                                              const void *eventData) {
+  if (!mEventWriter.has_value()) {
+    return;
+  }
+
+  bool matchedEvent = false;
+  for (uint32_t i = 0; i < mEventTypeCount; ++i) {
+    if (mEventTypes[i] == eventType) {
+      matchedEvent = true;
+      break;
+    }
+  }
+  if (!matchedEvent) {
+    LOGD("GatherEvents: Received event with type: %" PRIu16
+         " that did not match any gathered events",
+         eventType);
+    return;
+  }
+
+  LOGD("Gather events Received matching event with type: %" PRIu16, eventType);
+
+  chre_rpc_GeneralEventsMessage message;
+  message.status = false;
+  switch (eventType) {
+    case CHRE_EVENT_SENSOR_ACCELEROMETER_DATA: {
+      message.status = true;
+      message.which_data =
+          chre_rpc_GeneralEventsMessage_chreSensorThreeAxisData_tag;
+
+      const struct chreSensorThreeAxisData *data =
+          static_cast<const struct chreSensorThreeAxisData *>(eventData);
+      message.data.chreSensorThreeAxisData.header.baseTimestamp =
+          data->header.baseTimestamp;
+      message.data.chreSensorThreeAxisData.header.sensorHandle =
+          data->header.sensorHandle;
+      message.data.chreSensorThreeAxisData.header.readingCount =
+          data->header.readingCount;
+      message.data.chreSensorThreeAxisData.header.accuracy =
+          data->header.accuracy;
+      message.data.chreSensorThreeAxisData.header.reserved =
+          data->header.reserved;
+
+      uint32_t numReadings =
+          MIN(data->header.readingCount, kThreeAxisDataReadingsMaxCount);
+      message.data.chreSensorThreeAxisData.readings_count = numReadings;
+      for (uint32_t i = 0; i < numReadings; ++i) {
+        message.data.chreSensorThreeAxisData.readings[i].timestampDelta =
+            data->readings[i].timestampDelta;
+        message.data.chreSensorThreeAxisData.readings[i].x =
+            data->readings[i].x;
+        message.data.chreSensorThreeAxisData.readings[i].y =
+            data->readings[i].y;
+        message.data.chreSensorThreeAxisData.readings[i].z =
+            data->readings[i].z;
+      }
+      break;
+    }
+    case CHRE_EVENT_SENSOR_SAMPLING_CHANGE: {
+      const struct chreSensorSamplingStatusEvent *data =
+          static_cast<const struct chreSensorSamplingStatusEvent *>(eventData);
+      message.data.chreSensorSamplingStatusEvent.sensorHandle =
+          data->sensorHandle;
+      message.data.chreSensorSamplingStatusEvent.status.interval =
+          data->status.interval;
+      message.data.chreSensorSamplingStatusEvent.status.latency =
+          data->status.latency;
+      message.data.chreSensorSamplingStatusEvent.status.enabled =
+          data->status.enabled;
+
+      message.status = true;
+      message.which_data =
+          chre_rpc_GeneralEventsMessage_chreSensorSamplingStatusEvent_tag;
+      break;
+    }
+    default: {
+      LOGE("GatherEvents: event type: %" PRIu16 " not implemented", eventType);
+    }
+  }
+
+  if (!message.status) {
+    LOGE("GatherEvents: unable to create message for event with type: %" PRIu16,
+         eventType);
+    return;
+  }
+
+  ChreApiTestManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  pw::Status status = mEventWriter->Write(message);
+  CHRE_ASSERT(status.ok());
+  ++mEventSentCount;
+
+  if (mEventSentCount == mEventExpectedCount) {
+    chreTimerCancel(mEventTimerHandle);
+    mEventTimerHandle = CHRE_TIMER_INVALID;
+    finishAndCloseWriter(mEventWriter);
+    LOGD("GatherEvents: Finish");
+  }
+}
+
+void ChreApiTestService::handleTimerEvent(const void *cookie) {
+  if (mWriter.has_value() && cookie == &mSyncTimerHandle) {
+    sendFailureAndFinishCloseWriter(mWriter);
+    mSyncTimerHandle = CHRE_TIMER_INVALID;
+    LOGD("Active sync function: status: false (timeout)");
+  } else if (mEventWriter.has_value() && cookie == &mEventTimerHandle) {
+    finishAndCloseWriter(mEventWriter);
+    mEventTimerHandle = CHRE_TIMER_INVALID;
+    LOGD("Timeout for event collection");
+  }
+}
+
+void ChreApiTestService::handleHostEndpointNotificationEvent(
+    const chreHostEndpointNotification *data) {
+  if (data->notificationType != HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT) {
+    LOGW("Received non disconnected event");
+    return;
+  }
+
+  ++mReceivedHostEndpointDisconnectedNum;
+  mLatestHostEndpointNotification = *data;
+}
+
+void ChreApiTestService::copyString(char *destination, const char *source,
+                                    size_t maxChars) {
+  CHRE_ASSERT_NOT_NULL(destination);
+  CHRE_ASSERT_NOT_NULL(source);
+
+  if (maxChars == 0) {
+    return;
+  }
+
+  uint32_t i;
+  for (i = 0; i < maxChars - 1 && source[i] != '\0'; ++i) {
+    destination[i] = source[i];
+  }
+
+  memset(&destination[i], 0, maxChars - i);
+}
+
+bool ChreApiTestService::startSyncTimer() {
+  mSyncTimerHandle = chreTimerSet(
+      kSyncFunctionTimeout, &mSyncTimerHandle /* cookie */, true /* oneShot */);
+  return mSyncTimerHandle != CHRE_TIMER_INVALID;
+}
+
+// Start ChreApiTestManager functions
+
+bool ChreApiTestManager::start() {
+  chre::RpcServer::Service service = {.service = mChreApiTestService,
+                                      .id = 0x61002d392de8430a,
+                                      .version = 0x01000000};
+  if (!mServer.registerServices(1, &service)) {
+    LOGE("Error while registering the service");
+    return false;
+  }
+
+  return true;
+}
+
+void ChreApiTestManager::end() {
+  // do nothing
+}
+
+void ChreApiTestManager::handleEvent(uint32_t senderInstanceId,
+                                     uint16_t eventType,
+                                     const void *eventData) {
+  if (!mServer.handleEvent(senderInstanceId, eventType, eventData)) {
+    LOGE("An RPC error occurred");
+  }
+
+  mChreApiTestService.handleGatheringEvent(eventType, eventData);
+
+  switch (eventType) {
+    case CHRE_EVENT_BLE_ASYNC_RESULT:
+      mChreApiTestService.handleBleAsyncResult(
+          static_cast<const chreAsyncResult *>(eventData));
+      break;
+    case CHRE_EVENT_TIMER:
+      mChreApiTestService.handleTimerEvent(eventData);
+      break;
+    case CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION:
+      mChreApiTestService.handleHostEndpointNotificationEvent(
+          static_cast<const chreHostEndpointNotification *>(eventData));
+      break;
+    default: {
+      // ignore
+    }
+  }
+}
+
+void ChreApiTestManager::setPermissionForNextMessage(uint32_t permission) {
+  mServer.setPermissionForNextMessage(permission);
+}
+
+// End ChreApiTestManager functions
diff --git a/apps/test/common/chre_api_test/src/chre_api_test_service.cc b/apps/test/common/chre_api_test/src/chre_api_test_service.cc
new file mode 100644
index 0000000..b045551
--- /dev/null
+++ b/apps/test/common/chre_api_test/src/chre_api_test_service.cc
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2023 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_api_test_manager.h"
+
+#include "chre/util/nanoapp/ble.h"
+#include "chre/util/nanoapp/log.h"
+
+using ::chre::createBleGenericFilter;
+
+namespace {
+
+/**
+ * The following constants are defined in chre_api_test.options.
+ */
+constexpr uint32_t kMaxBleScanFilters = 10;
+constexpr uint32_t kMaxNameStringSize = 100;
+}  // namespace
+
+bool ChreApiTestService::validateInputAndCallChreBleGetCapabilities(
+    const chre_rpc_Void & /* request */, chre_rpc_Capabilities &response) {
+  response.capabilities = chreBleGetCapabilities();
+  LOGD("ChreBleGetCapabilities: capabilities: %" PRIu32, response.capabilities);
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreBleGetFilterCapabilities(
+    const chre_rpc_Void & /* request */, chre_rpc_Capabilities &response) {
+  response.capabilities = chreBleGetFilterCapabilities();
+  LOGD("ChreBleGetFilterCapabilities: capabilities: %" PRIu32,
+       response.capabilities);
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreBleStartScanAsync(
+    const chre_rpc_ChreBleStartScanAsyncInput &request,
+    chre_rpc_Status &response) {
+  bool success = false;
+  if (request.mode < _chre_rpc_ChreBleScanMode_MIN ||
+      request.mode > _chre_rpc_ChreBleScanMode_MAX ||
+      request.mode == chre_rpc_ChreBleScanMode_INVALID) {
+    LOGE("ChreBleStartScanAsync: invalid mode");
+  } else if (!request.hasFilter) {
+    chreBleScanMode mode = static_cast<chreBleScanMode>(request.mode);
+    response.status =
+        chreBleStartScanAsync(mode, request.reportDelayMs, nullptr);
+
+    LOGD("ChreBleStartScanAsync: mode: %s, reportDelayMs: %" PRIu32
+         ", filter: nullptr, status: %s",
+         mode == CHRE_BLE_SCAN_MODE_BACKGROUND
+             ? "background"
+             : (mode == CHRE_BLE_SCAN_MODE_FOREGROUND ? "foreground"
+                                                      : "aggressive"),
+         request.reportDelayMs, response.status ? "true" : "false");
+    success = true;
+  } else if (request.filter.rssiThreshold <
+                 std::numeric_limits<int8_t>::min() ||
+             request.filter.rssiThreshold >
+                 std::numeric_limits<int8_t>::max()) {
+    LOGE("ChreBleStartScanAsync: invalid filter.rssiThreshold");
+  } else if (request.filter.scanFilterCount == 0 ||
+             request.filter.scanFilterCount > kMaxBleScanFilters) {
+    LOGE("ChreBleStartScanAsync: invalid filter.scanFilterCount");
+  } else {
+    chreBleGenericFilter genericFilters[request.filter.scanFilterCount];
+    bool validateFiltersSuccess = true;
+    for (uint32_t i = 0;
+         validateFiltersSuccess && i < request.filter.scanFilterCount; ++i) {
+      const chre_rpc_ChreBleGenericFilter &scanFilter =
+          request.filter.scanFilters[i];
+      if (scanFilter.type > std::numeric_limits<uint8_t>::max() ||
+          scanFilter.length > std::numeric_limits<uint8_t>::max()) {
+        LOGE(
+            "ChreBleStartScanAsync: invalid request.filter.scanFilters member: "
+            "type: %" PRIu32 " or length: %" PRIu32,
+            scanFilter.type, scanFilter.length);
+        validateFiltersSuccess = false;
+      } else if (scanFilter.data.size < scanFilter.length ||
+                 scanFilter.mask.size < scanFilter.length) {
+        LOGE(
+            "ChreBleStartScanAsync: invalid request.filter.scanFilters member: "
+            "data or mask size");
+        validateFiltersSuccess = false;
+      } else {
+        genericFilters[i] = createBleGenericFilter(
+            scanFilter.type, scanFilter.length, scanFilter.data.bytes,
+            scanFilter.mask.bytes);
+      }
+    }
+
+    if (validateFiltersSuccess) {
+      struct chreBleScanFilter filter;
+      filter.rssiThreshold = request.filter.rssiThreshold;
+      filter.scanFilterCount = request.filter.scanFilterCount;
+      filter.scanFilters = genericFilters;
+
+      chreBleScanMode mode = static_cast<chreBleScanMode>(request.mode);
+      response.status =
+          chreBleStartScanAsync(mode, request.reportDelayMs, &filter);
+
+      LOGD("ChreBleStartScanAsync: mode: %s, reportDelayMs: %" PRIu32
+           ", scanFilterCount: %" PRIu32 ", status: %s",
+           mode == CHRE_BLE_SCAN_MODE_BACKGROUND
+               ? "background"
+               : (mode == CHRE_BLE_SCAN_MODE_FOREGROUND ? "foreground"
+                                                        : "aggressive"),
+           request.reportDelayMs, request.filter.scanFilterCount,
+           response.status ? "true" : "false");
+      success = true;
+    }
+  }
+  return success;
+}
+
+bool ChreApiTestService::validateInputAndCallChreBleStopScanAsync(
+    const chre_rpc_Void & /* request */, chre_rpc_Status &response) {
+  response.status = chreBleStopScanAsync();
+  LOGD("ChreBleStopScanAsync: status: %s", response.status ? "true" : "false");
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreSensorFindDefault(
+    const chre_rpc_ChreSensorFindDefaultInput &request,
+    chre_rpc_ChreSensorFindDefaultOutput &response) {
+  if (request.sensorType > std::numeric_limits<uint8_t>::max()) {
+    return false;
+  }
+
+  uint8_t sensorType = (uint8_t)request.sensorType;
+  response.foundSensor =
+      chreSensorFindDefault(sensorType, &response.sensorHandle);
+
+  LOGD("ChreSensorFindDefault: foundSensor: %s, sensorHandle: %" PRIu32,
+       response.foundSensor ? "true" : "false", response.sensorHandle);
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreGetSensorInfo(
+    const chre_rpc_ChreHandleInput &request,
+    chre_rpc_ChreGetSensorInfoOutput &response) {
+  struct chreSensorInfo sensorInfo;
+  memset(&sensorInfo, 0, sizeof(sensorInfo));
+
+  response.status = chreGetSensorInfo(request.handle, &sensorInfo);
+
+  if (response.status) {
+    copyString(response.sensorName, sensorInfo.sensorName, kMaxNameStringSize);
+    response.sensorType = sensorInfo.sensorType;
+    response.isOnChange = sensorInfo.isOnChange;
+    response.isOneShot = sensorInfo.isOneShot;
+    response.reportsBiasEvents = sensorInfo.reportsBiasEvents;
+    response.supportsPassiveMode = sensorInfo.supportsPassiveMode;
+    response.unusedFlags = sensorInfo.unusedFlags;
+    response.minInterval = sensorInfo.minInterval;
+    response.sensorIndex = sensorInfo.sensorIndex;
+
+    LOGD("ChreGetSensorInfo: status: true, sensorType: %" PRIu32
+         ", isOnChange: %" PRIu32
+         ", "
+         "isOneShot: %" PRIu32 ", reportsBiasEvents: %" PRIu32
+         ", supportsPassiveMode: %" PRIu32 ", unusedFlags: %" PRIu32
+         ", minInterval: %" PRIu64 ", sensorIndex: %" PRIu32,
+         response.sensorType, response.isOnChange, response.isOneShot,
+         response.reportsBiasEvents, response.supportsPassiveMode,
+         response.unusedFlags, response.minInterval, response.sensorIndex);
+  } else {
+    LOGD("ChreGetSensorInfo: status: false");
+  }
+
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreGetSensorSamplingStatus(
+    const chre_rpc_ChreHandleInput &request,
+    chre_rpc_ChreGetSensorSamplingStatusOutput &response) {
+  struct chreSensorSamplingStatus samplingStatus;
+  memset(&samplingStatus, 0, sizeof(samplingStatus));
+
+  response.status =
+      chreGetSensorSamplingStatus(request.handle, &samplingStatus);
+  if (response.status) {
+    response.interval = samplingStatus.interval;
+    response.latency = samplingStatus.latency;
+    response.enabled = samplingStatus.enabled;
+
+    LOGD("ChreGetSensorSamplingStatus: status: true, interval: %" PRIu64
+         ", latency: %" PRIu64 ", enabled: %s",
+         response.interval, response.latency,
+         response.enabled ? "true" : "false");
+  } else {
+    LOGD("ChreGetSensorSamplingStatus: status: false");
+  }
+
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreSensorConfigure(
+    const chre_rpc_ChreSensorConfigureInput &request,
+    chre_rpc_Status &response) {
+  chreSensorConfigureMode mode =
+      static_cast<chreSensorConfigureMode>(request.mode);
+  response.status = chreSensorConfigure(request.sensorHandle, mode,
+                                        request.interval, request.latency);
+
+  LOGD("ChreSensorConfigure: status: %s", response.status ? "true" : "false");
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreSensorConfigureModeOnly(
+    const chre_rpc_ChreSensorConfigureModeOnlyInput &request,
+    chre_rpc_Status &response) {
+  chreSensorConfigureMode mode =
+      static_cast<chreSensorConfigureMode>(request.mode);
+  response.status = chreSensorConfigureModeOnly(request.sensorHandle, mode);
+
+  LOGD("ChreSensorConfigureModeOnly: status: %s",
+       response.status ? "true" : "false");
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreAudioGetSource(
+    const chre_rpc_ChreHandleInput &request,
+    chre_rpc_ChreAudioGetSourceOutput &response) {
+  struct chreAudioSource audioSource;
+  memset(&audioSource, 0, sizeof(audioSource));
+  response.status = chreAudioGetSource(request.handle, &audioSource);
+
+  if (response.status) {
+    copyString(response.name, audioSource.name, kMaxNameStringSize);
+    response.sampleRate = audioSource.sampleRate;
+    response.minBufferDuration = audioSource.minBufferDuration;
+    response.maxBufferDuration = audioSource.maxBufferDuration;
+    response.format = audioSource.format;
+
+    LOGD("ChreAudioGetSource: status: true, name: %s, sampleRate %" PRIu32
+         ", minBufferDuration: %" PRIu64 ", maxBufferDuration %" PRIu64
+         ", format: %" PRIu32,
+         response.name, response.sampleRate, response.minBufferDuration,
+         response.maxBufferDuration, response.format);
+  } else {
+    LOGD("ChreAudioGetSource: status: false");
+  }
+
+  return true;
+}
+
+bool ChreApiTestService::
+    validateInputAndCallChreConfigureHostEndpointNotifications(
+        const chre_rpc_ChreConfigureHostEndpointNotificationsInput &request,
+        chre_rpc_Status &response) {
+  if (request.hostEndpointId > std::numeric_limits<uint16_t>::max()) {
+    LOGE("Host Endpoint Id cannot exceed max of uint16_t");
+    return false;
+  }
+
+  response.status = chreConfigureHostEndpointNotifications(
+      request.hostEndpointId, request.enable);
+  LOGD("ChreConfigureHostEndpointNotifications: status: %s",
+       response.status ? "true" : "false");
+  return true;
+}
+
+bool ChreApiTestService::
+    validateInputAndRetrieveLatestDisconnectedHostEndpointEvent(
+        const chre_rpc_Void & /* request */,
+        chre_rpc_RetrieveLatestDisconnectedHostEndpointEventOutput &response) {
+  response.disconnectedCount = mReceivedHostEndpointDisconnectedNum;
+  response.hostEndpointId = mLatestHostEndpointNotification.hostEndpointId;
+  return true;
+}
+
+bool ChreApiTestService::validateInputAndCallChreGetHostEndpointInfo(
+    const chre_rpc_ChreGetHostEndpointInfoInput &request,
+    chre_rpc_ChreGetHostEndpointInfoOutput &response) {
+  if (request.hostEndpointId > std::numeric_limits<uint16_t>::max()) {
+    LOGE("Host Endpoint Id cannot exceed max of uint16_t");
+    return false;
+  }
+
+  struct chreHostEndpointInfo hostEndpointInfo;
+  memset(&hostEndpointInfo, 0, sizeof(hostEndpointInfo));
+  response.status =
+      chreGetHostEndpointInfo(request.hostEndpointId, &hostEndpointInfo);
+
+  if (response.status) {
+    response.hostEndpointId = hostEndpointInfo.hostEndpointId;
+    response.hostEndpointType = hostEndpointInfo.hostEndpointType;
+    response.isNameValid = hostEndpointInfo.isNameValid;
+    response.isTagValid = hostEndpointInfo.isTagValid;
+    if (hostEndpointInfo.isNameValid) {
+      copyString(response.endpointName, hostEndpointInfo.endpointName,
+                 CHRE_MAX_ENDPOINT_NAME_LEN);
+    } else {
+      memset(response.endpointName, 0, CHRE_MAX_ENDPOINT_NAME_LEN);
+    }
+    if (hostEndpointInfo.isTagValid) {
+      copyString(response.endpointTag, hostEndpointInfo.endpointTag,
+                 CHRE_MAX_ENDPOINT_TAG_LEN);
+    } else {
+      memset(response.endpointTag, 0, CHRE_MAX_ENDPOINT_TAG_LEN);
+    }
+
+    LOGD("ChreGetHostEndpointInfo: status: true, hostEndpointID: %" PRIu32
+         ", hostEndpointType: %" PRIu32
+         ", isNameValid: %s, isTagValid: %s, endpointName: %s, endpointTag: %s",
+         response.hostEndpointId, response.hostEndpointType,
+         response.isNameValid ? "true" : "false",
+         response.isTagValid ? "true" : "false", response.endpointName,
+         response.endpointTag);
+  } else {
+    LOGD("ChreGetHostEndpointInfo: status: false");
+  }
+  return true;
+}
diff --git a/apps/test/common/chre_api_test2/Makefile b/apps/test/common/chre_api_test2/Makefile
new file mode 100644
index 0000000..2cca306
--- /dev/null
+++ b/apps/test/common/chre_api_test2/Makefile
@@ -0,0 +1,23 @@
+#
+# CHRE API Test Nanoapp Makefile
+#
+
+# Makefile Includes ############################################################
+
+include ../chre_api_test/chre_api_test.mk
+
+# Nanoapp Configuration ########################################################
+
+NANOAPP_NAME = chre_api_test2
+NANOAPP_ID = 0x476f6f675400000e
+NANOAPP_NAME_STRING = \"CHRE\ API\ Test\ 2\"
+NANOAPP_VERSION = 0x00000001
+
+# Compiler Flags ###############################################################
+
+# Defines
+COMMON_CFLAGS += -DLOG_TAG=\"[ChreApiTest2]\"
+
+# Makefile Includes ############################################################
+
+include $(CHRE_PREFIX)/build/nanoapp/app.mk
diff --git a/apps/test/common/chre_audio_concurrency_test/Makefile b/apps/test/common/chre_audio_concurrency_test/Makefile
index c98feaa..02e5e8d 100644
--- a/apps/test/common/chre_audio_concurrency_test/Makefile
+++ b/apps/test/common/chre_audio_concurrency_test/Makefile
@@ -23,6 +23,7 @@
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/chre_audio_concurrency_test
 TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
 
+
 # Protobuf Sources #############################################################
 
 NANOPB_EXTENSION = nanopb
@@ -35,6 +36,7 @@
 
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_audio_concurrency_test.cc
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_audio_concurrency_test_manager.cc
+COMMON_SRCS += $(TEST_SHARED_PATH)/src/audio_validation.cc
 COMMON_SRCS += $(TEST_SHARED_PATH)/src/send_message.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/callbacks.cc
 
@@ -42,6 +44,7 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Includes
 COMMON_CFLAGS += -I$(NANOAPP_PATH)/inc
diff --git a/apps/test/common/chre_audio_concurrency_test/inc/chre_audio_concurrency_test_manager.h b/apps/test/common/chre_audio_concurrency_test/inc/chre_audio_concurrency_test_manager.h
index a140f0d..06f3546 100644
--- a/apps/test/common/chre_audio_concurrency_test/inc/chre_audio_concurrency_test_manager.h
+++ b/apps/test/common/chre_audio_concurrency_test/inc/chre_audio_concurrency_test_manager.h
@@ -17,11 +17,11 @@
 #ifndef CHRE_AUDIO_CONCURRENCY_TEST_MANAGER_H_
 #define CHRE_AUDIO_CONCURRENCY_TEST_MANAGER_H_
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/optional.h"
 #include "chre/util/singleton.h"
+#include "chre_api/chre.h"
 
 namespace chre {
 
@@ -114,6 +114,11 @@
    */
   void handleAudioDataEvent(const chreAudioDataEvent *data);
 
+  /**
+   * @param data The audio status data.
+   */
+  void handleAudioSourceStatusEvent(const chreAudioSourceStatusEvent *data);
+
   // Use the first audio source available for this test.
   static constexpr uint32_t kAudioHandle = 0;
 
@@ -128,6 +133,12 @@
 
   //! True if CHRE audio is enabled for this nanoapp.
   bool mAudioEnabled = false;
+
+  // The last timestamp seen at the end of the last audio buffer.
+  Optional<uint64_t> mLastAudioBufferEndTimestampNs;
+
+  // True if there is a gap between the audio buffers.
+  bool mSawSuspendAudioEvent = false;
 };
 
 // The audio concurrency test manager singleton.
diff --git a/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test.cc b/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test.cc
index 8b43068..6c943fb 100644
--- a/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test.cc
+++ b/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test.cc
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
+#include "chre_api/chre.h"
 #include "chre_audio_concurrency_test_manager.h"
 
 namespace chre {
diff --git a/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test_manager.cc b/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test_manager.cc
index 0000ed9..dc97af5 100644
--- a/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test_manager.cc
+++ b/apps/test/common/chre_audio_concurrency_test/src/chre_audio_concurrency_test_manager.cc
@@ -18,6 +18,7 @@
 
 #include <pb_decode.h>
 
+#include "audio_validation.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
 #include "chre_audio_concurrency_test.nanopb.h"
@@ -27,6 +28,8 @@
 
 namespace chre {
 
+using test_shared::checkAudioSamplesAllSame;
+using test_shared::checkAudioSamplesAllZeros;
 using test_shared::sendEmptyMessageToHost;
 using test_shared::sendTestResultToHost;
 
@@ -38,6 +41,11 @@
 constexpr uint32_t kTestResultMessageType =
     chre_audio_concurrency_test_MessageType_TEST_RESULT;
 
+//! The maximum number of samples that can be missed before triggering a suspend
+//! event. 50 samples at a sample rate of 44100 (typical) is approximately 1 ms
+//! of audio gap.
+constexpr uint32_t kMaxMissedSamples = 50;
+
 bool isTestSupported() {
   // CHRE audio was supported in CHRE v1.2
   return chreGetVersion() >= CHRE_API_VERSION_1_2;
@@ -47,15 +55,18 @@
                  Manager::TestStep *step) {
   bool success = true;
   switch (command.step) {
-    case chre_audio_concurrency_test_TestCommand_Step_ENABLE_AUDIO:
+    case chre_audio_concurrency_test_TestCommand_Step_ENABLE_AUDIO: {
       *step = Manager::TestStep::ENABLE_AUDIO;
       break;
-    case chre_audio_concurrency_test_TestCommand_Step_VERIFY_AUDIO_RESUME:
+    }
+    case chre_audio_concurrency_test_TestCommand_Step_VERIFY_AUDIO_RESUME: {
       *step = Manager::TestStep::VERIFY_AUDIO_RESUME;
       break;
-    default:
+    }
+    default: {
       LOGE("Unknown test step %d", command.step);
       success = false;
+    }
   }
 
   return success;
@@ -72,16 +83,17 @@
 }
 
 bool Manager::handleTestCommandMessage(uint16_t hostEndpointId, TestStep step) {
-  bool success = true;
-
   // Treat as success if CHRE audio is unsupported
   // TODO: Use all available audio sources
   if (!isTestSupported() || !chreAudioGetSource(kAudioHandle, &mAudioSource)) {
     sendTestResultToHost(hostEndpointId, kTestResultMessageType,
                          true /* success */);
-  } else {
-    success = false;
-    if (step == TestStep::ENABLE_AUDIO) {
+    return true;
+  }
+
+  bool success = false;
+  switch (step) {
+    case TestStep::ENABLE_AUDIO: {
       if (!chreAudioConfigureSource(kAudioHandle, true /* enable */,
                                     mAudioSource.minBufferDuration,
                                     mAudioSource.minBufferDuration)) {
@@ -93,15 +105,21 @@
         // a reasonably long timeout.
         success = setTimeoutTimer(20 /* durationSeconds */);
       }
-    } else if (step == TestStep::VERIFY_AUDIO_RESUME) {
+      break;
+    }
+    case TestStep::VERIFY_AUDIO_RESUME: {
       success = setTimeoutTimer(20 /* durationSeconds */);
+      break;
     }
+    default: {
+      break;
+    }
+  }
 
-    if (success) {
-      mTestSession = TestSession(hostEndpointId, step);
-      LOGI("Starting test step %" PRIu8,
-           static_cast<uint8_t>(mTestSession->step));
-    }
+  if (success) {
+    mTestSession = TestSession(hostEndpointId, step);
+    LOGI("Starting test step %" PRIu8,
+         static_cast<uint8_t>(mTestSession->step));
   }
 
   return success;
@@ -149,7 +167,8 @@
       break;
 
     case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
-      /* ignore */
+      handleAudioSourceStatusEvent(
+          static_cast<const chreAudioSourceStatusEvent *>(eventData));
       break;
 
     default:
@@ -185,64 +204,120 @@
 }
 
 bool Manager::validateAudioDataEvent(const chreAudioDataEvent *data) {
-  bool ulaw8 = false;
-  if (data->format == CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW) {
-    ulaw8 = true;
+  bool success = false;
+  if (data == nullptr) {
+    LOGE("data is nullptr");
+  } else if (data->format == CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW) {
+    if (data->samplesULaw8 == nullptr) {
+      LOGE("samplesULaw8 is nullptr");
+    }
   } else if (data->format != CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM) {
     LOGE("Invalid format %" PRIu8, data->format);
-    return false;
+  } else if (data->samplesS16 == nullptr) {
+    LOGE("samplesS16 is nullptr");
+  } else if (data->sampleCount == 0) {
+    LOGE("The sample count is 0");
+  } else if (data->sampleCount <
+             static_cast<uint64_t>(mAudioSource.minBufferDuration *
+                                   static_cast<double>(data->sampleRate) /
+                                   kOneSecondInNanoseconds)) {
+    LOGE("The sample count is less than the minimum number of samples");
+  } else if (!checkAudioSamplesAllZeros(data->samplesS16, data->sampleCount)) {
+    LOGE("Audio samples are all zero");
+  } else if (!checkAudioSamplesAllSame(data->samplesS16, data->sampleCount)) {
+    LOGE("Audio samples are all the same");
+  } else {
+    // Verify that timestamp increases.
+    static uint64_t lastTimestamp = 0;
+    bool timestampValid = data->timestamp > lastTimestamp;
+    lastTimestamp = data->timestamp;
+
+    // Verify the gap was properly announced.
+    bool gapValidationValid = true;
+    double sampleTimeInNs =
+        kOneSecondInNanoseconds / static_cast<double>(data->sampleRate);
+    if (mLastAudioBufferEndTimestampNs.has_value() &&
+        data->timestamp > *mLastAudioBufferEndTimestampNs) {
+      uint64_t gapNs = data->timestamp - *mLastAudioBufferEndTimestampNs;
+      if (gapNs > kMaxMissedSamples * sampleTimeInNs &&
+          !mSawSuspendAudioEvent) {
+        LOGE(
+            "Audio was suspended, but we did not receive a "
+            "CHRE_EVENT_AUDIO_SAMPLING_CHANGE event.");
+        LOGE("gap = %" PRIu64 " ns", gapNs);
+        gapValidationValid = false;
+      }
+    }
+
+    // Record last audio timestamp at end of buffer
+    mLastAudioBufferEndTimestampNs =
+        data->timestamp + data->sampleCount * sampleTimeInNs;
+
+    success = timestampValid && gapValidationValid;
   }
 
-  // Verify that the audio data is not all zeroes
-  uint32_t numZeroes = 0;
-  for (uint32_t i = 0; i < data->sampleCount; i++) {
-    numZeroes +=
-        ulaw8 ? (data->samplesULaw8[i] == 0) : (data->samplesS16[i] == 0);
-  }
-  bool dataValid = numZeroes != data->sampleCount;
-
-  // Verify that timestamp increases
-  static uint64_t lastTimestamp = 0;
-  bool timestampValid = data->timestamp > lastTimestamp;
-  lastTimestamp = data->timestamp;
-
-  return dataValid && timestampValid;
+  return success;
 }
 
 void Manager::handleAudioDataEvent(const chreAudioDataEvent *data) {
-  if (mTestSession.has_value()) {
-    if (!validateAudioDataEvent(data)) {
-      sendTestResultToHost(mTestSession->hostEndpointId, kTestResultMessageType,
-                           false /* success */);
+  if (!mTestSession.has_value()) {
+    return;
+  }
+
+  if (!validateAudioDataEvent(data)) {
+    sendTestResultToHost(mTestSession->hostEndpointId, kTestResultMessageType,
+                         false /* success */);
+    mTestSession.reset();
+    return;
+  }
+
+  switch (mTestSession->step) {
+    case TestStep::ENABLE_AUDIO: {
+      cancelTimeoutTimer();
+      sendEmptyMessageToHost(
+          mTestSession->hostEndpointId,
+          chre_audio_concurrency_test_MessageType_TEST_AUDIO_ENABLED);
+
+      // Reset the test session to avoid sending multiple TEST_AUDIO_ENABLED
+      // messages to the host, while we wait for the next step.
       mTestSession.reset();
-    } else {
-      switch (mTestSession->step) {
-        case TestStep::ENABLE_AUDIO: {
-          cancelTimeoutTimer();
-          sendEmptyMessageToHost(
-              mTestSession->hostEndpointId,
-              chre_audio_concurrency_test_MessageType_TEST_AUDIO_ENABLED);
-
-          // Reset the test session to avoid sending multiple TEST_AUDIO_ENABLED
-          // messages to the host, while we wait for the next step.
-          mTestSession.reset();
-          break;
-        }
-
-        case TestStep::VERIFY_AUDIO_RESUME: {
-          cancelTimeoutTimer();
-          sendTestResultToHost(mTestSession->hostEndpointId,
-                               kTestResultMessageType, true /* success */);
-          mTestSession.reset();
-          break;
-        }
-
-        default:
-          LOGE("Unexpected test step %" PRIu8,
-               static_cast<uint8_t>(mTestSession->step));
-          break;
-      }
+      break;
     }
+
+    case TestStep::VERIFY_AUDIO_RESUME: {
+      cancelTimeoutTimer();
+      sendTestResultToHost(mTestSession->hostEndpointId, kTestResultMessageType,
+                           true /* success */);
+      mTestSession.reset();
+      break;
+    }
+
+    default:
+      LOGE("Unexpected test step %" PRIu8,
+           static_cast<uint8_t>(mTestSession->step));
+      break;
+  }
+}
+
+void Manager::handleAudioSourceStatusEvent(
+    const chreAudioSourceStatusEvent *data) {
+  if (data != nullptr) {
+    LOGI("Audio source status event received");
+    LOGI("Event: handle: %" PRIu32 ", enabled: %s, suspended: %s", data->handle,
+         data->status.enabled ? "true" : "false",
+         data->status.suspended ? "true" : "false");
+
+    if (mTestSession.has_value() &&
+        (mTestSession->step == TestStep::ENABLE_AUDIO ||
+         mTestSession->step == TestStep::VERIFY_AUDIO_RESUME) &&
+        data->handle == kAudioHandle && data->status.suspended) {
+      mSawSuspendAudioEvent = true;
+    }
+  } else if (mTestSession.has_value()) {
+    LOGE("Invalid data (data == nullptr)");
+    sendTestResultToHost(mTestSession->hostEndpointId, kTestResultMessageType,
+                         false /* success */);
+    mTestSession.reset();
   }
 }
 
diff --git a/apps/test/common/chre_cross_validator_gnss/Makefile b/apps/test/common/chre_cross_validator_gnss/Makefile
index f3e9258..e19f733 100644
--- a/apps/test/common/chre_cross_validator_gnss/Makefile
+++ b/apps/test/common/chre_cross_validator_gnss/Makefile
@@ -21,6 +21,7 @@
 
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/chre_cross_validator_gnss
 
+
 # Source Code ##################################################################
 
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_cross_validator_gnss.cc
@@ -29,6 +30,7 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Permission declarations ######################################################
 
diff --git a/apps/test/common/chre_cross_validator_gnss/src/chre_cross_validator_gnss.cc b/apps/test/common/chre_cross_validator_gnss/src/chre_cross_validator_gnss.cc
index 99aa371..b77ed76 100644
--- a/apps/test/common/chre_cross_validator_gnss/src/chre_cross_validator_gnss.cc
+++ b/apps/test/common/chre_cross_validator_gnss/src/chre_cross_validator_gnss.cc
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
+#include "chre_api/chre.h"
 namespace chre {
 
 extern "C" void nanoappHandleEvent(uint32_t senderInstanceId,
diff --git a/apps/test/common/chre_cross_validator_sensor/Makefile b/apps/test/common/chre_cross_validator_sensor/Makefile
index c8c320b..7602d0e 100644
--- a/apps/test/common/chre_cross_validator_sensor/Makefile
+++ b/apps/test/common/chre_cross_validator_sensor/Makefile
@@ -21,27 +21,33 @@
 NANOAPP_VERSION = 0x00000002
 
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/chre_cross_validator_sensor
+TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
+
 
 # Protobuf Sources #############################################################
 
 NANOPB_EXTENSION = nanopb
 
 NANOPB_SRCS += $(NANOAPP_PATH)/../proto/chre_cross_validation_sensor.proto
+NANOPB_SRCS += $(NANOAPP_PATH)/../proto/chre_test_common.proto
 NANOPB_INCLUDES = $(NANOAPP_PATH)/../proto
 
 # Source Code ##################################################################
 
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_cross_validator_sensor.cc
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_cross_validator_sensor_manager.cc
+COMMON_SRCS += $(TEST_SHARED_PATH)/src/send_message.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/callbacks.cc
 
 # Compiler Flags ###############################################################
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Includes
 COMMON_CFLAGS += -I$(NANOAPP_PATH)/inc
+COMMON_CFLAGS += -I$(TEST_SHARED_PATH)/inc
 
 # Makefile Includes ############################################################
 
diff --git a/apps/test/common/chre_cross_validator_sensor/inc/chre_cross_validator_sensor_manager.h b/apps/test/common/chre_cross_validator_sensor/inc/chre_cross_validator_sensor_manager.h
index b848155..89b6a80 100644
--- a/apps/test/common/chre_cross_validator_sensor/inc/chre_cross_validator_sensor_manager.h
+++ b/apps/test/common/chre_cross_validator_sensor/inc/chre_cross_validator_sensor_manager.h
@@ -17,9 +17,9 @@
 #ifndef CHRE_CROSS_VALIDATOR_MANAGER_H_
 #define CHRE_CROSS_VALIDATOR_MANAGER_H_
 
-#include <chre.h>
 #include <pb_encode.h>
 
+#include "chre_api/chre.h"
 #include "chre_cross_validation_sensor.nanopb.h"
 
 #include "chre/util/optional.h"
@@ -354,18 +354,6 @@
       const chre_cross_validation_sensor_SensorInfoResponse &infoResponse);
 
   /**
-   * Sends the provided message to the host.
-   *
-   * @param hostEndpoint The endpoint to send the message to.
-   * @param messageType The type of message being sent to the host.
-   * @param fields The fields of the provided struct that should be encoded.
-   * @param srcStruct The struct that should be encoded prior to sending to the
-   *     host.
-   */
-  void sendMessageToHost(uint16_t hostEndpoint, uint16_t messageType,
-                         const pb_field_t fields[], const void *srcStruct);
-
-  /**
    * Determine if nanoapp is ready to process new sensor data.
    *
    * @param header The sensor data header that was received with data.
diff --git a/apps/test/common/chre_cross_validator_sensor/src/chre_cross_validator_sensor_manager.cc b/apps/test/common/chre_cross_validator_sensor/src/chre_cross_validator_sensor_manager.cc
index 5d8c628..dfb6022 100644
--- a/apps/test/common/chre_cross_validator_sensor/src/chre_cross_validator_sensor_manager.cc
+++ b/apps/test/common/chre_cross_validator_sensor/src/chre_cross_validator_sensor_manager.cc
@@ -19,7 +19,6 @@
 #include <algorithm>
 #include <cinttypes>
 
-#include <chre.h>
 #include <pb_decode.h>
 #include <pb_encode.h>
 
@@ -28,7 +27,9 @@
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/optional.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 #include "chre_cross_validation_sensor.nanopb.h"
+#include "send_message.h"
 
 #define LOG_TAG "[ChreCrossValidator]"
 
@@ -612,42 +613,19 @@
 }
 
 void Manager::sendDataToHost(const chre_cross_validation_sensor_Data &data) {
-  sendMessageToHost(
-      mCrossValidatorState->hostEndpoint,
-      chre_cross_validation_sensor_MessageType_CHRE_CROSS_VALIDATION_DATA,
-      chre_cross_validation_sensor_Data_fields, &data);
+  test_shared::sendMessageToHost(
+      mCrossValidatorState->hostEndpoint, &data,
+      chre_cross_validation_sensor_Data_fields,
+      chre_cross_validation_sensor_MessageType_CHRE_CROSS_VALIDATION_DATA);
 }
 
 void Manager::sendInfoResponse(
     uint16_t hostEndpoint,
     const chre_cross_validation_sensor_SensorInfoResponse &infoResponse) {
-  sendMessageToHost(
-      hostEndpoint,
-      chre_cross_validation_sensor_MessageType_CHRE_CROSS_VALIDATION_INFO_RESPONSE,
-      chre_cross_validation_sensor_SensorInfoResponse_fields, &infoResponse);
-}
-
-void Manager::sendMessageToHost(uint16_t hostEndpoint, uint16_t messageType,
-                                const pb_field_t fields[],
-                                const void *srcStruct) {
-  size_t encodedSize;
-  if (!pb_get_encoded_size(&encodedSize, fields, srcStruct)) {
-    LOGE("Could not get encoded size of proto message");
-  } else {
-    pb_byte_t *buffer = static_cast<pb_byte_t *>(chreHeapAlloc(encodedSize));
-    if (buffer == nullptr) {
-      LOG_OOM();
-    } else {
-      pb_ostream_t ostream = pb_ostream_from_buffer(buffer, encodedSize);
-      if (!pb_encode(&ostream, fields, srcStruct)) {
-        LOGE("Could not encode proto message");
-      } else if (!chreSendMessageToHostEndpoint(
-                     static_cast<void *>(buffer), encodedSize, messageType,
-                     hostEndpoint, heapFreeMessageCallback)) {
-        LOGE("Could not send message to host");
-      }
-    }
-  }
+  test_shared::sendMessageToHost(
+      hostEndpoint, &infoResponse,
+      chre_cross_validation_sensor_SensorInfoResponse_fields,
+      chre_cross_validation_sensor_MessageType_CHRE_CROSS_VALIDATION_INFO_RESPONSE);
 }
 
 bool Manager::processSensorData(const chreSensorDataHeader &header,
diff --git a/apps/test/common/chre_cross_validator_wifi/Makefile b/apps/test/common/chre_cross_validator_wifi/Makefile
index bfa8b40..f09e4a7 100644
--- a/apps/test/common/chre_cross_validator_wifi/Makefile
+++ b/apps/test/common/chre_cross_validator_wifi/Makefile
@@ -20,6 +20,8 @@
 NANOAPP_VERSION = 0x00000001
 
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/chre_cross_validator_wifi
+TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
+
 
 # Protobuf Sources #############################################################
 
@@ -34,6 +36,7 @@
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_cross_validator_wifi.cc
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_cross_validator_wifi_manager.cc
 COMMON_SRCS += $(NANOAPP_PATH)/src/wifi_scan_result.cc
+COMMON_SRCS += $(TEST_SHARED_PATH)/src/send_message.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/callbacks.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/wifi.cc
 
@@ -42,9 +45,11 @@
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
 COMMON_CFLAGS += -DLOG_TAG=\"[ChreCrossValidatorWifi]\"
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Includes
 COMMON_CFLAGS += -I$(NANOAPP_PATH)/inc
+COMMON_CFLAGS += -I$(TEST_SHARED_PATH)/inc
 
 # Permission declarations ######################################################
 
diff --git a/apps/test/common/chre_cross_validator_wifi/inc/chre_cross_validator_wifi_manager.h b/apps/test/common/chre_cross_validator_wifi/inc/chre_cross_validator_wifi_manager.h
index 5c3ff80..ffc2bae 100644
--- a/apps/test/common/chre_cross_validator_wifi/inc/chre_cross_validator_wifi_manager.h
+++ b/apps/test/common/chre_cross_validator_wifi/inc/chre_cross_validator_wifi_manager.h
@@ -20,12 +20,12 @@
 #include <cinttypes>
 #include <cstdint>
 
-#include <chre.h>
 #include <pb_common.h>
 #include <pb_decode.h>
 #include <pb_encode.h>
 
 #include "chre/util/singleton.h"
+#include "chre_api/chre.h"
 
 #include "chre_cross_validation_wifi.nanopb.h"
 #include "chre_test_common.nanopb.h"
@@ -134,15 +134,6 @@
       uint32_t capabilitiesFromChre);
 
   /**
-   * Encode the proto message and send to host.
-   *
-   * @param message The proto message struct pointer.
-   * @param fields The fields descriptor of the proto message to encode.
-   * @param messageType The message type of the message.
-   */
-  void encodeAndSendMessageToHost(const void *message, const pb_field_t *fields,
-                                  uint32_t messageType);
-  /**
    * Handle a wifi scan result data message sent from AP.
    *
    * @param hostData The message.
diff --git a/apps/test/common/chre_cross_validator_wifi/inc/wifi_scan_result.h b/apps/test/common/chre_cross_validator_wifi/inc/wifi_scan_result.h
index 1e762f6..3e3a6e9 100644
--- a/apps/test/common/chre_cross_validator_wifi/inc/wifi_scan_result.h
+++ b/apps/test/common/chre_cross_validator_wifi/inc/wifi_scan_result.h
@@ -19,9 +19,9 @@
 
 #include <cinttypes>
 
-#include <chre.h>
 #include <pb_decode.h>
 
+#include "chre_api/chre.h"
 #include "chre_cross_validation_wifi.nanopb.h"
 
 class WifiScanResult {
diff --git a/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi.cc b/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi.cc
index abf2dce..2e6114d 100644
--- a/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi.cc
+++ b/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi.cc
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
+
 #include "chre_cross_validator_wifi_manager.h"
 
+#include "chre_api/chre.h"
+
 namespace chre {
 
 extern "C" void nanoappHandleEvent(uint32_t senderInstanceId,
diff --git a/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi_manager.cc b/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi_manager.cc
index e4b35d4..49af65e 100644
--- a/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi_manager.cc
+++ b/apps/test/common/chre_cross_validator_wifi/src/chre_cross_validator_wifi_manager.cc
@@ -15,7 +15,6 @@
 
 #include "chre_cross_validator_wifi_manager.h"
 
-#include <chre.h>
 #include <stdio.h>
 #include <algorithm>
 #include <cinttypes>
@@ -25,8 +24,10 @@
 #include "chre/util/nanoapp/callbacks.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/nanoapp/wifi.h"
+#include "chre_api/chre.h"
 #include "chre_cross_validation_wifi.nanopb.h"
 #include "chre_test_common.nanopb.h"
+#include "send_message.h"
 
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
 
@@ -98,8 +99,8 @@
     case chre_cross_validation_wifi_Step_CAPABILITIES: {
       chre_cross_validation_wifi_WifiCapabilities wifiCapabilities =
           makeWifiCapabilitiesMessage(chreWifiGetCapabilities());
-      encodeAndSendMessageToHost(
-          static_cast<void *>(&wifiCapabilities),
+      test_shared::sendMessageToHost(
+          mCrossValidatorState.hostEndpoint, &wifiCapabilities,
           chre_cross_validation_wifi_WifiCapabilities_fields,
           chre_cross_validation_wifi_MessageType_WIFI_CAPABILITIES);
       break;
@@ -108,12 +109,11 @@
       if (!chreWifiConfigureScanMonitorAsync(true /* enable */,
                                              &kScanMonitoringCookie)) {
         LOGE("chreWifiConfigureScanMonitorAsync() failed");
-        chre_test_common_TestResult testResult =
-            makeTestResultProtoMessage(false, "setupWifiScanMonitoring failed");
-        encodeAndSendMessageToHost(
-            static_cast<void *>(&testResult),
-            chre_test_common_TestResult_fields,
-            chre_cross_validation_wifi_MessageType_STEP_RESULT);
+        test_shared::sendTestResultWithMsgToHost(
+            mCrossValidatorState.hostEndpoint,
+            chre_cross_validation_wifi_MessageType_STEP_RESULT,
+            false /*success*/, "setupWifiScanMonitoring failed",
+            false /*abortOnFailure*/);
       } else {
         LOGD("chreWifiConfigureScanMonitorAsync() succeeded");
         if (stepStartCommand.has_chreScanCapacity) {
@@ -172,16 +172,19 @@
   // TODO(b/185188753): Log info about all scan results so that it is easier
   // to figure out which AP or CHRE scan results are missing or corrupted.
   if (belowMaxSizeCheck || aboveMaxSizeCheck) {
-    testResult = makeTestResultProtoMessage(
-        false, "There is a different number of AP and CHRE scan results.");
+    test_shared::sendTestResultWithMsgToHost(
+        mCrossValidatorState.hostEndpoint,
+        chre_cross_validation_wifi_MessageType_STEP_RESULT, false /*success*/,
+        "There is a different number of AP and CHRE scan results.",
+        false /*abortOnFailure*/);
     LOGE("AP and CHRE wifi scan result counts differ, AP = %" PRIu8
          ", CHRE = %" PRIu8,
          mApScanResultsSize, mChreScanResultsSize);
   } else {
     verifyScanResults(&testResult);
   }
-  encodeAndSendMessageToHost(
-      static_cast<const void *>(&testResult),
+  test_shared::sendMessageToHost(
+      mCrossValidatorState.hostEndpoint, &testResult,
       chre_test_common_TestResult_fields,
       chre_cross_validation_wifi_MessageType_STEP_RESULT);
 }
@@ -291,30 +294,6 @@
   return capabilities;
 }
 
-void Manager::encodeAndSendMessageToHost(const void *message,
-                                         const pb_field_t *fields,
-                                         uint32_t messageType) {
-  size_t encodedSize;
-  if (!pb_get_encoded_size(&encodedSize, fields, message)) {
-    LOGE("Could not get encoded size of test result message");
-  } else {
-    pb_byte_t *buffer = static_cast<pb_byte_t *>(chreHeapAlloc(encodedSize));
-    if (buffer == nullptr) {
-      LOG_OOM();
-    } else {
-      pb_ostream_t ostream = pb_ostream_from_buffer(buffer, encodedSize);
-      if (!pb_encode(&ostream, fields, message)) {
-        LOGE("Could not encode data proto message");
-      } else if (!chreSendMessageToHostEndpoint(
-                     static_cast<void *>(buffer), encodedSize, messageType,
-                     mCrossValidatorState.hostEndpoint,
-                     heapFreeMessageCallback)) {
-        LOGE("Could not send message to host");
-      }
-    }
-  }
-}
-
 void Manager::handleWifiAsyncResult(const chreAsyncResult *result) {
   chre_test_common_TestResult testResult;
   bool sendMessage = false;
@@ -343,8 +322,9 @@
     sendMessage = true;
   }
   if (sendMessage) {
-    encodeAndSendMessageToHost(
-        static_cast<void *>(&testResult), chre_test_common_TestResult_fields,
+    test_shared::sendMessageToHost(
+        mCrossValidatorState.hostEndpoint, &testResult,
+        chre_test_common_TestResult_fields,
         chre_cross_validation_wifi_MessageType_STEP_RESULT);
   }
 }
diff --git a/apps/test/common/chre_cross_validator_wifi/src/wifi_scan_result.cc b/apps/test/common/chre_cross_validator_wifi/src/wifi_scan_result.cc
index 41b7037..090f732 100644
--- a/apps/test/common/chre_cross_validator_wifi/src/wifi_scan_result.cc
+++ b/apps/test/common/chre_cross_validator_wifi/src/wifi_scan_result.cc
@@ -16,9 +16,8 @@
 
 #include "wifi_scan_result.h"
 
-#include <chre.h>
-
 #include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
 
 #include <stdio.h>
 #include <cstring>
diff --git a/apps/test/common/chre_settings_test/Makefile b/apps/test/common/chre_settings_test/Makefile
index 698add1..50ef499 100644
--- a/apps/test/common/chre_settings_test/Makefile
+++ b/apps/test/common/chre_settings_test/Makefile
@@ -21,28 +21,34 @@
 NANOAPP_VERSION = 0x00000001
 
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/chre_settings_test
+TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
+
 
 # Protobuf Sources #############################################################
 
 NANOPB_EXTENSION = nanopb
 
 NANOPB_SRCS += $(NANOAPP_PATH)/../proto/chre_settings_test.proto
+NANOPB_SRCS += $(NANOAPP_PATH)/../proto/chre_test_common.proto
 NANOPB_INCLUDES = $(NANOAPP_PATH)/../proto
 
 # Source Code ##################################################################
 
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_settings_test.cc
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_settings_test_manager.cc
-COMMON_SRCS += $(NANOAPP_PATH)/src/chre_settings_test_util.cc
+COMMON_SRCS += $(TEST_SHARED_PATH)/src/send_message.cc
+COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/ble.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/callbacks.cc
 
 # Compiler Flags ###############################################################
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Includes
 COMMON_CFLAGS += -I$(NANOAPP_PATH)/inc
+COMMON_CFLAGS += -I$(TEST_SHARED_PATH)/inc
 
 # Permission declarations ######################################################
 
@@ -50,6 +56,7 @@
 CHRE_NANOAPP_USES_WIFI = true
 CHRE_NANOAPP_USES_WWAN = true
 CHRE_NANOAPP_USES_AUDIO = true
+CHRE_NANOAPP_USES_BLE = true
 
 # Makefile Includes ############################################################
 
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_manager.h b/apps/test/common/chre_settings_test/inc/chre_settings_test_manager.h
index e903cf6..89863dc 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_manager.h
+++ b/apps/test/common/chre_settings_test/inc/chre_settings_test_manager.h
@@ -19,11 +19,11 @@
 
 #include "chre_settings_test.nanopb.h"
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/optional.h"
 #include "chre/util/singleton.h"
+#include "chre_api/chre.h"
 
 namespace chre {
 
@@ -41,6 +41,7 @@
     GNSS_MEASUREMENT,
     WWAN_CELL_INFO,
     AUDIO,
+    BLE_SCANNING,
   };
 
   enum class FeatureState : uint8_t {
@@ -155,7 +156,17 @@
    */
   void handleAudioDataEvent(const struct chreAudioDataEvent *event);
 
-  void handleTimeout();
+  /*
+   * @param data CHRE event data containing the cookie used to set the timer.
+   */
+  void handleTimeout(const void *data);
+
+  /**
+   * Handles the BLE async result
+   *
+   * @param result The BLE scan event result
+   */
+  void handleBleAsyncResult(const chreAsyncResult *result);
 
   /**
    * End the current test session and sends result to host.
@@ -170,6 +181,9 @@
 
   //! The cached target to issue an RTT ranging request.
   chre::Optional<chreWifiRangingTarget> mCachedRangingTarget;
+
+  //! The number of scan result received when after getting a wifi async result.
+  uint16_t mReceivedScanResults;
 };
 
 // The settings test manager singleton.
diff --git a/apps/test/common/chre_settings_test/src/chre_settings_test.cc b/apps/test/common/chre_settings_test/src/chre_settings_test.cc
index e5104c4..17f269f 100644
--- a/apps/test/common/chre_settings_test/src/chre_settings_test.cc
+++ b/apps/test/common/chre_settings_test/src/chre_settings_test.cc
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
+#include "chre_api/chre.h"
 #include "chre_settings_test_manager.h"
 
 namespace chre {
diff --git a/apps/test/common/chre_settings_test/src/chre_settings_test_manager.cc b/apps/test/common/chre_settings_test/src/chre_settings_test_manager.cc
index 0d6f89b..77b8ac6 100644
--- a/apps/test/common/chre_settings_test/src/chre_settings_test_manager.cc
+++ b/apps/test/common/chre_settings_test/src/chre_settings_test_manager.cc
@@ -20,14 +20,18 @@
 #include <pb_encode.h>
 
 #include "chre/util/macros.h"
+#include "chre/util/nanoapp/ble.h"
 #include "chre/util/nanoapp/callbacks.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
 #include "chre_settings_test.nanopb.h"
-#include "chre_settings_test_util.h"
+#include "send_message.h"
 
 #define LOG_TAG "[ChreSettingsTest]"
 
+using chre::createBleScanFilterForKnownBeacons;
+using chre::ble_constants::kNumScanFilters;
+
 namespace chre {
 
 namespace settings_test {
@@ -45,7 +49,10 @@
 // not-suspended event).
 bool gGotSourceEnabledEvent = false;
 
-uint32_t gTimerHandle = CHRE_TIMER_INVALID;
+uint32_t gAudioDataTimerHandle = CHRE_TIMER_INVALID;
+constexpr uint32_t kAudioDataTimerCookie = 0xc001cafe;
+uint32_t gAudioStatusTimerHandle = CHRE_TIMER_INVALID;
+constexpr uint32_t kAudioStatusTimerCookie = 0xb01dcafe;
 
 bool getFeature(const chre_settings_test_TestCommand &command,
                 Manager::Feature *feature) {
@@ -69,6 +76,9 @@
     case chre_settings_test_TestCommand_Feature_AUDIO:
       *feature = Manager::Feature::AUDIO;
       break;
+    case chre_settings_test_TestCommand_Feature_BLE_SCANNING:
+      *feature = Manager::Feature::BLE_SCANNING;
+      break;
     default:
       LOGE("Unknown feature %d", command.feature);
       success = false;
@@ -175,6 +185,12 @@
       supported = chreAudioGetSource(0 /* handle */, &source);
       break;
     }
+    case Feature::BLE_SCANNING: {
+      uint32_t capabilities = chreBleGetCapabilities();
+      supported = (version >= CHRE_API_VERSION_1_7) &&
+                  ((capabilities & CHRE_BLE_CAPABILITIES_SCAN) != 0);
+      break;
+    }
     default:
       LOGE("Unknown feature %" PRIu8, static_cast<uint8_t>(feature));
   }
@@ -217,7 +233,9 @@
   }
 
   if (!success) {
-    sendTestResultToHost(hostData->hostEndpoint, false /* success */);
+    test_shared::sendTestResultToHost(
+        hostData->hostEndpoint, chre_settings_test_MessageType_TEST_RESULT,
+        false /* success */);
   }
 }
 
@@ -265,7 +283,7 @@
         break;
 
       case CHRE_EVENT_TIMER:
-        handleTimeout();
+        handleTimeout(eventData);
         break;
 
       case CHRE_EVENT_WIFI_ASYNC_RESULT:
@@ -285,6 +303,10 @@
             static_cast<const chreWwanCellInfoResult *>(eventData));
         break;
 
+      case CHRE_EVENT_BLE_ASYNC_RESULT:
+        handleBleAsyncResult(static_cast<const chreAsyncResult *>(eventData));
+        break;
+
       default:
         LOGE("Unknown event type %" PRIu16, eventType);
     }
@@ -334,6 +356,15 @@
       break;
     }
 
+    case Feature::BLE_SCANNING: {
+      struct chreBleScanFilter filter;
+      chreBleGenericFilter uuidFilters[kNumScanFilters];
+      createBleScanFilterForKnownBeacons(filter, uuidFilters, kNumScanFilters);
+      success = chreBleStartScanAsync(CHRE_BLE_SCAN_MODE_FOREGROUND /* mode */,
+                                      0 /* reportDelayMs */, &filter);
+      break;
+    }
+
     default:
       LOGE("Unknown feature %" PRIu8, static_cast<uint8_t>(feature));
       return false;
@@ -414,6 +445,7 @@
       LOGE("Received empty WiFi scan result");
       sendTestResult(mTestSession->hostEndpointId, false /* success */);
     } else {
+      mReceivedScanResults += result->resultCount;
       chreWifiRangingTarget target;
       // Try to find an AP with the FTM responder flag set. The RTT ranging
       // request should still work equivalently even if the flag is not set (but
@@ -429,10 +461,12 @@
       }
       chreWifiRangingTargetFromScanResult(&result->results[index], &target);
       mCachedRangingTarget = target;
-
-      sendEmptyMessageToHost(
-          mTestSession->hostEndpointId,
-          chre_settings_test_MessageType_TEST_SETUP_COMPLETE);
+      if (result->resultTotal == mReceivedScanResults) {
+        mReceivedScanResults = 0;
+        test_shared::sendEmptyMessageToHost(
+            mTestSession->hostEndpointId,
+            chre_settings_test_MessageType_TEST_SETUP_COMPLETE);
+      }
     }
   }
 }
@@ -521,11 +555,11 @@
         if (chreAudioGetSource(0 /* handle */, &source)) {
           const uint64_t duration =
               source.minBufferDuration + kOneSecondInNanoseconds;
-          gTimerHandle =
-              chreTimerSet(duration, nullptr /* cookie */, true /* oneShot */);
+          gAudioDataTimerHandle = chreTimerSet(duration, &kAudioDataTimerCookie,
+                                               true /* oneShot */);
 
-          if (gTimerHandle == CHRE_TIMER_INVALID) {
-            LOGE("Failed to set timer");
+          if (gAudioDataTimerHandle == CHRE_TIMER_INVALID) {
+            LOGE("Failed to set data check timer");
           } else {
             success = true;
           }
@@ -533,7 +567,23 @@
           LOGE("Failed to query audio source");
         }
       } else {
-        LOGE("Source wasn't suspended when Mic Access was disabled");
+        // There might be a corner case where CHRE might have queued an audio
+        // available event just as the microphone disable setting change is
+        // received that might wrongfully indicate that microphone access
+        // wasn't disabled when it is dispatched. We add a 2 second timer to
+        // allow CHRE to send the source status change event to account for
+        // this, and fail the test if the timer expires without getting said
+        // event.
+        LOGW("Source wasn't suspended when Mic Access disabled, waiting 2 sec");
+        gAudioStatusTimerHandle =
+            chreTimerSet(2 * kOneSecondInNanoseconds, &kAudioStatusTimerCookie,
+                         true /* oneShot */);
+        if (gAudioStatusTimerHandle == CHRE_TIMER_INVALID) {
+          LOGE("Failed to set audio status check timer");
+        } else {
+          // continue the test, fail on timeout.
+          success = true;
+        }
       }
     } else {
       gGotSourceEnabledEvent = true;
@@ -552,9 +602,9 @@
   bool success = false;
   if (mTestSession.has_value()) {
     if (mTestSession->featureState == FeatureState::ENABLED) {
-      if (gTimerHandle != CHRE_TIMER_INVALID) {
-        chreTimerCancel(gTimerHandle);
-        gTimerHandle = CHRE_TIMER_INVALID;
+      if (gAudioDataTimerHandle != CHRE_TIMER_INVALID) {
+        chreTimerCancel(gAudioDataTimerHandle);
+        gAudioDataTimerHandle = CHRE_TIMER_INVALID;
       }
     } else if (gGotSourceEnabledEvent) {
       success = true;
@@ -566,15 +616,51 @@
   }
 }
 
-void Manager::handleTimeout() {
-  gTimerHandle = CHRE_TIMER_INVALID;
+void Manager::handleTimeout(const void *eventData) {
+  bool testSuccess = false;
+  auto *cookie = static_cast<const uint32_t *>(eventData);
+
+  if (*cookie == kAudioDataTimerCookie) {
+    gAudioDataTimerHandle = CHRE_TIMER_INVALID;
+    testSuccess = true;
+    if (gAudioStatusTimerHandle != CHRE_TIMER_INVALID) {
+      chreTimerCancel(gAudioStatusTimerHandle);
+      gAudioStatusTimerHandle = CHRE_TIMER_INVALID;
+    }
+  } else if (*cookie == kAudioStatusTimerCookie) {
+    LOGE("Source wasn't suspended when Mic Access was disabled");
+    gAudioStatusTimerHandle = CHRE_TIMER_INVALID;
+    testSuccess = false;
+  } else {
+    LOGE("Invalid timer cookie: %" PRIx32, *cookie);
+  }
   chreAudioConfigureSource(0 /*handle*/, false /*enable*/,
                            0 /*minBufferDuration*/, 0 /*maxBufferDuration*/);
-  sendTestResult(mTestSession->hostEndpointId, true /*success*/);
+  sendTestResult(mTestSession->hostEndpointId, testSuccess);
+}
+
+void Manager::handleBleAsyncResult(const chreAsyncResult *result) {
+  bool success = false;
+  switch (result->requestType) {
+    case CHRE_BLE_REQUEST_TYPE_START_SCAN: {
+      if (mTestSession->feature != Feature::BLE_SCANNING) {
+        LOGE("Unexpected BLE scan async result: test feature %" PRIu8,
+             static_cast<uint8_t>(mTestSession->feature));
+      } else {
+        success = validateAsyncResult(result, nullptr);
+      }
+      break;
+    }
+    default:
+      LOGE("Unexpected BLE request type %" PRIu8, result->requestType);
+  }
+
+  sendTestResult(mTestSession->hostEndpointId, success);
 }
 
 void Manager::sendTestResult(uint16_t hostEndpointId, bool success) {
-  sendTestResultToHost(hostEndpointId, success);
+  test_shared::sendTestResultToHost(
+      hostEndpointId, chre_settings_test_MessageType_TEST_RESULT, success);
   mTestSession.reset();
   mCachedRangingTarget.reset();
 }
diff --git a/apps/test/common/chre_settings_test/src/chre_settings_test_util.cc b/apps/test/common/chre_settings_test/src/chre_settings_test_util.cc
deleted file mode 100644
index 4f39b85..0000000
--- a/apps/test/common/chre_settings_test/src/chre_settings_test_util.cc
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <chre.h>
-#include <pb_encode.h>
-#include <cinttypes>
-
-#include "chre_settings_test.nanopb.h"
-#include "chre_settings_test_manager.h"
-
-#include "chre/util/nanoapp/callbacks.h"
-#include "chre/util/nanoapp/log.h"
-
-#define LOG_TAG "ChreSettingsTest"
-
-namespace chre {
-
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success) {
-  // Unspecified endpoint is not allowed in chreSendMessageToHostEndpoint.
-  if (hostEndpointId == CHRE_HOST_ENDPOINT_UNSPECIFIED) {
-    hostEndpointId = CHRE_HOST_ENDPOINT_BROADCAST;
-  }
-
-  chre_settings_test_TestResult result =
-      chre_settings_test_TestResult_init_default;
-  result.has_code = true;
-  result.code = success ? chre_settings_test_TestResult_Code_PASSED
-                        : chre_settings_test_TestResult_Code_FAILED;
-  size_t size;
-  if (!pb_get_encoded_size(&size, chre_settings_test_TestResult_fields,
-                           &result)) {
-    LOGE("Failed to get message size");
-  } else {
-    pb_byte_t *bytes = static_cast<pb_byte_t *>(chreHeapAlloc(size));
-    if (bytes == nullptr) {
-      LOG_OOM();
-    } else {
-      pb_ostream_t stream = pb_ostream_from_buffer(bytes, size);
-      if (!pb_encode(&stream, chre_settings_test_TestResult_fields, &result)) {
-        LOGE("Failed to encode test result error %s", PB_GET_ERROR(&stream));
-        chreHeapFree(bytes);
-      } else {
-        chreSendMessageToHostEndpoint(
-            bytes, size, chre_settings_test_MessageType_TEST_RESULT,
-            hostEndpointId, heapFreeMessageCallback);
-      }
-    }
-  }
-}
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType) {
-  // Unspecified endpoint is not allowed in chreSendMessageToHostEndpoint.
-  if (hostEndpointId == CHRE_HOST_ENDPOINT_UNSPECIFIED) {
-    hostEndpointId = CHRE_HOST_ENDPOINT_BROADCAST;
-  }
-
-  chreSendMessageToHostEndpoint(nullptr /* message */, 0 /* messageSize */,
-                                messageType, hostEndpointId,
-                                nullptr /* freeCallback */);
-}
-
-}  // namespace settings_test
-
-}  // namespace chre
diff --git a/apps/test/common/chre_stress_test/Makefile b/apps/test/common/chre_stress_test/Makefile
index a8a87fb..42f4e88 100644
--- a/apps/test/common/chre_stress_test/Makefile
+++ b/apps/test/common/chre_stress_test/Makefile
@@ -17,11 +17,12 @@
 NANOAPP_NAME = chre_stress_test
 NANOAPP_ID = 0x476f6f675400000a
 NANOAPP_NAME_STRING = \"CHRE\ Stress\ Test\"
-NANOAPP_VERSION = 0x00000004
+NANOAPP_VERSION = 0x00000007
 
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/chre_stress_test
 TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
 
+
 # Protobuf Sources #############################################################
 
 NANOPB_EXTENSION = nanopb
@@ -36,11 +37,13 @@
 COMMON_SRCS += $(NANOAPP_PATH)/src/chre_stress_test_manager.cc
 COMMON_SRCS += $(TEST_SHARED_PATH)/src/send_message.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/callbacks.cc
+COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/ble.cc
 
 # Compiler Flags ###############################################################
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Includes
 COMMON_CFLAGS += -I$(NANOAPP_PATH)/inc
@@ -51,6 +54,8 @@
 CHRE_NANOAPP_USES_WIFI = true
 CHRE_NANOAPP_USES_GNSS = true
 CHRE_NANOAPP_USES_WWAN = true
+CHRE_NANOAPP_USES_AUDIO = true
+CHRE_NANOAPP_USES_BLE = true
 
 # Makefile Includes ############################################################
 
diff --git a/apps/test/common/chre_stress_test/inc/chre_stress_test_manager.h b/apps/test/common/chre_stress_test/inc/chre_stress_test_manager.h
index b6c8f91..5604323 100644
--- a/apps/test/common/chre_stress_test/inc/chre_stress_test_manager.h
+++ b/apps/test/common/chre_stress_test/inc/chre_stress_test_manager.h
@@ -19,17 +19,24 @@
 
 #include "chre_stress_test.nanopb.h"
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/optional.h"
 #include "chre/util/singleton.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 
 namespace chre {
 
 namespace stress_test {
 
+//! Lists types of BLE scan request.
+enum BleScanRequestType {
+  NO_FILTER = 0,
+  SERVICE_DATA_16 = 1,
+  STOP_SCAN = 2,
+};
+
 /**
  * A class to manage a CHRE stress test session.
  */
@@ -51,6 +58,23 @@
     const void *cookie;
   };
 
+  struct SensorState {
+    //! Corresponds to types defined in chre_api/sensor_types.h.
+    const uint8_t type;
+
+    //! The sampling interval for the next sensor request.
+    uint64_t samplingInterval;
+
+    //! The sensor handle obtained from chreSensorFindDefault().
+    uint32_t handle;
+
+    //! Indicate if the sensor is already configured.
+    bool enabled;
+
+    //! Information about this sensor.
+    chreSensorInfo info;
+  };
+
   /**
    * Handles a message from the host.
    *
@@ -82,6 +106,16 @@
   void checkTimestamp(uint64_t timestamp, uint64_t pastTimestamp);
 
   /**
+   * Validates the difference between timestamps is below a certain interval
+   *
+   * @param timestamp The timestamp.
+   * @param pastTimestamp The previous timestamp.
+   * @param maxInterval The max interval allowed between the two timestamps.
+   */
+  void checkTimestampInterval(uint64_t timestamp, uint64_t pastTimestamp,
+                              uint64_t maxInterval);
+
+  /**
    * Handles a start command from the host.
    *
    * @param start true to start the test, stop otherwise.
@@ -91,6 +125,9 @@
   void handleGnssMeasurementStartCommand(bool start);
   void handleWwanStartCommand(bool start);
   void handleWifiScanMonitoringCommand(bool start);
+  void handleSensorStartCommand(bool start);
+  void handleAudioStartCommand(bool start);
+  void handleBleStartCommand(bool start);
 
   /**
    * @param result The WiFi async result from CHRE.
@@ -157,10 +194,69 @@
   void makeWwanCellInfoRequest();
 
   /**
+   * Send the capabilities to the host.
+   */
+  void sendCapabilitiesMessage();
+
+  /**
    * @param event The cell info event from CHRE.
    */
   void handleCellInfoResult(const chreWwanCellInfoResult *event);
 
+  /**
+   * @param eventData The sensor data from CHRE.
+   */
+  void handleAccelSensorDataEvent(const chreSensorThreeAxisData *eventData);
+  void handleGyroSensorDataEvent(const chreSensorThreeAxisData *eventData);
+  void handleInstantMotionSensorDataEvent(
+      const chreSensorOccurrenceData *eventData);
+  void handleSensorSamplingChangeEvent(
+      const chreSensorSamplingStatusEvent *eventData);
+
+  /**
+   * Makes the next sensor request.
+   */
+  void makeSensorRequests();
+
+  /**
+   * Send a disable request to all sensors.
+   */
+  void stopSensorRequests();
+
+  /**
+   * @param event The audio event from CHRE.
+   */
+  void handleAudioDataEvent(const chreAudioDataEvent *event);
+  void handleAudioSamplingChangeEvent(const chreAudioSourceStatusEvent *event);
+
+  /**
+   * Makes the next audio request.
+   */
+  void makeAudioRequest();
+
+  /**
+   * @param event The BLE advertisement event from CHRE.
+   */
+  void handleBleAdvertismentEvent(const chreBleAdvertisementEvent *event);
+
+  /**
+   * @param event The BLE event from CHRE.
+   */
+  void handleBleAsyncResult(const chreAsyncResult *result);
+
+  /**
+   * Makes the next Ble request.
+   */
+  void makeBleScanRequest();
+
+  /**
+   * @param scanRequestType The current BLE scan request type.
+   *
+   * @return The pointer to a chreBleScanFilter that corresponds to the scan
+   * request type.
+   */
+  chreBleScanFilter *getBleScanFilter(BleScanRequestType &scanRequestType);
+
   //! The host endpoint of the current test host.
   Optional<uint16_t> mHostEndpoint;
 
@@ -173,16 +269,28 @@
   uint32_t mGnssMeasurementAsyncTimerHandle = CHRE_TIMER_INVALID;
   uint32_t mWwanTimerHandle = CHRE_TIMER_INVALID;
   uint32_t mWifiScanMonitorAsyncTimerHandle = CHRE_TIMER_INVALID;
+  uint32_t mSensorTimerHandle = CHRE_TIMER_INVALID;
+  uint32_t mAudioTimerHandle = CHRE_TIMER_INVALID;
+  uint32_t mBleScanTimerHandle = CHRE_TIMER_INVALID;
 
   //! true if the test has been started for the feature.
   bool mWifiTestStarted = false;
   bool mGnssLocationTestStarted = false;
   bool mGnssMeasurementTestStarted = false;
   bool mWwanTestStarted = false;
+  bool mSensorTestStarted = false;
+  bool mAudioTestStarted = false;
+  bool mBleTestStarted = false;
 
   //! true if scan monitor is enabled for the nanoapp.
   bool mWifiScanMonitorEnabled = false;
 
+  //! True if audio is enabled for the nanoapp.
+  bool mAudioEnabled = false;
+
+  //! True if ble is enabled for the nanoapp.
+  bool mBleEnabled = false;
+
   //! The cookie to use for requests.
   const uint32_t kOnDemandWifiScanCookie = 0xface;
   const uint32_t kGnssLocationCookie = 0xbeef;
@@ -200,6 +308,46 @@
   uint64_t mPrevGnssMeasurementEventTimestampNs = 0;
   uint64_t mPrevWifiScanEventTimestampNs = 0;
   uint64_t mPrevWwanCellInfoEventTimestampNs = 0;
+  uint64_t mPrevAccelEventTimestampNs = 0;
+  uint64_t mPrevGyroEventTimestampNs = 0;
+  uint64_t mPrevInstantMotionEventTimestampNs = 0;
+  uint64_t mPrevAudioEventTimestampMs = 0;
+  uint64_t mPrevBleAdTimestampMs = 0;
+
+  //! Number of ble scan mode.
+  static constexpr uint32_t kNumBleScanModes = 3;
+
+  //! List of all ble scan mode.
+  const chreBleScanMode kScanModes[kNumBleScanModes] = {
+      CHRE_BLE_SCAN_MODE_BACKGROUND, CHRE_BLE_SCAN_MODE_FOREGROUND,
+      CHRE_BLE_SCAN_MODE_AGGRESSIVE};
+
+  //! Current number of sensors tested.
+  static constexpr uint32_t kNumSensors = 3;
+
+  //! List of sensors.
+  SensorState mSensors[kNumSensors] = {
+      {
+          .type = CHRE_SENSOR_TYPE_ACCELEROMETER,
+          .samplingInterval = CHRE_SENSOR_INTERVAL_DEFAULT,
+          .handle = 0,
+          .enabled = true,
+          .info = {},
+      },
+      {
+          .type = CHRE_SENSOR_TYPE_GYROSCOPE,
+          .samplingInterval = CHRE_SENSOR_INTERVAL_DEFAULT,
+          .handle = 0,
+          .enabled = true,
+          .info = {},
+      },
+      {
+          .type = CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT,
+          .samplingInterval = CHRE_SENSOR_INTERVAL_DEFAULT,
+          .handle = 0,
+          .enabled = true,
+          .info = {},
+      }};
 };
 
 // The stress test manager singleton.
diff --git a/apps/test/common/chre_stress_test/src/chre_stress_test.cc b/apps/test/common/chre_stress_test/src/chre_stress_test.cc
index c52122e..ed97652 100644
--- a/apps/test/common/chre_stress_test/src/chre_stress_test.cc
+++ b/apps/test/common/chre_stress_test/src/chre_stress_test.cc
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
+#include "chre_api/chre.h"
 #include "chre_stress_test_manager.h"
 
 namespace chre {
diff --git a/apps/test/common/chre_stress_test/src/chre_stress_test_manager.cc b/apps/test/common/chre_stress_test/src/chre_stress_test_manager.cc
index 4be5765..4c5c618 100644
--- a/apps/test/common/chre_stress_test/src/chre_stress_test_manager.cc
+++ b/apps/test/common/chre_stress_test/src/chre_stress_test_manager.cc
@@ -17,15 +17,24 @@
 #include "chre_stress_test_manager.h"
 
 #include <pb_decode.h>
+#include <pb_encode.h>
 
 #include "chre/util/macros.h"
+#include "chre/util/memory.h"
+#include "chre/util/nanoapp/audio.h"
+#include "chre/util/nanoapp/ble.h"
 #include "chre/util/nanoapp/callbacks.h"
 #include "chre/util/nanoapp/log.h"
+#include "chre/util/unique_ptr.h"
 #include "chre_stress_test.nanopb.h"
 #include "send_message.h"
 
 #define LOG_TAG "[ChreStressTest]"
 
+using chre::kOneMicrosecondInNanoseconds;
+using chre::kOneMillisecondInNanoseconds;
+using chre::ble_constants::kNumScanFilters;
+
 namespace chre {
 
 namespace stress_test {
@@ -37,6 +46,16 @@
 #define TIMEOUT_BUFFER_DELAY_NS (1 * CHRE_NSEC_PER_SEC)
 
 constexpr chre::Nanoseconds kWifiScanInterval = chre::Seconds(5);
+constexpr chre::Nanoseconds kSensorRequestInterval = chre::Seconds(5);
+constexpr chre::Nanoseconds kAudioRequestInterval = chre::Seconds(5);
+constexpr chre::Nanoseconds kBleRequestInterval = chre::Seconds(5);
+constexpr uint64_t kSensorSamplingDelayNs = 0;
+constexpr uint8_t kAccelSensorIndex = 0;
+constexpr uint8_t kGyroSensorIndex = 1;
+constexpr uint8_t kInstantMotionSensorIndex = 1;
+
+//! Report delay for BLE scans.
+constexpr uint32_t gBleBatchDurationMs = 0;
 
 bool isRequestTypeForLocation(uint8_t requestType) {
   return (requestType == CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START) ||
@@ -48,6 +67,18 @@
          (requestType == CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_STOP);
 }
 
+bool enableBleScans() {
+  struct chreBleScanFilter filter;
+  chreBleGenericFilter uuidFilters[kNumScanFilters];
+  createBleScanFilterForKnownBeacons(filter, uuidFilters, kNumScanFilters);
+  return chreBleStartScanAsync(CHRE_BLE_SCAN_MODE_BACKGROUND,
+                               gBleBatchDurationMs, &filter);
+}
+
+bool disableBleScans() {
+  return chreBleStopScanAsync();
+}
+
 }  // anonymous namespace
 
 void Manager::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
@@ -75,6 +106,9 @@
     // Do nothing and only update the host endpoint
     mHostEndpoint = hostData->hostEndpoint;
     success = true;
+  } else if (messageType == chre_stress_test_MessageType_GET_CAPABILITIES) {
+    sendCapabilitiesMessage();
+    success = true;
   } else if (messageType != chre_stress_test_MessageType_TEST_COMMAND) {
     LOGE("Invalid message type %" PRIu32, messageType);
   } else if (mHostEndpoint.has_value() &&
@@ -117,6 +151,18 @@
           handleWifiScanMonitoringCommand(testCommand.start);
           break;
         }
+        case chre_stress_test_TestCommand_Feature_SENSORS: {
+          handleSensorStartCommand(testCommand.start);
+          break;
+        }
+        case chre_stress_test_TestCommand_Feature_AUDIO: {
+          handleAudioStartCommand(testCommand.start);
+          break;
+        }
+        case chre_stress_test_TestCommand_Feature_BLE: {
+          handleBleStartCommand(testCommand.start);
+          break;
+        }
         default: {
           LOGE("Unknown feature %d", testCommand.feature);
           success = false;
@@ -168,6 +214,48 @@
           static_cast<const chreWwanCellInfoResult *>(eventData));
       break;
 
+    case CHRE_EVENT_SENSOR_ACCELEROMETER_DATA:
+      handleAccelSensorDataEvent(
+          static_cast<const chreSensorThreeAxisData *>(eventData));
+      break;
+
+    case CHRE_EVENT_SENSOR_GYROSCOPE_DATA:
+      handleGyroSensorDataEvent(
+          static_cast<const chreSensorThreeAxisData *>(eventData));
+      break;
+
+    case CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA:
+      handleInstantMotionSensorDataEvent(
+          static_cast<const chreSensorOccurrenceData *>(eventData));
+      break;
+
+    case CHRE_EVENT_SENSOR_SAMPLING_CHANGE:
+      handleSensorSamplingChangeEvent(
+          static_cast<const chreSensorSamplingStatusEvent *>(eventData));
+      break;
+
+    case CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO:
+      LOGI("Received gyro bias info");
+      break;
+
+    case CHRE_EVENT_AUDIO_DATA:
+      handleAudioDataEvent(static_cast<const chreAudioDataEvent *>(eventData));
+      break;
+
+    case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
+      handleAudioSamplingChangeEvent(
+          static_cast<const chreAudioSourceStatusEvent *>(eventData));
+      break;
+
+    case CHRE_EVENT_BLE_ADVERTISEMENT:
+      handleBleAdvertismentEvent(
+          static_cast<const chreBleAdvertisementEvent *>(eventData));
+      break;
+
+    case CHRE_EVENT_BLE_ASYNC_RESULT:
+      handleBleAsyncResult(static_cast<const chreAsyncResult *>(eventData));
+      break;
+
     default:
       LOGW("Unknown event type %" PRIu16, eventType);
       break;
@@ -183,6 +271,8 @@
     makeGnssLocationRequest();
   } else if (*handle == mGnssMeasurementTimerHandle) {
     makeGnssMeasurementRequest();
+  } else if (*handle == mSensorTimerHandle) {
+    makeSensorRequests();
   } else if (*handle == mGnssLocationAsyncTimerHandle &&
              mGnssLocationAsyncRequest.has_value()) {
     sendFailure("GNSS location async result timed out");
@@ -191,8 +281,12 @@
     sendFailure("GNSS measurement async result timed out");
   } else if (*handle == mWwanTimerHandle) {
     makeWwanCellInfoRequest();
+  } else if (*handle == mBleScanTimerHandle) {
+    makeBleScanRequest();
   } else if (*handle == mWifiScanMonitorAsyncTimerHandle) {
     sendFailure("WiFi scan monitor request timed out");
+  } else if (*handle == mAudioTimerHandle) {
+    makeAudioRequest();
   } else {
     sendFailure("Unknown timer handle");
   }
@@ -265,6 +359,19 @@
   }
 }
 
+void Manager::handleAudioDataEvent(const chreAudioDataEvent *event) {
+  uint64_t timestamp = event->timestamp;
+
+  checkTimestamp(timestamp, mPrevAudioEventTimestampMs);
+  mPrevAudioEventTimestampMs = timestamp;
+}
+
+void Manager::handleAudioSamplingChangeEvent(
+    const chreAudioSourceStatusEvent *event) {
+  LOGI("Received audio sampling change event - suspended: %d",
+       event->status.suspended);
+}
+
 void Manager::validateGnssAsyncResult(const chreAsyncResult *result,
                                       Optional<AsyncRequest> &request,
                                       uint32_t *asyncTimerHandle) {
@@ -280,6 +387,29 @@
   request.reset();
 }
 
+void Manager::handleBleAdvertismentEvent(
+    const chreBleAdvertisementEvent *event) {
+  for (uint8_t i = 0; i < event->numReports; i++) {
+    uint64_t timestamp =
+        event->reports[i].timestamp / chre::kOneMillisecondInNanoseconds;
+
+    checkTimestamp(timestamp, mPrevBleAdTimestampMs);
+    mPrevBleAdTimestampMs = timestamp;
+  }
+}
+
+void Manager::handleBleAsyncResult(const chreAsyncResult *result) {
+  const char *requestType =
+      result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN ? "start"
+                                                              : "stop";
+  if (result->success) {
+    LOGI("BLE %s scan success", requestType);
+    mBleEnabled = (result->requestType == CHRE_BLE_REQUEST_TYPE_START_SCAN);
+  } else {
+    LOGW("BLE %s scan failure: %" PRIu8, requestType, result->errorCode);
+  }
+}
+
 void Manager::checkTimestamp(uint64_t timestamp, uint64_t pastTimestamp) {
   if (timestamp < pastTimestamp) {
     sendFailure("Timestamp was too old");
@@ -288,6 +418,17 @@
   }
 }
 
+void Manager::checkTimestampInterval(uint64_t timestamp, uint64_t pastTimestamp,
+                                     uint64_t maxInterval) {
+  checkTimestamp(timestamp, pastTimestamp);
+  if (timestamp - pastTimestamp > maxInterval) {
+    LOGE("Timestamp is later than expected");
+    LOGI("Current timestamp %" PRIu64, timestamp);
+    LOGI("past timestamp %" PRIu64, pastTimestamp);
+    LOGI("Timestamp difference %" PRIu64, timestamp - pastTimestamp);
+  }
+}
+
 void Manager::handleGnssLocationEvent(const chreGnssLocationEvent *event) {
   LOGI("Received GNSS location event at %" PRIu64 " ms", event->timestamp);
 
@@ -328,6 +469,70 @@
   }
 }
 
+void Manager::handleAccelSensorDataEvent(
+    const chreSensorThreeAxisData *eventData) {
+  const auto &header = eventData->header;
+  uint64_t timestamp = header.baseTimestamp;
+
+  // Note: The stress test sends streaming data request for accel, so only
+  // non-batched data are checked for timestamp. The allowed interval between
+  // data events is selected 1 ms higher than the sensor sampling interval to
+  // account for processing delays.
+  if (header.readingCount == 1) {
+    if (mPrevAccelEventTimestampNs != 0) {
+      checkTimestampInterval(timestamp, mPrevAccelEventTimestampNs,
+                             mSensors[kAccelSensorIndex].samplingInterval +
+                                 kOneMillisecondInNanoseconds);
+    }
+    mPrevAccelEventTimestampNs = timestamp;
+  }
+}
+
+void Manager::handleGyroSensorDataEvent(
+    const chreSensorThreeAxisData *eventData) {
+  const auto &header = eventData->header;
+  uint64_t timestamp = header.baseTimestamp;
+
+  // Note: The stress test sends streaming data request for gyro, so only
+  // non-batched data are checked for timestamp. The interval is selected 1ms
+  // higher than the sensor sampling interval to account for processing delays.
+  if (header.readingCount == 1) {
+    if (mPrevGyroEventTimestampNs != 0) {
+      checkTimestampInterval(timestamp, mPrevGyroEventTimestampNs,
+                             mSensors[kGyroSensorIndex].samplingInterval +
+                                 kOneMillisecondInNanoseconds);
+    }
+    mPrevGyroEventTimestampNs = timestamp;
+  }
+}
+
+void Manager::handleInstantMotionSensorDataEvent(
+    const chreSensorOccurrenceData *eventData) {
+  const auto &header = eventData->header;
+  uint64_t timestamp = header.baseTimestamp;
+
+  mSensors[kInstantMotionSensorIndex].enabled = false;
+  checkTimestamp(timestamp, mPrevInstantMotionEventTimestampNs);
+  mPrevInstantMotionEventTimestampNs = timestamp;
+}
+
+void Manager::handleSensorSamplingChangeEvent(
+    const chreSensorSamplingStatusEvent *eventData) {
+  LOGI("Sampling Change: handle %" PRIu32 ", status: interval %" PRIu64
+       " latency %" PRIu64 " enabled %d",
+       eventData->sensorHandle, eventData->status.interval,
+       eventData->status.latency, eventData->status.enabled);
+  if (eventData->sensorHandle == mSensors[kAccelSensorIndex].handle &&
+      eventData->status.interval !=
+          mSensors[kAccelSensorIndex].samplingInterval) {
+    mSensors[kAccelSensorIndex].samplingInterval = eventData->status.interval;
+  } else if (eventData->sensorHandle == mSensors[kGyroSensorIndex].handle &&
+             eventData->status.interval !=
+                 mSensors[kGyroSensorIndex].samplingInterval) {
+    mSensors[kGyroSensorIndex].samplingInterval = eventData->status.interval;
+  }
+}
+
 void Manager::handleCellInfoResult(const chreWwanCellInfoResult *event) {
   LOGI("Received %" PRIu8 " cell info results", event->cellInfoCount);
 
@@ -369,7 +574,7 @@
       cancelTimer(&mGnssLocationTimerHandle);
     }
   } else {
-    sendFailure("Platform has no location capability");
+    LOGW("Platform has no location capability");
   }
 }
 
@@ -387,7 +592,7 @@
       cancelTimer(&mGnssMeasurementTimerHandle);
     }
   } else {
-    sendFailure("Platform has no GNSS measurement capability");
+    LOGW("Platform has no GNSS measurement capability");
   }
 }
 
@@ -405,7 +610,7 @@
       cancelTimer(&mWwanTimerHandle);
     }
   } else {
-    sendFailure("Platform has no WWAN cell info capability");
+    LOGW("Platform has no WWAN cell info capability");
   }
 }
 
@@ -423,7 +628,71 @@
                true /* oneShot */, &mWifiScanMonitorAsyncTimerHandle);
     }
   } else {
-    sendFailure("Platform has no WiFi scan monitoring capability");
+    LOGW("Platform has no WiFi scan monitoring capability");
+  }
+}
+
+void Manager::handleSensorStartCommand(bool start) {
+  mSensorTestStarted = start;
+  bool sensorsFound = true;
+
+  for (size_t i = 0; i < ARRAY_SIZE(mSensors); i++) {
+    SensorState &sensor = mSensors[i];
+    bool isInitialized = chreSensorFindDefault(sensor.type, &sensor.handle);
+    if (!isInitialized) {
+      sensorsFound = false;
+    } else {
+      chreSensorInfo &info = sensor.info;
+      bool infoStatus = chreGetSensorInfo(sensor.handle, &info);
+      if (infoStatus) {
+        sensor.samplingInterval = info.minInterval;
+        LOGI("SensorInfo: %s, Type=%" PRIu8
+             " OnChange=%d OneShot=%d Passive=%d "
+             "minInterval=%" PRIu64 "nsec",
+             info.sensorName, info.sensorType, info.isOnChange, info.isOneShot,
+             info.supportsPassiveMode, info.minInterval);
+      } else {
+        LOGE("chreGetSensorInfo failed");
+      }
+    }
+    LOGI("Sensor %zu initialized: %s with handle %" PRIu32, i,
+         isInitialized ? "true" : "false", sensor.handle);
+  }
+
+  if (sensorsFound) {
+    if (start) {
+      makeSensorRequests();
+    } else {
+      stopSensorRequests();
+      cancelTimer(&mSensorTimerHandle);
+    }
+  } else {
+    LOGW("Platform has no sensor capability");
+  }
+}
+
+void Manager::handleAudioStartCommand(bool start) {
+  mAudioTestStarted = start;
+  mAudioEnabled = true;
+
+  if (mAudioTestStarted) {
+    makeAudioRequest();
+  } else {
+    cancelTimer(&mAudioTimerHandle);
+  }
+}
+
+void Manager::handleBleStartCommand(bool start) {
+  if (chreBleGetCapabilities() & CHRE_BLE_CAPABILITIES_SCAN) {
+    mBleTestStarted = start;
+
+    if (start) {
+      makeBleScanRequest();
+    } else {
+      cancelTimer(&mBleScanTimerHandle);
+    }
+  } else {
+    sendFailure("Platform has no BLE capability");
   }
 }
 
@@ -446,6 +715,74 @@
   }
 }
 
+void Manager::makeSensorRequests() {
+  bool anySensorConfigured = false;
+  for (size_t i = 0; i < ARRAY_SIZE(mSensors); i++) {
+    SensorState &sensor = mSensors[i];
+    bool status = false;
+    if (!sensor.enabled) {
+      if (sensor.info.isOneShot) {
+        status = chreSensorConfigure(
+            sensor.handle, CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT,
+            CHRE_SENSOR_INTERVAL_DEFAULT, kSensorSamplingDelayNs);
+      } else {
+        status = chreSensorConfigure(
+            sensor.handle, CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
+            sensor.samplingInterval, kSensorSamplingDelayNs);
+      }
+    } else {
+      status = chreSensorConfigureModeOnly(sensor.handle,
+                                           CHRE_SENSOR_CONFIGURE_MODE_DONE);
+      if (i == kAccelSensorIndex) {
+        mPrevAccelEventTimestampNs = 0;
+      } else if (i == kGyroSensorIndex) {
+        mPrevGyroEventTimestampNs = 0;
+      }
+    }
+    if (status) {
+      sensor.enabled = !sensor.enabled;
+    }
+    LOGI("Configure [enable %d, status %d]: %s", sensor.enabled, status,
+         sensor.info.sensorName);
+    anySensorConfigured = anySensorConfigured || status;
+  }
+  if (anySensorConfigured) {
+    setTimer(kSensorRequestInterval.toRawNanoseconds(), true /* oneShot */,
+             &mSensorTimerHandle);
+  } else {
+    LOGW("Failed to make sensor request");
+  }
+}
+
+void Manager::stopSensorRequests() {
+  for (size_t i = 0; i < ARRAY_SIZE(mSensors); i++) {
+    SensorState &sensor = mSensors[i];
+    if (sensor.enabled) {
+      if (!chreSensorConfigureModeOnly(sensor.handle,
+                                       CHRE_SENSOR_CONFIGURE_MODE_DONE)) {
+        LOGE("Failed to disable sensor: %s", sensor.info.sensorName);
+      }
+    }
+  }
+}
+
+void Manager::makeBleScanRequest() {
+  bool success = false;
+
+  if (!mBleEnabled) {
+    success = enableBleScans();
+  } else {
+    success = disableBleScans();
+  }
+
+  if (!success) {
+    LOGE("Failed to send BLE %s scan request", !mBleEnabled ? "start" : "stop");
+  } else {
+    setTimer(kBleRequestInterval.toRawNanoseconds(), true /* oneShot */,
+             &mBleScanTimerHandle);
+  }
+}
+
 void Manager::makeGnssLocationRequest() {
   // The list of location intervals to iterate; wraps around.
   static const uint32_t kMinIntervalMsList[] = {1000, 0};
@@ -520,7 +857,7 @@
       setTimer(kWifiScanInterval.toRawNanoseconds(), true /* oneShot */,
                &mWifiScanTimerHandle);
     } else {
-      sendFailure("Platform has no on-demand scan capability");
+      LOGW("Platform has no on-demand scan capability");
     }
   }
 }
@@ -546,6 +883,39 @@
   }
 }
 
+void Manager::makeAudioRequest() {
+  bool success = false;
+  struct chreAudioSource source;
+  if (mAudioEnabled) {
+    for (uint32_t i = 0; chreAudioGetSource(i, &source); i++) {
+      if (chreAudioConfigureSource(i, true, source.minBufferDuration,
+                                   source.minBufferDuration)) {
+        LOGI("Successfully enabled audio for source %" PRIu32, i);
+        success = true;
+      } else {
+        LOGE("Failed to enable audio");
+      }
+    }
+  } else {
+    for (uint32_t i = 0; chreAudioGetSource(i, &source); i++) {
+      if (chreAudioConfigureSource(i, false, 0, 0)) {
+        LOGI("Successfully disabled audio for source %" PRIu32, i);
+        success = true;
+      } else {
+        LOGE("Failed to disable audio");
+      }
+    }
+  }
+
+  if (success) {
+    mAudioEnabled = !mAudioEnabled;
+    setTimer(kAudioRequestInterval.toRawNanoseconds(), true /* oneShot */,
+             &mAudioTimerHandle);
+  } else {
+    sendFailure("Failed to make audio request");
+  }
+}
+
 void Manager::sendFailure(const char *errorMessage) {
   test_shared::sendTestResultWithMsgToHost(
       mHostEndpoint.value(),
@@ -553,6 +923,40 @@
       false /* success */, errorMessage, false /* abortOnFailure */);
 }
 
+void Manager::sendCapabilitiesMessage() {
+  if (!mHostEndpoint.has_value()) {
+    LOGE("mHostEndpoint is not initialized");
+    return;
+  }
+
+  chre_stress_test_Capabilities capabilities =
+      chre_stress_test_Capabilities_init_default;
+  capabilities.wifi = chreWifiGetCapabilities();
+
+  size_t size;
+  if (!pb_get_encoded_size(&size, chre_stress_test_Capabilities_fields,
+                           &capabilities)) {
+    LOGE("Failed to get message size");
+    return;
+  }
+
+  pb_byte_t *bytes = static_cast<pb_byte_t *>(chreHeapAlloc(size));
+  if (size > 0 && bytes == nullptr) {
+    LOG_OOM();
+  } else {
+    pb_ostream_t stream = pb_ostream_from_buffer(bytes, size);
+    if (!pb_encode(&stream, chre_stress_test_Capabilities_fields,
+                   &capabilities)) {
+      LOGE("Failed to encode capabilities error %s", PB_GET_ERROR(&stream));
+      chreHeapFree(bytes);
+    } else {
+      chreSendMessageToHostEndpoint(
+          bytes, size, chre_stress_test_MessageType_CAPABILITIES,
+          mHostEndpoint.value(), heapFreeMessageCallback);
+    }
+  }
+}
+
 }  // namespace stress_test
 
 }  // namespace chre
diff --git a/apps/test/common/permission_test/Makefile b/apps/test/common/permission_test/Makefile
index 98d6a0c..a56ac3c 100644
--- a/apps/test/common/permission_test/Makefile
+++ b/apps/test/common/permission_test/Makefile
@@ -22,6 +22,7 @@
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/permission_test
 TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
 
+
 # Protobuf Sources #############################################################
 
 NANOPB_EXTENSION = nanopb
@@ -40,6 +41,7 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Includes
 COMMON_CFLAGS += -I$(TEST_SHARED_PATH)/inc
diff --git a/apps/test/common/permission_test/src/permission_test.cc b/apps/test/common/permission_test/src/permission_test.cc
index 63510af..2728354 100644
--- a/apps/test/common/permission_test/src/permission_test.cc
+++ b/apps/test/common/permission_test/src/permission_test.cc
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#include <chre.h>
-
 #include <pb_decode.h>
 #include <cinttypes>
 
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 #include "permission_test.nanopb.h"
 #include "send_message.h"
 
diff --git a/apps/test/common/ping_test/Makefile b/apps/test/common/ping_test/Makefile
index eea78f3..5f74624 100644
--- a/apps/test/common/ping_test/Makefile
+++ b/apps/test/common/ping_test/Makefile
@@ -22,6 +22,7 @@
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/ping_test
 TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
 
+
 # Protobuf Sources #############################################################
 
 NANOPB_EXTENSION = nanopb
@@ -40,6 +41,7 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Includes
 COMMON_CFLAGS += -I$(TEST_SHARED_PATH)/inc
diff --git a/apps/test/common/ping_test/src/ping_test.cc b/apps/test/common/ping_test/src/ping_test.cc
index 990b452..237d4ab 100644
--- a/apps/test/common/ping_test/src/ping_test.cc
+++ b/apps/test/common/ping_test/src/ping_test.cc
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include <pb_decode.h>
 
 #include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
 #include "ping_test.nanopb.h"
 #include "send_message.h"
 
diff --git a/apps/test/common/proto/chre_settings_test.proto b/apps/test/common/proto/chre_settings_test.proto
index 923f4ba..b5de7f7 100644
--- a/apps/test/common/proto/chre_settings_test.proto
+++ b/apps/test/common/proto/chre_settings_test.proto
@@ -32,6 +32,7 @@
     GNSS_MEASUREMENT = 4;
     WWAN_CELL_INFO = 5;
     AUDIO = 6;
+    BLE_SCANNING = 7;
   }
 
   // The state of this feature at the system level.
diff --git a/apps/test/common/proto/chre_stress_test.proto b/apps/test/common/proto/chre_stress_test.proto
index 4b6c645..557ace9 100644
--- a/apps/test/common/proto/chre_stress_test.proto
+++ b/apps/test/common/proto/chre_stress_test.proto
@@ -30,6 +30,13 @@
   // unicast messages.
   // No payload.
   TEST_HOST_RESTARTED = 4;
+
+  // H2C: Request Capabilities.
+  // No payload.
+  GET_CAPABILITIES = 5;
+
+  // C2H: Capabilities (response to a GET_CAPABILITIES request).
+  CAPABILITIES = 6;
 }
 
 // A message to start the test.
@@ -43,6 +50,12 @@
     WWAN = 4;
     // Enables WiFi scan monitoring only.
     WIFI_SCAN_MONITOR = 5;
+    // Enables sensor test (accel/gyro/instant motion) only.
+    SENSORS = 6;
+    // Audio stress testing.
+    AUDIO = 7;
+    // Enables BLE test only.
+    BLE = 8;
   }
 
   // The feature to test.
@@ -51,3 +64,12 @@
   // True to start the test, false to stop.
   optional bool start = 2;
 }
+
+/*
+ * CHRE capabilities
+ */
+message Capabilities {
+  // Wifi capabilities
+  // see //system/chre/chre_api/include/chre_api/chre/wifi.h
+  optional uint32 wifi = 1;
+}
diff --git a/apps/test/common/rpc_service_test/Makefile b/apps/test/common/rpc_service_test/Makefile
index eb47176..6ae2ee2 100644
--- a/apps/test/common/rpc_service_test/Makefile
+++ b/apps/test/common/rpc_service_test/Makefile
@@ -22,8 +22,6 @@
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/common/rpc_service_test
 TEST_SHARED_PATH = $(CHRE_PREFIX)/apps/test/common/shared
 
-# TODO(b/210138227) Enable PW RPC by default once build system is fixed
-NANOAPP_PW_RPC_ENABLED = false
 
 # Source Code ##################################################################
 
@@ -34,6 +32,8 @@
 
 # Defines
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
+COMMON_CFLAGS += -fno-threadsafe-statics
 
 # Includes
 COMMON_CFLAGS += -I$(TEST_SHARED_PATH)/inc
@@ -41,10 +41,7 @@
 
 # PW RPC protos ################################################################
 
-ifeq (NANOAPP_PW_RPC_ENABLED, true)
 PW_RPC_SRCS = $(ANDROID_BUILD_TOP)/external/pigweed/pw_rpc/echo.proto
-COMMON_CFLAGS += -DPW_RPC_SERVICE_ENABLED
-endif
 
 # Makefile Includes ############################################################
 
diff --git a/apps/test/common/rpc_service_test/inc/rpc_service_manager.h b/apps/test/common/rpc_service_test/inc/rpc_service_manager.h
index d9fc75e..f092c86 100644
--- a/apps/test/common/rpc_service_test/inc/rpc_service_manager.h
+++ b/apps/test/common/rpc_service_test/inc/rpc_service_manager.h
@@ -20,24 +20,16 @@
 #include <cinttypes>
 #include <cstdint>
 
-#include <chre.h>
-
 #include "chre/util/macros.h"
-#include "chre/util/singleton.h"
-
-#ifdef PW_RPC_SERVICE_ENABLED
-#include <span>
-
 #include "chre/util/pigweed/chre_channel_output.h"
-
-#include "pw_rpc/echo.rpc.pb.h"
-#include "pw_rpc/server.h"
-#endif  // PW_RPC_SERVICE_ENABLED
+#include "chre/util/pigweed/rpc_server.h"
+#include "chre/util/singleton.h"
+#include "chre_api/chre.h"
+#include "echo.rpc.pb.h"
 
 namespace chre {
 namespace rpc_service_test {
 
-#ifdef PW_RPC_SERVICE_ENABLED
 class EchoService final
     : public pw::rpc::pw_rpc::nanopb::EchoService::Service<EchoService> {
  public:
@@ -46,17 +38,12 @@
   pw::Status Echo(const pw_rpc_EchoMessage &request,
                   pw_rpc_EchoMessage &response);
 };
-#endif  // PW_RPC_SERVICE_ENABLED
 
 /**
  * Class to manage the CHRE rpc service nanoapp.
  */
 class RpcServiceManager {
  public:
-#ifdef PW_RPC_SERVICE_ENABLED
-  RpcServiceManager() : mServer(std::span(mChannels, ARRAY_SIZE(mChannels))) {}
-#endif
-
   /**
    * Allows the manager to do any init necessary as part of nanoappStart.
    */
@@ -72,17 +59,17 @@
   void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
                    const void *eventData);
 
+  /**
+   * Sets the permission for the next server message.
+   *
+   * @params permission Bitmasked CHRE_MESSAGE_PERMISSION_.
+   */
+  void setPermissionForNextMessage(uint32_t permission);
+
  private:
-#ifdef PW_RPC_SERVICE_ENABLED
+  RpcServer mServer;
   // pw_rpc service used to process the echo RPC
   EchoService mEchoService;
-
-  // TODO(b/210138227): Make # of channels dynamic
-  pw::rpc::Channel mChannels[5];
-  pw::rpc::Server mServer;
-
-  ChreHostChannelOutput mOutput;
-#endif  // PW_RPC_SERVICE_ENABLED
 };
 
 typedef chre::Singleton<RpcServiceManager> RpcServiceManagerSingleton;
diff --git a/apps/test/common/rpc_service_test/src/rpc_service_manager.cc b/apps/test/common/rpc_service_test/src/rpc_service_manager.cc
index 53c62f4..bea1036 100644
--- a/apps/test/common/rpc_service_test/src/rpc_service_manager.cc
+++ b/apps/test/common/rpc_service_test/src/rpc_service_manager.cc
@@ -24,49 +24,42 @@
 namespace chre {
 namespace rpc_service_test {
 
-#ifdef PW_RPC_SERVICE_ENABLED
 pw::Status EchoService::Echo(const pw_rpc_EchoMessage &request,
                              pw_rpc_EchoMessage &response) {
+  RpcServiceManagerSingleton::get()->setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
   memcpy(response.msg, request.msg,
          MIN(ARRAY_SIZE(response.msg), ARRAY_SIZE(request.msg)));
   return pw::OkStatus();
 }
-#endif  // PW_RPC_SERVICE_ENABLED
 
 bool RpcServiceManager::start() {
-  static chreNanoappRpcService sRpcService = {
-      .id = 0xca8f7150a3f05847,
-      .version = 0x01020034,
-  };
+  // Make sure nanoapps support publishing at least
+  // CHRE_MINIMUM_RPC_SERVICE_LIMIT services.
+  RpcServer::Service service{
+      .service = mEchoService, .id = 0xca8f7150a3f05847, .version = 0x01020034};
 
-#ifdef PW_RPC_SERVICE_ENABLED
-  mServer.RegisterService(mEchoService);
-#endif
-  return chrePublishRpcServices(&sRpcService, 1 /* numServices */);
+  bool success = true;
+
+  for (uint64_t i = 0; i < CHRE_MINIMUM_RPC_SERVICE_LIMIT - 1; i++) {
+    struct chreNanoappRpcService chreService = {.id = i, .version = 1};
+    success =
+        success && chrePublishRpcServices(&chreService, 1 /*numServices*/);
+  }
+
+  return success && mServer.registerServices(1 /*numServices*/, &service);
 }
 
 void RpcServiceManager::handleEvent(uint32_t senderInstanceId,
                                     uint16_t eventType, const void *eventData) {
-#ifdef PW_RPC_SERVICE_ENABLED
-  if (eventType == CHRE_EVENT_MESSAGE_FROM_HOST) {
-    auto *hostMessage = static_cast<const chreMessageFromHostData *>(eventData);
-    mOutput.setHostEndpoint(hostMessage->hostEndpoint);
-
-    pw::Status success = mServer.ProcessPacket(
-        std::span(static_cast<const std::byte *>(hostMessage->message),
-                  hostMessage->messageSize),
-        mOutput);
-    LOGI("Parsing packet %d", success == pw::OkStatus());
-  } else
-#else
-  UNUSED_VAR(eventData);
-#endif  // PW_RPC_SERVICE_ENABLED
-  {
-    LOGW("Got unknown event type from senderInstanceId %" PRIu32
-         " and with eventType %" PRIu16,
-         senderInstanceId, eventType);
+  if (!mServer.handleEvent(senderInstanceId, eventType, eventData)) {
+    LOGE("An RPC error occurred");
   }
 }
 
+void RpcServiceManager::setPermissionForNextMessage(uint32_t permission) {
+  mServer.setPermissionForNextMessage(permission);
+}
+
 }  // namespace rpc_service_test
 }  // namespace chre
diff --git a/apps/test/common/shared/inc/audio_validation.h b/apps/test/common/shared/inc/audio_validation.h
new file mode 100644
index 0000000..a23d9ca
--- /dev/null
+++ b/apps/test/common/shared/inc/audio_validation.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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_TEST_SHARED_AUDIO_VALIDATION_H_
+#define CHRE_TEST_SHARED_AUDIO_VALIDATION_H_
+
+#include <cinttypes>
+#include <cstddef>
+
+namespace chre {
+namespace test_shared {
+
+/**
+ * Check if the audio samples are all zeros.
+ *
+ * @return true on check passing.
+ */
+bool checkAudioSamplesAllZeros(const int16_t *data, const size_t dataLen);
+
+/**
+ * Check if adjacent audio samples are unique.
+ *
+ * @return true on check pass.
+ */
+bool checkAudioSamplesAllSame(const int16_t *data, const size_t dataLen);
+
+}  // namespace test_shared
+}  // namespace chre
+
+#endif  // CHRE_TEST_SHARED_AUDIO_VALIDATION_H_
diff --git a/apps/test/common/shared/inc/send_message.h b/apps/test/common/shared/inc/send_message.h
index df9691f..3193a5d 100644
--- a/apps/test/common/shared/inc/send_message.h
+++ b/apps/test/common/shared/inc/send_message.h
@@ -17,6 +17,7 @@
 #ifndef CHRE_TEST_SHARED_SEND_MESSAGE_H_
 #define CHRE_TEST_SHARED_SEND_MESSAGE_H_
 
+#include <pb_encode.h>
 #include <cinttypes>
 
 namespace chre {
@@ -53,6 +54,17 @@
  */
 void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
 
+/**
+ * Sends a message to the host.
+ *
+ * @param hostEndpointId The endpoint Id of the host to send the message to.
+ * @param message The proto message struct pointer.
+ * @param fields The fields descriptor of the proto message to encode.
+ * @param messageType The message type of the message.
+ */
+void sendMessageToHost(uint16_t hostEndpointId, const void *message,
+                       const pb_field_t *fields, uint32_t messageType);
+
 }  // namespace test_shared
 
 }  // namespace chre
diff --git a/apps/test/common/shared/src/audio_validation.cc b/apps/test/common/shared/src/audio_validation.cc
new file mode 100644
index 0000000..eea2616
--- /dev/null
+++ b/apps/test/common/shared/src/audio_validation.cc
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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 "audio_validation.h"
+
+#ifndef LOG_TAG
+#define LOG_TAG "[TestShared]"
+#endif
+
+namespace chre {
+namespace test_shared {
+
+bool checkAudioSamplesAllZeros(const int16_t *data, const size_t dataLen) {
+  for (size_t i = 0; i < dataLen; ++i) {
+    if (data[i] != 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool checkAudioSamplesAllSame(const int16_t *data, const size_t dataLen) {
+  if (dataLen > 0) {
+    const int16_t controlValue = data[0];
+    for (size_t i = 1; i < dataLen; ++i) {
+      if (data[i] != controlValue) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+}  // namespace test_shared
+}  // namespace chre
diff --git a/apps/test/common/shared/src/send_message.cc b/apps/test/common/shared/src/send_message.cc
index 3a62753..9dd9c51 100644
--- a/apps/test/common/shared/src/send_message.cc
+++ b/apps/test/common/shared/src/send_message.cc
@@ -16,16 +16,17 @@
 
 #include "send_message.h"
 
-#include <chre.h>
 #include <pb_encode.h>
 #include <cinttypes>
 
-#include "chre_test_common.nanopb.h"
-
 #include "chre/util/nanoapp/callbacks.h"
 #include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
+#include "chre_test_common.nanopb.h"
 
+#ifndef LOG_TAG
 #define LOG_TAG "[TestShared]"
+#endif
 
 namespace chre {
 namespace test_shared {
@@ -63,25 +64,9 @@
                            .arg = const_cast<char *>(errMessage)};
     LOGE("%s", errMessage);
   }
-  size_t size;
-  if (!pb_get_encoded_size(&size, chre_test_common_TestResult_fields,
-                           &result)) {
-    LOGE("Failed to get message size");
-  } else {
-    pb_byte_t *bytes = static_cast<pb_byte_t *>(chreHeapAlloc(size));
-    if (bytes == nullptr) {
-      LOG_OOM();
-    } else {
-      pb_ostream_t stream = pb_ostream_from_buffer(bytes, size);
-      if (!pb_encode(&stream, chre_test_common_TestResult_fields, &result)) {
-        LOGE("Failed to encode test result error %s", PB_GET_ERROR(&stream));
-        chreHeapFree(bytes);
-      } else {
-        chreSendMessageToHostEndpoint(bytes, size, messageType, hostEndpointId,
-                                      heapFreeMessageCallback);
-      }
-    }
-  }
+
+  sendMessageToHost(hostEndpointId, &result, chre_test_common_TestResult_fields,
+                    messageType);
 
   if (!success && abortOnFailure) {
     chreAbort(0);
@@ -108,6 +93,29 @@
                                 nullptr /* freeCallback */);
 }
 
+void sendMessageToHost(uint16_t hostEndpointId, const void *message,
+                       const pb_field_t *fields, uint32_t messageType) {
+  size_t size;
+  if (!pb_get_encoded_size(&size, fields, message)) {
+    LOGE("Failed to get message size");
+  } else {
+    pb_byte_t *bytes = static_cast<pb_byte_t *>(chreHeapAlloc(size));
+    if (size > 0 && bytes == nullptr) {
+      LOG_OOM();
+    } else {
+      pb_ostream_t stream = pb_ostream_from_buffer(bytes, size);
+      if (!pb_encode(&stream, fields, message)) {
+        LOGE("Failed to encode message error %s", PB_GET_ERROR(&stream));
+        chreHeapFree(bytes);
+      } else if (!chreSendMessageToHostEndpoint(bytes, size, messageType,
+                                                hostEndpointId,
+                                                heapFreeMessageCallback)) {
+        LOGE("Failed to send message to host");
+      }
+    }
+  }
+}
+
 }  // namespace test_shared
 
 }  // namespace chre
diff --git a/apps/test/pts/audio_enable_disable_test/Makefile b/apps/test/pts/audio_enable_disable_test/Makefile
index b2d1c2f..814e12e 100644
--- a/apps/test/pts/audio_enable_disable_test/Makefile
+++ b/apps/test/pts/audio_enable_disable_test/Makefile
@@ -22,6 +22,7 @@
 
 NANOAPP_PATH = $(CHRE_PREFIX)/apps/test/pts/audio_enable_disable_test
 
+
 # Protobuf Sources #############################################################
 
 NANOPB_EXTENSION = nanopb
diff --git a/apps/test/pts/audio_enable_disable_test/src/audio_enable_disable_test.cc b/apps/test/pts/audio_enable_disable_test/src/audio_enable_disable_test.cc
index 2611bcc..ec08b07 100644
--- a/apps/test/pts/audio_enable_disable_test/src/audio_enable_disable_test.cc
+++ b/apps/test/pts/audio_enable_disable_test/src/audio_enable_disable_test.cc
@@ -27,13 +27,13 @@
 
 #include <cinttypes>
 
-#include <chre.h>
 #include <pb_encode.h>
 
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/callbacks.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 #include "pts_chre.nanopb.h"
 
 #define LOG_TAG "[PtsAudioEnableDisable]"
diff --git a/apps/test/pts/proto/Android.bp b/apps/test/pts/proto/Android.bp
new file mode 100644
index 0000000..5c29259
--- /dev/null
+++ b/apps/test/pts/proto/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["system_chre_license"],
+}
+
+java_library {
+    name: "pts_chre_java_proto",
+    host_supported: true,
+    srcs: [
+        "pts_chre.proto",
+    ],
+    proto: {
+        type: "lite",
+    },
+    sdk_version: "system_current",
+}
diff --git a/apps/tflm_demo/Makefile b/apps/tflm_demo/Makefile
index b970790..7947011 100644
--- a/apps/tflm_demo/Makefile
+++ b/apps/tflm_demo/Makefile
@@ -31,6 +31,7 @@
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_INFO
 COMMON_CFLAGS += -DCHRE_NANOAPP_DISABLE_BACKCOMPAT
 COMMON_CFLAGS += -DNDEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # If OPT_LEVEL is unset, defaulting to 3.
 ifeq ($(OPT_LEVEL),)
diff --git a/apps/tflm_demo/src/main.cc b/apps/tflm_demo/src/main.cc
index b9b5f7a..5ed3c2c 100644
--- a/apps/tflm_demo/src/main.cc
+++ b/apps/tflm_demo/src/main.cc
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-#include <chre.h>
-
 #include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
 #include "model.h"
 
 #define LOG_TAG "[TFLM demo]"
diff --git a/apps/tflm_demo/src/model.cc b/apps/tflm_demo/src/model.cc
index 1e9a0cd..5fe197b 100644
--- a/apps/tflm_demo/src/model.cc
+++ b/apps/tflm_demo/src/model.cc
@@ -18,8 +18,8 @@
 
 #include "sine_model_data.h"
 #include "tensorflow/lite/micro/kernels/micro_ops.h"
-#include "tensorflow/lite/micro/micro_error_reporter.h"
 #include "tensorflow/lite/micro/micro_interpreter.h"
+#include "tensorflow/lite/micro/micro_log.h"
 #include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
 #include "tensorflow/lite/schema/schema_generated.h"
 
@@ -39,7 +39,6 @@
 
 namespace demo {
 float run(float x_val) {
-  tflite::MicroErrorReporter micro_error_reporter;
   const tflite::Model *model = tflite::GetModel(g_sine_model_data);
   // TODO(wangtz): Check for schema version.
 
@@ -48,8 +47,8 @@
   constexpr int kTensorAreanaSize = 2 * 1024;
   uint8_t tensor_arena[kTensorAreanaSize];
 
-  tflite::MicroInterpreter interpreter(
-      model, resolver, tensor_arena, kTensorAreanaSize, &micro_error_reporter);
+  tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
+                                       kTensorAreanaSize);
   interpreter.AllocateTensors();
 
   TfLiteTensor *input = interpreter.input(0);
@@ -57,7 +56,7 @@
   input->data.f[0] = x_val;
   TfLiteStatus invoke_status = interpreter.Invoke();
   if (invoke_status != kTfLiteOk) {
-    micro_error_reporter.ReportError(nullptr, "Internal error: invoke failed.");
+    MicroPrintf("Internal error: invoke failed.");
     return 0.0;
   }
   float y_val = output->data.f[0];
diff --git a/apps/timer_world/Makefile b/apps/timer_world/Makefile
index 0e00870..52b7a79 100644
--- a/apps/timer_world/Makefile
+++ b/apps/timer_world/Makefile
@@ -30,6 +30,7 @@
 
 # Defines.
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/timer_world/timer_world.cc b/apps/timer_world/timer_world.cc
index 41b4ab1..5bf7c22 100644
--- a/apps/timer_world/timer_world.cc
+++ b/apps/timer_world/timer_world.cc
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/audio.h"
 #include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
 
 #define LOG_TAG "[TimerWorld]"
 
diff --git a/apps/unload_tester/unload_tester.cc b/apps/unload_tester/unload_tester.cc
index f02024e..8c3c4b9 100644
--- a/apps/unload_tester/unload_tester.cc
+++ b/apps/unload_tester/unload_tester.cc
@@ -24,8 +24,8 @@
 #include "chre/util/nanoapp/app_id.h"
 #include "chre/util/system/napp_permissions.h"
 #include "chre/util/time.h"
-#include "chre_api/chre.h"
 
+#include "chre_api/chre.h"
 /**
  * @file
  * A nanoapp exclusively for testing, which unloads the spammer nanoapp after a
diff --git a/apps/wifi_world/Makefile b/apps/wifi_world/Makefile
index dc1fc9f..fafa16c 100644
--- a/apps/wifi_world/Makefile
+++ b/apps/wifi_world/Makefile
@@ -28,6 +28,7 @@
 
 COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
 COMMON_CFLAGS += -DLOG_TAG=\"[WifiWorld]\"
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/wifi_world/wifi_world.cc b/apps/wifi_world/wifi_world.cc
index 3c687e0..b0f2eb0 100644
--- a/apps/wifi_world/wifi_world.cc
+++ b/apps/wifi_world/wifi_world.cc
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 #include <cmath>
 
@@ -22,6 +21,7 @@
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/nanoapp/wifi.h"
 #include "chre/util/time.h"
+#include "chre_api/chre.h"
 
 using chre::kOneMillisecondInNanoseconds;
 using chre::Nanoseconds;
diff --git a/apps/wwan_world/Makefile b/apps/wwan_world/Makefile
index 54350a5..4940933 100644
--- a/apps/wwan_world/Makefile
+++ b/apps/wwan_world/Makefile
@@ -5,17 +5,30 @@
 # Environment Checks ###########################################################
 
 ifeq ($(CHRE_PREFIX),)
-$(error "The CHRE_PREFIX environment variable must be set to a path to the \
-         CHRE project root. Example: export CHRE_PREFIX=$$HOME/chre")
+ifneq ($(ANDROID_BUILD_TOP),)
+CHRE_PREFIX = $(ANDROID_BUILD_TOP)/system/chre
+else
+$(error "You must run 'lunch' to setup ANDROID_BUILD_TOP, or explicitly define \
+         the CHRE_PREFIX environment variable to point to the CHRE root \
+         directory.")
+endif
 endif
 
 # Nanoapp Configuration ########################################################
 
 NANOAPP_NAME = wwan_world
+NANOAPP_ID = 0x0123456789000014
+NANOAPP_VERSION = 0x00000000
+
+NANOAPP_NAME_STRING = \"Wwan\ World\"
+
 
 # Common Compiler Flags ########################################################
 
 COMMON_CFLAGS += -I.
+COMMON_CFLAGS += -DNANOAPP_MINIMUM_LOG_LEVEL=CHRE_LOG_LEVEL_DEBUG
+COMMON_CFLAGS += -DLOG_TAG=\"[WwanWorld]\"
+COMMON_CFLAGS += -DCHRE_ASSERTIONS_ENABLED
 
 # Common Source Files ##########################################################
 
diff --git a/apps/wwan_world/wwan_world.cc b/apps/wwan_world/wwan_world.cc
index e0f7515..0972b3e 100644
--- a/apps/wwan_world/wwan_world.cc
+++ b/apps/wwan_world/wwan_world.cc
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-#include <chre.h>
 #include <cinttypes>
 
 #include "chre/util/macros.h"
 #include "chre/util/nanoapp/log.h"
 #include "chre/util/time.h"
-
-#define LOG_TAG "[WwanWorld]"
+#include "chre_api/chre.h"
 
 #ifdef CHRE_NANOAPP_INTERNAL
 namespace chre {
@@ -32,7 +30,8 @@
 const uint32_t kCellInfoCookie = 0x1337;
 
 //! The interval for cell info requests.
-const Nanoseconds kCellInfoInterval = Nanoseconds(Seconds(10));
+const chre::Nanoseconds kCellInfoInterval =
+    chre::Nanoseconds(chre::Seconds(10));
 
 //! A handle for  the cyclic timer to request periodic cell info.
 uint32_t gCellInfoTimerHandle;
diff --git a/build/arch/cortexm.mk b/build/arch/cortexm.mk
index 7f0e317..26b9e8f 100644
--- a/build/arch/cortexm.mk
+++ b/build/arch/cortexm.mk
@@ -7,16 +7,22 @@
 # If building for the Cortex-M target, ensure that the user has specified a path
 # to the Cortex-M toolchain that they wish to use.
 ifeq ($(CORTEXM_TOOLS_PREFIX),)
-$(error "You must supply a CORTEXM_TOOLS_PREFIX environment variable \
-         containing a path to the Cortex-M toolchain. Example: \
-         export CORTEXM_TOOLS_PREFIX=$$HOME/bin/gcc-arm-none-eabi-5_3-2016q1")
-endif
+# Try to default to Android's Clang prebuilts if the tools prefix isn't
+# specified.
+include $(CHRE_PREFIX)/build/clang.mk
+endif # CORTEXM_TOOLS_PREFIX
 
 # Cortex-M Tools ###############################################################
 
+ifeq ($(IS_CLANG_TOOLCHAIN),)
 TARGET_AR = $(CORTEXM_TOOLS_PREFIX)/bin/arm-none-eabi-ar
 TARGET_CC = $(CORTEXM_TOOLS_PREFIX)/bin/arm-none-eabi-g++
-TARGET_LD = $(CORTEXM_TOOLS_PREFIX)/bin/arm-none-eabi-g++
+TARGET_LD = $(CORTEXM_TOOLS_PREFIX)/bin/arm-none-eabi-ld
+else
+TARGET_AR = $(CLANG_TOOLCHAIN_PATH)/bin/llvm-ar
+TARGET_CC = $(CLANG_TOOLCHAIN_PATH)/bin/clang
+TARGET_LD = $(CLANG_TOOLCHAIN_PATH)/bin/ld.lld
+endif
 
 # Cortex-M Compiler Flags ######################################################
 
@@ -24,11 +30,24 @@
 TARGET_CFLAGS += $(CORTEXM_CFLAGS)
 
 # Code generation flags.
-TARGET_CFLAGS += -mthumb
-TARGET_CFLAGS += -mfloat-abi=softfp
-TARGET_CFLAGS += -mno-thumb-interwork
-TARGET_CFLAGS += -ffast-math
-TARGET_CFLAGS += -fsingle-precision-constant
+GCC_CFLAGS += -mthumb
+GCC_CFLAGS += -mno-thumb-interwork
+GCC_CFLAGS += -ffast-math
+GCC_CFLAGS += -fsingle-precision-constant
+
+TARGET_CFLAGS += -ffunction-sections
+TARGET_CFLAGS += -fdata-sections
+TARGET_CFLAGS += -fshort-enums
+TARGET_CFLAGS += -fno-unwind-tables
+TARGET_CFLAGS += -mabi=aapcs
+
+ifneq ($(IS_NANOAPP_BUILD),)
+TARGET_CFLAGS += -fpic
+endif
+
+ifeq ($(IS_ARCHIVE_ONLY_BUILD), true)
+COMMON_CXX_CFLAGS += -fno-use-cxa-atexit
+endif
 
 # Sadly we must disable double promotion warnings due to logging macros. There
 # is a bug for this here: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431
@@ -37,10 +56,11 @@
 # Cortex-M Shared Object Linker Flags ##########################################
 
 TARGET_SO_LDFLAGS += -shared
+TARGET_SO_LDFLAGS += -z max-page-size=0x8
 
 # Supported Cortex-M Architectures #############################################
 
-CORTEXM_SUPPORTED_ARCHS = m4
+CORTEXM_SUPPORTED_ARCHS = m4 m4_hardfp
 
 # Environment Checks ###########################################################
 
@@ -54,10 +74,26 @@
 
 # Set the Cortex-M architecture.
 ifeq ($(CORTEXM_ARCH), m4)
-TARGET_CFLAGS += -mcpu=cortex-m4
+GCC_CFLAGS += -mcpu=cortex-m4
+CLANG_CFLAGS += --target=arm-none-eabi
+TARGET_CFLAGS += -mfloat-abi=softfp
 TARGET_CFLAGS += -mfpu=fpv4-sp-d16
 endif
 
+ifeq ($(CORTEXM_ARCH), m4_hardfp)
+GCC_CFLAGS += -mcpu=cortex-m4
+CLANG_CFLAGS += --target=arm-none-eabi
+TARGET_CFLAGS += -mfloat-abi=hard
+TARGET_CFLAGS += -mfpu=fpv4-sp-d16
+endif
+
+
+ifeq ($(IS_CLANG_TOOLCHAIN),)
+TARGET_CFLAGS += $(GCC_CFLAGS)
+else
+TARGET_CFLAGS += $(CLANG_CFLAGS)
+endif
+
 # Optimization Level ###########################################################
 
 TARGET_CFLAGS += -O$(OPT_LEVEL)
diff --git a/build/arch/riscv.mk b/build/arch/riscv.mk
new file mode 100644
index 0000000..7cb83f7
--- /dev/null
+++ b/build/arch/riscv.mk
@@ -0,0 +1,31 @@
+#
+# Build targets for a risc-v based architecture
+#
+
+# Environment Checks ###########################################################
+
+ifeq ($(ANDROID_BUILD_TOP),)
+$(error "You should supply an ANDROID_BUILD_TOP environment variable \
+         containing a path to the Android source tree. This is typically \
+         provided by initializing the Android build environment.")
+endif
+
+ifeq ($(RISCV_TOOLCHAIN_PATH),)
+$(error "The risc-v toolchain directory needs to be exported as the \
+         RISCV_TOOLCHAIN_PATH environment variable")
+endif
+
+# Tools ########################################################################
+
+TARGET_AR = $(RISCV_TOOLCHAIN_PATH)/bin/llvm-ar
+TARGET_CC = $(RISCV_TOOLCHAIN_PATH)/bin/clang
+TARGET_LD = $(RISCV_TOOLCHAIN_PATH)/bin/ld.lld
+
+# Shared Object Linker Flags ###################################################
+
+TARGET_SO_LDFLAGS += --gc-sections
+TARGET_SO_LDFLAGS += -shared
+
+# Optimization Level ###########################################################
+
+TARGET_CFLAGS += -O$(OPT_LEVEL)
diff --git a/build/arch/x86.mk b/build/arch/x86.mk
index 04712d2..f9fb500 100644
--- a/build/arch/x86.mk
+++ b/build/arch/x86.mk
@@ -9,13 +9,14 @@
          containing a path to the Android source tree. This is typically \
          provided by initializing the Android build environment.")
 endif
-export X86_TOOLS_PREFIX=$(ANDROID_BUILD_TOP)/prebuilts/clang/host/linux-x86/clang-r450784d/bin/
+
+include $(CHRE_PREFIX)/build/clang.mk
 
 # x86 Tools ####################################################################
 
-TARGET_AR  = $(X86_TOOLS_PREFIX)llvm-ar
-TARGET_CC  = $(X86_TOOLS_PREFIX)clang++
-TARGET_LD  = $(X86_TOOLS_PREFIX)clang++
+TARGET_AR  = $(CLANG_TOOLCHAIN_PATH)/bin/llvm-ar
+TARGET_CC  = $(CLANG_TOOLCHAIN_PATH)/bin/clang++
+TARGET_LD  = $(CLANG_TOOLCHAIN_PATH)/bin/clang++
 
 # x86 Compiler Flags ###########################################################
 
diff --git a/build/build_template.mk b/build/build_template.mk
index c969580..4907577 100644
--- a/build/build_template.mk
+++ b/build/build_template.mk
@@ -98,6 +98,13 @@
 # Optional Binary
 $(1)_BIN = $$(if $(9), $(OUT)/$(1)/$(OUTPUT_NAME), )
 
+# Optional token mapping
+$(1)_TOKEN_MAP = $$(if $(CHRE_TOKENIZED_LOGGING_ENABLED), \
+                    $(OUT)/$(1)/$(OUTPUT_NAME)_log_database.bin,)
+
+$(1)_TOKEN_MAP_CSV = $$(if $(CHRE_TOKENIZED_LOGGING_ENABLED), \
+                        $(OUT)/$(1)/$(OUTPUT_NAME)_log_database.csv,)
+
 # Top-level Build Rule #########################################################
 
 # Define the phony target.
@@ -113,11 +120,14 @@
 .PHONY: $(1)_header
 $(1)_header: $$($(1)_HEADER)
 
+.PHONY: $(1)_token_map
+$(1)_token_map: $$($(1)_TOKEN_MAP)
+
 .PHONY: $(1)
 ifeq ($(IS_ARCHIVE_ONLY_BUILD),true)
-$(1): $(1)_ar
+$(1): $(1)_ar $(1)_token_map
 else
-$(1): $(1)_ar $(1)_so $(1)_bin $(1)_header
+$(1): $(1)_ar $(1)_so $(1)_bin $(1)_header $(1)_token_map
 endif
 
 # If building the runtime, simply add the archive and shared object to the all
@@ -213,8 +223,17 @@
 
 $$($(1)_AR): $$($(1)_CC_OBJS) $$($(1)_CPP_OBJS) $$($(1)_C_OBJS) \
               $$($(1)_S_OBJS) | $$(OUT)/$(1) $$($(1)_DIRS)
+	@echo " [AR] $$@"
 	$(V)$(7) $$($(1)_ARFLAGS) $$@ $$(filter %.o, $$^)
 
+# Token Mapping ################################################################
+
+$$($(1)_TOKEN_MAP): $$($(1)_AR)
+	@echo " [TOKEN_MAP_GEN] $<"
+	$(V)mkdir -p $(OUT)/$(1)
+	$(V)$(TOKEN_MAP_GEN_CMD) $$($(1)_TOKEN_MAP) $$($(1)_AR)
+	$(V)$(TOKEN_MAP_CSV_GEN_CMD) $$($(1)_TOKEN_MAP_CSV) $$($(1)_AR)
+
 # Link #########################################################################
 
 $$($(1)_SO): $$($(1)_CC_DEPS) \
diff --git a/build/build_tinysys.sh b/build/build_tinysys.sh
new file mode 100755
index 0000000..802fcac
--- /dev/null
+++ b/build/build_tinysys.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# Script used to build Tinysys.
+
+# make sure $ANDROID_BUILD_TOP is set
+if [[ -z "$ANDROID_BUILD_TOP" ]]; then
+    echo "Must setup Android build environment first" 1>&2
+    echo "Run the following commands from the repo root:" 1>&2
+    echo " source build/envsetup.sh" 1>&2
+    echo " lunch vext_k6985v1_64-userdebug" 1>&2
+    exit 1
+fi
+
+# make sure $RISCV_TOOLCHAIN_PATH & $RISCV_TINYSYS_PREFIX are set
+if [[ -z "$RISCV_TOOLCHAIN_PATH" ]] || [[ -z "$RISCV_TINYSYS_PREFIX" ]]; then
+    echo "Must provide RISCV_TOOLCHAIN_PATH & RISCV_TINYSYS_PREFIX" 1>&2
+    echo "Example:" 1>&2
+    echo " RISCV_TOOLCHAIN_PATH=\$ANDROID_BUILD_TOP/prebuilts/clang/md32rv/linux-x86 \\" 1>&2
+    echo " RISCV_TINYSYS_PREFIX=\$ANDROID_BUILD_TOP/vendor/mediatek/proprietary/tinysys \\" 1>&2
+    echo " build/tools/build_tinysys.sh" 1>&2
+    exit 1
+fi
+
+pushd $ANDROID_BUILD_TOP/system/chre > /dev/null
+
+CHRE_VARIANT_MK_INCLUDES=variant/tinysys/variant.mk \
+ IS_ARCHIVE_ONLY_BUILD=true \
+ make aosp_riscv55e03_tinysys
+
+popd > /dev/null
diff --git a/build/clang.mk b/build/clang.mk
new file mode 100644
index 0000000..8481c9d
--- /dev/null
+++ b/build/clang.mk
@@ -0,0 +1,14 @@
+#
+# Clang config
+#
+
+# Environment Checks ##########################################################
+ifeq ($(ANDROID_BUILD_TOP),)
+$(error "You should supply an ANDROID_BUILD_TOP environment variable \
+         containing a path to the Android source tree. This is typically \
+         provided by initializing the Android build environment.")
+endif
+
+# Clang toolchain path ########################################################
+CLANG_TOOLCHAIN_PATH=$(ANDROID_BUILD_TOP)/prebuilts/clang/host/linux-x86/clang-r475365b
+IS_CLANG_TOOLCHAIN=true
diff --git a/build/defs.mk b/build/defs.mk
index 6afd5cb..208fc7c 100644
--- a/build/defs.mk
+++ b/build/defs.mk
@@ -14,4 +14,9 @@
 
 ifneq ($(CHRE_BUILD_VERBOSE),true)
 V=@
-endif
\ No newline at end of file
+endif
+
+# Default tools definitions ####################################################
+
+PYTHON ?= python3
+
diff --git a/build/nanoapp/app.mk b/build/nanoapp/app.mk
index 37ec92c..3f82778 100644
--- a/build/nanoapp/app.mk
+++ b/build/nanoapp/app.mk
@@ -95,6 +95,7 @@
 # Common Compiler Flags ########################################################
 
 # Add the CHRE API to the include search path.
+COMMON_CFLAGS += -I$(CHRE_PREFIX)/chre_api/include
 COMMON_CFLAGS += -I$(CHRE_PREFIX)/chre_api/include/chre_api
 
 # Don't pull in the utils folder if not desired
@@ -163,6 +164,7 @@
 # Makefile Includes ############################################################
 
 # Standard library overrides include
+CHRE_STD_OVERRIDES_ALLOWED ?= true
 include $(CHRE_PREFIX)/std_overrides/std_overrides.mk
 
 # Common includes
@@ -179,6 +181,8 @@
 ifneq ($(CHRE_TARGET_EXTENSION),)
 include $(CHRE_TARGET_EXTENSION)
 endif
+include $(CHRE_PREFIX)/build/variant/aosp_cm4_exynos-embos.mk
+include $(CHRE_PREFIX)/build/variant/aosp_riscv55e03_tinysys.mk
 include $(CHRE_PREFIX)/build/variant/google_arm64_android.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi.mk
 include $(CHRE_PREFIX)/build/variant/google_hexagonv62_slpi-uimg.mk
diff --git a/build/sys_support/qcom/chre.scons b/build/sys_support/qcom/chre.scons
index edf5e24..37e2681 100644
--- a/build/sys_support/qcom/chre.scons
+++ b/build/sys_support/qcom/chre.scons
@@ -327,6 +327,7 @@
     "${BUILDPATH}/system/chre/util/buffer_base.cc",
     "${BUILDPATH}/system/chre/util/dynamic_vector_base.cc",
     "${BUILDPATH}/system/chre/util/nanoapp/audio.cc",
+    "${BUILDPATH}/system/chre/util/nanoapp/ble.cc",
     "${BUILDPATH}/system/chre/util/nanoapp/callbacks.cc",
     "${BUILDPATH}/system/chre/util/nanoapp/debug.cc",
     "${BUILDPATH}/system/chre/util/nanoapp/wifi.cc",
diff --git a/build/variant/aosp_cm4_exynos-embos.mk b/build/variant/aosp_cm4_exynos-embos.mk
new file mode 100644
index 0000000..546c0bb
--- /dev/null
+++ b/build/variant/aosp_cm4_exynos-embos.mk
@@ -0,0 +1,84 @@
+include $(CHRE_PREFIX)/build/clean_build_template_args.mk
+
+TARGET_NAME = aosp_cm4_exynos-embos
+ifneq ($(filter $(TARGET_NAME)% all, $(MAKECMDGOALS)),)
+
+ifeq ($(RAINBOW_SDK_DIR),)
+$(error "The Rainbow SDK directory needs to be exported as the RAINBOW_SDK_DIR \
+         environment variable")
+endif
+
+EMBOS_V422_INCLUDE_DIR := $(RAINBOW_SDK_DIR)/OEM/LSI/exynos9925/embos/Start/Inc/Embos422
+
+CORTEXM_ARCH := m4_hardfp
+
+TARGET_CFLAGS += -I$(EMBOS_V422_INCLUDE_DIR)
+
+# 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
+
+# Used to expose libc headers to nanoapps that aren't supported on the given platform
+TARGET_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include/chre/platform/shared/libc
+
+TARGET_CFLAGS += $(ARM_CFLAGS)
+TARGET_CFLAGS += $(EMBOS_CFLAGS)
+TARGET_CFLAGS += $(EXYNOS_CFLAGS)
+TARGET_CFLAGS += -I$(RAINBOW_SDK_DIR)/OEM/LSI/exynos9925/firmware/os/platform/exynos/inc
+TARGET_CFLAGS += -I$(RAINBOW_SDK_DIR)/OEM/LSI/exynos9925/firmware/os/platform/exynos/inc/plat
+TARGET_CFLAGS += -I$(RAINBOW_SDK_DIR)/OEM/LSI/exynos9925/firmware/os/platform/exynos/inc/plat/cmsis
+TARGET_CFLAGS += -I$(RAINBOW_SDK_DIR)/OEM/LSI/exynos9925/firmware/os/platform/exynos/inc/plat/csp
+TARGET_CFLAGS += -I$(RAINBOW_SDK_DIR)/OEM/LSI/exynos9925/firmware/os/platform/exynos/inc/plat/mailbox
+TARGET_CFLAGS += -I$(RAINBOW_SDK_DIR)/OEM/LSI/exynos9925/embos/Project/erd9925/DeviceSupport
+
+# TODO(b/242765122): The target won't build out of the box until the
+# aforementioned bug is resolved since a set of standard library headers
+# that CHRE requires are missing. Please contact the CHRE team for a
+# workaround.
+
+# IAR interlinking compatibility flags
+TARGET_CFLAGS += -D__ARM7EM__
+TARGET_CFLAGS += -D__CORE__=__ARM7EM__
+TARGET_CFLAGS += -D__FPU_PRESENT=1
+TARGET_CFLAGS += -D_LIBCPP_HAS_THREAD_API_EXTERNAL
+GCC_SO_LDFLAGS += --no-wchar-size-warning
+
+# The Exynos lib has a macro that includes common headers based on a 'Chip' ID. Eg:
+# CSP_HEADER(csp_common) includes csp_common{CHIP}.h.
+TARGET_CFLAGS += -DCHIP=9925
+
+# There are quite a few macros gated by 'EMBOS' in the csp library.
+TARGET_CFLAGS += -DEMBOS
+
+# CMSIS uses the register keyword liberally, which results in a warning when
+# compiled with GCC.
+COMMON_CXX_CFLAGS += -Wno-register
+
+# Temporarily need the following define, since we use printfs for logging
+# until the logcat redirection is implemented.
+# Reference: https://en.cppreference.com/w/cpp/types/integer#Notes
+TARGET_CFLAGS += -D__int64_t_defined
+
+# Temporarily disable implicit double promotion warnings until logcat
+# redirection is implemented.
+TARGET_CFLAGS += -Wno-double-promotion
+
+# GCC is unnecessarily strict with shadow warnings in legal C++ constructor
+# syntax.
+TARGET_CFLAGS += -Wno-shadow
+
+TARGET_CFLAGS += -DCHRE_FIRST_SUPPORTED_API_VERSION=CHRE_API_VERSION_1_6
+
+TARGET_VARIANT_SRCS += $(EMBOS_SRCS)
+TARGET_VARIANT_SRCS += $(EXYNOS_SRCS)
+TARGET_VARIANT_SRCS += $(ARM_SRCS)
+TARGET_VARIANT_SRCS += $(DSO_SUPPORT_LIB_SRCS)
+
+TARGET_CFLAGS += $(DSO_SUPPORT_LIB_CFLAGS)
+
+TARGET_PLATFORM_ID = 0x476F6F676C002000
+
+include $(CHRE_PREFIX)/build/arch/cortexm.mk
+include $(CHRE_PREFIX)/build/build_template.mk
+endif
diff --git a/build/variant/aosp_riscv55e03_tinysys.mk b/build/variant/aosp_riscv55e03_tinysys.mk
new file mode 100644
index 0000000..5513f95
--- /dev/null
+++ b/build/variant/aosp_riscv55e03_tinysys.mk
@@ -0,0 +1,78 @@
+#
+# Google Reference CHRE Implementation for MTK riscv (v55e03) Tinysys
+#
+
+include $(CHRE_PREFIX)/build/clean_build_template_args.mk
+
+TARGET_NAME = aosp_riscv55e03_tinysys
+ifneq ($(filter $(TARGET_NAME)% all, $(MAKECMDGOALS)),)
+
+ifeq ($(RISCV_TINYSYS_PREFIX),)
+$(error "The tinysys code directory needs to be exported as the RISCV_TINYSYS_PREFIX \
+         environment variable")
+endif
+
+TARGET_CFLAGS = $(TINYSYS_CFLAGS)
+TARGET_VARIANT_SRCS = $(TINYSYS_SRCS)
+TARGET_BIN_LDFLAGS = $(AOSP_RISCV_TINYSYS_BIN_LDFLAGS)
+TARGET_SO_EARLY_LIBS = $(AOSP_RISCV_TINYSYS_EARLY_LIBS)
+TARGET_SO_LATE_LIBS = $(AOSP_RISCV_TINYSYS_LATE_LIBS)
+TARGET_PLATFORM_ID = 0x476f6f676c003000
+
+# Macros #######################################################################
+
+TINYSYS_CFLAGS += -D__riscv
+TINYSYS_CFLAGS += -DMRV55
+TINYSYS_CFLAGS += -D_LIBCPP_HAS_NO_LONG_LONG
+
+TINYSYS_CFLAGS += --target=riscv32-unknown-elf
+TINYSYS_CFLAGS += -march=rv32imafcv
+TINYSYS_CFLAGS += -mcpu=MRV55E03
+
+# Word size for this architecture
+TARGET_CFLAGS += -DCHRE_32_BIT_WORD_SIZE
+
+# chre platform
+TARGET_CFLAGS += -DCHRE_FIRST_SUPPORTED_API_VERSION=CHRE_API_VERSION_1_7
+# TODO(b/254121302): Needs to confirm with MTK about the max message size below
+TARGET_CFLAGS += -DCHRE_MESSAGE_TO_HOST_MAX_SIZE=4096
+TARGET_CFLAGS += -DCHRE_USE_BUFFERED_LOGGING
+# TODO(b/256870101): create mutex on heap for now
+TARGET_CFLAGS += -DCHRE_CREATE_MUTEX_ON_HEAP
+
+# Compiling flags ##############################################################
+
+# -fpic and -shared are only needed for dynamic linking
+ifeq ($(IS_ARCHIVE_ONLY_BUILD),)
+TARGET_SO_LDFLAGS += -shared
+TARGET_CFLAGS += -fpic
+
+# Enable compiler-rt dependencies
+LLVM_RTLIB=$(RISCV_TOOLCHAIN_PATH)/lib/clang/9.0.1/libpic/riscv32/MRV55E03
+TARGET_SO_LDFLAGS += -L$(LLVM_RTLIB)
+TARGET_SO_LDFLAGS += -lclang_rt.builtins-riscv32
+endif
+
+ifneq ($(IS_NANOAPP_BUILD),)
+# Used to expose libc headers to nanoapps that aren't supported on the given platform
+TARGET_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include/chre/platform/shared/libc
+
+TARGET_VARIANT_SRCS += $(DSO_SUPPORT_LIB_SRCS)
+TARGET_CFLAGS += $(DSO_SUPPORT_LIB_CFLAGS)
+
+ifeq ($(CHRE_TCM_ENABLED),true)
+TARGET_CFLAGS += -DCHRE_TCM_ENABLED
+# Flags:
+# Signed                 = 0x00000001
+# TCM-capable            = 0x00000004
+TARGET_NANOAPP_FLAGS = 0x00000005
+endif
+endif
+
+# Other makefiles ##############################################################
+
+include $(CHRE_PREFIX)/platform/shared/mbedtls/mbedtls.mk
+include $(CHRE_PREFIX)/build/arch/riscv.mk
+include $(CHRE_PREFIX)/build/build_template.mk
+endif
+
diff --git a/chpp/Android.bp b/chpp/Android.bp
index efdab92..0560224 100644
--- a/chpp/Android.bp
+++ b/chpp/Android.bp
@@ -24,76 +24,54 @@
     default_applicable_licenses: ["system_chre_license"],
 }
 
-cc_library_static {
-    name: "chre_chpp_linux",
+// Everything needed to run CHPP on Linux, except for the link layer.
+// Note that this is cc_defaults and not a lib because modules that inherit
+// these defaults may need to change compilation flags for sources here.
+cc_defaults {
+    name: "chre_chpp_core_without_link",
     vendor: true,
     cflags: [
-        "-std=c89",
         "-Wall",
         "-Wcast-align",
         "-Wcast-qual",
         "-Wconversion",
         "-Werror",
         "-Wextra",
-        "-Wmissing-prototypes",
         "-Wno-strict-aliasing",
         "-Wpointer-arith",
-        "-Wsign-compare",
         "-Wshadow",
-        "-Wstrict-prototypes",
         "-Wswitch",
-        "-DCHPP_CLIENT_ENABLED_DISCOVERY",
-        "-DCHPP_CLIENT_ENABLED_LOOPBACK",
-        "-DCHPP_CLIENT_ENABLED_TIMESYNC",
-        "-DCHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK",
-        "-DCHPP_CLIENT_ENABLED_GNSS",
-        "-DCHPP_CLIENT_ENABLED_WIFI",
-        "-DCHPP_CLIENT_ENABLED_WWAN",
-        "-DCHPP_SERVICE_ENABLED_GNSS",
-        "-DCHPP_SERVICE_ENABLED_WIFI",
-        "-DCHPP_SERVICE_ENABLED_WWAN",
-        "-DCHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK",
         "-DCHPP_MAX_REGISTERED_CLIENTS=3",
         "-DCHPP_MAX_REGISTERED_SERVICES=3",
         "-DCHPP_ENABLE_WORK_MONITOR",
         "-DCHPP_DEBUG_ASSERT_ENABLED",
-        "-DCHPP_WIFI_DEFAULT_CAPABILITIES=0xf",
-        "-DCHPP_WWAN_DEFAULT_CAPABILITIES=0x1",
-        "-DCHPP_GNSS_DEFAULT_CAPABILITIES=0x7",
+        "-DCHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK",
+        "-DCHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK",
+
         // clock_gettime() requires _POSIX_C_SOURCE >= 199309L
         "-D_POSIX_C_SOURCE=199309L",
         // Required for pthread_setname_np()
         "-D_GNU_SOURCE",
     ],
-    conlyflags: ["-std=c11"],
+    conlyflags: [
+        "-std=c11",
+        "-Wmissing-prototypes",
+        "-Wsign-compare",
+        "-Wstrict-prototypes",
+    ],
     srcs: [
         "transport.c",
         "app.c",
         "clients.c",
+        "platform/pal_api.c",
+        "platform/linux/memory.c",
+        "platform/linux/notifier.c",
+        "platform/shared/crc.c",
         "services.c",
-        "clients/discovery.c",
-        "clients/loopback.c",
-        "clients/timesync.c",
-        "clients/gnss.c",
-        "clients/wifi.c",
-        "clients/wwan.c",
-        "common/gnss_convert.c",
-        "common/wifi_convert.c",
-        "common/wifi_utils.c",
-        "common/wwan_convert.c",
         "services/discovery.c",
         "services/loopback.c",
         "services/nonhandle.c",
         "services/timesync.c",
-        "services/gnss.c",
-        "services/wifi.c",
-        "services/wwan.c",
-        "platform/pal_api.c",
-        "platform/linux/link.c",
-        "platform/linux/memory.c",
-        "platform/linux/notifier.c",
-        "platform/shared/crc.c",
-        "platform/linux/services/platform_gnss.c",
     ],
     export_include_dirs: [
         "platform/linux/include",
@@ -111,8 +89,57 @@
     host_supported: true,
 }
 
+// Meant to be combined with chre_chpp_core_without_link to add in the full set
+// of optional clients and services.
+cc_defaults {
+    name: "chre_chpp_clients_and_services",
+    cflags: [
+        "-DCHPP_CLIENT_ENABLED_DISCOVERY",
+        "-DCHPP_CLIENT_ENABLED_LOOPBACK",
+        "-DCHPP_CLIENT_ENABLED_TIMESYNC",
+        "-DCHPP_CLIENT_ENABLED_GNSS",
+        "-DCHPP_CLIENT_ENABLED_WIFI",
+        "-DCHPP_CLIENT_ENABLED_WWAN",
+        "-DCHPP_SERVICE_ENABLED_GNSS",
+        "-DCHPP_SERVICE_ENABLED_WIFI",
+        "-DCHPP_SERVICE_ENABLED_WWAN",
+        "-DCHPP_WIFI_DEFAULT_CAPABILITIES=0xf",
+        "-DCHPP_WWAN_DEFAULT_CAPABILITIES=0x1",
+        "-DCHPP_GNSS_DEFAULT_CAPABILITIES=0x7",
+    ],
+    srcs: [
+        "clients/discovery.c",
+        "clients/loopback.c",
+        "clients/timesync.c",
+        "clients/gnss.c",
+        "clients/wifi.c",
+        "clients/wwan.c",
+        "common/gnss_convert.c",
+        "common/wifi_convert.c",
+        "common/wifi_utils.c",
+        "common/wwan_convert.c",
+        "platform/linux/services/platform_gnss.c",
+        "services/gnss.c",
+        "services/wifi.c",
+        "services/wwan.c",
+    ]
+}
+
+cc_library_static {
+    name: "chre_chpp_linux",
+    defaults: [
+        "chre_chpp_core_without_link",
+        "chre_chpp_clients_and_services",
+    ],
+    srcs: [
+        "platform/linux/link.c",
+    ],
+}
+
 cc_test_host {
     name: "chre_chpp_linux_tests",
+    // TODO(b/232537107): Evaluate if isolated can be turned on
+    isolated: false,
     cflags: [
         "-DCHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK",
         "-DCHPP_CHECKSUM_ENABLED",
@@ -154,3 +181,28 @@
     ],
     static_libs: ["chre_chpp_linux"],
 }
+
+cc_test_host {
+    name: "chre_chpp_fake_link_sync_tests",
+    defaults: ["chre_chpp_core_without_link"],
+    cflags: [
+        // Speed up tests by setting timeouts to 10 ms
+        "-DCHPP_TRANSPORT_TX_TIMEOUT_NS=10000000",
+        "-DCHPP_TRANSPORT_RX_TIMEOUT_NS=10000000",
+    ],
+    local_include_dirs: [
+        "include",
+
+        // Note: this needs to come before platform/linux/include
+        "test/include/fake_link",
+        "platform/linux/include",
+    ],
+    srcs: [
+        "test/fake_link.cpp",
+        "test/fake_link_sync_test.cpp",
+        "test/packet_util.cpp",
+    ],
+    header_libs: [
+        "libbase_headers",
+    ]
+}
diff --git a/chpp/QUICKSTART.md b/chpp/QUICKSTART.md
index de5deb5..3c939f1 100644
--- a/chpp/QUICKSTART.md
+++ b/chpp/QUICKSTART.md
@@ -19,26 +19,15 @@
 
 ### 1. Link-Layer APIs
 
-The APIs that are needed to tie CHPP to the serial port are as follows. Details are provided in link.h.
-
-1. void chppPlatformLinkInit(\*params)
-1. void chppPlatformLinkDeinit(\*params)
-1. void chppPlatformLinkReset(\*params)
-1. enum ChppLinkErrorCode chppPlatformLinkSend(\*params, \*buf, len)
-1. Depending on implementation, void chppLinkSendDoneCb(\*params)
-1. void chppPlatformLinkDoWork(\*params)
-1. bool chppRxDataCb(\*context, \*buf, len)
-1. Optionally, chppRxPacketCompleteCb(\*context)
-
-In addition, the system must implement and initialize the platform-specific linkParams data structure as part of platform_link.h
+You need to create a `ChppLinkApi` API struct and a `ChppLinkConfiguration` configuration struct for your link layer.
+See details in link.h.
 
 ### 1. Initialization
 
 In order to initialize CHPP, it is necessary to
 
-1. Allocate the transportContext and appContext structs that hold the state for each instance of the application and transport layers (in any order)
+1. Allocate the linkContext, transportContext, and appContext structs that hold the state for each instance of the application, transport, and link layers (in any order)
 1. Call the layers’ initialization functions, chppTransportInit and chppAppInit (in any order)
-1. Initialize the platform-specific linkParams struct (part of the transport struct)
 1. Call chppWorkThreadStart to start the main thread for CHPP's Transport Layer
 
 ### 1. Testing
@@ -51,7 +40,7 @@
 
 1. Call chppWorkThreadStop() to stop the main worker thread.
 1. Call the layers’ deinitialization functions, chppTransportDeinit and chppAppDeinit (in any order)
-1. Deallocate the transportContext, appContext, and the platform-specific linkParams structs
+1. Deallocate the transportContext, appContext, and the linkContext structs
 
 ### 1. Single-threaded systems
 
diff --git a/chpp/README.md b/chpp/README.md
index bb2a022..ca3041d 100644
--- a/chpp/README.md
+++ b/chpp/README.md
@@ -114,15 +114,14 @@
 To tie corresponding transport and application layers together, each layer’s context struct includes a pointer to the context struct of the other corresponding layer (i.e. the transport layer’s context has a pointer to the corresponding app layer’s context struct and vice versa).
 Initializing CHPP involves:
 
-1. Allocating the structs for the application and transport layers (in any order).
+1. Allocating the structs for the application, transport, and link layers (in any order).
 2. Calling the application and transport layers’ initialization functions (in any order)
-3. Initializing the platform-specific link layer parameters within the transport struct.
 
-## void chppTransportInit(\*transportContext, \*appContext)
+## void chppTransportInit(\*transportContext, \*appContext, \*linkContext, \*linkApi)
 
 The CHPP Transport Layer state is stored in the ChppTransportState struct transportContext, and passed around between various functions. It is necessary to initialize the transport layer state for each transport layer instance on every platform.
 Each transport layer instance is associated with a single application layer instance. appContext points to the application layer status struct associated with this transport layer instance.
-After calling chppTransportInit, it is also necessary to separately initialize the platform-specific values of transportContext.linkParams.
+After calling chppTransportInit, it is also necessary to separately initialize the linkContext - note that this can be done in the link layer init function.
 
 ## void chppAppInit(\*transportContext, \*appContext)
 
@@ -143,16 +142,16 @@
 This is an optional function that enables the link layer to indicate the end of a packet. For packets with a corrupt length field, this function can enable the link layer to explicitly NACK the bad packet earlier.
 This function is designed exclusively for link layers that can identify the end of individual packets. The availability of this information depends on the link layer implementation.
 
-## enum ChppLinkErrorCode chppPlatformLinkSend(\*params, \*buf, len)
+## [Link API] enum ChppLinkErrorCode send(\*linkContext, \*buf, len)
 
-This function is the interface between the CHPP Transport layer and the communications link’s Tx path (e.g. to the UART driver). This function is called when any data should be sent to the serial interface. The data is provided through a pointer to \*buf, with its length specified as len. The struct params is platform-specific and should include link details and parameters as initialized by the implementation.
-Both synchronous and asynchronous implementations of this function are supported. A synchronous implementation refers to one where chppPlatformLinkSend() is done with buf and len when it returns (i.e. the caller can free or reuse buf and len). An asynchronous implementation refers to one where chppPlatformLinkSend() returns before completely consuming buf and len (e.g. the send is completed at a later time). In this case, it is up to the platform implementation to call chppLinkSendDoneCb() after processing the contents of buf and len.
+This function is the interface between the CHPP Transport layer and the communications link’s Tx path (e.g. to the UART driver). This function is called when any data should be sent to the serial interface. The data is stored in the link TxBuffer (see getTxBuffer), with its length specified as len. The linkContext should include link details and parameters as initialized by the implementation.
+Both synchronous and asynchronous implementations of this function are supported. A synchronous implementation refers to one where send() is done with buf and len when it returns (i.e. the caller can free or reuse buf and len). An asynchronous implementation refers to one where send() returns before completely consuming buf and len (e.g. the send is completed at a later time). In this case, it is up to the platform implementation to call chppLinkSendDoneCb() after processing the contents of buf and len.
 This function returns CHPP_LINK_ERROR_NONE_SENT if the platform implementation for this function is synchronous and CHPP_LINK_ERROR_NONE_QUEUED if it is implemented asynchronously. It can also return an error code from enum ChppLinkErrorCode.
 
-## void chppLinkSendDoneCb(\*params)
+## void chppLinkSendDoneCb(\*transportContext)
 
-Notifies the transport layer that the link layer is done sending the previous payload (as provided to platformLinkSend() through buf and len) and can accept more data.
-On systems that implement the link layer Tx asynchronously, where platformLinkSend() returns False before consuming the payload provided to it, the platform implementation must call this function after platformLinkSend() is done with the payload (i.e. buf and len).
+Notifies the transport layer that the link layer is done sending the previous payload (as provided to send()) and can accept more data.
+On systems that implement the link layer Tx asynchronously, where send() returns False before consuming the payload provided to it, the platform implementation must call this function after send() is done with the payload (i.e. buf and len).
 
 ## void chppWorkThreadStart(\*transportContext)
 
diff --git a/chpp/RELEASE_NOTES.md b/chpp/RELEASE_NOTES.md
index 71f67eb..75d4440 100644
--- a/chpp/RELEASE_NOTES.md
+++ b/chpp/RELEASE_NOTES.md
@@ -175,3 +175,92 @@
   - Client registration cleanup
   - Reset handling fixes
   - Testing improvements
+
+### 2023-01
+
+Update CHPP to make it possible to use different link layers on the same platform.
+
+**Before:**
+
+The link layer API is defined by:
+
+- A few global functions:
+  - `chppPlatformLinkInit`
+  - `chppPlatformLinkDeinit`
+  - `chppPlatformLinkSend`
+  - `chppPlatformLinkDoWork`
+  - `chppPlatformLinkReset`
+
+- A few defines:
+  - `CHPP_PLATFORM_LINK_TX_MTU_BYTES`
+  - `CHPP_PLATFORM_LINK_RX_MTU_BYTES`
+  - `CHPP_PLATFORM_TRANSPORT_TIMEOUT_MS`
+
+**After:**
+
+In order to be able to use different link layers, the link layer API is now defined by
+
+- A `ChppLinkApi` API struct composed of pointers to the entry points:
+  - `init`
+  - `deinit`
+  - `send`
+  - `doWork`
+  - `reset`
+  - `getConfig` [added]
+  - `getTxBuffer` [added]
+- A free form state,
+- A `ChppLinkConfiguration` struct replacing the former defines.
+
+#### Migration
+
+You first need to create a `struct` holding the state of the link layer.
+This state `struct` is free form but would usually contain:
+- The TX buffer - it was owned by the transport layer in the previous version.
+  The TX buffer size must be added to the configuration `ChppLinkConfiguration` struct.
+  You can compute the size from your former `CHPP_PLATFORM_LINK_TX_MTU_BYTES`.
+  The formula to use is `min(CHPP_PLATFORM_LINK_TX_MTU_BYTES, 1024) + CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES`.
+  For example if your `CHPP_PLATFORM_LINK_TX_MTU_BYTES` was 2048, the TX buffer size should be `1024 + CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES`.
+  Note that 1024 (or whatever the value of the min is) is the effective payload.
+  The TX buffer will be slightly larger to accommodate the transport layer encoding overhead.
+- A pointer to the transport layer state which is required for the transport layer callbacks
+
+You need to create an instance of `ChppLinkApi` with pointers to the link functions.
+The API of the existing function have changed. They now take a `void *` pointer to the free form link state where they used to take a `struct ChppPlatformLinkParameters *`. You should cast that `void* linkContext` pointer to the type of your free form state.
+
+The `init` function now takes a second `struct ChppTransportState *transportContext` parameter. That function should store it in the state as it will be needed later to callback into the transport layer. The `init` function might store the `ChppLinkConfiguration` configuration in the state (if the configuration varies across link layer instances).
+
+The `send` function does not take a pointer to the TX buffer (`uint8_t *buf`) any more. That's because this buffer is now owned by the link layer and part of the link state.
+
+The added `getConfig` function returns the configuration `ChppLinkConfiguration` struct. The configuration might be shared across link instances or specific to a given instance.
+
+The added `getTxBuffer` function returns a pointer to the TX buffer that is part in the state.
+
+Then you need to create the `ChppLinkConfiguration` struct. It contains the size of TX buffer, the size of the RX buffer. Those are equivalent to the former defines. Note that `CHPP_PLATFORM_TRANSPORT_TIMEOUT_MS` was not used and has been deleted.
+
+Other changes:
+
+- You need to pass the link state and the link `ChppLinkApi` struct when initializing the transport layer with `chppTransportInit`.
+- When calling the `chppLinkSendDoneCb` and `chppWorkThreadSignalFromLink` from the link layer the first parameter should now be a pointer to the transport layer. You would typically retrieve that pointer from the link state where you should have stored it in the `init` function.
+
+### 2023-03
+
+The `chppRegisterService` signature changes from
+
+```
+uint8_t chppRegisterService(struct ChppAppState *appContext,
+                            void *serviceContext,
+                            const struct ChppService *newService);
+```
+
+to
+
+```
+void chppRegisterService(struct ChppAppState *appContext, void *serviceContext,
+                         struct ChppServiceState *serviceState,
+                         const struct ChppService *newService);
+```
+
+The handle which used to be returned is now populated in `serviceState`.
+`service->appContext` is also initialized to the passed `appContext`.
+
+This change makes the signature and behavior consistent with `chreRegisterClient`.
\ No newline at end of file
diff --git a/chpp/app.c b/chpp/app.c
index afd0b7d..81aadfd 100644
--- a/chpp/app.c
+++ b/chpp/app.c
@@ -142,6 +142,11 @@
  */
 static bool chppProcessPredefinedServiceResponse(struct ChppAppState *context,
                                                  uint8_t *buf, size_t len) {
+  // Possibly unused if compiling without the clients below enabled
+  UNUSED_VAR(context);
+  UNUSED_VAR(buf);
+  UNUSED_VAR(len);
+
   struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
   bool handleValid = true;
   bool dispatchResult = true;
@@ -194,19 +199,11 @@
  */
 static bool chppProcessPredefinedClientNotification(
     struct ChppAppState *context, uint8_t *buf, size_t len) {
-  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
-  bool handleValid = true;
-  bool dispatchResult = true;
-
-  // No predefined services support these yet
-  handleValid = false;
-
   UNUSED_VAR(context);
   UNUSED_VAR(len);
-  UNUSED_VAR(rxHeader);
-  UNUSED_VAR(dispatchResult);
-
-  return handleValid;
+  UNUSED_VAR(buf);
+  // No predefined services support these.
+  return false;
 }
 
 /**
@@ -221,19 +218,11 @@
  */
 static bool chppProcessPredefinedServiceNotification(
     struct ChppAppState *context, uint8_t *buf, size_t len) {
-  struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
-  bool handleValid = true;
-  bool dispatchResult = true;
-
-  // No predefined clients support these yet
-  handleValid = false;
-
   UNUSED_VAR(context);
   UNUSED_VAR(len);
-  UNUSED_VAR(rxHeader);
-  UNUSED_VAR(dispatchResult);
-
-  return handleValid;
+  UNUSED_VAR(buf);
+  // No predefined clients support these.
+  return false;
 }
 
 /**
@@ -333,7 +322,6 @@
   switch (CHPP_APP_GET_MESSAGE_TYPE(type)) {
     case CHPP_MESSAGE_TYPE_CLIENT_REQUEST: {
       return chppServiceOfHandle(context, handle)->requestDispatchFunctionPtr;
-      break;
     }
     case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: {
       struct ChppClientState *clientState =
@@ -341,15 +329,13 @@
               context, handle, type);
       if (clientState->openState == CHPP_OPEN_STATE_CLOSED) {
         CHPP_LOGE("RX service response but client closed");
-      } else {
-        return chppClientOfHandle(context, handle)->responseDispatchFunctionPtr;
+        break;
       }
-      break;
+      return chppClientOfHandle(context, handle)->responseDispatchFunctionPtr;
     }
     case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: {
       return chppServiceOfHandle(context, handle)
           ->notificationDispatchFunctionPtr;
-      break;
     }
     case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: {
       struct ChppClientState *clientState =
@@ -357,11 +343,10 @@
               context, handle, type);
       if (clientState->openState == CHPP_OPEN_STATE_CLOSED) {
         CHPP_LOGE("RX service notification but client closed");
-      } else {
-        return chppClientOfHandle(context, handle)
-            ->notificationDispatchFunctionPtr;
+        break;
       }
-      break;
+      return chppClientOfHandle(context, handle)
+          ->notificationDispatchFunctionPtr;
     }
   }
 
@@ -574,55 +559,57 @@
     chppEnqueueTxErrorDatagram(context->transportContext,
                                CHPP_TRANSPORT_ERROR_APPLAYER);
     CHPP_DEBUG_ASSERT(false);
+    return;
+  }
 
-  } else {
-    ChppDispatchFunction *dispatchFunc =
-        chppGetDispatchFunction(context, rxHeader->handle, messageType);
-    if (dispatchFunc == NULL) {
-      CHPP_LOGE("H#%" PRIu8 " unsupported msg=0x%" PRIx8 " (len=%" PRIuSIZE
-                ", ID=%" PRIu8 ")",
-                rxHeader->handle, rxHeader->type, len, rxHeader->transaction);
-      chppEnqueueTxErrorDatagram(context->transportContext,
-                                 CHPP_TRANSPORT_ERROR_APPLAYER);
+  ChppDispatchFunction *dispatchFunc =
+      chppGetDispatchFunction(context, rxHeader->handle, messageType);
+  if (dispatchFunc == NULL) {
+    CHPP_LOGE("H#%" PRIu8 " unsupported msg=0x%" PRIx8 " (len=%" PRIuSIZE
+              ", ID=%" PRIu8 ")",
+              rxHeader->handle, rxHeader->type, len, rxHeader->transaction);
+    chppEnqueueTxErrorDatagram(context->transportContext,
+                               CHPP_TRANSPORT_ERROR_APPLAYER);
+    return;
+  }
 
-    } else {
-      // All good. Dispatch datagram and possibly notify a waiting client
+  // All good. Dispatch datagram and possibly notify a waiting client
+  enum ChppAppErrorCode error = dispatchFunc(clientServiceContext, buf, len);
 
-      enum ChppAppErrorCode error =
-          dispatchFunc(clientServiceContext, buf, len);
-      if (error != CHPP_APP_ERROR_NONE) {
-        CHPP_LOGE("RX dispatch err=0x%" PRIx16 " H#%" PRIu8 " type=0x%" PRIx8
-                  " ID=%" PRIu8 " cmd=0x%" PRIx16 " len=%" PRIuSIZE,
-                  error, rxHeader->handle, rxHeader->type,
-                  rxHeader->transaction, rxHeader->command, len);
+  if (error != CHPP_APP_ERROR_NONE) {
+    CHPP_LOGE("RX dispatch err=0x%" PRIx16 " H#%" PRIu8 " type=0x%" PRIx8
+              " ID=%" PRIu8 " cmd=0x%" PRIx16 " len=%" PRIuSIZE,
+              error, rxHeader->handle, rxHeader->type, rxHeader->transaction,
+              rxHeader->command, len);
 
-        // Only client requests require a dispatch failure response.
-        if (messageType == CHPP_MESSAGE_TYPE_CLIENT_REQUEST) {
-          struct ChppAppHeader *response =
-              chppAllocServiceResponseFixed(rxHeader, struct ChppAppHeader);
-          if (response == NULL) {
-            CHPP_LOG_OOM();
-          } else {
-            response->error = (uint8_t)error;
-            chppEnqueueTxDatagramOrFail(context->transportContext, response,
-                                        sizeof(*response));
-          }
-        }
-      } else if (messageType == CHPP_MESSAGE_TYPE_SERVICE_RESPONSE) {
-        // Datagram is a service response. Check for synchronous operation and
-        // notify waiting client if needed.
-
-        struct ChppClientState *clientState =
-            (struct ChppClientState *)clientServiceContext;
-        chppMutexLock(&clientState->responseMutex);
-        clientState->responseReady = true;
-        CHPP_LOGD(
-            "Finished dispatching a service response. Notifying a potential "
-            "synchronous client");
-        chppConditionVariableSignal(&clientState->responseCondVar);
-        chppMutexUnlock(&clientState->responseMutex);
+    // Only client requests require a dispatch failure response.
+    if (messageType == CHPP_MESSAGE_TYPE_CLIENT_REQUEST) {
+      struct ChppAppHeader *response =
+          chppAllocServiceResponseFixed(rxHeader, struct ChppAppHeader);
+      if (response == NULL) {
+        CHPP_LOG_OOM();
+      } else {
+        response->error = (uint8_t)error;
+        chppEnqueueTxDatagramOrFail(context->transportContext, response,
+                                    sizeof(*response));
       }
     }
+    return;
+  }
+
+  if (messageType == CHPP_MESSAGE_TYPE_SERVICE_RESPONSE) {
+    // Datagram is a service response. Check for synchronous operation and
+    // notify waiting client if needed.
+
+    struct ChppClientState *clientState =
+        (struct ChppClientState *)clientServiceContext;
+    chppMutexLock(&clientState->responseMutex);
+    clientState->responseReady = true;
+    CHPP_LOGD(
+        "Finished dispatching a service response. Notifying a potential "
+        "synchronous client");
+    chppConditionVariableSignal(&clientState->responseCondVar);
+    chppMutexUnlock(&clientState->responseMutex);
   }
 }
 
@@ -800,15 +787,13 @@
 uint8_t chppAppShortResponseErrorHandler(uint8_t *buf, size_t len,
                                          const char *responseName) {
   CHPP_ASSERT(len >= sizeof(struct ChppAppHeader));
-  uint8_t result = CHRE_ERROR;
   struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
 
   if (rxHeader->error == CHPP_APP_ERROR_NONE) {
     CHPP_LOGE("%s resp short len=%" PRIuSIZE, responseName, len);
-  } else {
-    CHPP_LOGI("%s resp short len=%" PRIuSIZE, responseName, len);
-    result = chppAppErrorToChreError(rxHeader->error);
+    return CHRE_ERROR;
   }
 
-  return result;
+  CHPP_LOGD("%s resp short len=%" PRIuSIZE, responseName, len);
+  return chppAppErrorToChreError(rxHeader->error);
 }
diff --git a/chpp/clients.c b/chpp/clients.c
index 5fea2bc..198e32b 100644
--- a/chpp/clients.c
+++ b/chpp/clients.c
@@ -173,30 +173,28 @@
   if (appContext->registeredClientCount >= CHPP_MAX_REGISTERED_CLIENTS) {
     CHPP_LOGE("Max clients registered: %" PRIu8,
               appContext->registeredClientCount);
-
-  } else {
-    clientState->appContext = appContext;
-    clientState->rRStates = rRStates;
-    clientState->index = appContext->registeredClientCount;
-
-    appContext->registeredClientContexts[appContext->registeredClientCount] =
-        clientContext;
-    appContext->registeredClientStates[appContext->registeredClientCount] =
-        clientState;
-    appContext->registeredClients[appContext->registeredClientCount] =
-        newClient;
-
-    char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
-    chppUuidToStr(newClient->descriptor.uuid, uuidText);
-    CHPP_LOGD("Client # %" PRIu8 " UUID=%s, version=%" PRIu8 ".%" PRIu8
-              ".%" PRIu16 ", min_len=%" PRIuSIZE,
-              appContext->registeredClientCount, uuidText,
-              newClient->descriptor.version.major,
-              newClient->descriptor.version.minor,
-              newClient->descriptor.version.patch, newClient->minLength);
-
-    appContext->registeredClientCount++;
+    return;
   }
+  clientState->appContext = appContext;
+  clientState->rRStates = rRStates;
+  clientState->index = appContext->registeredClientCount;
+
+  appContext->registeredClientContexts[appContext->registeredClientCount] =
+      clientContext;
+  appContext->registeredClientStates[appContext->registeredClientCount] =
+      clientState;
+  appContext->registeredClients[appContext->registeredClientCount] = newClient;
+
+  char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
+  chppUuidToStr(newClient->descriptor.uuid, uuidText);
+  CHPP_LOGD("Client # %" PRIu8 " UUID=%s, version=%" PRIu8 ".%" PRIu8
+            ".%" PRIu16 ", min_len=%" PRIuSIZE,
+            appContext->registeredClientCount, uuidText,
+            newClient->descriptor.version.major,
+            newClient->descriptor.version.minor,
+            newClient->descriptor.version.patch, newClient->minLength);
+
+  appContext->registeredClientCount++;
 }
 
 void chppInitBasicClients(struct ChppAppState *context) {
@@ -491,29 +489,29 @@
 
   if (request == NULL) {
     CHPP_LOG_OOM();
+    return false;
+  }
 
+  clientState->openState = CHPP_OPEN_STATE_OPENING;
+
+  if (blocking) {
+    CHPP_LOGD("Opening service - blocking");
+    result = chppSendTimestampedRequestAndWait(clientState, openRRState,
+                                               request, sizeof(*request));
   } else {
-    clientState->openState = CHPP_OPEN_STATE_OPENING;
+    CHPP_LOGD("Opening service - non-blocking");
+    result = chppSendTimestampedRequestOrFail(
+        clientState, openRRState, request, sizeof(*request),
+        CHPP_CLIENT_REQUEST_TIMEOUT_INFINITE);
+  }
 
-    if (blocking) {
-      CHPP_LOGD("Opening service - blocking");
-      result = chppSendTimestampedRequestAndWait(clientState, openRRState,
-                                                 request, sizeof(*request));
-    } else {
-      CHPP_LOGD("Opening service - non-blocking");
-      result = chppSendTimestampedRequestOrFail(
-          clientState, openRRState, request, sizeof(*request),
-          CHPP_CLIENT_REQUEST_TIMEOUT_INFINITE);
-    }
+  if (!result) {
+    CHPP_LOGE("Service open fail from state=%" PRIu8 " psudo=%d blocking=%d",
+              priorState, clientState->pseudoOpen, blocking);
+    clientState->openState = CHPP_OPEN_STATE_CLOSED;
 
-    if (!result) {
-      CHPP_LOGE("Service open fail from state=%" PRIu8 " psudo=%d blocking=%d",
-                priorState, clientState->pseudoOpen, blocking);
-      clientState->openState = CHPP_OPEN_STATE_CLOSED;
-
-    } else if (blocking) {
-      result = (clientState->openState == CHPP_OPEN_STATE_OPENED);
-    }
+  } else if (blocking) {
+    result = (clientState->openState == CHPP_OPEN_STATE_OPENED);
   }
 
   return result;
@@ -531,7 +529,7 @@
     CHPP_LOGE("Service open failed at service");
     clientState->openState = CHPP_OPEN_STATE_CLOSED;
   } else {
-    CHPP_LOGI("Service open succeeded at service");
+    CHPP_LOGD("Service open succeeded at service");
     clientState->openState = CHPP_OPEN_STATE_OPENED;
   }
 }
@@ -541,11 +539,11 @@
 
   for (uint8_t clientIdx = 0; clientIdx < context->registeredClientCount;
        clientIdx++) {
-    for (uint16_t cmdIdx = 0;
-         cmdIdx < context->registeredClients[clientIdx]->rRStateCount;
-         cmdIdx++) {
-      struct ChppRequestResponseState *rRState =
-          &context->registeredClientStates[clientIdx]->rRStates[cmdIdx];
+    const struct ChppClient *client = context->registeredClients[clientIdx];
+    for (uint16_t cmdIdx = 0; cmdIdx < client->rRStateCount; cmdIdx++) {
+      const struct ChppClientState *state =
+          context->registeredClientStates[clientIdx];
+      struct ChppRequestResponseState *rRState = &state->rRStates[cmdIdx];
 
       if (rRState->requestState == CHPP_REQUEST_STATE_REQUEST_SENT) {
         context->nextRequestTimeoutNs =
diff --git a/chpp/clients/discovery.c b/chpp/clients/discovery.c
index 8c8198b..e681752 100644
--- a/chpp/clients/discovery.c
+++ b/chpp/clients/discovery.c
@@ -35,20 +35,22 @@
 static inline bool chppIsClientCompatibleWithService(
     const struct ChppClientDescriptor *client,
     const struct ChppServiceDescriptor *service);
-static uint8_t chppFindMatchingClient(
-    struct ChppAppState *context, const struct ChppServiceDescriptor *service);
-static void chppDiscoveryProcessDiscoverAll(struct ChppAppState *context,
-                                            const uint8_t *buf, size_t len);
-ChppNotifierFunction *chppGetClientMatchNotifierFunction(
-    struct ChppAppState *context, uint8_t index);
+static uint8_t chppFindMatchingClientIndex(
+    struct ChppAppState *appState, const struct ChppServiceDescriptor *service);
+static void chppProcessDiscoverAllResponse(
+    struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
+    size_t responseLen);
+static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
+    struct ChppAppState *appState, uint8_t index);
 
 /************************************************
  *  Private Functions
  ***********************************************/
 
 /**
- * Determines if a client is compatible with a service. Compatibility
- * requirements are:
+ * Determines if a client is compatible with a service.
+ *
+ * Compatibility requirements are:
  * 1. UUIDs must match
  * 2. Major version numbers must match
  *
@@ -60,27 +62,28 @@
 static inline bool chppIsClientCompatibleWithService(
     const struct ChppClientDescriptor *client,
     const struct ChppServiceDescriptor *service) {
-  return (memcmp(client->uuid, service->uuid, CHPP_SERVICE_UUID_LEN) == 0 &&
-          client->version.major == service->version.major);
+  return memcmp(client->uuid, service->uuid, CHPP_SERVICE_UUID_LEN) == 0 &&
+         client->version.major == service->version.major;
 }
 
 /**
- * Attempts to match a registered client to a (discovered) service, responding
- * with either the client index or CHPP_CLIENT_INDEX_NONE if it fails.
+ * Matches a registered client to a (discovered) service.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  * @param service ChppServiceDescriptor of service.
  *
  * @param return Index of client matching the service, or CHPP_CLIENT_INDEX_NONE
  * if there is none.
  */
-static uint8_t chppFindMatchingClient(
-    struct ChppAppState *context, const struct ChppServiceDescriptor *service) {
+static uint8_t chppFindMatchingClientIndex(
+    struct ChppAppState *appState,
+    const struct ChppServiceDescriptor *service) {
   uint8_t result = CHPP_CLIENT_INDEX_NONE;
 
-  for (uint8_t i = 0; i < context->registeredClientCount; i++) {
-    if (chppIsClientCompatibleWithService(
-            &context->registeredClients[i]->descriptor, service)) {
+  const struct ChppClient **clients = appState->registeredClients;
+
+  for (uint8_t i = 0; i < appState->registeredClientCount; i++) {
+    if (chppIsClientCompatibleWithService(&clients[i]->descriptor, service)) {
       result = i;
       break;
     }
@@ -93,20 +96,19 @@
  * Processes the Discover All Services response
  * (CHPP_DISCOVERY_COMMAND_DISCOVER_ALL).
  *
- * @param context Maintains status for each app layer instance.
- * @param buf Input (request) datagram. Cannot be null.
- * @param len Length of input data in bytes.
+ * @param appState Application layer state.
+ * @param response The response from the discovery service.
+ * @param responseLen Length of the in bytes.
  */
-static void chppDiscoveryProcessDiscoverAll(struct ChppAppState *context,
-                                            const uint8_t *buf, size_t len) {
-  if (context->isDiscoveryComplete) {
+static void chppProcessDiscoverAllResponse(
+    struct ChppAppState *appState, const struct ChppDiscoveryResponse *response,
+    size_t responseLen) {
+  if (appState->isDiscoveryComplete) {
     CHPP_LOGE("Dupe discovery resp");
     return;
   }
 
-  const struct ChppDiscoveryResponse *response =
-      (const struct ChppDiscoveryResponse *)buf;
-  size_t servicesLen = len - sizeof(struct ChppAppHeader);
+  size_t servicesLen = responseLen - sizeof(struct ChppAppHeader);
   uint8_t serviceCount =
       (uint8_t)(servicesLen / sizeof(struct ChppServiceDescriptor));
 
@@ -124,91 +126,80 @@
   uint8_t matchedClients = 0;
   for (uint8_t i = 0; i < MIN(serviceCount, CHPP_MAX_DISCOVERED_SERVICES);
        i++) {
+    const struct ChppServiceDescriptor *service = &response->services[i];
+
     // Update lookup table
-    context->clientIndexOfServiceIndex[i] =
-        chppFindMatchingClient(context, &response->services[i]);
+    uint8_t clientIndex = chppFindMatchingClientIndex(appState, service);
+    appState->clientIndexOfServiceIndex[i] = clientIndex;
 
     char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
-    chppUuidToStr(response->services[i].uuid, uuidText);
+    chppUuidToStr(service->uuid, uuidText);
 
-    if (context->clientIndexOfServiceIndex[i] == CHPP_CLIENT_INDEX_NONE) {
+    if (clientIndex == CHPP_CLIENT_INDEX_NONE) {
       CHPP_LOGE(
           "No client for service #%d"
           " name=%s, UUID=%s, v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
-          CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText,
-          response->services[i].version.major,
-          response->services[i].version.minor,
-          response->services[i].version.patch);
-
-    } else {
-      CHPP_LOGD(
-          "Client # %" PRIu8
-          " matched to service on handle %d"
-          " with name=%s, UUID=%s. "
-          "client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
-          ", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
-          context->clientIndexOfServiceIndex[i],
-          CHPP_SERVICE_HANDLE_OF_INDEX(i), response->services[i].name, uuidText,
-          context->registeredClients[context->clientIndexOfServiceIndex[i]]
-              ->descriptor.version.major,
-          context->registeredClients[context->clientIndexOfServiceIndex[i]]
-              ->descriptor.version.minor,
-          context->registeredClients[context->clientIndexOfServiceIndex[i]]
-              ->descriptor.version.patch,
-          response->services[i].version.major,
-          response->services[i].version.minor,
-          response->services[i].version.patch);
-
-      // Initialize client
-      uint8_t idx = context->clientIndexOfServiceIndex[i];
-      if (context->registeredClients[idx]->initFunctionPtr(
-              context->registeredClientContexts[idx],
-              CHPP_SERVICE_HANDLE_OF_INDEX(i),
-              response->services[i].version) == false) {
-        CHPP_LOGE(
-            "Client v=%" PRIu8 ".%" PRIu8 ".%" PRIu16
-            " rejected init. Service v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
-            context->registeredClients[context->clientIndexOfServiceIndex[i]]
-                ->descriptor.version.major,
-            context->registeredClients[context->clientIndexOfServiceIndex[i]]
-                ->descriptor.version.minor,
-            context->registeredClients[context->clientIndexOfServiceIndex[i]]
-                ->descriptor.version.patch,
-            response->services[i].version.major,
-            response->services[i].version.minor,
-            response->services[i].version.patch);
-      } else {
-        matchedClients++;
-      }
+          CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name, uuidText,
+          service->version.major, service->version.minor,
+          service->version.patch);
+      continue;
     }
+
+    const struct ChppClient *client = appState->registeredClients[clientIndex];
+
+    CHPP_LOGD("Client # %" PRIu8
+              " matched to service on handle %d"
+              " with name=%s, UUID=%s. "
+              "client version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
+              ", service version=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
+              clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i), service->name,
+              uuidText, client->descriptor.version.major,
+              client->descriptor.version.minor,
+              client->descriptor.version.patch, service->version.major,
+              service->version.minor, service->version.patch);
+
+    // Initialize client
+    if (!client->initFunctionPtr(
+            appState->registeredClientContexts[clientIndex],
+            CHPP_SERVICE_HANDLE_OF_INDEX(i), service->version)) {
+      CHPP_LOGE("Client v=%" PRIu8 ".%" PRIu8 ".%" PRIu16
+                " rejected init. Service v=%" PRIu8 ".%" PRIu8 ".%" PRIu16,
+                client->descriptor.version.major,
+                client->descriptor.version.minor,
+                client->descriptor.version.patch, service->version.major,
+                service->version.minor, service->version.patch);
+      continue;
+    }
+
+    matchedClients++;
   }
 
   CHPP_LOGD("Matched %" PRIu8 " out of %" PRIu8 " clients and %" PRIu8
             " services",
-            matchedClients, context->registeredClientCount, serviceCount);
+            matchedClients, appState->registeredClientCount, serviceCount);
 
   // Notify any clients waiting on discovery completion
-  chppMutexLock(&context->discoveryMutex);
-  context->isDiscoveryComplete = true;
-  context->matchedClientCount = matchedClients;
-  context->discoveredServiceCount = serviceCount;
-  chppConditionVariableSignal(&context->discoveryCv);
-  chppMutexUnlock(&context->discoveryMutex);
+  chppMutexLock(&appState->discoveryMutex);
+  appState->isDiscoveryComplete = true;
+  appState->matchedClientCount = matchedClients;
+  appState->discoveredServiceCount = serviceCount;
+  chppConditionVariableSignal(&appState->discoveryCv);
+  chppMutexUnlock(&appState->discoveryMutex);
 
   // Notify clients of match
-  for (uint8_t i = 0; i < context->discoveredServiceCount; i++) {
-    uint8_t clientIndex = context->clientIndexOfServiceIndex[i];
+  for (uint8_t i = 0; i < appState->discoveredServiceCount; i++) {
+    uint8_t clientIndex = appState->clientIndexOfServiceIndex[i];
     if (clientIndex != CHPP_CLIENT_INDEX_NONE) {
       // Discovered service has a matched client
-      ChppNotifierFunction *MatchNotifierFunction =
-          chppGetClientMatchNotifierFunction(context, clientIndex);
+      ChppNotifierFunction *matchNotifierFunction =
+          chppGetClientMatchNotifierFunction(appState, clientIndex);
 
       CHPP_LOGD("Client #%" PRIu8 " (H#%d) match notifier found=%d",
                 clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i),
-                (MatchNotifierFunction != NULL));
+                (matchNotifierFunction != NULL));
 
-      if (MatchNotifierFunction != NULL) {
-        MatchNotifierFunction(context->registeredClientContexts[clientIndex]);
+      if (matchNotifierFunction != NULL) {
+        matchNotifierFunction(appState->registeredClientContexts[clientIndex]);
       }
     }
   }
@@ -219,65 +210,65 @@
  * client. The function pointer will be set to null by clients that do not need
  * or support a match notification.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  * @param index Index of the registered client.
  *
  * @return Pointer to the match notification function.
  */
-ChppNotifierFunction *chppGetClientMatchNotifierFunction(
-    struct ChppAppState *context, uint8_t index) {
-  return context->registeredClients[index]->matchNotifierFunctionPtr;
+static ChppNotifierFunction *chppGetClientMatchNotifierFunction(
+    struct ChppAppState *appState, uint8_t index) {
+  return appState->registeredClients[index]->matchNotifierFunctionPtr;
 }
 
 /************************************************
  *  Public Functions
  ***********************************************/
 
-void chppDiscoveryInit(struct ChppAppState *context) {
-  CHPP_ASSERT_LOG(!context->isDiscoveryClientInitialized,
+void chppDiscoveryInit(struct ChppAppState *appState) {
+  CHPP_ASSERT_LOG(!appState->isDiscoveryClientInitialized,
                   "Discovery client already initialized");
 
   CHPP_LOGD("Initializing CHPP discovery client");
 
-  if (!context->isDiscoveryClientInitialized) {
-    chppMutexInit(&context->discoveryMutex);
-    chppConditionVariableInit(&context->discoveryCv);
-    context->isDiscoveryClientInitialized = true;
+  if (!appState->isDiscoveryClientInitialized) {
+    chppMutexInit(&appState->discoveryMutex);
+    chppConditionVariableInit(&appState->discoveryCv);
+    appState->isDiscoveryClientInitialized = true;
   }
 
-  context->matchedClientCount = 0;
-  context->isDiscoveryComplete = false;
-  context->isDiscoveryClientInitialized = true;
+  appState->matchedClientCount = 0;
+  appState->isDiscoveryComplete = false;
+  appState->isDiscoveryClientInitialized = true;
 }
 
-void chppDiscoveryDeinit(struct ChppAppState *context) {
-  CHPP_ASSERT_LOG(context->isDiscoveryClientInitialized,
+void chppDiscoveryDeinit(struct ChppAppState *appState) {
+  CHPP_ASSERT_LOG(appState->isDiscoveryClientInitialized,
                   "Discovery client already deinitialized");
 
   CHPP_LOGD("Deinitializing CHPP discovery client");
-  context->isDiscoveryClientInitialized = false;
+  appState->isDiscoveryClientInitialized = false;
 }
 
-bool chppWaitForDiscoveryComplete(struct ChppAppState *context,
+bool chppWaitForDiscoveryComplete(struct ChppAppState *appState,
                                   uint64_t timeoutMs) {
   bool success = false;
 
-  if (!context->isDiscoveryClientInitialized) {
+  if (!appState->isDiscoveryClientInitialized) {
     timeoutMs = 0;
   } else {
     success = true;
 
-    chppMutexLock(&context->discoveryMutex);
+    chppMutexLock(&appState->discoveryMutex);
     if (timeoutMs == 0) {
-      success = context->isDiscoveryComplete;
+      success = appState->isDiscoveryComplete;
     } else {
-      while (success && !context->isDiscoveryComplete) {
+      while (success && !appState->isDiscoveryComplete) {
         success = chppConditionVariableTimedWait(
-            &context->discoveryCv, &context->discoveryMutex,
+            &appState->discoveryCv, &appState->discoveryMutex,
             timeoutMs * CHPP_NSEC_PER_MSEC);
       }
     }
-    chppMutexUnlock(&context->discoveryMutex);
+    chppMutexUnlock(&appState->discoveryMutex);
   }
 
   if (!success) {
@@ -286,14 +277,15 @@
   return success;
 }
 
-bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *context,
+bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *appState,
                                           const uint8_t *buf, size_t len) {
   const struct ChppAppHeader *rxHeader = (const struct ChppAppHeader *)buf;
   bool success = true;
 
   switch (rxHeader->command) {
     case CHPP_DISCOVERY_COMMAND_DISCOVER_ALL: {
-      chppDiscoveryProcessDiscoverAll(context, buf, len);
+      chppProcessDiscoverAllResponse(
+          appState, (const struct ChppDiscoveryResponse *)buf, len);
       break;
     }
     default: {
@@ -304,14 +296,14 @@
   return success;
 }
 
-void chppInitiateDiscovery(struct ChppAppState *context) {
-  if (context->isDiscoveryComplete) {
+void chppInitiateDiscovery(struct ChppAppState *appState) {
+  if (appState->isDiscoveryComplete) {
     CHPP_LOGE("Duplicate discovery init");
     return;
   }
 
   for (uint8_t i = 0; i < CHPP_MAX_DISCOVERED_SERVICES; i++) {
-    context->clientIndexOfServiceIndex[i] = CHPP_CLIENT_INDEX_NONE;
+    appState->clientIndexOfServiceIndex[i] = CHPP_CLIENT_INDEX_NONE;
   }
 
   struct ChppAppHeader *request = chppMalloc(sizeof(struct ChppAppHeader));
@@ -321,15 +313,15 @@
   request->error = CHPP_APP_ERROR_NONE;
   request->command = CHPP_DISCOVERY_COMMAND_DISCOVER_ALL;
 
-  chppEnqueueTxDatagramOrFail(context->transportContext, request,
+  chppEnqueueTxDatagramOrFail(appState->transportContext, request,
                               sizeof(*request));
 }
 
-bool chppAreAllClientsMatched(struct ChppAppState *context) {
+bool chppAreAllClientsMatched(struct ChppAppState *appState) {
   bool success = false;
-  chppMutexLock(&context->discoveryMutex);
-  success = (context->isDiscoveryComplete) &&
-            (context->registeredClientCount == context->matchedClientCount);
-  chppMutexUnlock(&context->discoveryMutex);
+  chppMutexLock(&appState->discoveryMutex);
+  success = (appState->isDiscoveryComplete) &&
+            (appState->registeredClientCount == appState->matchedClientCount);
+  chppMutexUnlock(&appState->discoveryMutex);
   return success;
 }
diff --git a/chpp/clients/gnss.c b/chpp/clients/gnss.c
index 068d83d..ca131d7 100644
--- a/chpp/clients/gnss.c
+++ b/chpp/clients/gnss.c
@@ -328,7 +328,7 @@
       !gnssClientContext->client.pseudoOpen) {
     CHPP_LOGW("GNSS client reset but wasn't open");
   } else {
-    CHPP_LOGI("GNSS client reopening from state=%" PRIu8,
+    CHPP_LOGD("GNSS client reopening from state=%" PRIu8,
               gnssClientContext->client.openState);
     gnssClientContext->requestStateResyncPending = true;
     chppClientSendOpenRequest(&gGnssClientContext.client,
@@ -810,6 +810,7 @@
  ***********************************************/
 
 void chppRegisterGnssClient(struct ChppAppState *appContext) {
+  memset(&gGnssClientContext, 0, sizeof(gGnssClientContext));
   chppRegisterClient(appContext, (void *)&gGnssClientContext,
                      &gGnssClientContext.client, gGnssClientContext.rRState,
                      &kGnssClientConfig);
diff --git a/chpp/clients/loopback.c b/chpp/clients/loopback.c
index 4458704..b8f19e8 100644
--- a/chpp/clients/loopback.c
+++ b/chpp/clients/loopback.c
@@ -53,163 +53,145 @@
  *  Public Functions
  ***********************************************/
 
-void chppLoopbackClientInit(struct ChppAppState *context) {
+void chppLoopbackClientInit(struct ChppAppState *appState) {
   CHPP_LOGD("Loopback client init");
+  CHPP_DEBUG_NOT_NULL(appState);
 
-  context->loopbackClientContext =
+  appState->loopbackClientContext =
       chppMalloc(sizeof(struct ChppLoopbackClientState));
-  CHPP_NOT_NULL(context->loopbackClientContext);
-  memset(context->loopbackClientContext, 0,
-         sizeof(struct ChppLoopbackClientState));
+  CHPP_NOT_NULL(appState->loopbackClientContext);
+  struct ChppLoopbackClientState *state = appState->loopbackClientContext;
+  memset(state, 0, sizeof(struct ChppLoopbackClientState));
 
-  context->loopbackClientContext->client.appContext = context;
-  chppClientInit(&context->loopbackClientContext->client, CHPP_HANDLE_LOOPBACK);
-  context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_NONE;
-  context->loopbackClientContext->client.openState = CHPP_OPEN_STATE_OPENED;
+  state->client.appContext = appState;
+  chppClientInit(&state->client, CHPP_HANDLE_LOOPBACK);
+  state->testResult.error = CHPP_APP_ERROR_NONE;
+  state->client.openState = CHPP_OPEN_STATE_OPENED;
 }
 
-void chppLoopbackClientDeinit(struct ChppAppState *context) {
+void chppLoopbackClientDeinit(struct ChppAppState *appState) {
   CHPP_LOGD("Loopback client deinit");
+  CHPP_NOT_NULL(appState);
+  CHPP_NOT_NULL(appState->loopbackClientContext);
 
-  CHPP_NOT_NULL(context);
-  CHPP_NOT_NULL(context->loopbackClientContext);
-
-  chppClientDeinit(&context->loopbackClientContext->client);
-  CHPP_FREE_AND_NULLIFY(context->loopbackClientContext);
+  chppClientDeinit(&appState->loopbackClientContext->client);
+  CHPP_FREE_AND_NULLIFY(appState->loopbackClientContext);
 }
 
-bool chppDispatchLoopbackServiceResponse(struct ChppAppState *context,
+bool chppDispatchLoopbackServiceResponse(struct ChppAppState *appState,
                                          const uint8_t *response, size_t len) {
   CHPP_LOGD("Loopback client dispatch service response");
-
   CHPP_ASSERT(len >= CHPP_LOOPBACK_HEADER_LEN);
   CHPP_NOT_NULL(response);
-  CHPP_NOT_NULL(context->loopbackClientContext);
-  CHPP_NOT_NULL(context->loopbackClientContext->loopbackData);
+  CHPP_DEBUG_NOT_NULL(appState);
+  struct ChppLoopbackClientState *state = appState->loopbackClientContext;
+  CHPP_NOT_NULL(state);
+  CHPP_NOT_NULL(state->loopbackData);
 
-  CHPP_ASSERT(chppClientTimestampResponse(
-      &context->loopbackClientContext->client,
-      &context->loopbackClientContext->runLoopbackTest,
-      (const struct ChppAppHeader *)response));
+  CHPP_ASSERT(
+      chppClientTimestampResponse(&state->client, &state->runLoopbackTest,
+                                  (const struct ChppAppHeader *)response));
 
-  context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_NONE;
-  context->loopbackClientContext->testResult.responseLen = len;
-  context->loopbackClientContext->testResult.firstError = len;
-  context->loopbackClientContext->testResult.byteErrors = 0;
-  context->loopbackClientContext->testResult.rttNs =
-      context->loopbackClientContext->runLoopbackTest.responseTimeNs -
-      context->loopbackClientContext->runLoopbackTest.requestTimeNs;
+  struct ChppLoopbackTestResult *result = &state->testResult;
 
-  if (context->loopbackClientContext->testResult.requestLen !=
-      context->loopbackClientContext->testResult.responseLen) {
-    context->loopbackClientContext->testResult.error =
-        CHPP_APP_ERROR_INVALID_LENGTH;
-    context->loopbackClientContext->testResult.firstError =
-        MIN(context->loopbackClientContext->testResult.requestLen,
-            context->loopbackClientContext->testResult.responseLen);
+  result->error = CHPP_APP_ERROR_NONE;
+  result->responseLen = len;
+  result->firstError = len;
+  result->byteErrors = 0;
+  result->rttNs = state->runLoopbackTest.responseTimeNs -
+                  state->runLoopbackTest.requestTimeNs;
+
+  if (result->requestLen != result->responseLen) {
+    result->error = CHPP_APP_ERROR_INVALID_LENGTH;
+    result->firstError = MIN(result->requestLen, result->responseLen);
   }
 
   for (size_t loc = CHPP_LOOPBACK_HEADER_LEN;
-       loc < MIN(context->loopbackClientContext->testResult.requestLen,
-                 context->loopbackClientContext->testResult.responseLen);
-       loc++) {
-    if (context->loopbackClientContext
-            ->loopbackData[loc - CHPP_LOOPBACK_HEADER_LEN] != response[loc]) {
-      context->loopbackClientContext->testResult.error =
-          CHPP_APP_ERROR_UNSPECIFIED;
-      context->loopbackClientContext->testResult.firstError =
-          MIN(context->loopbackClientContext->testResult.firstError,
-              loc - CHPP_LOOPBACK_HEADER_LEN);
-      context->loopbackClientContext->testResult.byteErrors++;
+       loc < MIN(result->requestLen, result->responseLen); loc++) {
+    if (state->loopbackData[loc - CHPP_LOOPBACK_HEADER_LEN] != response[loc]) {
+      result->error = CHPP_APP_ERROR_UNSPECIFIED;
+      result->firstError =
+          MIN(result->firstError, loc - CHPP_LOOPBACK_HEADER_LEN);
+      result->byteErrors++;
     }
   }
 
-  CHPP_LOGI("Loopback client RX err=0x%" PRIx16 " len=%" PRIuSIZE
+  CHPP_LOGD("Loopback client RX err=0x%" PRIx16 " len=%" PRIuSIZE
             " req len=%" PRIuSIZE " first err=%" PRIuSIZE
             " total err=%" PRIuSIZE,
-            context->loopbackClientContext->testResult.error,
-            context->loopbackClientContext->testResult.responseLen,
-            context->loopbackClientContext->testResult.requestLen,
-            context->loopbackClientContext->testResult.firstError,
-            context->loopbackClientContext->testResult.byteErrors);
+            result->error, result->responseLen, result->requestLen,
+            result->firstError, result->byteErrors);
 
   // Notify waiting (synchronous) client
-  chppMutexLock(&context->loopbackClientContext->client.responseMutex);
-  context->loopbackClientContext->client.responseReady = true;
-  chppConditionVariableSignal(
-      &context->loopbackClientContext->client.responseCondVar);
-  chppMutexUnlock(&context->loopbackClientContext->client.responseMutex);
+  chppMutexLock(&state->client.responseMutex);
+  state->client.responseReady = true;
+  chppConditionVariableSignal(&state->client.responseCondVar);
+  chppMutexUnlock(&state->client.responseMutex);
 
   return true;
 }
 
-struct ChppLoopbackTestResult chppRunLoopbackTest(struct ChppAppState *context,
+struct ChppLoopbackTestResult chppRunLoopbackTest(struct ChppAppState *appState,
                                                   const uint8_t *buf,
                                                   size_t len) {
-  CHPP_LOGI("Loopback client TX len=%" PRIuSIZE,
+  CHPP_LOGD("Loopback client TX len=%" PRIuSIZE,
             len + CHPP_LOOPBACK_HEADER_LEN);
 
-  struct ChppLoopbackTestResult result;
-  if (context == NULL) {
+  if (appState == NULL) {
     CHPP_LOGE("Cannot run loopback test with null app");
+    struct ChppLoopbackTestResult result;
     result.error = CHPP_APP_ERROR_UNSUPPORTED;
-  } else if (!chppWaitForDiscoveryComplete(context, 0 /* timeoutMs */)) {
-    result.error = CHPP_APP_ERROR_NOT_READY;
-  } else {
-    CHPP_NOT_NULL(context->loopbackClientContext);
-    if (context->loopbackClientContext->testResult.error ==
-        CHPP_APP_ERROR_BLOCKED) {
-      CHPP_DEBUG_ASSERT_LOG(false, "Another loopback in progress");
-
-    } else {
-      context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_BLOCKED;
-      context->loopbackClientContext->testResult.requestLen =
-          len + CHPP_LOOPBACK_HEADER_LEN;
-      context->loopbackClientContext->testResult.responseLen = 0;
-      context->loopbackClientContext->testResult.firstError = 0;
-      context->loopbackClientContext->testResult.byteErrors = 0;
-      context->loopbackClientContext->testResult.rttNs = 0;
-      context->loopbackClientContext->runLoopbackTest.requestTimeNs =
-          CHPP_TIME_NONE;
-      context->loopbackClientContext->runLoopbackTest.responseTimeNs =
-          CHPP_TIME_NONE;
-
-      if (len == 0) {
-        CHPP_LOGE("Loopback payload=0!");
-        context->loopbackClientContext->testResult.error =
-            CHPP_APP_ERROR_INVALID_LENGTH;
-
-      } else {
-        uint8_t *loopbackRequest = (uint8_t *)chppAllocClientRequest(
-            &context->loopbackClientContext->client,
-            context->loopbackClientContext->testResult.requestLen);
-
-        if (loopbackRequest == NULL) {
-          // OOM
-          context->loopbackClientContext->testResult.requestLen = 0;
-          context->loopbackClientContext->testResult.error = CHPP_APP_ERROR_OOM;
-          CHPP_LOG_OOM();
-
-        } else {
-          context->loopbackClientContext->loopbackData = buf;
-          memcpy(&loopbackRequest[CHPP_LOOPBACK_HEADER_LEN], buf, len);
-
-          if (!chppSendTimestampedRequestAndWaitTimeout(
-                  &context->loopbackClientContext->client,
-                  &context->loopbackClientContext->runLoopbackTest,
-                  loopbackRequest,
-                  context->loopbackClientContext->testResult.requestLen,
-                  CHPP_NSEC_PER_SEC /* 1s */)) {
-            context->loopbackClientContext->testResult.error =
-                CHPP_APP_ERROR_UNSPECIFIED;
-          }  // else {context->loopbackClientContext->testResult is now
-             // populated}
-        }
-      }
-    }
-
-    result = context->loopbackClientContext->testResult;
+    return result;
   }
 
-  return result;
+  if (!chppWaitForDiscoveryComplete(appState, 0 /* timeoutMs */)) {
+    struct ChppLoopbackTestResult result;
+    result.error = CHPP_APP_ERROR_NOT_READY;
+    return result;
+  }
+
+  struct ChppLoopbackClientState *state = appState->loopbackClientContext;
+  CHPP_NOT_NULL(state);
+  struct ChppLoopbackTestResult *result = &state->testResult;
+
+  if (result->error == CHPP_APP_ERROR_BLOCKED) {
+    CHPP_DEBUG_ASSERT_LOG(false, "Another loopback in progress");
+    return *result;
+  }
+
+  result->error = CHPP_APP_ERROR_BLOCKED;
+  result->requestLen = len + CHPP_LOOPBACK_HEADER_LEN;
+  result->responseLen = 0;
+  result->firstError = 0;
+  result->byteErrors = 0;
+  result->rttNs = 0;
+  state->runLoopbackTest.requestTimeNs = CHPP_TIME_NONE;
+  state->runLoopbackTest.responseTimeNs = CHPP_TIME_NONE;
+
+  if (len == 0) {
+    CHPP_LOGE("Loopback payload=0!");
+    result->error = CHPP_APP_ERROR_INVALID_LENGTH;
+    return *result;
+  }
+
+  uint8_t *loopbackRequest =
+      (uint8_t *)chppAllocClientRequest(&state->client, result->requestLen);
+
+  if (loopbackRequest == NULL) {
+    result->requestLen = 0;
+    result->error = CHPP_APP_ERROR_OOM;
+    CHPP_LOG_OOM();
+    return *result;
+  }
+
+  state->loopbackData = buf;
+  memcpy(&loopbackRequest[CHPP_LOOPBACK_HEADER_LEN], buf, len);
+
+  if (!chppSendTimestampedRequestAndWaitTimeout(
+          &state->client, &state->runLoopbackTest, loopbackRequest,
+          result->requestLen, 5 * CHPP_NSEC_PER_SEC)) {
+    result->error = CHPP_APP_ERROR_UNSPECIFIED;
+  }
+
+  return *result;
 }
diff --git a/chpp/clients/timesync.c b/chpp/clients/timesync.c
index e431835..de0664b 100644
--- a/chpp/clients/timesync.c
+++ b/chpp/clients/timesync.c
@@ -50,132 +50,124 @@
  *  Public Functions
  ***********************************************/
 
-void chppTimesyncClientInit(struct ChppAppState *context) {
+void chppTimesyncClientInit(struct ChppAppState *appState) {
   CHPP_LOGD("Timesync client init");
+  CHPP_DEBUG_NOT_NULL(appState);
 
-  context->timesyncClientContext =
+  appState->timesyncClientContext =
       chppMalloc(sizeof(struct ChppTimesyncClientState));
-  CHPP_NOT_NULL(context->timesyncClientContext);
-  memset(context->timesyncClientContext, 0,
-         sizeof(struct ChppTimesyncClientState));
+  CHPP_NOT_NULL(appState->timesyncClientContext);
+  struct ChppTimesyncClientState *state = appState->timesyncClientContext;
 
-  context->timesyncClientContext->client.appContext = context;
-  context->timesyncClientContext->timesyncResult.error = CHPP_APP_ERROR_NONE;
+  memset(state, 0, sizeof(struct ChppTimesyncClientState));
+  state->client.appContext = appState;
+  state->timesyncResult.error = CHPP_APP_ERROR_NONE;
 
-  chppClientInit(&context->timesyncClientContext->client, CHPP_HANDLE_TIMESYNC);
-  context->timesyncClientContext->timesyncResult.error =
-      CHPP_APP_ERROR_UNSPECIFIED;
-  context->timesyncClientContext->client.openState = CHPP_OPEN_STATE_OPENED;
+  chppClientInit(&state->client, CHPP_HANDLE_TIMESYNC);
+  state->timesyncResult.error = CHPP_APP_ERROR_UNSPECIFIED;
+  state->client.openState = CHPP_OPEN_STATE_OPENED;
 }
 
-void chppTimesyncClientDeinit(struct ChppAppState *context) {
+void chppTimesyncClientDeinit(struct ChppAppState *appState) {
   CHPP_LOGD("Timesync client deinit");
-
-  CHPP_NOT_NULL(context->timesyncClientContext);
-  chppClientDeinit(&context->timesyncClientContext->client);
-  CHPP_FREE_AND_NULLIFY(context->timesyncClientContext);
+  CHPP_DEBUG_NOT_NULL(appState);
+  CHPP_NOT_NULL(appState->timesyncClientContext);
+  chppClientDeinit(&appState->timesyncClientContext->client);
+  CHPP_FREE_AND_NULLIFY(appState->timesyncClientContext);
 }
 
-void chppTimesyncClientReset(struct ChppAppState *context) {
+void chppTimesyncClientReset(struct ChppAppState *appState) {
   CHPP_LOGD("Timesync client reset");
+  CHPP_DEBUG_NOT_NULL(appState);
+  struct ChppTimesyncClientState *state = appState->timesyncClientContext;
+  CHPP_NOT_NULL(state);
 
-  CHPP_NOT_NULL(context->timesyncClientContext);
-
-  context->timesyncClientContext->timesyncResult.error = CHPP_APP_ERROR_NONE;
-  context->timesyncClientContext->timesyncResult.offsetNs = 0;
-  context->timesyncClientContext->timesyncResult.rttNs = 0;
-  context->timesyncClientContext->timesyncResult.measurementTimeNs = 0;
+  state->timesyncResult.error = CHPP_APP_ERROR_NONE;
+  state->timesyncResult.offsetNs = 0;
+  state->timesyncResult.rttNs = 0;
+  state->timesyncResult.measurementTimeNs = 0;
 }
 
-bool chppDispatchTimesyncServiceResponse(struct ChppAppState *context,
+bool chppDispatchTimesyncServiceResponse(struct ChppAppState *appState,
                                          const uint8_t *buf, size_t len) {
   CHPP_LOGD("Timesync client dispatch service response");
-
-  CHPP_NOT_NULL(context->timesyncClientContext);
+  CHPP_DEBUG_NOT_NULL(appState);
+  struct ChppTimesyncClientState *state = appState->timesyncClientContext;
+  CHPP_NOT_NULL(state);
   CHPP_NOT_NULL(buf);
 
   if (len < sizeof(struct ChppTimesyncResponse)) {
     CHPP_LOGE("Timesync resp short len=%" PRIuSIZE, len);
-    context->timesyncClientContext->timesyncResult.error =
-        CHPP_APP_ERROR_INVALID_LENGTH;
+    state->timesyncResult.error = CHPP_APP_ERROR_INVALID_LENGTH;
     return false;
   }
 
   const struct ChppTimesyncResponse *response =
       (const struct ChppTimesyncResponse *)buf;
-  if (chppClientTimestampResponse(
-          &context->timesyncClientContext->client,
-          &context->timesyncClientContext->measureOffset, &response->header)) {
-    context->timesyncClientContext->timesyncResult.rttNs =
-        context->timesyncClientContext->measureOffset.responseTimeNs -
-        context->timesyncClientContext->measureOffset.requestTimeNs;
+  if (chppClientTimestampResponse(&state->client, &state->measureOffset,
+                                  &response->header)) {
+    state->timesyncResult.rttNs = state->measureOffset.responseTimeNs -
+                                  state->measureOffset.requestTimeNs;
     int64_t offsetNs =
-        (int64_t)(response->timeNs -
-                  context->timesyncClientContext->measureOffset.responseTimeNs);
-    int64_t offsetChangeNs =
-        offsetNs - context->timesyncClientContext->timesyncResult.offsetNs;
+        (int64_t)(response->timeNs - state->measureOffset.responseTimeNs);
+    int64_t offsetChangeNs = offsetNs - state->timesyncResult.offsetNs;
 
     int64_t clippedOffsetChangeNs = offsetChangeNs;
-    if (context->timesyncClientContext->timesyncResult.offsetNs != 0) {
+    if (state->timesyncResult.offsetNs != 0) {
       clippedOffsetChangeNs = MIN(clippedOffsetChangeNs,
                                   (int64_t)CHPP_CLIENT_TIMESYNC_MAX_CHANGE_NS);
       clippedOffsetChangeNs = MAX(clippedOffsetChangeNs,
                                   -(int64_t)CHPP_CLIENT_TIMESYNC_MAX_CHANGE_NS);
     }
 
-    context->timesyncClientContext->timesyncResult.offsetNs +=
-        clippedOffsetChangeNs;
+    state->timesyncResult.offsetNs += clippedOffsetChangeNs;
 
     if (offsetChangeNs != clippedOffsetChangeNs) {
       CHPP_LOGW("Drift=%" PRId64 " clipped to %" PRId64 " at t=%" PRIu64,
                 offsetChangeNs / (int64_t)CHPP_NSEC_PER_MSEC,
                 clippedOffsetChangeNs / (int64_t)CHPP_NSEC_PER_MSEC,
-                context->timesyncClientContext->measureOffset.responseTimeNs /
-                    CHPP_NSEC_PER_MSEC);
+                state->measureOffset.responseTimeNs / CHPP_NSEC_PER_MSEC);
     } else {
-      context->timesyncClientContext->timesyncResult.measurementTimeNs =
-          context->timesyncClientContext->measureOffset.responseTimeNs;
+      state->timesyncResult.measurementTimeNs =
+          state->measureOffset.responseTimeNs;
     }
 
-    context->timesyncClientContext->timesyncResult.error = CHPP_APP_ERROR_NONE;
+    state->timesyncResult.error = CHPP_APP_ERROR_NONE;
 
-    CHPP_LOGI("Timesync RTT=%" PRIu64 " correction=%" PRId64 " offset=%" PRId64
+    CHPP_LOGD("Timesync RTT=%" PRIu64 " correction=%" PRId64 " offset=%" PRId64
               " t=%" PRIu64,
-              context->timesyncClientContext->timesyncResult.rttNs /
-                  CHPP_NSEC_PER_MSEC,
+              state->timesyncResult.rttNs / CHPP_NSEC_PER_MSEC,
               clippedOffsetChangeNs / (int64_t)CHPP_NSEC_PER_MSEC,
               offsetNs / (int64_t)CHPP_NSEC_PER_MSEC,
-              context->timesyncClientContext->timesyncResult.measurementTimeNs /
-                  CHPP_NSEC_PER_MSEC);
+              state->timesyncResult.measurementTimeNs / CHPP_NSEC_PER_MSEC);
   }
 
   return true;
 }
 
-bool chppTimesyncMeasureOffset(struct ChppAppState *context) {
+bool chppTimesyncMeasureOffset(struct ChppAppState *appState) {
   bool result = false;
-  CHPP_LOGI("Measuring timesync t=%" PRIu64,
+  CHPP_LOGD("Measuring timesync t=%" PRIu64,
             chppGetCurrentTimeNs() / CHPP_NSEC_PER_MSEC);
+  CHPP_DEBUG_NOT_NULL(appState);
+  struct ChppTimesyncClientState *state = appState->timesyncClientContext;
+  CHPP_NOT_NULL(state);
 
-  CHPP_NOT_NULL(context->timesyncClientContext);
-
-  context->timesyncClientContext->timesyncResult.error =
+  state->timesyncResult.error =
       CHPP_APP_ERROR_BUSY;  // A measurement is in progress
 
   struct ChppAppHeader *request = chppAllocClientRequestCommand(
-      &context->timesyncClientContext->client, CHPP_TIMESYNC_COMMAND_GETTIME);
+      &state->client, CHPP_TIMESYNC_COMMAND_GETTIME);
   size_t requestLen = sizeof(*request);
 
   if (request == NULL) {
-    context->timesyncClientContext->timesyncResult.error = CHPP_APP_ERROR_OOM;
+    state->timesyncResult.error = CHPP_APP_ERROR_OOM;
     CHPP_LOG_OOM();
 
   } else if (!chppSendTimestampedRequestOrFail(
-                 &context->timesyncClientContext->client,
-                 &context->timesyncClientContext->measureOffset, request,
-                 requestLen, CHPP_CLIENT_REQUEST_TIMEOUT_INFINITE)) {
-    context->timesyncClientContext->timesyncResult.error =
-        CHPP_APP_ERROR_UNSPECIFIED;
+                 &state->client, &state->measureOffset, request, requestLen,
+                 CHPP_CLIENT_REQUEST_TIMEOUT_INFINITE)) {
+    state->timesyncResult.error = CHPP_APP_ERROR_UNSPECIFIED;
 
   } else {
     result = true;
@@ -184,28 +176,31 @@
   return result;
 }
 
-int64_t chppTimesyncGetOffset(struct ChppAppState *context,
+int64_t chppTimesyncGetOffset(struct ChppAppState *appState,
                               uint64_t maxTimesyncAgeNs) {
-  bool timesyncNeverDone =
-      (context->timesyncClientContext->timesyncResult.offsetNs == 0);
+  CHPP_DEBUG_NOT_NULL(appState);
+  struct ChppTimesyncClientState *state = appState->timesyncClientContext;
+  CHPP_DEBUG_NOT_NULL(state);
+
+  bool timesyncNeverDone = state->timesyncResult.offsetNs == 0;
   bool timesyncIsStale =
-      (chppGetCurrentTimeNs() -
-           context->timesyncClientContext->timesyncResult.measurementTimeNs >
-       maxTimesyncAgeNs);
+      chppGetCurrentTimeNs() - state->timesyncResult.measurementTimeNs >
+      maxTimesyncAgeNs;
 
   if (timesyncNeverDone || timesyncIsStale) {
-    chppTimesyncMeasureOffset(context);
+    chppTimesyncMeasureOffset(appState);
   } else {
     CHPP_LOGD("No need to timesync at t~=%" PRIu64 "offset=%" PRId64,
               chppGetCurrentTimeNs() / CHPP_NSEC_PER_MSEC,
-              context->timesyncClientContext->timesyncResult.offsetNs /
-                  (int64_t)CHPP_NSEC_PER_MSEC);
+              state->timesyncResult.offsetNs / (int64_t)CHPP_NSEC_PER_MSEC);
   }
 
-  return context->timesyncClientContext->timesyncResult.offsetNs;
+  return state->timesyncResult.offsetNs;
 }
 
 const struct ChppTimesyncResult *chppTimesyncGetResult(
-    struct ChppAppState *context) {
-  return &context->timesyncClientContext->timesyncResult;
+    struct ChppAppState *appState) {
+  CHPP_DEBUG_NOT_NULL(appState);
+  CHPP_DEBUG_NOT_NULL(appState->timesyncClientContext);
+  return &appState->timesyncClientContext->timesyncResult;
 }
diff --git a/chpp/clients/wifi.c b/chpp/clients/wifi.c
index 0c8db89..b544684 100644
--- a/chpp/clients/wifi.c
+++ b/chpp/clients/wifi.c
@@ -46,6 +46,11 @@
 #define CHPP_WIFI_MAX_TIMESYNC_AGE_NS CHPP_TIMESYNC_DEFAULT_MAX_AGE_NS
 #endif
 
+#ifndef CHPP_WIFI_SCAN_RESULT_TIMEOUT_NS
+#define CHPP_WIFI_SCAN_RESULT_TIMEOUT_NS \
+  (CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS - CHRE_NSEC_PER_SEC)
+#endif
+
 /************************************************
  *  Prototypes
  ***********************************************/
@@ -407,7 +412,7 @@
 static void chppWiFiRecoverScanMonitor(
     struct ChppWifiClientState *clientContext) {
   if (clientContext->scanMonitorEnabled) {
-    CHPP_LOGI("Re-enabling WiFi scan monitoring after reset");
+    CHPP_LOGD("Re-enabling WiFi scan monitoring after reset");
     clientContext->scanMonitorEnabled = false;
     clientContext->scanMonitorSilenceCallback = true;
 
@@ -553,7 +558,6 @@
   struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
 
   if (rxHeader->error != CHPP_APP_ERROR_NONE) {
-    CHPP_LOGE("Ranging failed at service" PRIu8);
     gCallbacks->rangingEventCallback(chppAppErrorToChreError(rxHeader->error),
                                      NULL);
 
@@ -968,10 +972,14 @@
     request->header.error = CHPP_APP_ERROR_NONE;
     request->header.command = CHPP_WIFI_REQUEST_SCAN_ASYNC;
 
+    CHPP_STATIC_ASSERT(
+        CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS > CHPP_WIFI_SCAN_RESULT_TIMEOUT_NS,
+        "Chpp wifi scan timeout needs to be smaller than CHRE wifi scan "
+        "timeout");
     result = chppSendTimestampedRequestOrFail(
         &gWifiClientContext.client,
         &gWifiClientContext.rRState[CHPP_WIFI_REQUEST_SCAN_ASYNC], request,
-        requestLen, CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS);
+        requestLen, CHPP_WIFI_SCAN_RESULT_TIMEOUT_NS);
   }
 
   return result;
@@ -1152,11 +1160,19 @@
   return result;
 }
 
+static bool chppWifiGetNanCapabilites(
+    struct chreWifiNanCapabilities *capabilities) {
+  // Not implemented yet.
+  UNUSED_VAR(capabilities);
+  return false;
+}
+
 /************************************************
  *  Public Functions
  ***********************************************/
 
 void chppRegisterWifiClient(struct ChppAppState *appContext) {
+  memset(&gWifiClientContext, 0, sizeof(gWifiClientContext));
   chppRegisterClient(appContext, (void *)&gWifiClientContext,
                      &gWifiClientContext.client, gWifiClientContext.rRState,
                      &kWifiClientConfig);
@@ -1194,6 +1210,7 @@
       .nanSubscribeCancel = chppWifiClientNanSubscribeCancel,
       .releaseNanDiscoveryEvent = chppWifiClientNanReleaseDiscoveryEvent,
       .requestNanRanging = chppWifiClientNanRequestNanRanging,
+      .getNanCapabilities = chppWifiGetNanCapabilites,
   };
 
   CHPP_STATIC_ASSERT(
diff --git a/chpp/clients/wwan.c b/chpp/clients/wwan.c
index 85c7a5a..98aea88 100644
--- a/chpp/clients/wwan.c
+++ b/chpp/clients/wwan.c
@@ -20,6 +20,7 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <string.h>
 
 #include "chpp/app.h"
 #include "chpp/clients/discovery.h"
@@ -528,6 +529,7 @@
  ***********************************************/
 
 void chppRegisterWwanClient(struct ChppAppState *appContext) {
+  memset(&gWwanClientContext, 0, sizeof(gWwanClientContext));
   chppRegisterClient(appContext, (void *)&gWwanClientContext,
                      &gWwanClientContext.client, gWwanClientContext.rRState,
                      &kWwanClientConfig);
diff --git a/chpp/common/wifi_utils.c b/chpp/common/wifi_utils.c
index 8a9a779..4549536 100644
--- a/chpp/common/wifi_utils.c
+++ b/chpp/common/wifi_utils.c
@@ -24,7 +24,7 @@
 static uint8_t gResultAcc = 0;
 static uint8_t gExpectedIndex = 0;
 
-void chppCheckWifiScanEventNotificationReset() {
+void chppCheckWifiScanEventNotificationReset(void) {
   gResultTotal = 0;
   gResultAcc = 0;
   gExpectedIndex = 0;
diff --git a/chpp/include/chpp/clients.h b/chpp/include/chpp/clients.h
index bf757fa..f48686f 100644
--- a/chpp/include/chpp/clients.h
+++ b/chpp/include/chpp/clients.h
@@ -125,7 +125,7 @@
  * by CHPP_CLIENT_ENABLED_xxxx definitions. This function is automatically
  * called by chppAppInit().
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppRegisterCommonClients(struct ChppAppState *context);
 
@@ -134,15 +134,16 @@
  * by CHPP_CLIENT_ENABLED_xxxx definitions. This function is automatically
  * called by chppAppDeinit().
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppDeregisterCommonClients(struct ChppAppState *context);
 
 /**
- * Registers a new client on CHPP. This function is to be called by the
- * platform initialization code for every non-common client available on a
- * server (if any), i.e. except those that are registered through
- * chppRegisterCommonClients().
+ * Registers a new client on CHPP.
+ *
+ * This function is to be called by the platform initialization code for every
+ * non-common client available on a server (if any), i.e. except those that are
+ * registered through chppRegisterCommonClients().
  *
  * Registered clients are matched with discovered services during discovery.
  * When a match succeeds, the client's initialization function (pointer) is
@@ -152,8 +153,8 @@
  * can specified as CHPP_MAX_REGISTERED_CLIENTS by the initialization code.
  * Otherwise, a default value will be used.
  *
- * @param appContext Maintains status for each app layer instance.
- * @param clientContext Maintains status for each client instance.
+ * @param appContext State of the app layer.
+ * @param clientContext State of the client instance.
  * @param clientState State variable of the client.
  * @param rRStates Pointer to array of request-response states, if any.
  * @param newClient The client to be registered on this platform.
@@ -166,7 +167,7 @@
 /**
  * Initializes basic CHPP clients.
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppInitBasicClients(struct ChppAppState *context);
 
@@ -189,14 +190,14 @@
 /**
  * Deinitializes basic clients.
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppDeinitBasicClients(struct ChppAppState *context);
 
 /**
  * Deinitializes all matched clients.
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppDeinitMatchedClients(struct ChppAppState *context);
 
@@ -329,7 +330,7 @@
     uint64_t timeoutNs);
 
 /**
- * Markes a closed client as pseudo-open, so that it would be opened upon a
+ * Marks a closed client as pseudo-open, so that it would be opened upon a
  * reset.
  *
  * @param clientState State variable of the client.
@@ -364,7 +365,7 @@
 /**
  * Recalculates the next upcoming client request timeout time.
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppClientRecalculateNextTimeout(struct ChppAppState *context);
 
diff --git a/chpp/include/chpp/clients/discovery.h b/chpp/include/chpp/clients/discovery.h
index a47523a..4edcf51 100644
--- a/chpp/include/chpp/clients/discovery.h
+++ b/chpp/include/chpp/clients/discovery.h
@@ -34,36 +34,40 @@
 /**
  * CHPP discovery state initialization that should be called on CHPP startup. It
  * may be called during transient internal resets, but is a no-op.
+ *
+ * @param appState Application layer state.
  */
-void chppDiscoveryInit(struct ChppAppState *context);
+void chppDiscoveryInit(struct ChppAppState *appState);
 
 /**
  * CHPP discovery state de-initialization that should be called during shutdown.
+ *
+ * @param appState Application layer state.
  */
-void chppDiscoveryDeinit(struct ChppAppState *context);
+void chppDiscoveryDeinit(struct ChppAppState *appState);
 
 /**
  * A method that can be invoked to block until the CHPP discovery sequence
  * completes. This can be useful to wait until CHPP client invocations can
  * succeed.
  *
- * @param context The non-null pointer to the ChppAppState of this instance.
+ * @param appState Application layer state.
  * @param timeoutMs The timeout in milliseconds.
  *
  * @return False if timed out waiting for discovery completion.
  */
-bool chppWaitForDiscoveryComplete(struct ChppAppState *context,
+bool chppWaitForDiscoveryComplete(struct ChppAppState *appState,
                                   uint64_t timeoutMs);
 
 /**
  * Dispatches an Rx Datagram from the transport layer that is determined to be
  * for the CHPP Discovery Client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  * @param buf Input (request) datagram. Cannot be null.
  * @param len Length of input data in bytes.
  */
-bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *context,
+bool chppDispatchDiscoveryServiceResponse(struct ChppAppState *appState,
                                           const uint8_t *buf, size_t len);
 
 /**
@@ -72,19 +76,19 @@
  * expected that this function be called upon initialization, after sending or
  * receiving a reset-ack.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  */
-void chppInitiateDiscovery(struct ChppAppState *context);
+void chppInitiateDiscovery(struct ChppAppState *appState);
 
 /**
  * Checks if all discovery clients have been matched with a remote service.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  *
  * @return true if all registered clients have been matched by a discovered
  * service.
  */
-bool chppAreAllClientsMatched(struct ChppAppState *context);
+bool chppAreAllClientsMatched(struct ChppAppState *appState);
 
 #ifdef __cplusplus
 }
diff --git a/chpp/include/chpp/clients/loopback.h b/chpp/include/chpp/clients/loopback.h
index 6095a97..5f2b382 100644
--- a/chpp/include/chpp/clients/loopback.h
+++ b/chpp/include/chpp/clients/loopback.h
@@ -55,35 +55,35 @@
 /**
  * Initializes the client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  */
-void chppLoopbackClientInit(struct ChppAppState *context);
+void chppLoopbackClientInit(struct ChppAppState *appState);
 
 /**
  * Deinitializes the client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  */
-void chppLoopbackClientDeinit(struct ChppAppState *context);
+void chppLoopbackClientDeinit(struct ChppAppState *appState);
 
 /**
  * Dispatches an Rx Datagram from the transport layer that is determined to
  * be for the CHPP Loopback Client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  * @param response Input (response) datagram. Cannot be null.
  * @param len Length of input data in bytes.
  */
-bool chppDispatchLoopbackServiceResponse(struct ChppAppState *context,
+bool chppDispatchLoopbackServiceResponse(struct ChppAppState *appState,
                                          const uint8_t *response, size_t len);
 
 /**
  * Initiates a CHPP service loopback from the client side.
  * Note that only one loopback test may be run at any time on each client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  */
-struct ChppLoopbackTestResult chppRunLoopbackTest(struct ChppAppState *context,
+struct ChppLoopbackTestResult chppRunLoopbackTest(struct ChppAppState *appState,
                                                   const uint8_t *buf,
                                                   size_t len);
 
diff --git a/chpp/include/chpp/clients/timesync.h b/chpp/include/chpp/clients/timesync.h
index a905057..e94b976 100644
--- a/chpp/include/chpp/clients/timesync.h
+++ b/chpp/include/chpp/clients/timesync.h
@@ -61,68 +61,68 @@
 /**
  * Initializes the client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  */
-void chppTimesyncClientInit(struct ChppAppState *context);
+void chppTimesyncClientInit(struct ChppAppState *appState);
 
 /**
  * Deinitializes the client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  */
-void chppTimesyncClientDeinit(struct ChppAppState *context);
+void chppTimesyncClientDeinit(struct ChppAppState *appState);
 
 /**
  * Resets the client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  */
-void chppTimesyncClientReset(struct ChppAppState *context);
+void chppTimesyncClientReset(struct ChppAppState *appState);
 
 /**
  * Dispatches an Rx Datagram from the transport layer that is determined to
  * be for the CHPP Timesync Client.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  * @param buf Input (response) datagram. Cannot be null.
  * @param len Length of input data in bytes.
  *
  * @return Indicates success or failure.
  */
-bool chppDispatchTimesyncServiceResponse(struct ChppAppState *context,
+bool chppDispatchTimesyncServiceResponse(struct ChppAppState *appState,
                                          const uint8_t *buf, size_t len);
 
 /**
  * Initiates a CHPP timesync to measure time offset of the service.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  *
  * @return Indicates success or failure.
  */
-bool chppTimesyncMeasureOffset(struct ChppAppState *context);
+bool chppTimesyncMeasureOffset(struct ChppAppState *appState);
 
 /**
  * Provides the time offset of the service. If the latest measurement is within
  * maxTimesyncAgeNs, this function reuses the last measurement. Otherwise, it
  * will initiate a new measurement.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  * @param maxTimesyncAgeNs Maximum acceptable age of measuement.
  *
  * @return Time offset of service vs client (service - client)
  */
-int64_t chppTimesyncGetOffset(struct ChppAppState *context,
+int64_t chppTimesyncGetOffset(struct ChppAppState *appState,
                               uint64_t maxTimesyncAgeNs);
 
 /**
  * Provides the raw results of the latest timesync measurement.
  *
- * @param context Maintains status for each app layer instance.
+ * @param appState Application layer state.
  *
  * @return Latest result.
  */
 const struct ChppTimesyncResult *chppTimesyncGetResult(
-    struct ChppAppState *context);
+    struct ChppAppState *appState);
 
 #ifdef __cplusplus
 }
diff --git a/chpp/include/chpp/common/wifi.h b/chpp/include/chpp/common/wifi.h
index 40ba930..c1a6d2b 100644
--- a/chpp/include/chpp/common/wifi.h
+++ b/chpp/include/chpp/common/wifi.h
@@ -31,7 +31,7 @@
  *  Public Definitions
  ***********************************************/
 
-#define CHPP_PAL_WIFI_API_VERSION CHRE_PAL_WIFI_API_V1_6
+#define CHPP_PAL_WIFI_API_VERSION CHRE_PAL_WIFI_API_V1_8
 
 /**
  * Data structures used by the Configure Scan Monitor request.
diff --git a/chpp/include/chpp/link.h b/chpp/include/chpp/link.h
index 9a35f47..d3fc31e 100644
--- a/chpp/include/chpp/link.h
+++ b/chpp/include/chpp/link.h
@@ -14,15 +14,6 @@
  * limitations under the License.
  */
 
-/*
- * Implementation Notes:
- * Each platform must supply a platform-specific platform_link.h to provide the
- * definitions and a platform-specific link.c to provide the implementation for
- * the definitions in this file.
- * The platform must also initialize the ChppPlatformLinkParameters for each
- * link (context.linkParams).
- */
-
 #ifndef CHPP_LINK_H_
 #define CHPP_LINK_H_
 
@@ -59,72 +50,121 @@
   CHPP_LINK_ERROR_UNSPECIFIED = 255
 };
 
-/*
- * Platform-specific struct with link details / parameters.
- */
-struct ChppPlatformLinkParameters;
+struct ChppTransportState;
 
 /**
- * Platform-specific function to initialize the link layer.
- *
- * @param params Platform-specific struct with link details / parameters.
+ * Link layer configuration.
  */
-void chppPlatformLinkInit(struct ChppPlatformLinkParameters *params);
+struct ChppLinkConfiguration {
+  /**
+   * Size of the TX buffer in bytes.
+   * The TX buffer is provided by the link layer (@see getTxBuffer).
+   *
+   * The TX buffer stores the effective payload and the transport encoding
+   * overhead. The size of the effective payload is txBufferLen -
+   * CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES.
+   */
+  size_t txBufferLen;
+  /**
+   * Size of the RX buffer in bytes.
+   *
+   * The RX buffer stores the effective payload and the transport encoding
+   * overhead. The size of the effective payload is rxBufferLen -
+   * CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES.
+   */
+  size_t rxBufferLen;
+};
 
-/**
- * Platform-specific function to deinitialize the link layer (e.g. clean exit).
- *
- * @param params Platform-specific struct with link details / parameters.
- */
-void chppPlatformLinkDeinit(struct ChppPlatformLinkParameters *params);
+struct ChppLinkApi {
+  /**
+   * Platform-specific function to initialize the link layer.
+   *
+   * @param linkContext Platform-specific struct with link state.
+   * @param transportContext State of the associated transport layer.
+   *
+   * The init function typically:
+   * - stores the transportContext in the link state. It is needed when calling
+   *   the transport layer callbacks,
+   * - initializes anything required for the link layer operations.
+   *
+   * It can also store the configuration in the link state when the
+   * configuration differs across link instances.
+   */
+  void (*init)(void *linkContext, struct ChppTransportState *transportContext);
 
-/*
- * Platform-specific function to send Tx data over to the link layer.
- *
- * @param params Platform-specific struct with link details / parameters.
- * @param buf Data to be sent.
- * @param len Length of the data to be sent in bytes.
- *
- * @return CHPP_LINK_ERROR_NONE_SENT if the platform implementation for this
- * function is synchronous, i.e. it is done with buf and len once the function
- * returns. A return value of CHPP_LINK_ERROR_NONE_QUEUED indicates that this
- * function is implemented asynchronously. In this case, it is up to the
- * platform implementation to call chppLinkSendDoneCb() after processing the
- * contents of buf and len. Otherwise, an error code is returned per enum
- * ChppLinkErrorCode.
- */
-enum ChppLinkErrorCode chppPlatformLinkSend(
-    struct ChppPlatformLinkParameters *params, uint8_t *buf, size_t len);
+  /**
+   * Platform-specific function to deinitialize the link layer (e.g. clean
+   * exit).
+   *
+   * @param linkContext Platform-specific struct with link details / parameters.
+   */
+  void (*deinit)(void *linkContext);
 
-/**
- * Platform-specific function to perform a task from the main CHPP transport
- * work thread. The task can be specified by the signal argument, which is
- * triggered by previous call[s] to chppWorkThreadSignalFromLink(). An example
- * of the type of work that can be performed is processing RX data from the
- * physical layer.
- *
- * @param params Platform-specific struct with link details / parameters.
- * @param signal The signal that describes the work to be performed. Only bits
- * specified by CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK can be set.
- */
-void chppPlatformLinkDoWork(struct ChppPlatformLinkParameters *params,
-                            uint32_t signal);
+  /**
+   * Platform-specific function to send Tx data over to the link layer.
+   *
+   * The TX data is located in the Tx buffer, @see getTxBuffer.
+   *
+   * @param linkContext Platform-specific struct with link details / parameters.
+   * @param len Length of the data to be sent in bytes.
+   *
+   * @return CHPP_LINK_ERROR_NONE_SENT if the platform implementation for this
+   * function is synchronous, i.e. it is done with buf and len once the function
+   * returns. A return value of CHPP_LINK_ERROR_NONE_QUEUED indicates that this
+   * function is implemented asynchronously. In this case, it is up to the
+   * platform implementation to call chppLinkSendDoneCb() after processing the
+   * contents TX buffer. Otherwise, an error code is returned per enum
+   * ChppLinkErrorCode.
+   */
+  enum ChppLinkErrorCode (*send)(void *linkContext, size_t len);
 
-/*
- * Platform-specific function to reset a non-synchronous link, where the link
- * implementation is responsible for calling chppLinkSendDoneCb() after
- * processing the contents of buf and len. For such links, a reset called before
- * chppLinkSendDoneCb() indicates to the link to abort sending out buf, and that
- * the contents of buf and len will become invalid.
- *
- * @param params Platform-specific struct with link details / parameters.
- */
-void chppPlatformLinkReset(struct ChppPlatformLinkParameters *params);
+  /**
+   * Platform-specific function to perform a task from the main CHPP transport
+   * work thread. The task can be specified by the signal argument, which is
+   * triggered by previous call[s] to chppWorkThreadSignalFromLink(). An example
+   * of the type of work that can be performed is processing RX data from the
+   * physical layer.
+   *
+   * @param linkContext Platform-specific struct with link details / parameters.
+   * @param signal The signal that describes the work to be performed. Only bits
+   * specified by CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK can be set.
+   */
+  void (*doWork)(void *linkContext, uint32_t signal);
+
+  /**
+   * Platform-specific function to reset a non-synchronous link, where the link
+   * implementation is responsible for calling chppLinkSendDoneCb() after
+   * processing the contents of buf and len. For such links, a reset called
+   * before chppLinkSendDoneCb() indicates to the link to abort sending out buf,
+   * and that the contents of buf and len will become invalid.
+   *
+   * @param linkContext Platform-specific struct with link details / parameters.
+   */
+  void (*reset)(void *linkContext);
+
+  /**
+   * Returns the link layer configuration.
+   *
+   * @param linkContext Platform-specific struct with link details / parameters.
+   */
+  struct ChppLinkConfiguration (*getConfig)(void *linkContext);
+
+  /**
+   * Returns a pointer to the TX buffer.
+   *
+   * The associated transport layer will write control bytes and payload to this
+   * buffer.
+   *
+   * The size of the buffer must be returned in the configuration.
+   * @see getConfig.
+   *
+   * @param linkContext Platform-specific struct with link details / parameters.
+   */
+  uint8_t *(*getTxBuffer)(void *linkContext);
+};
 
 #ifdef __cplusplus
 }
 #endif
 
-#include "chpp/platform/platform_link.h"
-
 #endif  // CHPP_LINK_H_
diff --git a/chpp/include/chpp/macros.h b/chpp/include/chpp/macros.h
index b294813..4017107 100644
--- a/chpp/include/chpp/macros.h
+++ b/chpp/include/chpp/macros.h
@@ -76,6 +76,10 @@
 #endif  // CHPP_DEBUG_ASSERT_ENABLED
 #endif  // CHPP_DEBUG_ASSERT
 
+#ifndef CHPP_DEBUG_NOT_NULL
+#define CHPP_DEBUG_NOT_NULL(var) CHPP_DEBUG_ASSERT((var) != NULL)
+#endif
+
 #ifndef CHPP_DEBUG_ASSERT_LOG
 #define CHPP_DEBUG_ASSERT_LOG(var, fmt, ...) \
   do {                                       \
@@ -91,21 +95,18 @@
 #define PRIu64 "llu"
 #endif
 
-#if defined(__GNUC__) && (__STDC_VERSION__ >= 201112L)
-#define CHPP_C11_OR_NEWER
-#endif
+#if (defined(__cpp_static_assert) && (__cpp_static_assert >= 200410)) || \
+    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L))
+#define CHPP_STATIC_ASSERT static_assert
 
-#ifdef CHPP_C11_OR_NEWER
-#define CHPP_STATIC_ASSERT _Static_assert
-
-#else
+#else  // Use fallback implementation as static_assert is not available.
 #define CHPP_STATIC_ASSERT4(cond, var) typedef char var[(!!(cond)) * 2 - 1]
 #define CHPP_STATIC_ASSERT3(cond, line) \
   CHPP_STATIC_ASSERT4(cond, static_assertion_at_line_##line)
 #define CHPP_STATIC_ASSERT2(cond, line) CHPP_STATIC_ASSERT3(cond, line)
 #define CHPP_STATIC_ASSERT(cond, msg) CHPP_STATIC_ASSERT2(cond, __LINE__)
 
-#endif  // CHPP_C11_OR_NEWER
+#endif  // Use C11 or C++11 static_assert
 
 // Time-related macros
 #define CHPP_TIME_NONE 0
diff --git a/chpp/include/chpp/services.h b/chpp/include/chpp/services.h
index ad75e9b..ddd5a93 100644
--- a/chpp/include/chpp/services.h
+++ b/chpp/include/chpp/services.h
@@ -95,8 +95,7 @@
 
 /**
  * Maintains the basic state of a service.
- * This is expected to be included once in the (context) status variable of each
- * service.
+ * This is expected to be included in the state of each service.
  */
 struct ChppServiceState {
   struct ChppAppState *appContext;  // Pointer to app layer context
@@ -114,7 +113,7 @@
  * by CHPP_SERVICE_ENABLED_xxxx definitions. This function is automatically
  * called by chppAppInit().
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppRegisterCommonServices(struct ChppAppState *context);
 
@@ -123,7 +122,7 @@
  * enabled by CHPP_SERVICE_ENABLED_xxxx definitions. This function is
  * automatically called by chppAppInit().
  *
- * @param context Maintains status for each app layer instance.
+ * @param context State of the app layer.
  */
 void chppDeregisterCommonServices(struct ChppAppState *context);
 
@@ -137,15 +136,14 @@
  * can specified as CHPP_MAX_REGISTERED_SERVICES by the initialization code.
  * Otherwise, a default value will be used.
  *
- * @param appContext Maintains status for each app layer instance.
- * @param serviceContext Maintains status for each service instance.
+ * @param appContext State of the app layer.
+ * @param serviceContext State of the service instance.
+ * @param serviceState State variable of the client.
  * @param newService The service to be registered on this platform.
- *
- * @return Handle number of the registered service.
  */
-uint8_t chppRegisterService(struct ChppAppState *appContext,
-                            void *serviceContext,
-                            const struct ChppService *newService);
+void chppRegisterService(struct ChppAppState *appContext, void *serviceContext,
+                         struct ChppServiceState *serviceState,
+                         const struct ChppService *newService);
 
 /**
  * Allocates a service notification of a specified length.
@@ -156,7 +154,7 @@
  * calling this function directly.
  *
  * @param len Length of the notification (including header) in bytes. Note
- * that the specified length must be at least equal to the lendth of the app
+ * that the specified length must be at least equal to the length of the app
  * layer header.
  *
  * @return Pointer to allocated memory
@@ -165,7 +163,7 @@
 
 /**
  * Allocates a service response message of a specified length, populating the
- * (app layer) service response header accorging to the provided client request
+ * (app layer) service response header according to the provided client request
  * (app layer) header.
  *
  * It is expected that for most use cases, the chppAllocServiceResponseFixed()
@@ -174,7 +172,7 @@
  *
  * @param requestHeader Client request header.
  * @param len Length of the response message (including header) in bytes. Note
- * that the specified length must be at least equal to the lendth of the app
+ * that the specified length must be at least equal to the length of the app
  * layer header.
  *
  * @return Pointer to allocated memory
@@ -191,8 +189,7 @@
  * This function prints an error message if a duplicate request is received
  * while outstanding request is still pending without a response.
  *
- * @param rRState Maintains the basic state for each request/response
- * functionality of a service.
+ * @param rRStateState State of the current request/response.
  * @param requestHeader Client request header.
  */
 void chppServiceTimestampRequest(struct ChppRequestResponseState *rRState,
@@ -205,16 +202,13 @@
  * B) Mark them as fulfilled
  * part of the request/response's ChppRequestResponseState struct.
  *
- * This function prints an error message if a response is attempted without an
- * outstanding request.
- *
  * For most responses, it is expected that chppSendTimestampedResponseOrFail()
  * shall be used to both timestamp and send the response in one shot.
  *
- * @param rRState Maintains the basic state for each request/response
- * functionality of a service.
+ * @param rRState State of the current request/response.
+ * @return The last response time (CHPP_TIME_NONE for the first response).
  */
-void chppServiceTimestampResponse(struct ChppRequestResponseState *rRState);
+uint64_t chppServiceTimestampResponse(struct ChppRequestResponseState *rRState);
 
 /**
  * Timestamps a service response using chppServiceTimestampResponse() and
@@ -222,9 +216,11 @@
  *
  * Refer to their respective documentation for details.
  *
+ * This function logs an error message if a response is attempted without an
+ * outstanding request.
+ *
  * @param serviceState State of the service sending the response service.
- * @param rRState Maintains the basic state for each request/response
- * functionality of a service.
+ * @param rRState State of the current request/response.
  * @param buf Datagram payload allocated through chppMalloc. Cannot be null.
  * @param len Datagram length in bytes.
  *
diff --git a/chpp/include/chpp/transport.h b/chpp/include/chpp/transport.h
index e791b98..19c701c 100644
--- a/chpp/include/chpp/transport.h
+++ b/chpp/include/chpp/transport.h
@@ -84,6 +84,9 @@
 #define CHPP_TRANSPORT_TIMEOUT_INFINITE UINT64_MAX
 #define CHPP_TRANSPORT_TIMEOUT_IMMEDIATE 0
 
+// This lint rule is meant to ensure we make appropriate test updates whenever
+// there are changes to the transport protocol.
+// LINT.IfChange
 /**
  * CHPP Transport header flags bitmap
  *
@@ -130,30 +133,10 @@
 #define CHPP_TX_DATAGRAM_QUEUE_LEN ((uint8_t)16)
 
 /**
- * Maximum payload of packets at the link layer.
- * TODO: Negotiate or advertise MTU. In the mean time, set default as to achieve
- * transport TX MTU of 1024.
+ * Encoding overhead of the transport layer in bytes.
  */
-#define CHPP_LINK_TX_MTU_BYTES                                               \
-  ((uint16_t)MIN(                                                            \
-      CHPP_PLATFORM_LINK_TX_MTU_BYTES,                                       \
-      (1024 + CHPP_PREAMBLE_LEN_BYTES + sizeof(struct ChppTransportHeader) + \
-       sizeof(struct ChppTransportFooter))))
-
-/**
- * Maximum payload of packets at the transport layer.
- */
-#define CHPP_TRANSPORT_TX_MTU_BYTES                              \
-  ((uint16_t)(CHPP_LINK_TX_MTU_BYTES - CHPP_PREAMBLE_LEN_BYTES - \
-              sizeof(struct ChppTransportHeader) -               \
-              sizeof(struct ChppTransportFooter)))
-
-/**
- * Maximum payload of packets at the transport layer.
- */
-#define CHPP_TRANSPORT_RX_MTU_BYTES                                       \
-  ((uint16_t)(CHPP_PLATFORM_LINK_RX_MTU_BYTES - CHPP_PREAMBLE_LEN_BYTES - \
-              sizeof(struct ChppTransportHeader) -                        \
+#define CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES                               \
+  ((uint16_t)(CHPP_PREAMBLE_LEN_BYTES + sizeof(struct ChppTransportHeader) + \
               sizeof(struct ChppTransportFooter)))
 
 /************************************************
@@ -295,25 +278,24 @@
 CHPP_PACKED_END
 
 /**
- * Payload that is sent along reset and reset-ack packets. This may be used to
- * advertise the configuration parameters of this CHPP instance, and/or set the
- * configuration parameters of the remote side (TODO).
+ * Payload that is sent along reset and reset-ack packets.
  */
 CHPP_PACKED_START
 struct ChppTransportConfiguration {
   //! CHPP transport version.
   struct ChppVersion version;
 
-  //! Receive MTU size.
-  uint16_t rxMtu;
+  //! CHPP 1.0.0 unused "Receive MTU size".
+  uint16_t reserved1;
 
-  //! Max outstanding packet window size (1 for current implementation).
-  uint16_t windowSize;
+  //! CHPP 1.0.0 unused "window size".
+  uint16_t reserved2;
 
-  //! Transport layer timeout in milliseconds (i.e. to receive ACK).
-  uint16_t timeoutInMs;
+  //! CHPP 1.0.0 unused "Transport layer timeout in milliseconds".
+  uint16_t reserved3;
 } CHPP_PACKED_ATTR;
 CHPP_PACKED_END
+// LINT.ThenChange(chpp/test/packet_util.cpp)
 
 struct ChppRxStatus {
   //! Current receiving state, as described in ChppRxState.
@@ -382,18 +364,10 @@
   //! How many bytes of the front-of-queue datagram has been acked
   size_t ackedLocInDatagram;
 
-  //! Whether the link layer is still processing pendingTxPacket
+  //! Whether the link layer is still processing the pending packet
   bool linkBusy;
 };
 
-struct PendingTxPacket {
-  //! Length of outgoing packet to the Link Layer
-  size_t length;
-
-  //! Payload of outgoing packet to the Link Layer
-  uint8_t payload[CHPP_LINK_TX_MTU_BYTES];
-};
-
 struct ChppDatagram {
   //! Length of datagram payload in bytes (A datagram can be constituted from
   //! one or more packets)
@@ -431,7 +405,11 @@
 
   struct ChppTxStatus txStatus;                // Tx state
   struct ChppTxDatagramQueue txDatagramQueue;  // Queue of datagrams to be Tx
-  struct PendingTxPacket pendingTxPacket;      // Outgoing packet to Link Layer
+
+  size_t linkBufferSize;  // Number of bytes currently in the Tx Buffer
+  void *linkContext;      // Pointer to the link layer state
+  const struct ChppLinkApi *linkApi;  // Link API
+
   struct ChppDatagram transportLoopbackData;   // Transport-layer loopback
                                                // request data, if any
 
@@ -448,12 +426,6 @@
 #ifdef CHPP_ENABLE_WORK_MONITOR
   struct ChppWorkMonitor workMonitor;  // Monitor used for the transport thread
 #endif
-
-  //! This MUST be the last field in the ChppTransportState structure, otherwise
-  //! chppResetTransportContext() will not work properly.
-  struct ChppPlatformLinkParameters linkParams;  // For corresponding link layer
-
-  // !!! DO NOT ADD ANY NEW FIELDS HERE - ADD THEM BEFORE linkParams !!!
 };
 
 /************************************************
@@ -469,16 +441,18 @@
  * instance. appContext points to the application layer status struct associated
  * with this transport layer instance.
  *
- * Note: It is necessary to initialize the platform-specific values of
- * transportContext.linkParams (prior to the call, if needed in the link layer
- * APIs, such as chppPlatformLinkInit()).
+ * Calling this method will in turn call the init method of the linkApi with the
+ * linkContext as the first parameter.
  *
- * @param transportContext Maintains status for each transport layer instance.
- * @param appContext The app layer status struct associated with this transport
- * layer instance.
+ * @param transportContext Maintains state for each transport layer instance.
+ * @param appContext The app layer state associated with this transport
+ *                   layer instance.
+ * @param linkContext The associated link layer state.
+ * @param linkApi The API of the link layer
  */
 void chppTransportInit(struct ChppTransportState *transportContext,
-                       struct ChppAppState *appContext);
+                       struct ChppAppState *appContext, void *linkContext,
+                       const struct ChppLinkApi *linkApi);
 
 /**
  * Deinitializes the CHPP transport layer and does necessary clean-ups for
@@ -511,7 +485,7 @@
  *
  * TODO: Add sufficient outward facing documentation
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Input data. Cannot be null.
  * @param len Length of input data in bytes.
  *
@@ -527,7 +501,7 @@
  * packet. The availability of this information depends on the link layer
  * implementation.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 void chppRxPacketCompleteCb(struct ChppTransportState *context);
 
@@ -542,7 +516,7 @@
  * Note that the ownership of buf is taken from the caller when this method is
  * invoked.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Datagram payload allocated through chppMalloc. Cannot be null.
  * @param len Datagram length in bytes.
  *
@@ -556,7 +530,7 @@
  * Enables the App Layer to enqueue an outgoing error datagram, for example for
  * an OOM situation over the wire.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param errorCode Error code to be sent.
  */
 void chppEnqueueTxErrorDatagram(struct ChppTransportState *context,
@@ -577,7 +551,7 @@
  * A return value of CHPP_TRANSPORT_TIMEOUT_IMMEDIATE indicates that
  * chppTransportDoWork() should be run immediately.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  *
  * @return Time until chppTransportDoWork() must be called in nanoseconds.
  */
@@ -599,7 +573,7 @@
  * chppTransportGetTimeUntilNextDoWorkNs() can be used to replicate the
  * functionality of chppNotifierTimedWait().
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 void chppWorkThreadStart(struct ChppTransportState *context);
 
@@ -615,7 +589,7 @@
  * pending signals MUST be handled prior to the suspension of the outer control
  * loop, and any initialization sequence MUST be replicated.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param signals The signals to process. Should be obtained via
  * chppNotifierTimedWait() for the given transport context's notifier.
  *
@@ -637,10 +611,7 @@
  * specified by CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK can be set.
  */
 static inline void chppWorkThreadSignalFromLink(
-    struct ChppPlatformLinkParameters *params, uint32_t signal) {
-  struct ChppTransportState *context =
-      container_of(params, struct ChppTransportState, linkParams);
-
+    struct ChppTransportState *context, uint32_t signal) {
   CHPP_ASSERT((signal & ~(CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK)) == 0);
   chppNotifierSignal(&context->notifier,
                      signal & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK);
@@ -651,7 +622,7 @@
  * calling chppWorkThreadStart(). Stopping this thread may be necessary for
  * testing and debugging purposes.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 void chppWorkThreadStop(struct ChppTransportState *context);
 
@@ -665,10 +636,10 @@
  * (i.e. buf and len), the platform implementation must call this function after
  * platformLinkSend() is done with the payload (i.e. buf and len).
  *
- * @param params Platform-specific struct with link details / parameters.
+ * @param context Maintains state for each transport layer instance.
  * @param error Indicates success or failure type.
  */
-void chppLinkSendDoneCb(struct ChppPlatformLinkParameters *params,
+void chppLinkSendDoneCb(struct ChppTransportState *context,
                         enum ChppLinkErrorCode error);
 
 /**
@@ -679,7 +650,7 @@
  * TODO: Look into automatically doing this when a response is sent back by a
  * service.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Pointer to the buf given to chppAppProcessRxDatagram. Cannot be
  * null.
  */
@@ -694,7 +665,7 @@
  * The result will be available later, asynchronously, as a ChppAppErrorCode
  * enum in context->loopbackResult.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Pointer to the loopback data to be sent. Cannot be null.
  * @param len Length of the loopback data.
  *
@@ -716,7 +687,7 @@
  * chppWorkThreadStart() would require to call this function after initializing
  * CHPP.
  *
- * @param transportContext Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param resetType Distinguishes a reset from a reset-ack, as defined in the
  * ChppTransportPacketAttributes struct.
  * @param error Provides the error that led to the reset.
@@ -725,6 +696,24 @@
                             enum ChppTransportPacketAttributes resetType,
                             enum ChppTransportErrorCode error);
 
+/**
+ * Returns the Tx MTU size at the transport layer in bytes.
+ *
+ * This the link MTU minus the transport overhead.
+ *
+ * @param context Maintains state for each transport layer instance.
+ */
+size_t chppTransportTxMtuSize(const struct ChppTransportState *context);
+
+/**
+ * Returns the Rx MTU size at the transport layer in bytes.
+ *
+ * This the link MTU minus the transport overhead.
+ *
+ * @param context Maintains state for each transport layer instance.
+ */
+size_t chppTransportRxMtuSize(const struct ChppTransportState *context);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/chpp/include/chpp/transport_signals.h b/chpp/include/chpp/transport_signals.h
index 95a3d44..befd4fc 100644
--- a/chpp/include/chpp/transport_signals.h
+++ b/chpp/include/chpp/transport_signals.h
@@ -29,8 +29,10 @@
 //! (ran in chppWorkThreadStart()).
 #define CHPP_TRANSPORT_SIGNAL_EXIT UINT32_C(1 << 0)
 #define CHPP_TRANSPORT_SIGNAL_EVENT UINT32_C(1 << 1)
-#define CHPP_TRANSPORT_SIGNAL_TIMEOUT \
-  UINT32_C(1 << 15)  // Usage is optional and platform-dependent
+
+//! No data to process, just to trigger an iteration of the work thread so that
+//! the next timeout can be recalculated
+#define CHPP_TRANSPORT_SIGNAL_RECALC_TIMEOUT UINT32_C(1 << 2)
 
 //! These bits are reserved for platform use (e.g. in the platform-specific
 //! link layer implementation).
diff --git a/chpp/platform/linux/include/chpp/platform/platform_link.h b/chpp/platform/linux/include/chpp/platform/platform_link.h
index a359d93..886ee74 100644
--- a/chpp/platform/linux/include/chpp/platform/platform_link.h
+++ b/chpp/platform/linux/include/chpp/platform/platform_link.h
@@ -28,22 +28,20 @@
 extern "C" {
 #endif
 
-#define CHPP_PLATFORM_LINK_TX_MTU_BYTES 1280
-#define CHPP_PLATFORM_LINK_RX_MTU_BYTES 1280
-
-#define CHPP_PLATFORM_TRANSPORT_TIMEOUT_MS 1000
+#define CHPP_LINUX_LINK_TX_MTU_BYTES 1280
+#define CHPP_LINUX_LINK_RX_MTU_BYTES 1280
 
 // Forward declaration
 struct ChppTransportState;
 
-struct ChppPlatformLinkParameters {
+struct ChppLinuxLinkState {
   //! Indicates that the link to the remote endpoint has been established.
   //! This simulates the establishment of the physical link, so
-  //! chppPlatformLinkSend() will fail if this field is set to false.
+  //! link send() will fail if this field is set to false.
   bool linkEstablished;
 
-  //! A pointer to the transport context of the remote endpoint.
-  struct ChppTransportState *remoteTransportContext;
+  //! A pointer to the link context of the remote endpoint.
+  struct ChppLinuxLinkState *remoteLinkState;
 
   //! A thread to use when sending data to the remote endpoint asynchronously.
   pthread_t linkSendThread;
@@ -51,11 +49,14 @@
   //! The notifier for linkSendThread.
   struct ChppNotifier notifier;
 
+  //! The notifier to unblock TX thread when RX is complete.
+  struct ChppNotifier rxNotifier;
+
   //! The mutex to protect buf/bufLen.
   struct ChppMutex mutex;
 
-  //! The temporary buffer to use to send data to the remote endpoint.
-  uint8_t buf[CHPP_PLATFORM_LINK_TX_MTU_BYTES];
+  //! The buffer to use to send data to the remote endpoint.
+  uint8_t buf[CHPP_LINUX_LINK_TX_MTU_BYTES];
   size_t bufLen;
 
   //! The string name of the linkSendThread.
@@ -67,8 +68,32 @@
   //! A flag to indicate if the link is active. Setting this value to false
   //! will cause the CHPP link layer to fail to send/receive messages.
   bool isLinkActive;
+
+  //! Whether to wait for cycleSendThread to be called to unblock the send
+  //! thread loop.
+  bool manualSendCycle;
+
+  //! State of the associated transport layer.
+  struct ChppTransportState *transportContext;
+
+  //! Run the RX callback (chppRxDataCb) in the context of the remote worker.
+  //! Setting this to true will attribute the logs to the expected worker.
+  //! However this might lead to deadlock situation and is better used for
+  //! debugging only.
+  bool rxInRemoteEndpointWorker;
 };
 
+/**
+ * @return a pointer to the link layer API.
+ */
+const struct ChppLinkApi *getLinuxLinkApi(void);
+
+/**
+ * Starts the send thread loop when manualSendCycle is true.
+ * This function is a noop when manualSendCycle is false.
+ */
+void cycleSendThread(void);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/chpp/platform/linux/include/chpp/platform/platform_log.h b/chpp/platform/linux/include/chpp/platform/platform_log.h
index e2918a0..5fdcda7 100644
--- a/chpp/platform/linux/include/chpp/platform/platform_log.h
+++ b/chpp/platform/linux/include/chpp/platform/platform_log.h
@@ -21,6 +21,8 @@
 #include <pthread.h>
 #include <stdio.h>
 
+#include "chpp/platform/platform_time.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -35,12 +37,13 @@
 
 // TODO: Should use PRIu8 etc. from inttypes.h instead of %d, etc. (add -Wall
 // and -Werror to cflags to catch these)
-#define CHPP_LINUX_LOG(level, color, fmt, ...)                                 \
-  {                                                                            \
-    char name[16];                                                             \
-    pthread_getname_np(pthread_self(), name, 16);                              \
-    printf("\e[" color "m%s %s:%d\t (%s) " fmt "\e[0m\n", level, __FILENAME__, \
-           __LINE__, name, ##__VA_ARGS__);                                     \
+#define CHPP_LINUX_LOG(level, color, fmt, ...)                              \
+  {                                                                         \
+    char name[16];                                                          \
+    uint64_t ms = chppGetCurrentTimeNs() / 1000000;                         \
+    pthread_getname_np(pthread_self(), name, 16);                           \
+    printf("\e[" color "m[%" PRIu64 "] %s %s:%d\t (%s) " fmt "\e[0m\n", ms, \
+           level, __FILENAME__, __LINE__, name, ##__VA_ARGS__);             \
   }
 
 #define CHPP_LOGE(fmt, ...) CHPP_LINUX_LOG("E", "91", fmt, ##__VA_ARGS__)
diff --git a/chpp/platform/linux/link.c b/chpp/platform/linux/link.c
index 158729a..f3c67bf 100644
--- a/chpp/platform/linux/link.c
+++ b/chpp/platform/linux/link.c
@@ -23,106 +23,182 @@
 
 #include "chpp/log.h"
 #include "chpp/macros.h"
+#include "chpp/notifier.h"
+#include "chpp/platform/platform_link.h"
 #include "chpp/transport.h"
 
 // The set of signals to use for the linkSendThread.
 #define SIGNAL_EXIT UINT32_C(1 << 0)
 #define SIGNAL_DATA UINT32_C(1 << 1)
+#define SIGNAL_DATA_RX UINT32_C(1 << 2)
+
+struct ChppNotifier gCycleSendThreadNotifier;
+
+void cycleSendThread(void) {
+  chppNotifierSignal(&gCycleSendThreadNotifier, 1);
+}
 
 /**
  * This thread is used to "send" TX data to the remote endpoint. The remote
  * endpoint is defined by the ChppTransportState pointer, so a loopback link
  * with a single CHPP instance can be supported.
  */
-static void *linkSendThread(void *arg) {
-  struct ChppPlatformLinkParameters *params =
-      (struct ChppPlatformLinkParameters *)arg;
+static void *linkSendThread(void *linkContext) {
+  struct ChppLinuxLinkState *context =
+      (struct ChppLinuxLinkState *)(linkContext);
   while (true) {
-    uint32_t signal = chppNotifierTimedWait(&params->notifier, CHPP_TIME_MAX);
+    if (context->manualSendCycle) {
+      chppNotifierWait(&gCycleSendThreadNotifier);
+    }
+    uint32_t signal = chppNotifierTimedWait(&context->notifier, CHPP_TIME_MAX);
 
     if (signal & SIGNAL_EXIT) {
       break;
     }
+
     if (signal & SIGNAL_DATA) {
       enum ChppLinkErrorCode error;
 
-      chppMutexLock(&params->mutex);
+      chppMutexLock(&context->mutex);
 
-      if (params->remoteTransportContext == NULL) {
-        CHPP_LOGW("remoteTransportContext is NULL");
+      if (context->remoteLinkState == NULL) {
+        CHPP_LOGW("remoteLinkState is NULL");
         error = CHPP_LINK_ERROR_NONE_SENT;
 
-      } else if (!params->linkEstablished) {
+      } else if (!context->linkEstablished) {
         CHPP_LOGE("No (fake) link");
         error = CHPP_LINK_ERROR_NO_LINK;
 
-      } else if (!chppRxDataCb(params->remoteTransportContext, params->buf,
-                               params->bufLen)) {
-        CHPP_LOGW("chppRxDataCb return state!=preamble (packet incomplete)");
-        error = CHPP_LINK_ERROR_NONE_SENT;
-
       } else {
+        // Use notifiers only when there are 2 different link layers (i.e. no
+        // loopback). Otherwise call chppRxDataCb directly.
+        if (context->rxInRemoteEndpointWorker &&
+            context->remoteLinkState != context) {
+          chppNotifierSignal(&context->remoteLinkState->notifier,
+                             SIGNAL_DATA_RX);
+
+          // Wait for the RX thread to consume the buffer before we can modify
+          // it.
+          chppNotifierTimedWait(&context->rxNotifier, CHPP_TIME_MAX);
+        } else if (!chppRxDataCb(context->remoteLinkState->transportContext,
+                                 context->buf, context->bufLen)) {
+          CHPP_LOGW("chppRxDataCb return state!=preamble (packet incomplete)");
+        }
         error = CHPP_LINK_ERROR_NONE_SENT;
       }
 
-      params->bufLen = 0;
-      chppLinkSendDoneCb(params, error);
+      context->bufLen = 0;
+      chppLinkSendDoneCb(context->transportContext, error);
 
-      chppMutexUnlock(&params->mutex);
+      chppMutexUnlock(&context->mutex);
+    }
+
+    if (signal & SIGNAL_DATA_RX) {
+      CHPP_NOT_NULL(context->transportContext);
+      CHPP_NOT_NULL(context->remoteLinkState);
+      // Process RX data which are the TX data from the remote link.
+      chppRxDataCb(context->transportContext, context->remoteLinkState->buf,
+                   context->remoteLinkState->bufLen);
+      // Unblock the TX thread when the buffer has been consumed.
+      chppNotifierSignal(&context->remoteLinkState->rxNotifier, 0x01);
     }
   }
 
   return NULL;
 }
 
-void chppPlatformLinkInit(struct ChppPlatformLinkParameters *params) {
-  params->bufLen = 0;
-  chppMutexInit(&params->mutex);
-  chppNotifierInit(&params->notifier);
-  pthread_create(&params->linkSendThread, NULL /* attr */, linkSendThread,
-                 params);
-  if (params->linkThreadName != NULL) {
-    pthread_setname_np(params->linkSendThread, params->linkThreadName);
+static void init(void *linkContext,
+                 struct ChppTransportState *transportContext) {
+  struct ChppLinuxLinkState *context =
+      (struct ChppLinuxLinkState *)(linkContext);
+  context->bufLen = 0;
+  context->transportContext = transportContext;
+  chppMutexInit(&context->mutex);
+  chppNotifierInit(&context->notifier);
+  chppNotifierInit(&context->rxNotifier);
+  chppNotifierInit(&gCycleSendThreadNotifier);
+  pthread_create(&context->linkSendThread, NULL /* attr */, linkSendThread,
+                 context);
+  if (context->linkThreadName != NULL) {
+    pthread_setname_np(context->linkSendThread, context->linkThreadName);
   }
 }
 
-void chppPlatformLinkDeinit(struct ChppPlatformLinkParameters *params) {
-  params->bufLen = 0;
-  chppNotifierSignal(&params->notifier, SIGNAL_EXIT);
-  pthread_join(params->linkSendThread, NULL /* retval */);
-  chppNotifierDeinit(&params->notifier);
-  chppMutexDeinit(&params->mutex);
+static void deinit(void *linkContext) {
+  struct ChppLinuxLinkState *context =
+      (struct ChppLinuxLinkState *)(linkContext);
+  context->bufLen = 0;
+  chppNotifierSignal(&context->notifier, SIGNAL_EXIT);
+  if (context->manualSendCycle) {
+    // Unblock the send thread so it exits.
+    cycleSendThread();
+  }
+  pthread_join(context->linkSendThread, NULL /* retval */);
+  chppNotifierDeinit(&context->notifier);
+  chppNotifierDeinit(&context->rxNotifier);
+  chppNotifierDeinit(&gCycleSendThreadNotifier);
+  chppMutexDeinit(&context->mutex);
 }
 
-enum ChppLinkErrorCode chppPlatformLinkSend(
-    struct ChppPlatformLinkParameters *params, uint8_t *buf, size_t len) {
+static enum ChppLinkErrorCode send(void *linkContext, size_t len) {
+  struct ChppLinuxLinkState *context =
+      (struct ChppLinuxLinkState *)(linkContext);
   bool success = false;
-  chppMutexLock(&params->mutex);
-  if (params->bufLen != 0) {
+  chppMutexLock(&context->mutex);
+  if (context->bufLen != 0) {
     CHPP_LOGE("Failed to send data - link layer busy");
-  } else if (!params->isLinkActive) {
+  } else if (!context->isLinkActive) {
     success = false;
   } else {
     success = true;
-    memcpy(params->buf, buf, len);
-    params->bufLen = len;
+    context->bufLen = len;
   }
-  chppMutexUnlock(&params->mutex);
+  chppMutexUnlock(&context->mutex);
 
   if (success) {
-    chppNotifierSignal(&params->notifier, SIGNAL_DATA);
+    chppNotifierSignal(&context->notifier, SIGNAL_DATA);
   }
 
   return success ? CHPP_LINK_ERROR_NONE_QUEUED : CHPP_LINK_ERROR_BUSY;
 }
 
-void chppPlatformLinkDoWork(struct ChppPlatformLinkParameters *params,
-                            uint32_t signal) {
-  UNUSED_VAR(params);
+static void doWork(void *linkContext, uint32_t signal) {
+  UNUSED_VAR(linkContext);
   UNUSED_VAR(signal);
 }
 
-void chppPlatformLinkReset(struct ChppPlatformLinkParameters *params) {
-  chppPlatformLinkDeinit(params);
-  chppPlatformLinkInit(params);
+static void reset(void *linkContext) {
+  struct ChppLinuxLinkState *context =
+      (struct ChppLinuxLinkState *)(linkContext);
+  deinit(context);
+  init(context, context->transportContext);
+}
+
+static struct ChppLinkConfiguration getConfig(void *linkContext) {
+  UNUSED_VAR(linkContext);
+  const struct ChppLinkConfiguration config = {
+      .txBufferLen = CHPP_LINUX_LINK_TX_MTU_BYTES,
+      .rxBufferLen = CHPP_LINUX_LINK_RX_MTU_BYTES,
+  };
+  return config;
+}
+
+static uint8_t *getTxBuffer(void *linkContext) {
+  struct ChppLinuxLinkState *context =
+      (struct ChppLinuxLinkState *)(linkContext);
+  return &context->buf[0];
+}
+
+const struct ChppLinkApi gLinuxLinkApi = {
+    .init = &init,
+    .deinit = &deinit,
+    .send = &send,
+    .doWork = &doWork,
+    .reset = &reset,
+    .getConfig = &getConfig,
+    .getTxBuffer = &getTxBuffer,
+};
+
+const struct ChppLinkApi *getLinuxLinkApi(void) {
+  return &gLinuxLinkApi;
 }
diff --git a/chpp/platform/linux/memory.c b/chpp/platform/linux/memory.c
index dc07a21..6c4a69b 100644
--- a/chpp/platform/linux/memory.c
+++ b/chpp/platform/linux/memory.c
@@ -103,10 +103,10 @@
   return ptr;
 }
 
-void chppClearTotalAllocBytes() {
+void chppClearTotalAllocBytes(void) {
   gTotalAllocBytes = 0;
 }
 
-size_t chppGetTotalAllocBytes() {
+size_t chppGetTotalAllocBytes(void) {
   return gTotalAllocBytes;
 }
diff --git a/chpp/platform/linux/services/platform_gnss.c b/chpp/platform/linux/services/platform_gnss.c
index a177ef2..b16b868 100644
--- a/chpp/platform/linux/services/platform_gnss.c
+++ b/chpp/platform/linux/services/platform_gnss.c
@@ -52,7 +52,7 @@
   return true;  // If successful
 }
 
-void gnssPalSendLocationEvent() {
+void gnssPalSendLocationEvent(void) {
   struct chreGnssLocationEvent *event =
       (struct chreGnssLocationEvent *)(chppMalloc(
           sizeof(struct chreGnssLocationEvent)));
@@ -66,7 +66,7 @@
   }
 }
 
-void gnssPalSendMeasurementEvent() {
+void gnssPalSendMeasurementEvent(void) {
   struct chreGnssDataEvent *event = (struct chreGnssDataEvent *)(chppMalloc(
       sizeof(struct chreGnssDataEvent)));
   struct chreGnssMeasurement *measurement =
diff --git a/chpp/services.c b/chpp/services.c
index 7847f3d..3445524 100644
--- a/chpp/services.c
+++ b/chpp/services.c
@@ -84,37 +84,43 @@
 #endif
 }
 
-uint8_t chppRegisterService(struct ChppAppState *appContext,
-                            void *serviceContext,
-                            const struct ChppService *newService) {
-  CHPP_NOT_NULL(newService);
+void chppRegisterService(struct ChppAppState *appContext, void *serviceContext,
+                         struct ChppServiceState *serviceState,
+                         const struct ChppService *newService) {
+  CHPP_DEBUG_NOT_NULL(appContext);
+  CHPP_DEBUG_NOT_NULL(serviceContext);
+  CHPP_DEBUG_NOT_NULL(serviceState);
+  CHPP_DEBUG_NOT_NULL(newService);
 
-  if (appContext->registeredServiceCount >= CHPP_MAX_REGISTERED_SERVICES) {
-    CHPP_LOGE("Max services registered: # %" PRIu8,
-              appContext->registeredServiceCount);
-    return CHPP_HANDLE_NONE;
+  const uint8_t numServices = appContext->registeredServiceCount;
 
-  } else {
-    appContext->registeredServices[appContext->registeredServiceCount] =
-        newService;
-    appContext->registeredServiceContexts[appContext->registeredServiceCount] =
-        serviceContext;
+  serviceState->openState = CHPP_OPEN_STATE_CLOSED;
+  serviceState->appContext = appContext;
 
-    char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
-    chppUuidToStr(newService->descriptor.uuid, uuidText);
-    CHPP_LOGD("Registered service # %" PRIu8
-              " on handle %d"
-              " with name=%s, UUID=%s, version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
-              ", min_len=%" PRIuSIZE " ",
-              appContext->registeredServiceCount,
-              CHPP_SERVICE_HANDLE_OF_INDEX(appContext->registeredServiceCount),
-              newService->descriptor.name, uuidText,
-              newService->descriptor.version.major,
-              newService->descriptor.version.minor,
-              newService->descriptor.version.patch, newService->minLength);
-
-    return CHPP_SERVICE_HANDLE_OF_INDEX(appContext->registeredServiceCount++);
+  if (numServices >= CHPP_MAX_REGISTERED_SERVICES) {
+    CHPP_LOGE("Max services registered: # %" PRIu8, numServices);
+    serviceState->handle = CHPP_HANDLE_NONE;
+    return;
   }
+
+  serviceState->handle = CHPP_SERVICE_HANDLE_OF_INDEX(numServices);
+
+  appContext->registeredServices[numServices] = newService;
+  appContext->registeredServiceContexts[numServices] = serviceContext;
+  appContext->registeredServiceCount++;
+
+  char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
+  chppUuidToStr(newService->descriptor.uuid, uuidText);
+  CHPP_LOGD("Registered service # %" PRIu8
+            " on handle %d"
+            " with name=%s, UUID=%s, version=%" PRIu8 ".%" PRIu8 ".%" PRIu16
+            ", min_len=%" PRIuSIZE " ",
+            numServices, serviceState->handle, newService->descriptor.name,
+            uuidText, newService->descriptor.version.major,
+            newService->descriptor.version.minor,
+            newService->descriptor.version.patch, newService->minLength);
+
+  return;
 }
 
 struct ChppAppHeader *chppAllocServiceNotification(size_t len) {
@@ -156,9 +162,17 @@
   rRState->transaction = requestHeader->transaction;
 }
 
-void chppServiceTimestampResponse(struct ChppRequestResponseState *rRState) {
+uint64_t chppServiceTimestampResponse(
+    struct ChppRequestResponseState *rRState) {
   uint64_t previousResponseTime = rRState->responseTimeNs;
   rRState->responseTimeNs = chppGetCurrentTimeNs();
+  return previousResponseTime;
+}
+
+bool chppSendTimestampedResponseOrFail(struct ChppServiceState *serviceState,
+                                       struct ChppRequestResponseState *rRState,
+                                       void *buf, size_t len) {
+  uint64_t previousResponseTime = chppServiceTimestampResponse(rRState);
 
   if (rRState->requestTimeNs == CHPP_TIME_NONE) {
     CHPP_LOGE("TX response w/ no req t=%" PRIu64,
@@ -167,22 +181,17 @@
   } else if (previousResponseTime != CHPP_TIME_NONE) {
     CHPP_LOGW("TX additional response t=%" PRIu64 " for req t=%" PRIu64,
               rRState->responseTimeNs / CHPP_NSEC_PER_MSEC,
-              rRState->responseTimeNs / CHPP_NSEC_PER_MSEC);
+              rRState->requestTimeNs / CHPP_NSEC_PER_MSEC);
 
   } else {
     CHPP_LOGD("Sending initial response at t=%" PRIu64
               " for request at t=%" PRIu64 " (RTT=%" PRIu64 ")",
               rRState->responseTimeNs / CHPP_NSEC_PER_MSEC,
-              rRState->responseTimeNs / CHPP_NSEC_PER_MSEC,
+              rRState->requestTimeNs / CHPP_NSEC_PER_MSEC,
               (rRState->responseTimeNs - rRState->requestTimeNs) /
                   CHPP_NSEC_PER_MSEC);
   }
-}
 
-bool chppSendTimestampedResponseOrFail(struct ChppServiceState *serviceState,
-                                       struct ChppRequestResponseState *rRState,
-                                       void *buf, size_t len) {
-  chppServiceTimestampResponse(rRState);
   return chppEnqueueTxDatagramOrFail(serviceState->appContext->transportContext,
                                      buf, len);
 }
diff --git a/chpp/services/gnss.c b/chpp/services/gnss.c
index 0bf2e09..33f2ca4 100644
--- a/chpp/services/gnss.c
+++ b/chpp/services/gnss.c
@@ -254,7 +254,7 @@
     error = CHPP_APP_ERROR_BEYOND_CHPP;
 
   } else {
-    CHPP_LOGI("GNSS service opened");
+    CHPP_LOGD("GNSS service opened");
     gnssServiceContext->service.openState = CHPP_OPEN_STATE_OPENED;
 
     struct ChppAppHeader *response =
@@ -290,7 +290,7 @@
   gnssServiceContext->api->close();
   gnssServiceContext->service.openState = CHPP_OPEN_STATE_CLOSED;
 
-  CHPP_LOGI("GNSS service closed");
+  CHPP_LOGD("GNSS service closed");
 
   struct ChppAppHeader *response =
       chppAllocServiceResponseFixed(requestHeader, struct ChppAppHeader);
@@ -320,7 +320,7 @@
   if (gnssServiceContext->service.openState != CHPP_OPEN_STATE_OPENED) {
     CHPP_LOGW("GNSS service reset but wasn't open");
   } else {
-    CHPP_LOGI("GNSS service reset. Closing");
+    CHPP_LOGD("GNSS service reset. Closing");
     gnssServiceContext->service.openState = CHPP_OPEN_STATE_CLOSED;
     gnssServiceContext->api->close();
   }
@@ -669,10 +669,8 @@
                           "GNSS PAL API incompatible. Cannot register service");
 
   } else {
-    gGnssServiceContext.service.appContext = appContext;
-    gGnssServiceContext.service.openState = CHPP_OPEN_STATE_CLOSED;
-    gGnssServiceContext.service.handle = chppRegisterService(
-        appContext, (void *)&gGnssServiceContext, &kGnssServiceConfig);
+    chppRegisterService(appContext, (void *)&gGnssServiceContext,
+                        &gGnssServiceContext.service, &kGnssServiceConfig);
     CHPP_DEBUG_ASSERT(gGnssServiceContext.service.handle);
   }
 }
diff --git a/chpp/services/loopback.c b/chpp/services/loopback.c
index c0812f5..e5d1289 100644
--- a/chpp/services/loopback.c
+++ b/chpp/services/loopback.c
@@ -39,7 +39,7 @@
                                CHPP_TRANSPORT_ERROR_OOM);
 
   } else {
-    CHPP_LOGI("Looping back len=%" PRIuSIZE, len);
+    CHPP_LOGD("Looping back len=%" PRIuSIZE, len);
 
     memcpy(response, buf, len);
 
diff --git a/chpp/services/timesync.c b/chpp/services/timesync.c
index e66b512..c81187a 100644
--- a/chpp/services/timesync.c
+++ b/chpp/services/timesync.c
@@ -34,14 +34,10 @@
  * provided to CHPP.
  *
  * @param context Maintains status for each app layer instance.
- * @param buf Input data. Cannot be null.
- * @param len Length of input data in bytes.
+ * @param requestHeader Header of the request.
  */
 static void chppTimesyncGetTime(struct ChppAppState *context,
-                                const uint8_t *buf, size_t len) {
-  UNUSED_VAR(len);
-  const struct ChppAppHeader *requestHeader = (const struct ChppAppHeader *)buf;
-
+                                const struct ChppAppHeader *requestHeader) {
   struct ChppTimesyncResponse *response =
       chppAllocServiceResponseFixed(requestHeader, struct ChppTimesyncResponse);
   size_t responseLen = sizeof(*response);
@@ -49,7 +45,6 @@
   if (response == NULL) {
     CHPP_LOG_OOM();
     CHPP_DEBUG_ASSERT(false);
-
   } else {
     response->timeNs = chppGetCurrentTimeNs();
     CHPP_LOGD("chppTimesyncGetTime returning %" PRIuSIZE
@@ -73,7 +68,7 @@
 
   switch (rxHeader->command) {
     case CHPP_TIMESYNC_COMMAND_GETTIME: {
-      chppTimesyncGetTime(context, buf, len);
+      chppTimesyncGetTime(context, rxHeader);
       break;
     }
     default: {
diff --git a/chpp/services/wifi.c b/chpp/services/wifi.c
index 40e8059..966e77f 100644
--- a/chpp/services/wifi.c
+++ b/chpp/services/wifi.c
@@ -303,7 +303,7 @@
     error = CHPP_APP_ERROR_BEYOND_CHPP;
 
   } else {
-    CHPP_LOGI("WiFi service opened");
+    CHPP_LOGD("WiFi service opened");
     wifiServiceContext->service.openState = CHPP_OPEN_STATE_OPENED;
 
     struct ChppAppHeader *response =
@@ -339,7 +339,7 @@
   wifiServiceContext->api->close();
   wifiServiceContext->service.openState = CHPP_OPEN_STATE_CLOSED;
 
-  CHPP_LOGI("WiFi service closed");
+  CHPP_LOGD("WiFi service closed");
 
   struct ChppAppHeader *response =
       chppAllocServiceResponseFixed(requestHeader, struct ChppAppHeader);
@@ -368,7 +368,7 @@
   if (wifiServiceContext->service.openState != CHPP_OPEN_STATE_OPENED) {
     CHPP_LOGW("WiFi service reset but wasn't open");
   } else {
-    CHPP_LOGI("WiFi service reset. Closing");
+    CHPP_LOGD("WiFi service reset. Closing");
     wifiServiceContext->service.openState = CHPP_OPEN_STATE_CLOSED;
     wifiServiceContext->api->close();
   }
@@ -1019,10 +1019,8 @@
                           "WiFi PAL API incompatible. Cannot register service");
 
   } else {
-    gWifiServiceContext.service.appContext = appContext;
-    gWifiServiceContext.service.openState = CHPP_OPEN_STATE_CLOSED;
-    gWifiServiceContext.service.handle = chppRegisterService(
-        appContext, (void *)&gWifiServiceContext, &kWifiServiceConfig);
+    chppRegisterService(appContext, (void *)&gWifiServiceContext,
+                        &gWifiServiceContext.service, &kWifiServiceConfig);
     CHPP_DEBUG_ASSERT(gWifiServiceContext.service.handle);
   }
 }
diff --git a/chpp/services/wwan.c b/chpp/services/wwan.c
index 0af43d1..bd28403 100644
--- a/chpp/services/wwan.c
+++ b/chpp/services/wwan.c
@@ -218,7 +218,7 @@
     error = CHPP_APP_ERROR_BEYOND_CHPP;
 
   } else {
-    CHPP_LOGI("WWAN service opened");
+    CHPP_LOGD("WWAN service opened");
     wwanServiceContext->service.openState = CHPP_OPEN_STATE_OPENED;
 
     struct ChppAppHeader *response =
@@ -254,7 +254,7 @@
   wwanServiceContext->api->close();
   wwanServiceContext->service.openState = CHPP_OPEN_STATE_CLOSED;
 
-  CHPP_LOGI("WWAN service closed");
+  CHPP_LOGD("WWAN service closed");
 
   struct ChppAppHeader *response =
       chppAllocServiceResponseFixed(requestHeader, struct ChppAppHeader);
@@ -284,7 +284,7 @@
   if (wwanServiceContext->service.openState != CHPP_OPEN_STATE_OPENED) {
     CHPP_LOGW("WWAN service reset but wasn't open");
   } else {
-    CHPP_LOGI("WWAN service reset. Closing");
+    CHPP_LOGD("WWAN service reset. Closing");
     wwanServiceContext->service.openState = CHPP_OPEN_STATE_CLOSED;
     wwanServiceContext->api->close();
   }
@@ -414,10 +414,8 @@
                           "WWAN PAL API incompatible. Cannot register service");
 
   } else {
-    gWwanServiceContext.service.appContext = appContext;
-    gWwanServiceContext.service.openState = CHPP_OPEN_STATE_CLOSED;
-    gWwanServiceContext.service.handle = chppRegisterService(
-        appContext, (void *)&gWwanServiceContext, &kWwanServiceConfig);
+    chppRegisterService(appContext, (void *)&gWwanServiceContext,
+                        &gWwanServiceContext.service, &kWwanServiceConfig);
     CHPP_DEBUG_ASSERT(gWwanServiceContext.service.handle);
   }
 }
diff --git a/chpp/test/app_test.cpp b/chpp/test/app_test.cpp
index 248688c..84d826c 100644
--- a/chpp/test/app_test.cpp
+++ b/chpp/test/app_test.cpp
@@ -18,7 +18,6 @@
 
 #include <stddef.h>
 #include <stdint.h>
-#include <string.h>
 #include <thread>
 
 #include "app_test_base.h"
@@ -27,6 +26,7 @@
 #include "chpp/clients/loopback.h"
 #include "chpp/clients/timesync.h"
 #include "chpp/log.h"
+#include "chpp/platform/platform_link.h"
 #include "chpp/transport.h"
 
 /*
@@ -35,17 +35,20 @@
 namespace chpp {
 namespace {
 
-TEST_F(AppTestBase, SimpleStartStop) {
+class ChppAppTest : public AppTestBase {};
+
+TEST_F(ChppAppTest, SimpleStartStop) {
   // Simple test to make sure start/stop work threads work without crashing
-  ASSERT_TRUE(mClientTransportContext.linkParams.linkEstablished);
-  ASSERT_TRUE(mServiceTransportContext.linkParams.linkEstablished);
+  ASSERT_TRUE(mClientLinkContext.linkEstablished);
+  ASSERT_TRUE(mServiceLinkContext.linkEstablished);
 }
 
-TEST_F(AppTestBase, TransportLayerLoopback) {
+TEST_F(ChppAppTest, TransportLayerLoopback) {
   // This tests the more limited transport-layer-looopback. In contrast,
   // the regular application-layer loopback test provides a more thorough test
   // and test results.
-  constexpr size_t kTestLen = CHPP_TRANSPORT_TX_MTU_BYTES;
+  constexpr size_t kTestLen =
+      CHPP_LINUX_LINK_TX_MTU_BYTES - CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES;
   uint8_t buf[kTestLen];
   for (size_t i = 0; i < kTestLen; i++) {
     buf[i] = (uint8_t)(i + 100);
@@ -84,9 +87,10 @@
             mClientAppContext.transportContext->loopbackResult);
 }
 
-TEST_F(AppTestBase, SimpleLoopback) {
-  constexpr size_t kTestLen =
-      CHPP_TRANSPORT_TX_MTU_BYTES - CHPP_LOOPBACK_HEADER_LEN;
+TEST_F(ChppAppTest, SimpleLoopback) {
+  constexpr size_t kTestLen = CHPP_LINUX_LINK_TX_MTU_BYTES -
+                              CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES -
+                              CHPP_LOOPBACK_HEADER_LEN;
   uint8_t buf[kTestLen];
   for (size_t i = 0; i < kTestLen; i++) {
     buf[i] = (uint8_t)(i + 100);
@@ -96,9 +100,9 @@
       "Starting loopback test without fragmentation (max buffer = %zu)...",
       kTestLen);
 
-  struct ChppLoopbackTestResult result;
+  struct ChppLoopbackTestResult result =
+      chppRunLoopbackTest(&mClientAppContext, buf, kTestLen);
 
-  result = chppRunLoopbackTest(&mClientAppContext, buf, kTestLen);
   EXPECT_EQ(result.error, CHPP_APP_ERROR_NONE);
 
   result = chppRunLoopbackTest(&mClientAppContext, buf, 10);
@@ -111,21 +115,20 @@
   EXPECT_EQ(result.error, CHPP_APP_ERROR_INVALID_LENGTH);
 }
 
-TEST_F(AppTestBase, FragmentedLoopback) {
+TEST_F(ChppAppTest, FragmentedLoopback) {
   constexpr size_t kTestLen = UINT16_MAX;
   uint8_t buf[kTestLen];
   for (size_t i = 0; i < kTestLen; i++) {
-    buf[i] = (uint8_t)(
-        (i % 251) + 64);  // Arbitrary data. A modulus of 251, a prime number,
-                          // reduces the chance of alignment with the MTU.
+    // Arbitrary data. A modulus of 251, a prime number, reduces the chance of
+    // alignment with the MTU.
+    buf[i] = (uint8_t)((i % 251) + 64);
   }
 
   CHPP_LOGI("Starting loopback test with fragmentation (max buffer = %zu)...",
             kTestLen);
 
-  struct ChppLoopbackTestResult result;
-
-  result = chppRunLoopbackTest(&mClientAppContext, buf, kTestLen);
+  struct ChppLoopbackTestResult result =
+      chppRunLoopbackTest(&mClientAppContext, buf, kTestLen);
   EXPECT_EQ(result.error, CHPP_APP_ERROR_NONE);
 
   result = chppRunLoopbackTest(&mClientAppContext, buf, 50000);
@@ -133,11 +136,12 @@
 
   result = chppRunLoopbackTest(
       &mClientAppContext, buf,
-      CHPP_TRANSPORT_TX_MTU_BYTES - CHPP_LOOPBACK_HEADER_LEN + 1);
+      chppTransportTxMtuSize(mClientAppContext.transportContext) -
+          CHPP_LOOPBACK_HEADER_LEN + 1);
   EXPECT_EQ(result.error, CHPP_APP_ERROR_NONE);
 }
 
-TEST_F(AppTestBase, Timesync) {
+TEST_F(ChppAppTest, Timesync) {
   constexpr uint64_t kMaxRtt = 2 * CHPP_NSEC_PER_MSEC;    // in ms
   constexpr int64_t kMaxOffset = 1 * CHPP_NSEC_PER_MSEC;  // in ms
 
@@ -158,7 +162,7 @@
   EXPECT_NE(chppTimesyncGetResult(&mClientAppContext)->offsetNs, 0);
 }
 
-TEST_F(AppTestBase, DiscoveryMatched) {
+TEST_F(ChppAppTest, DiscoveryMatched) {
   constexpr uint64_t kTimeoutMs = 5000;
   EXPECT_TRUE(chppWaitForDiscoveryComplete(&mClientAppContext, kTimeoutMs));
   EXPECT_TRUE(chppAreAllClientsMatched(&mClientAppContext));
diff --git a/chpp/test/app_test_base.cpp b/chpp/test/app_test_base.cpp
index c4fd813..907b15e 100644
--- a/chpp/test/app_test_base.cpp
+++ b/chpp/test/app_test_base.cpp
@@ -34,10 +34,12 @@
 namespace {
 
 void *workThread(void *arg) {
-  ChppTransportState *context = static_cast<ChppTransportState *>(arg);
-  pthread_setname_np(pthread_self(), context->linkParams.workThreadName);
+  ChppTransportState *transportContext = static_cast<ChppTransportState *>(arg);
+  struct ChppLinuxLinkState *linkContext =
+      (struct ChppLinuxLinkState *)(transportContext->linkContext);
+  pthread_setname_np(pthread_self(), linkContext->workThreadName);
 
-  chppWorkThreadStart(context);
+  chppWorkThreadStart(transportContext);
 
   return nullptr;
 }
@@ -46,22 +48,20 @@
 
 void AppTestBase::SetUp() {
   chppClearTotalAllocBytes();
-  memset(&mClientTransportContext.linkParams, 0,
-         sizeof(mClientTransportContext.linkParams));
-  memset(&mServiceTransportContext.linkParams, 0,
-         sizeof(mServiceTransportContext.linkParams));
+  memset(&mClientLinkContext, 0, sizeof(mClientLinkContext));
+  memset(&mServiceLinkContext, 0, sizeof(mServiceLinkContext));
   // The linkSendThread in the link layer is a link "to" the remote end.
-  mServiceTransportContext.linkParams.linkThreadName = "Link to client";
-  mServiceTransportContext.linkParams.workThreadName = "Service work";
-  mClientTransportContext.linkParams.linkThreadName = "Link to service";
-  mClientTransportContext.linkParams.workThreadName = "Client work";
-  mClientTransportContext.linkParams.isLinkActive = true;
-  mServiceTransportContext.linkParams.isLinkActive = true;
+  mServiceLinkContext.linkThreadName = "Link to client";
+  mServiceLinkContext.workThreadName = "Service work";
+  mServiceLinkContext.isLinkActive = true;
+  mServiceLinkContext.remoteLinkState = &mClientLinkContext;
+  mServiceLinkContext.rxInRemoteEndpointWorker = false;
 
-  mClientTransportContext.linkParams.remoteTransportContext =
-      &mServiceTransportContext;
-  mServiceTransportContext.linkParams.remoteTransportContext =
-      &mClientTransportContext;
+  mClientLinkContext.linkThreadName = "Link to service";
+  mClientLinkContext.workThreadName = "Client work";
+  mClientLinkContext.isLinkActive = true;
+  mClientLinkContext.remoteLinkState = &mServiceLinkContext;
+  mClientLinkContext.rxInRemoteEndpointWorker = false;
 
   struct ChppClientServiceSet set;
   memset(&set, 0, sizeof(set));
@@ -70,7 +70,10 @@
   set.wwanClient = 1;
   set.loopbackClient = 1;
 
-  chppTransportInit(&mClientTransportContext, &mClientAppContext);
+  const struct ChppLinkApi *linkApi = getLinuxLinkApi();
+
+  chppTransportInit(&mClientTransportContext, &mClientAppContext,
+                    &mClientLinkContext, linkApi);
   chppAppInitWithClientServiceSet(&mClientAppContext, &mClientTransportContext,
                                   set);
   pthread_create(&mClientWorkThread, NULL, workThread,
@@ -84,14 +87,15 @@
   set.gnssService = 1;
   set.wwanService = 1;
 
-  chppTransportInit(&mServiceTransportContext, &mServiceAppContext);
+  chppTransportInit(&mServiceTransportContext, &mServiceAppContext,
+                    &mServiceLinkContext, linkApi);
   chppAppInitWithClientServiceSet(&mServiceAppContext,
                                   &mServiceTransportContext, set);
   pthread_create(&mServiceWorkThread, NULL, workThread,
                  &mServiceTransportContext);
 
-  mClientTransportContext.linkParams.linkEstablished = true;
-  mServiceTransportContext.linkParams.linkEstablished = true;
+  mClientLinkContext.linkEstablished = true;
+  mServiceLinkContext.linkEstablished = true;
 
   constexpr uint64_t kResetWaitTimeMs = 1500;
   chppTransportWaitForResetComplete(&mClientTransportContext, kResetWaitTimeMs);
diff --git a/chpp/test/app_test_base.h b/chpp/test/app_test_base.h
index a3b63e6..e09a67c 100644
--- a/chpp/test/app_test_base.h
+++ b/chpp/test/app_test_base.h
@@ -26,6 +26,7 @@
 #include "chpp/clients/loopback.h"
 #include "chpp/log.h"
 #include "chpp/macros.h"
+#include "chpp/platform/platform_link.h"
 #include "chpp/transport.h"
 
 namespace chpp {
@@ -38,9 +39,11 @@
   void SetUp() override;
   void TearDown() override;
 
+  ChppLinuxLinkState mClientLinkContext = {};
   ChppTransportState mClientTransportContext = {};
   ChppAppState mClientAppContext = {};
 
+  ChppLinuxLinkState mServiceLinkContext = {};
   ChppTransportState mServiceTransportContext = {};
   ChppAppState mServiceAppContext = {};
 
diff --git a/chpp/test/clients_test.cpp b/chpp/test/clients_test.cpp
index cd6a048..5f69ec8 100644
--- a/chpp/test/clients_test.cpp
+++ b/chpp/test/clients_test.cpp
@@ -23,8 +23,12 @@
 
 #include "chpp/app.h"
 #include "chpp/clients.h"
+#include "chpp/clients/gnss.h"
+#include "chpp/clients/wifi.h"
+#include "chpp/clients/wwan.h"
 #include "chpp/macros.h"
 #include "chpp/memory.h"
+#include "chpp/platform/platform_link.h"
 #include "chpp/platform/utils.h"
 #include "chpp/services.h"
 #include "chpp/time.h"
@@ -35,10 +39,14 @@
  protected:
   void SetUp() override {
     chppClearTotalAllocBytes();
-    memset(&mTransportContext.linkParams, 0,
-           sizeof(mTransportContext.linkParams));
-    mTransportContext.linkParams.linkEstablished = true;
-    chppTransportInit(&mTransportContext, &mAppContext);
+
+    memset(&mAppContext, 0, sizeof(mAppContext));
+    memset(&mTransportContext, 0, sizeof(mTransportContext));
+    memset(&mLinkContext, 0, sizeof(mLinkContext));
+    mLinkContext.linkEstablished = true;
+
+    chppTransportInit(&mTransportContext, &mAppContext, &mLinkContext,
+                      getLinuxLinkApi());
     chppAppInit(&mAppContext, &mTransportContext);
     mClientState =
         (struct ChppClientState *)mAppContext.registeredClientContexts[0];
@@ -56,7 +64,7 @@
 
   struct ChppTransportState mTransportContext;
   struct ChppAppState mAppContext;
-  struct ChppClient mClient;
+  struct ChppLinuxLinkState mLinkContext;
   struct ChppClientState *mClientState;
   struct ChppRequestResponseState mRRState;
 };
@@ -352,4 +360,4 @@
 
   chppFree(reqHeader1);
   chppFree(reqHeader2);
-}
+}
\ No newline at end of file
diff --git a/chpp/test/fake_link.cpp b/chpp/test/fake_link.cpp
new file mode 100644
index 0000000..243bd87
--- /dev/null
+++ b/chpp/test/fake_link.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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 "fake_link.h"
+
+#include <cstring>
+
+#include "chpp/log.h"
+#include "packet_util.h"
+
+namespace chpp::test {
+
+void FakeLink::appendTxPacket(uint8_t *data, size_t len) {
+  std::vector<uint8_t> pkt;
+  pkt.resize(len);
+  memcpy(pkt.data(), data, len);
+  checkPacketValidity(pkt);
+  {
+    std::lock_guard<std::mutex> lock(mMutex);
+    mTxPackets.emplace_back(std::move(pkt));
+    mCondVar.notify_all();
+  }
+}
+
+int FakeLink::getTxPacketCount() {
+  std::lock_guard<std::mutex> lock(mMutex);
+  return static_cast<int>(mTxPackets.size());
+}
+
+bool FakeLink::waitForTxPacket(std::chrono::milliseconds timeout) {
+  std::unique_lock<std::mutex> lock(mMutex);
+  auto now = std::chrono::system_clock::now();
+  CHPP_LOGD("FakeLink::WaitForTxPacket waiting...");
+  while (mTxPackets.empty()) {
+    std::cv_status status = mCondVar.wait_until(lock, now + timeout);
+    if (status == std::cv_status::timeout) {
+      return false;
+    }
+  }
+  return true;
+}
+
+std::vector<uint8_t> FakeLink::popTxPacket() {
+  std::lock_guard<std::mutex> lock(mMutex);
+  assert(!mTxPackets.empty());
+  std::vector<uint8_t> vec = std::move(mTxPackets.back());
+  mTxPackets.pop_back();
+  return vec;
+}
+
+void FakeLink::reset() {
+  std::lock_guard<std::mutex> lock(mMutex);
+  mTxPackets.clear();
+}
+
+}  // namespace chpp::test
\ No newline at end of file
diff --git a/chpp/test/fake_link.h b/chpp/test/fake_link.h
new file mode 100644
index 0000000..3a77444
--- /dev/null
+++ b/chpp/test/fake_link.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 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 <chrono>
+#include <condition_variable>
+#include <cstdint>
+#include <deque>
+#include <mutex>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+
+#include "chpp/transport.h"
+
+using ::std::literals::chrono_literals::operator""ms;
+
+namespace chpp::test {
+
+/**
+ * Wrapper for a fake CHPP link layer which puts outgoing packets on a queue
+ * where they can be extracted and inspected.
+ */
+class FakeLink {
+ public:
+  //! How long CHPP is expected to wait on an ACK for a transmitted packet
+  static constexpr auto kTransportTimeout =
+      std::chrono::duration_cast<std::chrono::milliseconds>(
+          std::chrono::nanoseconds(CHPP_TRANSPORT_TX_TIMEOUT_NS));
+
+  // Our default timeout covers the retry timeout, plus some extra buffer to
+  // account for processing delays
+  static constexpr auto kDefaultTimeout = 10 * (kTransportTimeout + 5ms);
+
+  /**
+   * Call from link send. Makes a copy of the provided buffer and
+   * appends it to the TX packet queue.
+   */
+  void appendTxPacket(uint8_t *data, size_t len);
+
+  //! Returns the number of TX packets waiting to be popped
+  int getTxPacketCount();  // int to make EXPECT_EQ against a literal simpler
+                           // with -Wsign-compare enabled
+
+  /**
+   * Wait up to the provided timeout for a packet to hit the TX queue, or return
+   * immediately if a packet is already waiting to be popped.
+   *
+   * @return true if a packet is waiting, false on timeout
+   */
+  bool waitForTxPacket(std::chrono::milliseconds timeout = kDefaultTimeout);
+
+  //! Pop and return the oldest packet on the TX queue, or assert if queue is
+  //! empty
+  std::vector<uint8_t> popTxPacket();
+
+  //! Empties the TX packet queue
+  void reset();
+
+ private:
+  std::mutex mMutex;
+  std::condition_variable mCondVar;
+  std::deque<std::vector<uint8_t>> mTxPackets GUARDED_BY(mMutex);
+};
+
+}  // namespace chpp::test
\ No newline at end of file
diff --git a/chpp/test/fake_link_sync_test.cpp b/chpp/test/fake_link_sync_test.cpp
new file mode 100644
index 0000000..c4ec2a1
--- /dev/null
+++ b/chpp/test/fake_link_sync_test.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 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 <cstdint>
+#include <iostream>
+#include <thread>
+#include <type_traits>
+
+#include "chpp/app.h"
+#include "chpp/crc.h"
+#include "chpp/link.h"
+#include "chpp/log.h"
+#include "chpp/platform/platform_link.h"
+#include "chpp/transport.h"
+#include "fake_link.h"
+#include "packet_util.h"
+
+using chpp::test::FakeLink;
+
+namespace {
+
+static void init(void *linkContext,
+                 struct ChppTransportState *transportContext) {
+  auto context = static_cast<struct ChppTestLinkState *>(linkContext);
+  context->fake = new FakeLink();
+  context->transportContext = transportContext;
+}
+
+static void deinit(void *linkContext) {
+  auto context = static_cast<struct ChppTestLinkState *>(linkContext);
+  auto *fake = reinterpret_cast<FakeLink *>(context->fake);
+  delete fake;
+}
+
+static enum ChppLinkErrorCode send(void *linkContext, size_t len) {
+  auto context = static_cast<struct ChppTestLinkState *>(linkContext);
+  auto *fake = reinterpret_cast<FakeLink *>(context->fake);
+  fake->appendTxPacket(&context->txBuffer[0], len);
+  return CHPP_LINK_ERROR_NONE_SENT;
+}
+
+static void doWork(void * /*linkContext*/, uint32_t /*signal*/) {}
+
+static void reset(void * /*linkContext*/) {}
+
+struct ChppLinkConfiguration getConfig(void * /*linkContext*/) {
+  return ChppLinkConfiguration{
+      .txBufferLen = CHPP_TEST_LINK_TX_MTU_BYTES,
+      .rxBufferLen = CHPP_TEST_LINK_RX_MTU_BYTES,
+  };
+}
+
+uint8_t *getTxBuffer(void *linkContext) {
+  auto context = static_cast<struct ChppTestLinkState *>(linkContext);
+  return &context->txBuffer[0];
+}
+
+}  // namespace
+
+const struct ChppLinkApi gLinkApi = {
+    .init = &init,
+    .deinit = &deinit,
+    .send = &send,
+    .doWork = &doWork,
+    .reset = &reset,
+    .getConfig = &getConfig,
+    .getTxBuffer = &getTxBuffer,
+};
+
+namespace chpp::test {
+
+class FakeLinkSyncTests : public testing::Test {
+ protected:
+  void SetUp() override {
+    chppTransportInit(&mTransportContext, &mAppContext, &mLinkContext,
+                      &gLinkApi);
+    chppAppInitWithClientServiceSet(&mAppContext, &mTransportContext,
+                                    /*clientServiceSet=*/{});
+    mFakeLink = reinterpret_cast<FakeLink *>(mLinkContext.fake);
+
+    mWorkThread = std::thread(chppWorkThreadStart, &mTransportContext);
+
+    // Proceed to the initialized state by performing the CHPP 3-way handshake
+    ASSERT_TRUE(mFakeLink->waitForTxPacket());
+    std::vector<uint8_t> resetPkt = mFakeLink->popTxPacket();
+    ASSERT_TRUE(comparePacket(resetPkt, generateResetPacket()))
+        << "Full packet: " << asResetPacket(resetPkt);
+
+    ChppResetPacket resetAck = generateResetAckPacket();
+    chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&resetAck),
+                 sizeof(resetAck));
+
+    ASSERT_TRUE(mFakeLink->waitForTxPacket());
+    std::vector<uint8_t> ackPkt = mFakeLink->popTxPacket();
+    ASSERT_TRUE(comparePacket(ackPkt, generateEmptyPacket()))
+        << "Full packet: " << asChpp(ackPkt);
+  }
+
+  void TearDown() override {
+    chppWorkThreadStop(&mTransportContext);
+    mWorkThread.join();
+    EXPECT_EQ(mFakeLink->getTxPacketCount(), 0);
+  }
+
+  void txPacket() {
+    uint32_t *payload = static_cast<uint32_t *>(chppMalloc(sizeof(uint32_t)));
+    *payload = 0xdeadbeef;
+    bool enqueued = chppEnqueueTxDatagramOrFail(&mTransportContext, payload,
+                                                sizeof(uint32_t));
+    EXPECT_TRUE(enqueued);
+  }
+
+  ChppTransportState mTransportContext = {};
+  ChppAppState mAppContext = {};
+  ChppTestLinkState mLinkContext = {};
+  FakeLink *mFakeLink;
+  std::thread mWorkThread;
+};
+
+TEST_F(FakeLinkSyncTests, CheckRetryOnTimeout) {
+  txPacket();
+  ASSERT_TRUE(mFakeLink->waitForTxPacket());
+  EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
+
+  std::vector<uint8_t> pkt1 = mFakeLink->popTxPacket();
+
+  // Ideally, to speed up the test, we'd have a mechanism to trigger
+  // chppNotifierWait() to return immediately, to simulate timeout
+  ASSERT_TRUE(mFakeLink->waitForTxPacket());
+  EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
+  std::vector<uint8_t> pkt2 = mFakeLink->popTxPacket();
+
+  // The retry packet should be an exact match of the first one
+  EXPECT_EQ(pkt1, pkt2);
+}
+
+TEST_F(FakeLinkSyncTests, NoRetryAfterAck) {
+  txPacket();
+  ASSERT_TRUE(mFakeLink->waitForTxPacket());
+  EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
+
+  // Generate and reply back with an ACK
+  std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
+  ChppEmptyPacket ack = generateAck(pkt);
+  chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
+               sizeof(ack));
+
+  // We shouldn't get that packet again
+  EXPECT_FALSE(mFakeLink->waitForTxPacket());
+}
+
+TEST_F(FakeLinkSyncTests, MultipleNotifications) {
+  constexpr int kNumPackets = 5;
+  for (int i = 0; i < kNumPackets; i++) {
+    txPacket();
+  }
+
+  for (int i = 0; i < kNumPackets; i++) {
+    ASSERT_TRUE(mFakeLink->waitForTxPacket());
+
+    // Generate and reply back with an ACK
+    std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
+    ChppEmptyPacket ack = generateAck(pkt);
+    chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
+                 sizeof(ack));
+  }
+
+  EXPECT_FALSE(mFakeLink->waitForTxPacket());
+}
+
+}  // namespace chpp::test
diff --git a/chpp/test/gnss_test.cpp b/chpp/test/gnss_test.cpp
index d1f98d4..10fd185 100644
--- a/chpp/test/gnss_test.cpp
+++ b/chpp/test/gnss_test.cpp
@@ -21,11 +21,9 @@
 
 #include <stddef.h>
 #include <stdint.h>
-#include <string.h>
 #include <thread>
 
 #include "app_test_base.h"
-#include "chpp/app.h"
 #include "chpp/clients/gnss.h"
 #include "chpp/log.h"
 #include "chpp/platform/platform_gnss.h"
@@ -36,7 +34,10 @@
 namespace chpp {
 namespace {
 
+class ChppGnssTest : public AppTestBase {};
+
 const struct chrePalGnssApi *gApi;
+
 void chrePalRequestStateResync() {}
 
 void chrePalLocationStatusChangeCallback(bool /* enabled */,
@@ -55,7 +56,7 @@
   gApi->releaseMeasurementDataEvent(event);
 }
 
-TEST_F(AppTestBase, SimpleGnss) {
+TEST_F(ChppGnssTest, SimpleGnss) {
   gApi = chppPalGnssGetApi(CHRE_PAL_GNSS_API_CURRENT_VERSION);
   ASSERT_NE(gApi, nullptr);
 
@@ -77,7 +78,7 @@
   gApi->close();
 }
 
-TEST_F(AppTestBase, GnssCapabilitiesTest) {
+TEST_F(ChppGnssTest, GnssCapabilitiesTest) {
   gApi = chppPalGnssGetApi(CHRE_PAL_GNSS_API_CURRENT_VERSION);
   ASSERT_NE(gApi, nullptr);
 
@@ -94,10 +95,10 @@
   // Set the linkActive flag to false so that CHPP link layer does not
   // receive/send message, which causes the capabilities to be set to the
   // default CHPP_GNSS_DEFAULT_CAPABILITIES
-  mClientTransportContext.linkParams.isLinkActive = false;
+  mClientLinkContext.isLinkActive = false;
   uint32_t capabilities = gApi->getCapabilities();
   ASSERT_EQ(capabilities, CHPP_GNSS_DEFAULT_CAPABILITIES);
-  mClientTransportContext.linkParams.isLinkActive = true;
+  mClientLinkContext.isLinkActive = true;
 
   gApi->close();
 }
diff --git a/chpp/test/include/fake_link/chpp/platform/platform_link.h b/chpp/test/include/fake_link/chpp/platform/platform_link.h
new file mode 100644
index 0000000..bec3e56
--- /dev/null
+++ b/chpp/test/include/fake_link/chpp/platform/platform_link.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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 CHPP_PLATFORM_LINK_H_
+#define CHPP_PLATFORM_LINK_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CHPP_TEST_LINK_TX_MTU_BYTES ((size_t)1280)
+#define CHPP_TEST_LINK_RX_MTU_BYTES ((size_t)1280)
+
+struct ChppTestLinkState {
+  void *fake;
+  uint8_t txBuffer[CHPP_TEST_LINK_TX_MTU_BYTES];
+  const struct ChppTransportState *transportContext;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHPP_PLATFORM_LINK_H_
diff --git a/chpp/test/packet_util.cpp b/chpp/test/packet_util.cpp
new file mode 100644
index 0000000..44a4b42
--- /dev/null
+++ b/chpp/test/packet_util.cpp
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2022 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 "packet_util.h"
+
+#include <cstring>
+
+namespace chpp::test {
+
+// Utilities for packet creation -----------------------------------------------
+
+ChppEmptyPacket generateEmptyPacket(uint8_t ackSeq, uint8_t seq,
+                                    uint8_t error) {
+  // clang-format off
+  ChppEmptyPacket pkt = {
+    .preamble = kPreamble,
+    .header = {
+      .flags = CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM,
+      .packetCode = static_cast<uint8_t>(CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
+          CHPP_TRANSPORT_ATTR_NONE, error)),
+      .ackSeq = ackSeq,
+      .seq = seq,
+      .length = 0,
+      .reserved = 0,
+    },
+  };
+  // clang-format on
+  pkt.footer.checksum = computeCrc(pkt);
+  return pkt;
+}
+
+ChppResetPacket generateResetPacket(uint8_t ackSeq, uint8_t seq) {
+  // clang-format off
+  ChppResetPacket pkt = {
+    .preamble = kPreamble,
+    .header = {
+      .flags = CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM,
+      .packetCode = static_cast<uint8_t>(CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
+          CHPP_TRANSPORT_ATTR_RESET,
+          CHPP_TRANSPORT_ERROR_NONE
+      )),
+      .ackSeq = ackSeq,
+      .seq = seq,
+      .length = sizeof(ChppTransportConfiguration),
+      .reserved = 0,
+    },
+    .config = {
+      .version = {
+        .major = 1,
+        .minor = 0,
+        .patch = 0,
+      },
+      .reserved1 = 0,
+      .reserved2 = 0,
+      .reserved3 = 0,
+    }
+  };
+  // clang-format on
+  pkt.footer.checksum = computeCrc(pkt);
+  return pkt;
+}
+
+ChppResetPacket generateResetAckPacket(uint8_t ackSeq, uint8_t seq) {
+  ChppResetPacket pkt = generateResetPacket(ackSeq, seq);
+  pkt.header.packetCode =
+      static_cast<uint8_t>(CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
+          CHPP_TRANSPORT_ATTR_RESET_ACK, CHPP_TRANSPORT_ERROR_NONE));
+  pkt.footer.checksum = computeCrc(pkt);
+  return pkt;
+}
+
+ChppEmptyPacket generateAck(std::vector<uint8_t> &pkt) {
+  // An ACK consists of an empty packet with the ackSeq set to the received
+  // packet's seq + 1 (since ackSeq indicates the next seq value we expect), and
+  // seq set to the received packet's ackSeq - 1 (since we don't increment seq
+  // on empty packets and ackSeq indicates the next expected seq)
+  ChppTransportHeader &hdr = getHeader(pkt);
+  return generateEmptyPacket(/*acqSeq=*/hdr.seq + 1, /*seq=*/hdr.ackSeq - 1);
+}
+
+// Utilities for debugging -----------------------------------------------------
+
+void dumpRaw(std::ostream &os, const void *ptr, size_t len) {
+  const char *buffer = static_cast<const char *>(ptr);
+  char line[32];
+  char lineChars[32];
+  size_t offset = 0;
+  size_t offsetChars = 0;
+
+  for (size_t i = 1; i <= len; i++) {
+    // This ignores potential errors returned by snprintf. This is a relatively
+    // simple case and the deliberate decision to ignore them has been made.
+    offset += static_cast<size_t>(
+        snprintf(&line[offset], sizeof(line) - offset, "%02x ", buffer[i - 1]));
+    offsetChars += static_cast<size_t>(
+        snprintf(&lineChars[offsetChars], sizeof(lineChars) - offsetChars, "%c",
+                 (isprint(buffer[i - 1])) ? buffer[i - 1] : '.'));
+    if ((i % 8) == 0) {
+      os << "  " << line << "\t" << lineChars << std::endl;
+      offset = 0;
+      offsetChars = 0;
+    } else if ((i % 4) == 0) {
+      offset += static_cast<size_t>(
+          snprintf(&line[offset], sizeof(line) - offset, " "));
+    }
+  }
+
+  if (offset > 0) {
+    char tabs[8];
+    char *pos = tabs;
+    while (offset < 28) {
+      *pos++ = '\t';
+      offset += 8;
+    }
+    *pos = '\0';
+    os << "  " << line << tabs << lineChars << std::endl;
+  }
+}
+
+void dumpPreamble(std::ostream &os, uint16_t preamble) {
+  const char *p = reinterpret_cast<const char *>(&preamble);
+  os << std::endl
+     << "Preamble: 0x" << std::hex << preamble << " \"" << p[0] << p[1] << "\"";
+  if (preamble == kPreamble) {
+    os << " (ok)";
+  } else {
+    os << " (invalid -- expected 0x" << std::hex << kPreamble << ")";
+  }
+  os << std::endl;
+}
+
+void dumpHeader(std::ostream &os, const ChppTransportHeader &hdr) {
+  os << "Header {" << std::endl
+     << "  flags: 0x" << std::hex << (unsigned)hdr.flags;
+  if (hdr.flags & CHPP_TRANSPORT_FLAG_UNFINISHED_DATAGRAM) {
+    os << " (unfinished)";
+  } else {
+    os << " (finished)";
+  }
+  os << std::endl
+     << "  packetCode: 0x" << std::hex << (unsigned)hdr.packetCode
+     << " (attr: ";
+  uint8_t attr = CHPP_TRANSPORT_GET_ATTR(hdr.packetCode);
+  switch (attr) {
+    case CHPP_TRANSPORT_ATTR_NONE:
+      os << "none";
+      break;
+    case CHPP_TRANSPORT_ATTR_RESET:
+      os << "reset";
+      break;
+    case CHPP_TRANSPORT_ATTR_RESET_ACK:
+      os << "reset-ack";
+      break;
+    case CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST:
+      os << "loopback-req";
+      break;
+    case CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE:
+      os << "loopback-rsp";
+      break;
+    default:
+      os << "invalid";
+  }
+  os << " | error: ";
+  uint8_t error = CHPP_TRANSPORT_GET_ERROR(hdr.packetCode);
+  switch (error) {
+    case CHPP_TRANSPORT_ERROR_NONE:
+      os << "none";
+      break;
+    case CHPP_TRANSPORT_ERROR_CHECKSUM:
+      os << "checksum";
+      break;
+    case CHPP_TRANSPORT_ERROR_OOM:
+      os << "oom";
+      break;
+    case CHPP_TRANSPORT_ERROR_BUSY:
+      os << "busy";
+      break;
+    case CHPP_TRANSPORT_ERROR_HEADER:
+      os << "header";
+      break;
+    case CHPP_TRANSPORT_ERROR_ORDER:
+      os << "order";
+      break;
+    case CHPP_TRANSPORT_ERROR_TIMEOUT:
+      os << "timeout";
+      break;
+    case CHPP_TRANSPORT_ERROR_MAX_RETRIES:
+      os << "max-retries";
+      break;
+    case CHPP_TRANSPORT_ERROR_APPLAYER:
+      os << "app-layer";
+      break;
+    default:
+      os << "invalid";
+  }
+  os << ")" << std::endl
+     << "  ackSeq: " << std::dec << (unsigned)hdr.ackSeq << std::endl
+     << "  seq: " << std::dec << (unsigned)hdr.seq << std::endl
+     << "  length: " << std::dec << hdr.length << std::endl
+     << "  reserved: " << std::dec << hdr.reserved << std::endl
+     << "}" << std::endl;
+}
+
+void dumpConfig(std::ostream &os, const ChppTransportConfiguration &cfg) {
+  os << "Config {" << std::endl
+     << "  version: " << std::dec << (unsigned)cfg.version.major << "."
+     << std::dec << (unsigned)cfg.version.minor << "." << std::dec
+     << cfg.version.patch << std::endl
+     << "}" << std::endl;
+}
+
+void dumpEmptyPacket(std::ostream &os, const ChppEmptyPacket &pkt) {
+  dumpPreamble(os, pkt.preamble);
+  dumpHeader(os, pkt.header);
+  dumpFooter(os, pkt);
+}
+
+void dumpResetPacket(std::ostream &os, const ChppResetPacket &pkt) {
+  dumpPreamble(os, pkt.preamble);
+  dumpHeader(os, pkt.header);
+  dumpConfig(os, pkt.config);
+  dumpFooter(os, pkt);
+}
+
+void dumpPacket(std::ostream &os, const ChppPacketPrefix &pkt) {
+  dumpPreamble(os, pkt.preamble);
+  dumpHeader(os, pkt.header);
+  os << "Payload {" << std::endl;
+  dumpRaw(os, pkt.payload, pkt.header.length);
+  os << "}" << std::endl;
+
+  const auto &footer = *reinterpret_cast<const ChppTransportFooter *>(
+      &pkt.payload[pkt.header.length]);
+  uint32_t crc = chppCrc32(0, reinterpret_cast<const uint8_t *>(&pkt.header),
+                           sizeof(pkt.header) + pkt.header.length);
+  os << "CRC: 0x" << std::hex << footer.checksum;
+  if (footer.checksum != crc) {
+    os << " (invalid, expected " << crc << ")";
+  } else {
+    os << " (ok)";
+  }
+  os << std::endl;
+}
+
+std::ostream &operator<<(std::ostream &os, const ChppEmptyPacket &pkt) {
+  dumpEmptyPacket(os, pkt);
+  return os;
+}
+
+std::ostream &operator<<(std::ostream &os, const ChppResetPacket &pkt) {
+  dumpResetPacket(os, pkt);
+  return os;
+}
+
+std::ostream &operator<<(std::ostream &os, const ChppPacketPrefix &pkt) {
+  dumpPacket(os, pkt);
+  return os;
+}
+
+// Utilities for gtest packet checking -----------------------------------------
+
+void checkPacketValidity(std::vector<uint8_t> &received) {
+  const ChppPacketPrefix &pkt = asChpp(received);
+  EXPECT_GE(received.size(), sizeof(ChppEmptyPacket));
+  EXPECT_EQ(pkt.preamble, kPreamble);
+
+  constexpr size_t kFixedLenPortion =
+      sizeof(pkt.preamble) + sizeof(pkt.header) + sizeof(ChppTransportFooter);
+  EXPECT_EQ(pkt.header.length, received.size() - kFixedLenPortion);
+
+  EXPECT_EQ(pkt.header.flags & CHPP_TRANSPORT_FLAG_RESERVED, 0);
+  EXPECT_EQ(pkt.header.reserved, 0);
+
+  uint8_t error = CHPP_TRANSPORT_GET_ERROR(pkt.header.packetCode);
+  EXPECT_TRUE(error <= CHPP_TRANSPORT_ERROR_MAX_RETRIES ||
+              error == CHPP_TRANSPORT_ERROR_APPLAYER);
+  uint8_t attrs = CHPP_TRANSPORT_GET_ATTR(pkt.header.packetCode);
+  EXPECT_TRUE(attrs <= CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE);
+
+  uint32_t crc = chppCrc32(0, reinterpret_cast<const uint8_t *>(&pkt.header),
+                           sizeof(pkt.header) + pkt.header.length);
+  const auto *footer = reinterpret_cast<const ChppTransportFooter *>(
+      &received[sizeof(pkt.preamble) + sizeof(pkt.header) + pkt.header.length]);
+  EXPECT_EQ(footer->checksum, crc);
+}
+
+bool comparePacketHeader(const ChppTransportHeader &rx,
+                         const ChppTransportHeader &expected) {
+  EXPECT_EQ(rx.flags, expected.flags);
+  EXPECT_EQ(rx.packetCode, expected.packetCode);
+  EXPECT_EQ(rx.ackSeq, expected.ackSeq);
+  EXPECT_EQ(rx.seq, expected.seq);
+  EXPECT_EQ(rx.length, expected.length);
+  EXPECT_EQ(rx.reserved, 0u);
+  return (memcmp(&rx, &expected, sizeof(rx)) == 0);
+}
+
+bool comparePacket(const std::vector<uint8_t> &received,
+                   const ChppEmptyPacket &expected) {
+  EXPECT_EQ(received.size(), sizeof(expected));
+  if (received.size() == sizeof(expected)) {
+    const auto *rx = reinterpret_cast<const ChppEmptyPacket *>(received.data());
+    EXPECT_EQ(rx->preamble, expected.preamble);
+    comparePacketHeader(rx->header, expected.header);
+    EXPECT_EQ(rx->footer.checksum, expected.footer.checksum);
+  }
+  return (received.size() == sizeof(expected) &&
+          memcmp(received.data(), &expected, sizeof(expected)) == 0);
+}
+
+bool comparePacket(const std::vector<uint8_t> &received,
+                   const ChppResetPacket &expected) {
+  EXPECT_EQ(received.size(), sizeof(expected));
+  if (received.size() == sizeof(expected)) {
+    const auto *rx = reinterpret_cast<const ChppResetPacket *>(received.data());
+    EXPECT_EQ(rx->preamble, expected.preamble);
+    comparePacketHeader(rx->header, expected.header);
+    EXPECT_EQ(rx->config.version.major, expected.config.version.major);
+    EXPECT_EQ(rx->config.version.minor, expected.config.version.minor);
+    EXPECT_EQ(rx->config.version.patch, expected.config.version.patch);
+    EXPECT_EQ(rx->footer.checksum, expected.footer.checksum);
+  }
+  return (received.size() == sizeof(expected) &&
+          memcmp(received.data(), &expected, sizeof(expected)) == 0);
+}
+
+}  // namespace chpp::test
\ No newline at end of file
diff --git a/chpp/test/packet_util.h b/chpp/test/packet_util.h
new file mode 100644
index 0000000..faedd08
--- /dev/null
+++ b/chpp/test/packet_util.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+/**
+ * @file Utilities for working with raw CHPP packets in a test setting
+ */
+
+#include <cinttypes>
+#include <iostream>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "chpp/crc.h"
+#include "chpp/transport.h"
+
+namespace chpp::test {
+
+// Note: the preamble is actually sent in the reverse byte order one might
+// expect (0x68 'h', 0x43 'C'); the simplification below assumes little endian
+constexpr uint16_t kPreamble =
+    (CHPP_PREAMBLE_BYTE_SECOND << 8) | CHPP_PREAMBLE_BYTE_FIRST;
+
+struct ChppEmptyPacket {
+  uint16_t preamble;
+  ChppTransportHeader header;
+  ChppTransportFooter footer;
+} CHPP_PACKED_ATTR;
+
+struct ChppResetPacket {
+  uint16_t preamble;
+  ChppTransportHeader header;
+  ChppTransportConfiguration config;
+  ChppTransportFooter footer;
+} CHPP_PACKED_ATTR;
+
+struct ChppPacketPrefix {
+  uint16_t preamble;
+  ChppTransportHeader header;
+  uint8_t payload[1];  // Variable size per header.length
+} CHPP_PACKED_ATTR;
+
+// Utilities for packet creation -----------------------------------------------
+
+//! Computes the CRC of one of the complete packet types defined above
+template <typename PktType>
+uint32_t computeCrc(const PktType &pkt) {
+  return chppCrc32(0, reinterpret_cast<const uint8_t *>(&pkt.header),
+                   sizeof(pkt) - sizeof(pkt.preamble) - sizeof(pkt.footer));
+}
+
+ChppResetPacket generateResetPacket(uint8_t ackSeq = 0, uint8_t seq = 0);
+ChppResetPacket generateResetAckPacket(uint8_t ackSeq = 1, uint8_t seq = 0);
+ChppEmptyPacket generateEmptyPacket(uint8_t ackSeq = 1, uint8_t seq = 0,
+                                    uint8_t error = CHPP_TRANSPORT_ERROR_NONE);
+
+//! Create an empty ACK packet for the given packet
+ChppEmptyPacket generateAck(std::vector<uint8_t> &pkt);
+
+// Utilities for packet parsing ------------------------------------------------
+
+inline ChppEmptyPacket &asEmptyPacket(std::vector<uint8_t> &pkt) {
+  EXPECT_EQ(pkt.size(), sizeof(ChppEmptyPacket));
+  return *reinterpret_cast<ChppEmptyPacket *>(pkt.data());
+}
+
+inline ChppResetPacket &asResetPacket(std::vector<uint8_t> &pkt) {
+  EXPECT_EQ(pkt.size(), sizeof(ChppResetPacket));
+  return *reinterpret_cast<ChppResetPacket *>(pkt.data());
+}
+
+inline ChppPacketPrefix &asChpp(std::vector<uint8_t> &pkt) {
+  EXPECT_GE(pkt.size(), sizeof(ChppEmptyPacket));
+  return *reinterpret_cast<ChppPacketPrefix *>(pkt.data());
+}
+
+inline ChppTransportHeader &getHeader(std::vector<uint8_t> &pkt) {
+  static_assert(CHPP_PREAMBLE_LEN_BYTES == sizeof(uint16_t));
+  EXPECT_GE(pkt.size(), sizeof(uint16_t) + sizeof(ChppTransportHeader));
+  return *reinterpret_cast<ChppTransportHeader *>(&pkt[sizeof(uint16_t)]);
+}
+
+// Utilities for debugging -----------------------------------------------------
+
+//! Tuned for outputting a raw binary buffer (e.g. payload or full packet)
+void dumpRaw(std::ostream &os, const void *ptr, size_t len);
+
+void dumpPreamble(std::ostream &os, uint16_t preamble);
+void dumpHeader(std::ostream &os, const ChppTransportHeader &hdr);
+void dumpConfig(std::ostream &os, const ChppTransportConfiguration &cfg);
+
+template <typename PktType>
+void dumpFooter(std::ostream &os, const PktType &pkt) {
+  os << "CRC: 0x" << std::hex << pkt.footer.checksum;
+  uint32_t computed = computeCrc(pkt);
+  if (pkt.footer.checksum != computed) {
+    os << " (invalid, expected " << computed << ")";
+  } else {
+    os << " (ok)";
+  }
+  os << std::endl;
+}
+
+void dumpEmptyPacket(std::ostream &os, const ChppEmptyPacket &pkt);
+void dumpResetPacket(std::ostream &os, const ChppResetPacket &pkt);
+void dumpPacket(std::ostream &os, const ChppPacketPrefix &pkt);
+
+std::ostream &operator<<(std::ostream &os, const ChppEmptyPacket &pkt);
+std::ostream &operator<<(std::ostream &os, const ChppResetPacket &pkt);
+std::ostream &operator<<(std::ostream &os, const ChppPacketPrefix &pkt);
+
+// Utilities for gtest packet checking -----------------------------------------
+
+//! Confirms that the supplied packet has a valid preamble, CRC, length, etc.,
+//! raising a gtest failure (via EXPECT_*) if not
+void checkPacketValidity(std::vector<uint8_t> &received);
+
+// These return true if the packets are the same, false otherwise
+
+bool comparePacketHeader(const ChppTransportHeader &rx,
+                         const ChppTransportHeader &expected);
+
+bool comparePacket(const std::vector<uint8_t> &received,
+                   const ChppEmptyPacket &expected);
+bool comparePacket(const std::vector<uint8_t> &received,
+                   const ChppResetPacket &expected);
+
+}  // namespace chpp::test
\ No newline at end of file
diff --git a/chpp/test/transport_test.cpp b/chpp/test/transport_test.cpp
index 625cfde..2ef0bed 100644
--- a/chpp/test/transport_test.cpp
+++ b/chpp/test/transport_test.cpp
@@ -22,6 +22,7 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
+#include <chrono>
 #include <thread>
 
 #include "chpp/app.h"
@@ -35,6 +36,7 @@
 #include "chpp/crc.h"
 #include "chpp/macros.h"
 #include "chpp/memory.h"
+#include "chpp/platform/platform_link.h"
 #include "chpp/platform/utils.h"
 #include "chpp/services/discovery.h"
 #include "chpp/services/loopback.h"
@@ -50,9 +52,8 @@
 // Max size of payload sent to chppRxDataCb (bytes)
 constexpr size_t kMaxChunkSize = 20000;
 
-constexpr size_t kMaxPacketSize = kMaxChunkSize + CHPP_PREAMBLE_LEN_BYTES +
-                                  sizeof(ChppTransportHeader) +
-                                  sizeof(ChppTransportFooter);
+constexpr size_t kMaxPacketSize =
+    kMaxChunkSize + CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES;
 
 // Input sizes to test the entire range of sizes with a few tests
 constexpr int kChunkSizes[] = {0, 1, 2, 3, 4, 21, 100, 1000, 10001, 20000};
@@ -60,6 +61,9 @@
 // Number of services
 constexpr int kServiceCount = 3;
 
+// State of the link layer.
+struct ChppLinuxLinkState gChppLinuxLinkContext;
+
 /*
  * Test suite for the CHPP Transport Layer
  */
@@ -67,11 +71,13 @@
  protected:
   void SetUp() override {
     chppClearTotalAllocBytes();
-    memset(&mTransportContext.linkParams, 0,
-           sizeof(mTransportContext.linkParams));
-    mTransportContext.linkParams.linkEstablished = true;
-    mTransportContext.linkParams.isLinkActive = true;
-    chppTransportInit(&mTransportContext, &mAppContext);
+    memset(&gChppLinuxLinkContext, 0, sizeof(struct ChppLinuxLinkState));
+    gChppLinuxLinkContext.manualSendCycle = true;
+    gChppLinuxLinkContext.linkEstablished = true;
+    gChppLinuxLinkContext.isLinkActive = true;
+    const struct ChppLinkApi *linkApi = getLinuxLinkApi();
+    chppTransportInit(&mTransportContext, &mAppContext, &gChppLinuxLinkContext,
+                      linkApi);
     chppAppInit(&mAppContext, &mTransportContext);
 
     mTransportContext.resetState = CHPP_RESET_STATE_NONE;
@@ -97,26 +103,12 @@
 /**
  * Wait for chppTransportDoWork() to finish after it is notified by
  * chppEnqueueTxPacket to run.
- *
- * TODO: (b/177616847) Improve test robustness / synchronization without adding
- * overhead to CHPP
  */
 void WaitForTransport(struct ChppTransportState *transportContext) {
-  // Wait for linkParams.notifier.signal to be triggered and processed
-  volatile uint32_t k = 1;
-  while (transportContext->linkParams.notifier.signal == 0 && k > 0) {
-    k++;
-  }
-  while (transportContext->linkParams.notifier.signal != 0 && k > 0) {
-    k++;
-  }
-  ASSERT_FALSE(k == 0);
-  while (k < UINT16_MAX) {
-    k++;
-  }
-  while (k > 0) {
-    k--;
-  }
+  // Start sending data out.
+  cycleSendThread();
+  // Wait for data to be received and processed.
+  std::this_thread::sleep_for(std::chrono::milliseconds(20));
 
   // Should have reset loc and length for next packet / datagram
   EXPECT_EQ(transportContext->rxStatus.locInDatagram, 0);
@@ -299,15 +291,15 @@
   WaitForTransport(transportContext);
 
   // Validate common response fields
-  EXPECT_EQ(validateChppTestResponse(transportContext->pendingTxPacket.payload,
-                                     nextSeq, handle, transactionID),
+  EXPECT_EQ(validateChppTestResponse(gChppLinuxLinkContext.buf, nextSeq, handle,
+                                     transactionID),
             CHPP_APP_ERROR_NONE);
 
   // Check response length
   EXPECT_EQ(sizeof(ChppTestResponse), CHPP_PREAMBLE_LEN_BYTES +
                                           sizeof(ChppTransportHeader) +
                                           sizeof(ChppAppHeader));
-  EXPECT_EQ(transportContext->pendingTxPacket.length,
+  EXPECT_EQ(transportContext->linkBufferSize,
             sizeof(ChppTestResponse) + sizeof(ChppTransportFooter));
 }
 
@@ -352,8 +344,8 @@
   WaitForTransport(transportContext);
 
   // Validate common response fields
-  EXPECT_EQ(validateChppTestResponse(transportContext->pendingTxPacket.payload,
-                                     nextSeq, handle, transactionID),
+  EXPECT_EQ(validateChppTestResponse(gChppLinuxLinkContext.buf, nextSeq, handle,
+                                     transactionID),
             CHPP_APP_ERROR_NONE);
 }
 
@@ -398,7 +390,7 @@
 TEST_P(TransportTests, RxPayloadOfZeros) {
   mTransportContext.rxStatus.state = CHPP_STATE_PREAMBLE;
   size_t len = static_cast<size_t>(GetParam());
-  bool validLen = (len <= CHPP_TRANSPORT_RX_MTU_BYTES);
+  bool isLenValid = (len <= chppTransportRxMtuSize(&mTransportContext));
 
   mTransportContext.txStatus.hasPacketsToSend = true;
   std::thread t1(chppWorkThreadStart, &mTransportContext);
@@ -418,9 +410,9 @@
     EXPECT_EQ(
         chppRxDataCb(&mTransportContext, mBuf,
                      CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader)),
-        !validLen);
+        !isLenValid);
 
-    if (!validLen) {
+    if (!isLenValid) {
       EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PREAMBLE);
     } else if (len > 0) {
       EXPECT_EQ(mTransportContext.rxStatus.state, CHPP_STATE_PAYLOAD);
@@ -431,7 +423,7 @@
     // Correct decoding of packet length
     EXPECT_EQ(mTransportContext.rxHeader.length, len);
     EXPECT_EQ(mTransportContext.rxStatus.locInDatagram, 0);
-    EXPECT_EQ(mTransportContext.rxDatagram.length, validLen ? len : 0);
+    EXPECT_EQ(mTransportContext.rxDatagram.length, isLenValid ? len : 0);
 
     // Send payload if any and check for correct state
     if (len > 0) {
@@ -440,13 +432,13 @@
               &mTransportContext,
               &mBuf[CHPP_PREAMBLE_LEN_BYTES + sizeof(ChppTransportHeader)],
               len),
-          !validLen);
+          !isLenValid);
       EXPECT_EQ(mTransportContext.rxStatus.state,
-                validLen ? CHPP_STATE_FOOTER : CHPP_STATE_PREAMBLE);
+                isLenValid ? CHPP_STATE_FOOTER : CHPP_STATE_PREAMBLE);
     }
 
     // Should have complete packet payload by now
-    EXPECT_EQ(mTransportContext.rxStatus.locInDatagram, validLen ? len : 0);
+    EXPECT_EQ(mTransportContext.rxStatus.locInDatagram, isLenValid ? len : 0);
 
     // But no ACK yet
     EXPECT_EQ(mTransportContext.rxStatus.expectedSeq, transHeader->seq);
@@ -459,16 +451,12 @@
 
     // The next expected packet sequence # should incremented only if the
     // received packet is payload-bearing.
-    uint8_t nextSeq = transHeader->seq + ((validLen && len > 0) ? 1 : 0);
+    uint8_t nextSeq = transHeader->seq + ((isLenValid && len > 0) ? 1 : 0);
     EXPECT_EQ(mTransportContext.rxStatus.expectedSeq, nextSeq);
 
     // Check for correct ACK crafting if applicable (i.e. if the received packet
     // is payload-bearing).
-    if (validLen && len > 0) {
-      // TODO: Remove later as can cause flaky tests
-      // These are expected to change shortly afterwards, as chppTransportDoWork
-      // is run
-      // EXPECT_TRUE(mTransportContext.txStatus.hasPacketsToSend);
+    if (isLenValid && len > 0) {
       EXPECT_EQ(mTransportContext.txStatus.packetCodeToSend,
                 CHPP_TRANSPORT_ERROR_NONE);
       EXPECT_EQ(mTransportContext.txDatagramQueue.pending, 0);
@@ -477,15 +465,15 @@
 
       // Check response packet fields
       struct ChppTransportHeader *txHeader =
-          (struct ChppTransportHeader *)&mTransportContext.pendingTxPacket
-              .payload[CHPP_PREAMBLE_LEN_BYTES];
+          (struct ChppTransportHeader *)&gChppLinuxLinkContext
+              .buf[CHPP_PREAMBLE_LEN_BYTES];
       EXPECT_EQ(txHeader->flags, CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM);
       EXPECT_EQ(txHeader->packetCode, CHPP_TRANSPORT_ERROR_NONE);
       EXPECT_EQ(txHeader->ackSeq, nextSeq);
       EXPECT_EQ(txHeader->length, 0);
 
       // Check outgoing packet length
-      EXPECT_EQ(mTransportContext.pendingTxPacket.length,
+      EXPECT_EQ(mTransportContext.linkBufferSize,
                 CHPP_PREAMBLE_LEN_BYTES + sizeof(struct ChppTransportHeader) +
                     sizeof(struct ChppTransportFooter));
     }
@@ -862,8 +850,7 @@
   size_t responseLoc = sizeof(ChppTestResponse);
 
   // Validate capabilities
-  uint32_t *capabilities =
-      (uint32_t *)&mTransportContext.pendingTxPacket.payload[responseLoc];
+  uint32_t *capabilities = (uint32_t *)&gChppLinuxLinkContext.buf[responseLoc];
   responseLoc += sizeof(uint32_t);
 
   // Cleanup
@@ -905,8 +892,7 @@
   t1.join();
 
   // Validate capabilities
-  uint32_t *capabilities =
-      (uint32_t *)&mTransportContext.pendingTxPacket.payload[responseLoc];
+  uint32_t *capabilities = (uint32_t *)&gChppLinuxLinkContext.buf[responseLoc];
   responseLoc += sizeof(uint32_t);
 
   uint32_t capabilitySet = CHRE_WIFI_CAPABILITIES_SCAN_MONITORING |
@@ -951,8 +937,7 @@
   t1.join();
 
   // Validate capabilities
-  uint32_t *capabilities =
-      (uint32_t *)&mTransportContext.pendingTxPacket.payload[responseLoc];
+  uint32_t *capabilities = (uint32_t *)&gChppLinuxLinkContext.buf[responseLoc];
   responseLoc += sizeof(uint32_t);
 
   uint32_t capabilitySet =
diff --git a/chpp/transport.c b/chpp/transport.c
index 1e3f7ea..14001cf 100644
--- a/chpp/transport.c
+++ b/chpp/transport.c
@@ -72,11 +72,11 @@
 static struct ChppTransportHeader *chppAddHeader(
     struct ChppTransportState *context);
 static void chppAddPayload(struct ChppTransportState *context);
-static void chppAddFooter(struct PendingTxPacket *packet);
+static void chppAddFooter(struct ChppTransportState *context);
 size_t chppDequeueTxDatagram(struct ChppTransportState *context);
 static void chppClearTxDatagramQueue(struct ChppTransportState *context);
 static void chppTransportDoWork(struct ChppTransportState *context);
-static void chppAppendToPendingTxPacket(struct PendingTxPacket *packet,
+static void chppAppendToPendingTxPacket(struct ChppTransportState *context,
                                         const uint8_t *buf, size_t len);
 static const char *chppGetPacketAttrStr(uint8_t packetCode);
 static bool chppEnqueueTxDatagram(struct ChppTransportState *context,
@@ -102,7 +102,7 @@
  * counter among that state (rxStatus.locInState) is also reset at the same
  * time.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param newState Next Rx state.
  */
 static void chppSetRxState(struct ChppTransportState *context,
@@ -122,7 +122,7 @@
  * Any future backwards-incompatible versions of CHPP Transport will use a
  * different preamble.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Input data.
  * @param len Length of input data in bytes.
  *
@@ -169,7 +169,7 @@
  * stream.
  * Moves the Rx state to CHPP_STATE_PAYLOAD afterwards.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Input data.
  * @param len Length of input data in bytes.
  *
@@ -235,7 +235,7 @@
  * by the header, from the incoming data stream.
  * Moves the Rx state to CHPP_STATE_FOOTER afterwards.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Input data
  * @param len Length of input data in bytes
  *
@@ -264,7 +264,7 @@
  * stream. Checks checksum, triggering the correct response (ACK / NACK).
  * Moves the Rx state to CHPP_STATE_PREAMBLE afterwards.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Input data.
  * @param len Length of input data in bytes.
  *
@@ -361,7 +361,7 @@
  * Discards of an incomplete Rx packet during receive (e.g. due to a timeout or
  * bad checksum).
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppAbortRxPacket(struct ChppTransportState *context) {
   size_t undoLen = 0;
@@ -422,7 +422,7 @@
 /**
  * Processes a request that is determined to be for a transport-layer loopback.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 #ifdef CHPP_SERVICE_ENABLED_TRANSPORT_LOOPBACK
 static void chppProcessTransportLoopbackRequest(
@@ -431,35 +431,34 @@
     CHPP_LOGE("Link busy; trans-loopback dropped");
 
   } else {
+    uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
     context->txStatus.linkBusy = true;
-    context->pendingTxPacket.length = 0;
-    context->pendingTxPacket.length +=
-        chppAddPreamble(&context->pendingTxPacket.payload[0]);
+    context->linkBufferSize = 0;
+    context->linkBufferSize += chppAddPreamble(&linkTxBuffer[0]);
 
     struct ChppTransportHeader *txHeader =
-        (struct ChppTransportHeader *)&context->pendingTxPacket
-            .payload[context->pendingTxPacket.length];
-    context->pendingTxPacket.length += sizeof(*txHeader);
+        (struct ChppTransportHeader *)&linkTxBuffer[context->linkBufferSize];
+    context->linkBufferSize += sizeof(*txHeader);
 
     *txHeader = context->rxHeader;
     txHeader->packetCode = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
         CHPP_TRANSPORT_ATTR_LOOPBACK_RESPONSE, txHeader->packetCode);
 
     size_t payloadLen =
-        MIN(context->rxDatagram.length, CHPP_TRANSPORT_TX_MTU_BYTES);
-    chppAppendToPendingTxPacket(&context->pendingTxPacket,
-                                context->rxDatagram.payload, payloadLen);
+        MIN(context->rxDatagram.length, chppTransportTxMtuSize(context));
+    chppAppendToPendingTxPacket(context, context->rxDatagram.payload,
+                                payloadLen);
     CHPP_FREE_AND_NULLIFY(context->rxDatagram.payload);
     chppClearRxDatagram(context);
 
-    chppAddFooter(&context->pendingTxPacket);
+    chppAddFooter(context);
 
-    CHPP_LOGI("Trans-looping back len=%" PRIu16 " RX len=%" PRIuSIZE,
+    CHPP_LOGD("Trans-looping back len=%" PRIu16 " RX len=%" PRIuSIZE,
               txHeader->length, context->rxDatagram.length);
     enum ChppLinkErrorCode error = chppSendPendingPacket(context);
 
     if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
-      chppLinkSendDoneCb(&context->linkParams, error);
+      chppLinkSendDoneCb(context, error);
     }
   }
 }
@@ -468,7 +467,7 @@
 /**
  * Processes a response that is determined to be for a transport-layer loopback.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 #ifdef CHPP_CLIENT_ENABLED_TRANSPORT_LOOPBACK
 static void chppProcessTransportLoopbackResponse(
@@ -505,7 +504,7 @@
 /**
  * Method to invoke when the reset sequence is completed.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppSetResetComplete(struct ChppTransportState *context) {
   context->resetState = CHPP_RESET_STATE_NONE;
@@ -517,7 +516,7 @@
  * An incoming reset-ack packet indicates that a reset is complete at the other
  * end of the CHPP link.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppProcessResetAck(struct ChppTransportState *context) {
   if (context->resetState == CHPP_RESET_STATE_NONE) {
@@ -549,11 +548,7 @@
   chppClearRxDatagram(context);
 
 #ifdef CHPP_CLIENT_ENABLED_DISCOVERY
-  if (!context->appContext->isDiscoveryComplete) {
-    chppMutexUnlock(&context->mutex);
-    chppInitiateDiscovery(context->appContext);
-    chppMutexLock(&context->mutex);
-  } else {
+  if (context->appContext->isDiscoveryComplete) {
     chppEnqueueTxPacket(context, CHPP_TRANSPORT_ERROR_NONE);
   }
 #else
@@ -569,7 +564,7 @@
 /**
  * Process a received, checksum-validated packet.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppProcessRxPacket(struct ChppTransportState *context) {
   uint64_t now = chppGetCurrentTimeNs();
@@ -603,6 +598,12 @@
   } else if (context->rxHeader.length > 0) {
     // Process payload and send ACK
     chppProcessRxPayload(context);
+  } else if (!context->txStatus.hasPacketsToSend) {
+    // Nothing to send and nothing to receive, i.e. this is an ACK before an
+    // indefinite period of inactivity. Kick the work thread so it recalculates
+    // the notifier timeout.
+    chppNotifierSignal(&context->notifier,
+                       CHPP_TRANSPORT_SIGNAL_RECALC_TIMEOUT);
   }
 }
 
@@ -610,7 +611,7 @@
  * Process the payload of a validated payload-bearing packet and send out the
  * ACK.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppProcessRxPayload(struct ChppTransportState *context) {
   context->rxStatus.expectedSeq++;  // chppProcessRxPacket() already confirms
@@ -656,7 +657,7 @@
  * layer to inform the transport layer using chppDatagramProcessDoneCb() once it
  * is done with the buffer so it is freed.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppClearRxDatagram(struct ChppTransportState *context) {
   context->rxStatus.locInDatagram = 0;
@@ -667,7 +668,7 @@
 /**
  * Validates the checksum of an incoming packet.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  *
  * @return True if and only if the checksum is correct.
  */
@@ -695,7 +696,7 @@
  * Performs consistency checks on received packet header to determine if it is
  * obviously corrupt / invalid / duplicate / out-of-order.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  *
  * @return True if and only if header passes checks
  */
@@ -703,7 +704,7 @@
     const struct ChppTransportState *context) {
   enum ChppTransportErrorCode result = CHPP_TRANSPORT_ERROR_NONE;
 
-  if (context->rxHeader.length > CHPP_TRANSPORT_RX_MTU_BYTES) {
+  if (context->rxHeader.length > chppTransportRxMtuSize(context)) {
     result = CHPP_TRANSPORT_ERROR_HEADER;
   }
 
@@ -721,7 +722,7 @@
  * Registers a received ACK. If an outgoing datagram is fully ACKed, it is
  * popped from the TX queue.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppRegisterRxAck(struct ChppTransportState *context) {
   uint8_t rxAckSeq = context->rxHeader.ackSeq;
@@ -751,7 +752,7 @@
       context->txStatus.txAttempts = 0;
 
       // Process and if necessary pop from Tx datagram queue
-      context->txStatus.ackedLocInDatagram += CHPP_TRANSPORT_TX_MTU_BYTES;
+      context->txStatus.ackedLocInDatagram += chppTransportTxMtuSize(context);
       if (context->txStatus.ackedLocInDatagram >=
           context->txDatagramQueue.datagram[context->txDatagramQueue.front]
               .length) {
@@ -778,7 +779,7 @@
  * CHPP_TRANSPORT_ERROR_NONE indicates that no error was reported (i.e. either
  * an ACK or an implicit NACK)
  *
- * Note that the decision as to wheather to include a payload will be taken
+ * Note that the decision as to whether to include a payload will be taken
  * later, i.e. before the packet is being sent out from the queue. A payload is
  * expected to be included if there is one or more pending Tx datagrams and we
  * are not waiting on a pending ACK. A (repeat) payload is also included if we
@@ -788,7 +789,7 @@
  * would only need to send an ACK for the last (correct) packet, hence we only
  * need a queue length of one here.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param packetCode Error code and packet attributes to be sent.
  */
 static void chppEnqueueTxPacket(struct ChppTransportState *context,
@@ -817,18 +818,18 @@
 }
 
 /**
- * Adds the packet header to pendingTxPacket.
+ * Adds the packet header to link tx buffer.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  *
  * @return Pointer to the added packet header.
  */
 static struct ChppTransportHeader *chppAddHeader(
     struct ChppTransportState *context) {
+  uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
   struct ChppTransportHeader *txHeader =
-      (struct ChppTransportHeader *)&context->pendingTxPacket
-          .payload[context->pendingTxPacket.length];
-  context->pendingTxPacket.length += sizeof(*txHeader);
+      (struct ChppTransportHeader *)&linkTxBuffer[context->linkBufferSize];
+  context->linkBufferSize += sizeof(*txHeader);
 
   txHeader->packetCode = context->txStatus.packetCodeToSend;
   context->txStatus.packetCodeToSend = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
@@ -841,14 +842,14 @@
 }
 
 /**
- * Adds the packet payload to pendingTxPacket.
+ * Adds the packet payload to link tx buffer.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppAddPayload(struct ChppTransportState *context) {
+  uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
   struct ChppTransportHeader *txHeader =
-      (struct ChppTransportHeader *)&context->pendingTxPacket
-          .payload[CHPP_PREAMBLE_LEN_BYTES];
+      (struct ChppTransportHeader *)&linkTxBuffer[CHPP_PREAMBLE_LEN_BYTES];
 
   size_t remainingBytes =
       context->txDatagramQueue.datagram[context->txDatagramQueue.front].length -
@@ -858,10 +859,10 @@
             " of pending datagrams=%" PRIu8,
             txHeader->seq, remainingBytes, context->txDatagramQueue.pending);
 
-  if (remainingBytes > CHPP_TRANSPORT_TX_MTU_BYTES) {
+  if (remainingBytes > chppTransportTxMtuSize(context)) {
     // Send an unfinished part of a datagram
     txHeader->flags = CHPP_TRANSPORT_FLAG_UNFINISHED_DATAGRAM;
-    txHeader->length = CHPP_TRANSPORT_TX_MTU_BYTES;
+    txHeader->length = (uint16_t)chppTransportTxMtuSize(context);
   } else {
     // Send final (or only) part of a datagram
     txHeader->flags = CHPP_TRANSPORT_FLAG_FINISHED_DATAGRAM;
@@ -870,7 +871,7 @@
 
   // Copy payload
   chppAppendToPendingTxPacket(
-      &context->pendingTxPacket,
+      context,
       context->txDatagramQueue.datagram[context->txDatagramQueue.front]
               .payload +
           context->txStatus.ackedLocInDatagram,
@@ -883,26 +884,29 @@
 /**
  * Adds a footer (containing the checksum) to a packet.
  *
- * @param packet The packet from which to calculate the checksum and append the
- * footer.
+ * @param context Maintains state for each transport layer instance.
  */
-static void chppAddFooter(struct PendingTxPacket *packet) {
+static void chppAddFooter(struct ChppTransportState *context) {
   struct ChppTransportFooter footer;
-  footer.checksum = chppCrc32(0, &packet->payload[CHPP_PREAMBLE_LEN_BYTES],
-                              packet->length - CHPP_PREAMBLE_LEN_BYTES);
+  uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
+  size_t bufferSize = context->linkBufferSize;
+
+  footer.checksum = chppCrc32(0, &linkTxBuffer[CHPP_PREAMBLE_LEN_BYTES],
+                              bufferSize - CHPP_PREAMBLE_LEN_BYTES);
 
   CHPP_LOGD("Adding transport footer. Checksum=0x%" PRIx32 ", len: %" PRIuSIZE
             " -> %" PRIuSIZE,
-            footer.checksum, packet->length, packet->length + sizeof(footer));
+            footer.checksum, bufferSize, bufferSize + sizeof(footer));
 
-  chppAppendToPendingTxPacket(packet, (const uint8_t *)&footer, sizeof(footer));
+  chppAppendToPendingTxPacket(context, (const uint8_t *)&footer,
+                              sizeof(footer));
 }
 
 /**
  * Dequeues the datagram at the front of the datagram tx queue, if any, and
  * frees the payload. Returns the number of remaining datagrams in the queue.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @return Number of remaining datagrams in queue.
  */
 size_t chppDequeueTxDatagram(struct ChppTransportState *context) {
@@ -935,7 +939,7 @@
 /**
  * Flushes the Tx datagram queue of any pending packets.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppClearTxDatagramQueue(struct ChppTransportState *context) {
   while (context->txDatagramQueue.pending > 0) {
@@ -955,12 +959,11 @@
  * Repeat payload: If we haven't received an ACK yet for our previous payload,
  * i.e. we have registered an explicit or implicit NACK.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppTransportDoWork(struct ChppTransportState *context) {
   bool havePacketForLinkLayer = false;
   struct ChppTransportHeader *txHeader;
-  struct ChppAppHeader *timeoutResponse = NULL;
 
   // Note: For a future ACK window >1, there needs to be a loop outside the lock
   chppMutexLock(&context->mutex);
@@ -970,12 +973,14 @@
     havePacketForLinkLayer = true;
     context->txStatus.linkBusy = true;
 
-    context->pendingTxPacket.length = 0;
-    memset(&context->pendingTxPacket.payload, 0, CHPP_LINK_TX_MTU_BYTES);
+    context->linkBufferSize = 0;
+    uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
+    const struct ChppLinkConfiguration linkConfig =
+        context->linkApi->getConfig(context->linkContext);
+    memset(linkTxBuffer, 0, linkConfig.txBufferLen);
 
     // Add preamble
-    context->pendingTxPacket.length +=
-        chppAddPreamble(&context->pendingTxPacket.payload[0]);
+    context->linkBufferSize += chppAddPreamble(linkTxBuffer);
 
     // Add header
     txHeader = chppAddHeader(context);
@@ -1011,7 +1016,7 @@
       context->txStatus.hasPacketsToSend = false;
     }
 
-    chppAddFooter(&context->pendingTxPacket);
+    chppAddFooter(context);
 
   } else {
     CHPP_LOGW(
@@ -1028,44 +1033,53 @@
     CHPP_LOGD("TX->Link: len=%" PRIuSIZE " flags=0x%" PRIx8 " code=0x%" PRIx8
               " ackSeq=%" PRIu8 " seq=%" PRIu8 " payloadLen=%" PRIu16
               " pending=%" PRIu8,
-              context->pendingTxPacket.length, txHeader->flags,
-              txHeader->packetCode, txHeader->ackSeq, txHeader->seq,
-              txHeader->length, context->txDatagramQueue.pending);
+              context->linkBufferSize, txHeader->flags, txHeader->packetCode,
+              txHeader->ackSeq, txHeader->seq, txHeader->length,
+              context->txDatagramQueue.pending);
     enum ChppLinkErrorCode error = chppSendPendingPacket(context);
 
     if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
       // Platform implementation for platformLinkSend() is synchronous or an
       // error occurred. In either case, we should call chppLinkSendDoneCb()
-      // here to release the contents of pendingTxPacket.
-      chppLinkSendDoneCb(&context->linkParams, error);
+      // here to release the contents of tx link buffer.
+      chppLinkSendDoneCb(context, error);
     }
   }
 
 #ifdef CHPP_CLIENT_ENABLED
-  timeoutResponse = chppTransportGetClientRequestTimeoutResponse(context);
-#endif
-  if (timeoutResponse != NULL) {
-    CHPP_LOGE("Response timeout H#%" PRIu8 " cmd=%" PRIu16 " ID=%" PRIu8,
-              timeoutResponse->handle, timeoutResponse->command,
-              timeoutResponse->transaction);
-    chppAppProcessRxDatagram(context->appContext, (uint8_t *)timeoutResponse,
-                             sizeof(struct ChppAppHeader));
+  {  // create a scope to declare timeoutResponse (C89).
+    struct ChppAppHeader *timeoutResponse =
+        chppTransportGetClientRequestTimeoutResponse(context);
+
+    if (timeoutResponse != NULL) {
+      CHPP_LOGE("Response timeout H#%" PRIu8 " cmd=%" PRIu16 " ID=%" PRIu8,
+                timeoutResponse->handle, timeoutResponse->command,
+                timeoutResponse->transaction);
+      chppAppProcessRxDatagram(context->appContext, (uint8_t *)timeoutResponse,
+                               sizeof(struct ChppAppHeader));
+    }
   }
+#endif  // CHPP_CLIENT_ENABLED
 }
 
 /**
- * Appends data from a buffer of length len to a PendingTxPacket, updating its
+ * Appends data from a buffer of length len to a link tx buffer, updating its
  * length.
  *
- * @param packet The PendingTxBuffer to be appended to.
+ * @param context Maintains state for each transport layer instance.
  * @param buf Input data to be copied from.
  * @param len Length of input data in bytes.
  */
-static void chppAppendToPendingTxPacket(struct PendingTxPacket *packet,
+static void chppAppendToPendingTxPacket(struct ChppTransportState *context,
                                         const uint8_t *buf, size_t len) {
-  CHPP_ASSERT(packet->length + len <= sizeof(packet->payload));
-  memcpy(&packet->payload[packet->length], buf, len);
-  packet->length += len;
+  uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
+
+  size_t bufferSize = context->linkBufferSize;
+
+  CHPP_ASSERT(bufferSize + len <=
+              context->linkApi->getConfig(context->linkContext).txBufferLen);
+  memcpy(&linkTxBuffer[bufferSize], buf, len);
+  context->linkBufferSize += len;
 }
 
 /**
@@ -1095,7 +1109,7 @@
  * If enqueueing is unsuccessful, it is up to the caller to decide when or if
  * to free the payload and/or resend it later.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @param packetCode Error code and packet attributes to be sent.
  * @param buf Datagram payload allocated through chppMalloc. Cannot be null.
  * @param len Datagram length in bytes.
@@ -1113,13 +1127,13 @@
   } else {
     if ((len < sizeof(struct ChppAppHeader)) ||
         (CHPP_TRANSPORT_GET_ATTR(packetCode) != 0)) {
-      CHPP_LOGI("Enqueue TX: code=0x%" PRIx8 "%s len=%" PRIuSIZE
+      CHPP_LOGD("Enqueue TX: code=0x%" PRIx8 "%s len=%" PRIuSIZE
                 " pending=%" PRIu8,
                 packetCode, chppGetPacketAttrStr(packetCode), len,
                 (uint8_t)(context->txDatagramQueue.pending + 1));
     } else {
       struct ChppAppHeader *header = buf;
-      CHPP_LOGI(
+      CHPP_LOGD(
           "Enqueue TX: len=%" PRIuSIZE " H#%" PRIu8 " type=0x%" PRIx8
           " ID=%" PRIu8 " err=%" PRIu8 " cmd=0x%" PRIx16 " pending=%" PRIu8,
           len, header->handle, header->type, header->transaction, header->error,
@@ -1154,18 +1168,17 @@
 }
 
 /**
- * Sends the pending outgoing packet (context->pendingTxPacket) over to the link
- * layer using chppPlatformLinkSend() and updates the last Tx packet time.
+ * Sends the pending outgoing packet over to the link
+ * layer using Send() and updates the last Tx packet time.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  *
- * @return Result of chppPlatformLinkSend().
+ * @return Result of Send().
  */
 enum ChppLinkErrorCode chppSendPendingPacket(
     struct ChppTransportState *context) {
-  enum ChppLinkErrorCode error = chppPlatformLinkSend(
-      &context->linkParams, context->pendingTxPacket.payload,
-      context->pendingTxPacket.length);
+  enum ChppLinkErrorCode error =
+      context->linkApi->send(context->linkContext, context->linkBufferSize);
 
   context->txStatus.lastTxTimeNs = chppGetCurrentTimeNs();
 
@@ -1175,7 +1188,7 @@
 /**
  * Resets the transport state, maintaining the link layer parameters.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  */
 static void chppResetTransportContext(struct ChppTransportState *context) {
   memset(&context->rxStatus, 0, sizeof(struct ChppRxStatus));
@@ -1197,9 +1210,9 @@
  *
  * If the link layer is busy, this function will reset the link as well.
  * This function retains and restores the platform-specific values of
- * transportContext.linkParams.
+ * transportContext.linkContext.
  *
- * @param transportContext Maintains status for each transport layer instance.
+ * @param transportContext Maintains state for each transport layer instance.
  * @param resetType Type of reset to send after resetting CHPP (reset vs.
  * reset-ack), as defined in the ChppTransportPacketAttributes struct.
  * @param error Provides the error that led to the reset.
@@ -1218,7 +1231,7 @@
   if (transportContext->txStatus.linkBusy == true) {
     // TODO: Give time for link layer to finish before resorting to a reset
 
-    chppPlatformLinkReset(&transportContext->linkParams);
+    transportContext->linkApi->reset(transportContext->linkContext);
   }
 
   // Free memory allocated for any ongoing rx datagrams
@@ -1256,7 +1269,7 @@
  * Checks for a timed out client request and generates a timeout response if a
  * client request timeout has occurred.
  *
- * @param context Maintains status for each transport layer instance.
+ * @param context Maintains state for each transport layer instance.
  * @return App layer response header if a timeout has occurred. Null otherwise.
  */
 #ifdef CHPP_CLIENT_ENABLED
@@ -1331,12 +1344,13 @@
  ***********************************************/
 
 void chppTransportInit(struct ChppTransportState *transportContext,
-                       struct ChppAppState *appContext) {
+                       struct ChppAppState *appContext, void *linkContext,
+                       const struct ChppLinkApi *linkApi) {
   CHPP_NOT_NULL(transportContext);
   CHPP_NOT_NULL(appContext);
+
   CHPP_ASSERT_LOG(!transportContext->initialized,
                   "CHPP transport already init");
-
   CHPP_LOGD("Initializing CHPP transport");
 
   chppResetTransportContext(transportContext);
@@ -1350,7 +1364,30 @@
   transportContext->appContext = appContext;
   transportContext->initialized = true;
 
-  chppPlatformLinkInit(&transportContext->linkParams);
+  CHPP_NOT_NULL(linkApi);
+  CHPP_DEBUG_NOT_NULL(linkApi->init);
+  CHPP_DEBUG_NOT_NULL(linkApi->deinit);
+  CHPP_DEBUG_NOT_NULL(linkApi->send);
+  CHPP_DEBUG_NOT_NULL(linkApi->doWork);
+  CHPP_DEBUG_NOT_NULL(linkApi->reset);
+  CHPP_DEBUG_NOT_NULL(linkApi->getConfig);
+  CHPP_DEBUG_NOT_NULL(linkApi->getTxBuffer);
+  transportContext->linkApi = linkApi;
+
+  CHPP_NOT_NULL(linkContext);
+  linkApi->init(linkContext, transportContext);
+  transportContext->linkContext = linkContext;
+
+#ifdef CHPP_DEBUG_ASSERT_ENABLED
+  const struct ChppLinkConfiguration linkConfig =
+      linkApi->getConfig(linkContext);
+  CHPP_ASSERT_LOG(
+      linkConfig.txBufferLen > CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES,
+      "The link TX buffer is too small");
+  CHPP_ASSERT_LOG(
+      linkConfig.rxBufferLen > CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES,
+      "The link RX buffer is too small");
+#endif  // CHPP_DEBUG_ASSERT_ENABLED
 }
 
 void chppTransportDeinit(struct ChppTransportState *transportContext) {
@@ -1358,7 +1395,7 @@
   CHPP_ASSERT_LOG(transportContext->initialized,
                   "CHPP transport already deinitialized");
 
-  chppPlatformLinkDeinit(&transportContext->linkParams);
+  transportContext->linkApi->deinit(transportContext->linkContext);
 #ifdef CHPP_ENABLE_WORK_MONITOR
   chppWorkMonitorDeinit(&transportContext->workMonitor);
 #endif
@@ -1368,6 +1405,8 @@
 
   chppClearTxDatagramQueue(transportContext);
 
+  CHPP_FREE_AND_NULLIFY(transportContext->rxDatagram.payload);
+
   transportContext->initialized = false;
 }
 
@@ -1520,18 +1559,20 @@
                                      : context->txStatus.lastTxTimeNs));
   }
 
+  if (nextDoWorkTime == CHPP_TIME_MAX) {
+    CHPP_LOGD("NextDoWork=n/a currentTime=%" PRIu64,
+              currentTime / CHPP_NSEC_PER_MSEC);
+    return CHPP_TRANSPORT_TIMEOUT_INFINITE;
+  }
+
   CHPP_LOGD("NextDoWork=%" PRIu64 " currentTime=%" PRIu64 " delta=%" PRId64,
             nextDoWorkTime / CHPP_NSEC_PER_MSEC,
             currentTime / CHPP_NSEC_PER_MSEC,
-            (nextDoWorkTime - currentTime) / (int64_t)CHPP_NSEC_PER_MSEC);
+            (nextDoWorkTime > currentTime ? nextDoWorkTime - currentTime : 0) /
+                (int64_t)CHPP_NSEC_PER_MSEC);
 
-  if (nextDoWorkTime == CHPP_TIME_MAX) {
-    return CHPP_TRANSPORT_TIMEOUT_INFINITE;
-  } else if (nextDoWorkTime <= currentTime) {
-    return CHPP_TRANSPORT_TIMEOUT_IMMEDIATE;
-  } else {
-    return nextDoWorkTime - currentTime;
-  }
+  return nextDoWorkTime <= currentTime ? CHPP_TRANSPORT_TIMEOUT_IMMEDIATE
+                                       : nextDoWorkTime - currentTime;
 }
 
 void chppWorkThreadStart(struct ChppTransportState *context) {
@@ -1596,8 +1637,8 @@
     }
 
     if (signals & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK) {
-      chppPlatformLinkDoWork(&context->linkParams,
-                             signals & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK);
+      context->linkApi->doWork(context->linkContext,
+                               signals & CHPP_TRANSPORT_SIGNAL_PLATFORM_MASK);
     }
   }
 
@@ -1612,21 +1653,18 @@
   chppNotifierSignal(&context->notifier, CHPP_TRANSPORT_SIGNAL_EXIT);
 }
 
-void chppLinkSendDoneCb(struct ChppPlatformLinkParameters *params,
+void chppLinkSendDoneCb(struct ChppTransportState *context,
                         enum ChppLinkErrorCode error) {
   if (error != CHPP_LINK_ERROR_NONE_SENT) {
     CHPP_LOGE("Async send failure: %" PRIu8, error);
   }
 
-  struct ChppTransportState *context =
-      container_of(params, struct ChppTransportState, linkParams);
-
   chppMutexLock(&context->mutex);
 
   context->txStatus.linkBusy = false;
 
-  // No need to free anything as pendingTxPacket.payload is static. Likewise, we
-  // keep pendingTxPacket.length to assist testing.
+  // No need to free anything as link Tx buffer is static. Likewise, we
+  // keep linkBufferSize to assist testing.
 
   chppMutexUnlock(&context->mutex);
 }
@@ -1649,7 +1687,7 @@
   result = CHPP_APP_ERROR_NONE;
   context->loopbackResult = CHPP_APP_ERROR_UNSPECIFIED;
 
-  if (len == 0 || len > CHPP_TRANSPORT_TX_MTU_BYTES) {
+  if (len == 0 || len > chppTransportTxMtuSize(context)) {
     result = CHPP_APP_ERROR_INVALID_LENGTH;
     context->loopbackResult = result;
 
@@ -1667,37 +1705,38 @@
     context->loopbackResult = result;
 
   } else {
+    uint8_t *linkTxBuffer = context->linkApi->getTxBuffer(context->linkContext);
     context->transportLoopbackData.length = len;
     memcpy(context->transportLoopbackData.payload, buf, len);
 
     context->txStatus.linkBusy = true;
-    context->pendingTxPacket.length = 0;
-    memset(&context->pendingTxPacket.payload, 0, CHPP_LINK_TX_MTU_BYTES);
-    context->pendingTxPacket.length +=
-        chppAddPreamble(&context->pendingTxPacket.payload[0]);
+    context->linkBufferSize = 0;
+    const struct ChppLinkConfiguration linkConfig =
+        context->linkApi->getConfig(context->linkContext);
+    memset(linkTxBuffer, 0, linkConfig.txBufferLen);
+    context->linkBufferSize += chppAddPreamble(linkTxBuffer);
 
     struct ChppTransportHeader *txHeader =
-        (struct ChppTransportHeader *)&context->pendingTxPacket
-            .payload[context->pendingTxPacket.length];
-    context->pendingTxPacket.length += sizeof(*txHeader);
+        (struct ChppTransportHeader *)&linkTxBuffer[context->linkBufferSize];
+    context->linkBufferSize += sizeof(*txHeader);
 
     txHeader->packetCode = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
         CHPP_TRANSPORT_ATTR_LOOPBACK_REQUEST, txHeader->packetCode);
 
-    size_t payloadLen = MIN(len, CHPP_TRANSPORT_TX_MTU_BYTES);
+    size_t payloadLen = MIN(len, chppTransportTxMtuSize(context));
     txHeader->length = (uint16_t)payloadLen;
-    chppAppendToPendingTxPacket(&context->pendingTxPacket, buf, payloadLen);
+    chppAppendToPendingTxPacket(context, buf, payloadLen);
 
-    chppAddFooter(&context->pendingTxPacket);
+    chppAddFooter(context);
 
     CHPP_LOGD("Sending transport-loopback request (packet len=%" PRIuSIZE
               ", payload len=%" PRIu16 ", asked len was %" PRIuSIZE ")",
-              context->pendingTxPacket.length, txHeader->length, len);
+              context->linkBufferSize, txHeader->length, len);
     enum ChppLinkErrorCode error = chppSendPendingPacket(context);
 
     if (error != CHPP_LINK_ERROR_NONE_QUEUED) {
       // Either sent synchronously or an error has occurred
-      chppLinkSendDoneCb(&context->linkParams, error);
+      chppLinkSendDoneCb(context, error);
 
       if (error != CHPP_LINK_ERROR_NONE_SENT) {
         // An error has occurred
@@ -1733,26 +1772,17 @@
     config->version.minor = 0;
     config->version.patch = 0;
 
-    // Rx MTU size
-    config->rxMtu = CHPP_PLATFORM_LINK_RX_MTU_BYTES;
-
-    // Max Rx window size
-    // Note: current implementation does not support a window size >1
-    config->windowSize = 1;
-
-    // Advertised transport layer (ACK) timeout
-    config->timeoutInMs = CHPP_PLATFORM_TRANSPORT_TIMEOUT_MS;
+    config->reserved1 = 0;
+    config->reserved2 = 0;
+    config->reserved3 = 0;
 
     if (resetType == CHPP_TRANSPORT_ATTR_RESET_ACK) {
       CHPP_LOGD("Sending RESET-ACK");
+      chppSetResetComplete(context);
     } else {
       CHPP_LOGD("Sending RESET");
     }
 
-    if (resetType == CHPP_TRANSPORT_ATTR_RESET_ACK) {
-      chppSetResetComplete(context);
-    }
-
     context->resetTimeNs = chppGetCurrentTimeNs();
 
     chppEnqueueTxDatagram(context,
@@ -1760,3 +1790,17 @@
                           config, sizeof(*config));
   }
 }
+
+size_t chppTransportTxMtuSize(const struct ChppTransportState *context) {
+  const struct ChppLinkConfiguration linkConfig =
+      context->linkApi->getConfig(context->linkContext);
+
+  return linkConfig.txBufferLen - CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES;
+}
+
+size_t chppTransportRxMtuSize(const struct ChppTransportState *context) {
+  const struct ChppLinkConfiguration linkConfig =
+      context->linkApi->getConfig(context->linkContext);
+
+  return linkConfig.rxBufferLen - CHPP_TRANSPORT_ENCODING_OVERHEAD_BYTES;
+}
\ No newline at end of file
diff --git a/chre_api/archive_chre_api.sh b/chre_api/archive_chre_api.sh
new file mode 100755
index 0000000..b6c36a5
--- /dev/null
+++ b/chre_api/archive_chre_api.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Quit if any command produces an error.
+set -e
+
+# Parse variables
+MAJOR_VERSION=$1
+: ${MAJOR_VERSION:?
+    "You must specify the major version of the API to be archived."
+    "Usage ./archive_chre_api.sh <major_version> <minor_version>"}
+
+MINOR_VERSION=$2
+: ${MINOR_VERSION:?
+    "You must specify the minor version of the API to be archived."
+    "Usage ./archive_chre_api.sh <major_version> <minor_version>"}
+
+DIRECTORY=v${MAJOR_VERSION}_${MINOR_VERSION}
+
+mkdir legacy/$DIRECTORY
+cp -r include/chre_api/* legacy/$DIRECTORY
diff --git a/chre_api/chre_api_version.mk b/chre_api/chre_api_version.mk
index f810e5f..7811573 100644
--- a/chre_api/chre_api_version.mk
+++ b/chre_api/chre_api_version.mk
@@ -10,11 +10,11 @@
 CURRENT_CHRE_API_VERSION_MK = $(OUT)/current_chre_api_version.mk
 
 $(PRINT_CURRENT_CHRE_API_VERSION_BIN): $(PRINT_CURRENT_CHRE_API_VERSION_SRCS)
-	mkdir -p $(OUT)
-	$(CHRE_HOST_CC) -I$(CHRE_PREFIX)/chre_api/include/chre_api $^ -o $@
+	$(V)mkdir -p $(OUT)
+	$(V)$(CHRE_HOST_CC) -I$(CHRE_PREFIX)/chre_api/include/chre_api $^ -o $@
 
 $(CURRENT_CHRE_API_VERSION_MK): $(PRINT_CURRENT_CHRE_API_VERSION_BIN)
-	./$< > $@
+	$(V)$< > $@
 
 # Only include default version if this is not a clean operation.
 ifeq ($(filter clean, $(MAKECMDGOALS)),)
diff --git a/chre_api/include/chre_api/chre/ble.h b/chre_api/include/chre_api/chre/ble.h
index 5212891..9f04964 100644
--- a/chre_api/include/chre_api/chre/ble.h
+++ b/chre_api/include/chre_api/chre/ble.h
@@ -52,6 +52,8 @@
 
 //! CHRE BLE supports batching of scan results, either through Android-specific
 //! HCI (OCF: 0x156), or by the CHRE framework, internally.
+//! @since v1.7 Platforms with this capability must also support flushing scan
+//! results during a batched scan.
 #define CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING UINT32_C(1 << 1)
 
 //! CHRE BLE scan supports best-effort hardware filtering. If filtering is
@@ -62,6 +64,9 @@
 //! If only one nanoapp is requesting BLE scans and there are no BLE scans from
 //! the AP, only filtered results will be provided to the nanoapp.
 #define CHRE_BLE_CAPABILITIES_SCAN_FILTER_BEST_EFFORT UINT32_C(1 << 2)
+
+//! CHRE BLE supports reading the RSSI of a specified LE-ACL connection handle.
+#define CHRE_BLE_CAPABILITIES_READ_RSSI UINT32_C(1 << 3)
 /** @} */
 
 /**
@@ -84,6 +89,11 @@
 //! CHRE BLE supports RSSI filters
 #define CHRE_BLE_FILTER_CAPABILITIES_RSSI UINT32_C(1 << 1)
 
+//! CHRE BLE supports Manufacturer Data filters (Corresponding HCI OCF: 0x0157,
+//! Sub-command: 0x06)
+//! @since v1.8
+#define CHRE_BLE_FILTER_CAPABILITIES_MANUFACTURER_DATA UINT32_C(1 << 6)
+
 //! CHRE BLE supports Service Data filters (Corresponding HCI OCF: 0x0157,
 //! Sub-command: 0x07)
 #define CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA UINT32_C(1 << 7)
@@ -122,6 +132,52 @@
  */
 #define CHRE_EVENT_BLE_ADVERTISEMENT CHRE_BLE_EVENT_ID(1)
 
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Indicates that a flush request made via chreBleFlushAsync() is complete, and
+ * all batched advertisements resulting from the flush have been delivered via
+ * preceding CHRE_EVENT_BLE_ADVERTISEMENT events.
+ *
+ * @since v1.7
+ */
+#define CHRE_EVENT_BLE_FLUSH_COMPLETE CHRE_BLE_EVENT_ID(2)
+
+/**
+ * nanoappHandleEvent argument: struct chreBleReadRssiEvent
+ *
+ * Provides the RSSI of an LE ACL connection following a call to
+ * chreBleReadRssiAsync().
+ *
+ * @since v1.8
+ */
+#define CHRE_EVENT_BLE_RSSI_READ CHRE_BLE_EVENT_ID(3)
+
+/**
+ * nanoappHandleEvent argument: struct chreBatchCompleteEvent
+ *
+ * This event is generated if the platform enabled batching, and when all
+ * events in a single batch has been delivered (for example, batching
+ * CHRE_EVENT_BLE_ADVERTISEMENT events if the platform has
+ * CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING enabled, and a non-zero
+ * reportDelayMs in chreBleStartScanAsync() was accepted).
+ *
+ * If the nanoapp receives a CHRE_EVENT_BLE_SCAN_STATUS_CHANGE with a non-zero
+ * reportDelayMs and enabled set to true, then this event must be generated.
+ *
+ * @since v1.8
+ */
+#define CHRE_EVENT_BLE_BATCH_COMPLETE CHRE_BLE_EVENT_ID(4)
+
+/**
+ * nanoappHandleEvent argument: struct chreBleScanStatus
+ *
+ * This event is generated when the values in chreBleScanStatus changes.
+ *
+ * @since v1.8
+ */
+#define CHRE_EVENT_BLE_SCAN_STATUS_CHANGE CHRE_BLE_EVENT_ID(5)
+
 // NOTE: Do not add new events with ID > 15
 /** @} */
 
@@ -199,12 +255,21 @@
 /** @} */
 
 /**
+ * The maximum amount of time allowed to elapse between the call to
+ * chreBleFlushAsync() and when CHRE_EVENT_BLE_FLUSH_COMPLETE is delivered to
+ * the nanoapp on a successful flush.
+ */
+#define CHRE_BLE_FLUSH_COMPLETE_TIMEOUT_NS (5 * CHRE_NSEC_PER_SEC)
+
+/**
  * Indicates a type of request made in this API. Used to populate the resultType
  * field of struct chreAsyncResult sent with CHRE_EVENT_BLE_ASYNC_RESULT.
  */
 enum chreBleRequestType {
   CHRE_BLE_REQUEST_TYPE_START_SCAN = 1,
   CHRE_BLE_REQUEST_TYPE_STOP_SCAN = 2,
+  CHRE_BLE_REQUEST_TYPE_FLUSH = 3,      //!< @since v1.7
+  CHRE_BLE_REQUEST_TYPE_READ_RSSI = 4,  //!< @since v1.8
 };
 
 /**
@@ -241,7 +306,19 @@
  */
 enum chreBleAdType {
   //! Service Data with 16-bit UUID
+  //! @deprecated as of v1.8
+  //! TODO(b/285207430): Remove this enum once CHRE has been updated.
   CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 = 0x16,
+
+  //! Service Data with 16-bit UUID
+  //! @since v1.8 CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 was renamed
+  //! CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE to reflect that nanoapps
+  //! compiled against v1.8+ should use OTA format for service data filters.
+  CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE = 0x16,
+
+  //! Manufacturer Specific Data
+  //! @since v1.8
+  CHRE_BLE_AD_TYPE_MANUFACTURER_DATA = 0xff,
 };
 
 /**
@@ -257,19 +334,43 @@
  * as defined in the Bluetooth spec Assigned Numbers, Generic Access Profile
  * (ref: https://www.bluetooth.com/specifications/assigned-numbers/). This
  * generic structure is used by the Advertising Packet Content Filter
- * (APCF) HCI generic AD type sub-command 0x08 (ref:
+ * (APCF) HCI generic AD type sub-command 0x09 (ref:
  * https://source.android.com/devices/bluetooth/hci_requirements#le_apcf_command).
  *
  * Note that the CHRE implementation may not support every kind of filter that
  * can be represented by this structure. Use chreBleGetFilterCapabilities() to
  * discover supported filtering capabilities at runtime.
  *
- * For example, to filter on a 16 bit service data UUID of 0xFE2C, the following
+ * @since v1.8 The data and dataMask must be in OTA format. The nanoapp support
+ * library will handle converting the data and dataMask values to the correct
+ * format if a pre v1.8 version of CHRE is running.
+ *
+ * NOTE: CHRE versions 1.6 and 1.7 expect the 2-byte UUID prefix in
+ * CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 to be given as big-endian. This
+ * was corrected in v1.8 to match the OTA format and Bluetooth specification,
+ * which uses little-endian. This enum has been removed and replaced with
+ * CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE to ensure that nanoapps
+ * compiled against v1.8 and later specify their UUID filter using
+ * little-endian. Nanoapps compiled against v1.7 or earlier should continue to
+ * use big-endian, as CHRE must provide cross-version compatibility for all
+ * possible version combinations.
+ *
+ * Example 1: To filter on a 16 bit service data UUID of 0xFE2C, the following
  * settings would be used:
  *   type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16
  *   len = 2
- *   data = {0xFE, 0x2C}
+ *   data = {0x2C, 0xFE}
  *   dataMask = {0xFF, 0xFF}
+ *
+ * Example 2: To filter for manufacturer data of 0x12, 0x34 from Google (0x00E0),
+ * the following settings would be used:
+ *   type = CHRE_BLE_AD_TYPE_MANUFACTURER_DATA
+ *   len = 4
+ *   data = {0xE0, 0x00, 0x12, 0x34}
+ *   dataMask = {0xFF, 0xFF, 0xFF, 0xFF}
+ *
+ * Refer to "Supplement to the Bluetooth Core Specification for details (v9,
+ * Part A, Section 1.4)" for details regarding the manufacturer data format.
  */
 struct chreBleGenericFilter {
   //! Acceptable values among enum chreBleAdType
@@ -282,10 +383,10 @@
   uint8_t len;
 
   //! Used in combination with dataMask to filter an advertisement
-  char data[CHRE_BLE_DATA_LEN_MAX];
+  uint8_t data[CHRE_BLE_DATA_LEN_MAX];
 
   //! Used in combination with data to filter an advertisement
-  char dataMask[CHRE_BLE_DATA_LEN_MAX];
+  uint8_t dataMask[CHRE_BLE_DATA_LEN_MAX];
 };
 
 /**
@@ -427,11 +528,13 @@
   //! device).
   uint8_t directAddress[CHRE_BLE_ADDRESS_LEN];
 
-  //! Length of data field. Acceptable range is [0, 31] for legacy and
-  //! [0, 229] for extended advertisements.
+  //! Length of data field. Acceptable range is [0, 62] for legacy and
+  //! [0, 255] for extended advertisements.
   uint16_t dataLength;
 
-  //! dataLength bytes of data, or null if dataLength is 0
+  //! dataLength bytes of data, or null if dataLength is 0. This represents
+  //! the ADV_IND payload, optionally concatenated with SCAN_RSP, as indicated
+  //! by eventTypeAndDataStatus.
   const uint8_t *data;
 
   //! Reserved for future use; set to 0
@@ -454,6 +557,42 @@
 };
 
 /**
+ * The RSSI read on a particular LE connection handle, based on the parameters
+ * in BT Core Spec v5.3, Vol 4, Part E, Section 7.5.4, Read RSSI command
+ */
+struct chreBleReadRssiEvent {
+  //! Structure which contains the cookie associated with the original request,
+  //! along with an error code that indicates request success or failure.
+  struct chreAsyncResult result;
+
+  //! The handle upon which CHRE attempted to read RSSI.
+  uint16_t connectionHandle;
+
+  //! The RSSI of the last packet received on this connection, if valid
+  //! (-127 to 20)
+  int8_t rssi;
+};
+
+/**
+ * Describes the current status of the BLE request in the platform.
+ *
+ * @since v1.8
+ */
+struct chreBleScanStatus {
+  //! The currently configured report delay in the scan configuration.
+  //! If enabled is false, this value does not have meaning.
+  uint32_t reportDelayMs;
+
+  //! True if the BLE scan is currently enabled. This can be set to false
+  //! if BLE scan was temporarily disabled (e.g. BT subsystem is down,
+  //! or due to user settings).
+  bool enabled;
+
+  //! Reserved for future use - set to zero.
+  uint8_t reserved[3];
+};
+
+/**
  * Retrieves a set of flags indicating the BLE features supported by the
  * current CHRE implementation. The value returned by this function must be
  * consistent for the entire duration of the nanoapp's execution.
@@ -552,8 +691,18 @@
  * The scan results will be delivered asynchronously via the CHRE event
  * CHRE_EVENT_BLE_ADVERTISEMENT.
  *
- * If the Bluetooth setting is disabled at the Android level, CHRE is expected
- * to return a result with CHRE_ERROR_FUNCTION_DISABLED.
+ * If CHRE_USER_SETTING_BLE_AVAILABLE is disabled, CHRE is expected to return an
+ * async result with error CHRE_ERROR_FUNCTION_DISABLED. If this setting is
+ * enabled, the Bluetooth subsystem may still be powered down in the scenario
+ * where the main Bluetooth toggle is disabled, but the Bluetooth scanning
+ * setting is enabled, and there is no request for BLE to be enabled at the
+ * Android level. In this scenario, CHRE will return an async result with error
+ * CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * To ensure that Bluetooth remains powered on in this settings configuration so
+ * that a nanoapp can scan, the nanoapp's Android host entity should use the
+ * BluetoothAdapter.enableBLE() API to register this request with the Android
+ * Bluetooth stack.
  *
  * If chreBleStartScanAsync() is called while a previous scan has been started,
  * the previous scan will be stopped first and replaced with the new scan.
@@ -567,6 +716,9 @@
  * Legacy-only: false
  * PHY type: PHY_LE_ALL_SUPPORTED
  *
+ * For v1.8 and greater, a CHRE_EVENT_BLE_SCAN_STATUS_CHANGE will be generated
+ * if the values in chreBleScanStatus changes as a result of this call.
+ *
  * @param mode Scanning mode selected among enum chreBleScanMode
  * @param reportDelayMs Maximum requested batching delay in ms. 0 indicates no
  *                      batching. Note that the system may deliver results
@@ -595,6 +747,81 @@
 bool chreBleStopScanAsync(void);
 
 /**
+ * Requests to immediately deliver batched scan results. The nanoapp must
+ * have an active BLE scan request. If a request is accepted, it will be treated
+ * as though the reportDelayMs has expired for a batched scan. Upon accepting
+ * the request, CHRE works to immediately deliver scan results currently kept in
+ * batching memory, if any, via regular CHRE_EVENT_BLE_ADVERTISEMENT events,
+ * followed by a CHRE_EVENT_BLE_FLUSH_COMPLETE event.
+ *
+ * If the underlying system fails to complete the flush operation within
+ * CHRE_BLE_FLUSH_COMPLETE_TIMEOUT_NS, CHRE will send a
+ * CHRE_EVENT_BLE_FLUSH_COMPLETE event with CHRE_ERROR_TIMEOUT.
+ *
+ * If multiple flush requests are made prior to flush completion, then the
+ * requesting nanoapp will receive all batched samples existing at the time of
+ * the latest flush request. In this case, the number of
+ * CHRE_EVENT_BLE_FLUSH_COMPLETE events received must equal the number of flush
+ * requests made.
+ *
+ * If chreBleStopScanAsync() is called while a flush operation is in progress,
+ * it is unspecified whether the flush operation will complete successfully or
+ * return an error, such as CHRE_ERROR_FUNCTION_DISABLED, but in any case,
+ * CHRE_EVENT_BLE_FLUSH_COMPLETE must still be delivered. The same applies if
+ * the Bluetooth user setting is disabled during a flush operation.
+ *
+ * If called while running on a CHRE API version below v1.7, this function
+ * returns false and has no effect.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *               sent as a response to this request.
+ *
+ * @return True to indicate the request was accepted. False otherwise.
+ *
+ * @since v1.7
+ */
+bool chreBleFlushAsync(const void *cookie);
+
+/**
+ * Requests to read the RSSI of a peer device on the given LE connection
+ * handle.
+ *
+ * If the request is accepted, the response will be delivered in a
+ * CHRE_EVENT_BLE_RSSI_READ event with the same cookie.
+ *
+ * The request may be rejected if resources are not available to service the
+ * request (such as if too many outstanding requests already exist). If so, the
+ * client may retry later.
+ *
+ * Note that the connectionHandle is valid only while the connection remains
+ * active. If a peer device disconnects then reconnects, the handle may change.
+ * BluetoothDevice#getConnectionHandle() can be used from the Android framework
+ * to get the latest handle upon reconnection.
+ *
+ * @param connectionHandle
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *               embedded in the response to this request.
+ * @return True if the request has been accepted and dispatched to the
+ *         controller. False otherwise.
+ *
+ * @since v1.8
+ *
+ */
+bool chreBleReadRssiAsync(uint16_t connectionHandle, const void *cookie);
+
+/**
+ * Retrieves the current state of the BLE scan on the platform.
+ *
+ * @param status A non-null pointer to where the scan status will be
+ *               populated.
+ *
+ * @return True if the status was obtained successfully.
+ *
+ * @since v1.8
+ */
+bool chreBleGetScanStatus(struct chreBleScanStatus *status);
+
+/**
  * Definitions for handling unsupported CHRE BLE scenarios.
  */
 #else  // defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
@@ -609,6 +836,12 @@
 #define chreBleStopScanAsync(...) \
   CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleStopScanAsync")
 
+#define chreBleFlushAsync(...) \
+  CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleFlushAsync")
+
+#define chreBleReadRssiAsync(...) \
+  CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleReadRssiAsync")
+
 #endif  // defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
 
 #ifdef __cplusplus
diff --git a/chre_api/include/chre_api/chre/common.h b/chre_api/include/chre_api/chre/common.h
index 8f5292e..9e3378e 100644
--- a/chre_api/include/chre_api/chre/common.h
+++ b/chre_api/include/chre_api/chre/common.h
@@ -172,6 +172,18 @@
     const void *cookie;
 };
 
+/**
+ * A structure to store an event describing the end of batched events.
+ *
+ * @since v1.8
+ */
+struct chreBatchCompleteEvent {
+    //! Indicates the type of event (of type CHRE_EVENT_TYPE_*) that was batched.
+    uint16_t eventType;
+
+    //! Reserved for future use, set to 0
+    uint8_t reserved[2];
+};
 
 #ifdef __cplusplus
 }
diff --git a/chre_api/include/chre_api/chre/event.h b/chre_api/include/chre_api/chre/event.h
index 77811b8..e519c3d 100644
--- a/chre_api/include/chre_api/chre/event.h
+++ b/chre_api/include/chre_api/chre/event.h
@@ -309,6 +309,13 @@
 /** @} */
 
 /**
+ * @see chrePublishRpcServices
+ *
+ * @since v1.8
+ */
+#define CHRE_MINIMUM_RPC_SERVICE_LIMIT UINT8_C(4)
+
+/**
  * Data provided with CHRE_EVENT_MESSAGE_FROM_HOST.
  */
 struct chreMessageFromHostData {
@@ -395,12 +402,38 @@
      * address an event specifically to this nanoapp.  This identifier is
      * guaranteed to be unique among all nanoapps in the system.
      *
-     * @since v1.6
-     * Instance ID is guaranteed to never go beyond INT16_MAX. This helps the
-     * instance ID be packed into other information inside an int (useful for
-     * RPC routing).
+     * As of CHRE API v1.6, instance ID is guaranteed to never be greater than
+     * UINT16_MAX. This allows for the instance ID be packed with other data
+     * inside a 32-bit integer (useful for RPC routing).
      */
     uint32_t instanceId;
+
+    /**
+     * Reserved for future use.
+     * Always set to 0.
+     */
+    uint8_t reserved[3];
+
+    /**
+     * The number of RPC services exposed by this nanoapp.
+     * The service details are available in the rpcServices array.
+     * Must always be set to 0 when running on a CHRE implementation prior to
+     * v1.8
+     *
+     * @since v1.8
+     */
+    uint8_t rpcServiceCount;
+
+    /*
+     * Array of RPC services published by this nanoapp.
+     * Services are published via chrePublishRpcServices.
+     * The array contains rpcServiceCount entries.
+     *
+     * The pointer is only valid when rpcServiceCount is greater than 0.
+     *
+     * @since v1.8
+     */
+    const struct chreNanoappRpcService *rpcServices;
 };
 
 /**
@@ -456,6 +489,9 @@
 //! The host endpoint is an Android app.
 #define CHRE_HOST_ENDPOINT_TYPE_APP UINT8_C(1)
 
+//! The host endpoint is an Android native program.
+#define CHRE_HOST_ENDPOINT_TYPE_NATIVE UINT8_C(2)
+
 //! Values in the range [CHRE_HOST_ENDPOINT_TYPE_VENDOR_START,
 //! CHRE_HOST_ENDPOINT_TYPE_VENDOR_END] can be a custom defined host endpoint
 //! type for platform-specific vendor use.
@@ -831,7 +867,8 @@
  * endpoint that is connected with the Context Hub.
  *
  * If this API succeeds, the nanoapp will receive disconnection notifications,
- * via the CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION event with type
+ * via the CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION event with an eventData of type
+ * chreHostEndpointNotification with its notificationType set to
  * HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT, which can be invoked if the host
  * has disconnected from the Context Hub either explicitly or implicitly (e.g.
  * crashes). Nanoapps can use this notifications to clean up any resources
@@ -852,7 +889,7 @@
                                             bool enable);
 
 /**
- * Publishes an RPC service from this nanoapp.
+ * Publishes RPC services from this nanoapp.
  *
  * When this API is invoked, the list of RPC services will be provided to
  * host applications interacting with the nanoapp.
@@ -860,6 +897,15 @@
  * This function must be invoked from nanoappStart(), to guarantee stable output
  * of the list of RPC services supported by the nanoapp.
  *
+ * Although nanoapps are recommended to only call this API once with all
+ * services it intends to publish, if it is called multiple times, each
+ * call will append to the list of published services.
+ *
+ * Starting in CHRE API v1.8, the implementation must allow for a nanoapp to
+ * publish at least CHRE_MINIMUM_RPC_SERVICE_LIMIT services and at most
+ * UINT8_MAX services. If calling this function would result in exceeding
+ * the limit, the services must not be published and it must return false.
+ *
  * @param services A non-null pointer to the list of RPC services to publish.
  * @param numServices The number of services to publish, i.e. the length of the
  *   services array.
diff --git a/chre_api/include/chre_api/chre/sensor.h b/chre_api/include/chre_api/chre/sensor.h
index 7ead7ee..4166374 100644
--- a/chre_api/include/chre_api/chre/sensor.h
+++ b/chre_api/include/chre_api/chre/sensor.h
@@ -639,7 +639,10 @@
  */
 struct chreSensorSamplingStatus {
     /**
-     * The interval, in nanoseconds, at which the sensor is now sampling.
+     * The interval, in nanoseconds, at which sensor data is being sampled at.
+     * This should be used by nanoapps to determine the rate at which samples
+     * will be generated and not to indicate what the sensor is truly sampling
+     * at since resampling may occur to limit incoming data.
      *
      * If this is CHRE_SENSOR_INTERVAL_DEFAULT, then a sampling interval
      * isn't meaningful for this sensor.
@@ -649,7 +652,7 @@
     uint64_t interval;
 
     /**
-     * The latency, in nanoseconds, at which the senor is now reporting.
+     * The latency, in nanoseconds, at which the sensor is now reporting.
      *
      * If this is CHRE_SENSOR_LATENCY_DEFAULT, then a latency
      * isn't meaningful for this sensor.
@@ -795,12 +798,17 @@
  * CHRE_EVENT_SENSOR_SAMPLING_CHANGE event will trigger in this case, so it's
  * not necessary to poll for such a change.
  *
+ * This function must return a valid status if the provided sensor is being
+ * actively sampled by a nanoapp and a CHRE_EVENT_SENSOR_SAMPLING_CHANGE has
+ * been delivered indicating their request has taken effect. It is not required
+ * to return a valid status if no nanoapp is actively sampling the sensor.
+ *
  * @param sensorHandle  The sensor handle, as obtained from
  *     chreSensorFindDefault() or passed to nanoappHandleEvent().
- * @param status  If the sensor is valid, then this memory will be filled with
- *     the sampling status contents for this sensor.  This argument must be
- *     non-NULL.
- * @return true if the senor handle is valid and 'status' was filled in;
+ * @param status  If the sensor is actively enabled by a nanoapp, then this
+ *     memory must be filled with the sampling status contents for this sensor.
+ *     This argument must be non-NULL.
+ * @return true if the sensor handle is valid and 'status' was filled in;
  *     false otherwise.
  */
 bool chreGetSensorSamplingStatus(uint32_t sensorHandle,
diff --git a/chre_api/include/chre_api/chre/user_settings.h b/chre_api/include/chre_api/chre/user_settings.h
index 3780a3d..c40be90 100644
--- a/chre_api/include/chre_api/chre/user_settings.h
+++ b/chre_api/include/chre_api/chre/user_settings.h
@@ -43,9 +43,12 @@
  * connectivity but enabled for location, the WIFI available setting is
  * enabled.
  *
- * NOTE: The BLE available setting indicates the overall availability of
- * BLE related functionality. For example, if BLE is disabled for connectivity,
- * but enabled for location, the BLE available setting is enabled.
+ * NOTE: The BLE available setting is the logical OR of the main Bluetooth
+ * setting and the Bluetooth scanning setting found under Location settings.
+ * Note that this indicates whether the user is allowing Bluetooth to be used,
+ * however the system may still fully power down the BLE chip in some scenarios
+ * if no request for it exists on the Android host side. See the
+ * chreBleStartScanAsync() API documentation for more information.
  *
  * @defgroup CHRE_USER_SETTINGS
  * @{
diff --git a/chre_api/include/chre_api/chre/version.h b/chre_api/include/chre_api/chre/version.h
index f903ac2..6872fdd 100644
--- a/chre_api/include/chre_api/chre/version.h
+++ b/chre_api/include/chre_api/chre/version.h
@@ -112,14 +112,37 @@
 /**
  * Value for version 1.6 of the Context Hub Runtime Environment API interface.
  *
- * This version of the CHRE API is shipped with the Android T release.
+ * This version of the CHRE API is shipped with the Android T release. It adds
+ * support for BLE scanning, subscribing to the WiFi NAN discovery engine,
+ * subscribing to host endpoint notifications, requesting metadata for a host
+ * endpoint ID, nanoapps publishing RPC services they support, and limits the
+ * nanoapp instance ID size to INT16_MAX.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_6 UINT32_C(0x01060000)
+
+/**
+ * Value for version 1.7 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API is shipped with a post-launch update to the
+ * Android T release. It adds the BLE flush API.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_7 UINT32_C(0x01070000)
+
+/**
+ * Value for version 1.8 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API is shipped with the Android U release.
  *
  * @note This version of the CHRE API has not been finalized yet, and is
  * currently considered a preview that is subject to change.
  *
  * @see CHRE_API_VERSION
  */
-#define CHRE_API_VERSION_1_6 UINT32_C(0x01060000)
+#define CHRE_API_VERSION_1_8 UINT32_C(0x01080000)
 
 /**
  * Major and Minor Version of this Context Hub Runtime Environment API.
@@ -138,7 +161,7 @@
  * Note that version numbers can always be numerically compared with
  * expected results, so 1.0.0 < 1.0.4 < 1.1.0 < 2.0.300 < 3.5.0.
  */
-#define CHRE_API_VERSION CHRE_API_VERSION_1_6
+#define CHRE_API_VERSION CHRE_API_VERSION_1_8
 
 /**
  * Utility macro to extract only the API major version of a composite CHRE
diff --git a/chre_api/legacy/v1_6/chre.h b/chre_api/legacy/v1_6/chre.h
new file mode 100644
index 0000000..9b87d08
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre.h
@@ -0,0 +1,201 @@
+/*
+ * 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_H_
+#define _CHRE_H_
+
+/**
+ * @file
+ * This header file includes all the headers which combine to fully define the
+ * interface for the Context Hub Runtime Environment (CHRE).  This interface is
+ * of interest to both implementers of CHREs and authors of nanoapps.  The API
+ * documentation attempts to address concerns of both.
+ *
+ * See individual header files for API details, and general comments below
+ * for overall platform information.
+ */
+
+#include <chre/audio.h>
+#include <chre/ble.h>
+#include <chre/common.h>
+#include <chre/event.h>
+#include <chre/gnss.h>
+#include <chre/nanoapp.h>
+#include <chre/re.h>
+#include <chre/sensor.h>
+#include <chre/toolchain.h>
+#include <chre/user_settings.h>
+#include <chre/version.h>
+#include <chre/wifi.h>
+#include <chre/wwan.h>
+
+/**
+ * @mainpage
+ * CHRE is the Context Hub Runtime Environment.  CHRE is used in Android to run
+ * contextual applications, called nanoapps, in a low-power processing domain
+ * other than the applications processor that runs Android itself.  The CHRE
+ * API, documented herein, is the common interface exposed to nanoapps for any
+ * compatible CHRE implementation.  The CHRE API provides the ability for
+ * creating nanoapps that are code-compatible across different CHRE
+ * implementations and underlying platforms. Refer to the following sections for
+ * a discussion on some important details of CHRE that aren't explicitly exposed
+ * in the API itself.
+ *
+ * @section entry_points Entry points
+ *
+ * The following entry points are used to bind a nanoapp to the CHRE system, and
+ * all three must be implemented by any nanoapp (see chre/nanoapp.h):
+ * - nanoappStart: initialization
+ * - nanoappHandleEvent: hook for event-driven processing
+ * - nanoappEnd: graceful teardown
+ *
+ * The CHRE implementation must also ensure that it performs these functions
+ * prior to invoking nanoappStart, or after nanoappEnd returns:
+ * - bss section zeroed out (prior to nanoappStart)
+ * - static variables initialized (prior to nanoappStart)
+ * - global C++ constructors called (prior to nanoappStart)
+ * - global C++ destructors called (after nanoappEnd)
+ *
+ * @section threading Threading model
+ *
+ * A CHRE implementation is free to choose among many different
+ * threading models, including a single-threaded system or a multi-threaded
+ * system with preemption.  The current platform definition is agnostic to this
+ * underlying choice.  However, the CHRE implementation must ensure that time
+ * spent executing within a nanoapp does not significantly degrade or otherwise
+ * interfere with other functions of the system in which CHRE is implemented,
+ * especially latency-sensitive tasks such as sensor event delivery to the AP.
+ * In other words, it must ensure that these functions can either occur in
+ * parallel or preempt a nanoapp's execution.  The current version of the API
+ * does not specify whether the implementation allows for CPU sharing between
+ * nanoapps on a more granular level than the handling of individual events [1].
+ * In any case, event ordering from the perspective of an individual nanoapp
+ * must be FIFO, but the CHRE implementation may choose to violate total
+ * ordering of events across all nanoapps to achieve more fair resource sharing,
+ * but this is not required.
+ *
+ * This version of the CHRE API does require that all nanoapps are treated as
+ * non-reentrant, meaning that only one instance of program flow can be inside
+ * an individual nanoapp at any given time.  That is, any of the functions of
+ * the nanoapp, including the entry points and all other callbacks, cannot be
+ * invoked if a previous invocation to the same or any other function in the
+ * nanoapp has not completed yet.
+ *
+ * For example, if a nanoapp is currently in nanoappHandleEvent(), the CHRE is
+ * not allowed to call nanoappHandleEvent() again, or to call a memory freeing
+ * callback.  Similarly, if a nanoapp is currently in a memory freeing
+ * callback, the CHRE is not allowed to call nanoappHandleEvent(), or invoke
+ * another memory freeing callback.
+ *
+ * There are two exceptions to this rule: If an invocation of chreSendEvent()
+ * fails (returns 'false'), it is allowed to immediately invoke the memory
+ * freeing callback passed into that function.  This is a rare case, and one
+ * where otherwise a CHRE implementation is likely to leak memory. Similarly,
+ * chreSendMessageToHost() is allowed to invoke the memory freeing callback
+ * directly, whether it returns 'true' or 'false'.  This is because the CHRE
+ * implementation may copy the message data to its own buffer, and therefore
+ * wouldn't need the nanoapp-supplied buffer after chreSendMessageToHost()
+ * returns.
+ *
+ * For a nanoapp author, this means no thought needs to be given to
+ * synchronization issues with global objects, as they will, by definition,
+ * only be accessed by a single thread at once.
+ *
+ * [1]: Note to CHRE implementers: A future version of the CHRE platform may
+ * require multi-threading with preemption.  This is mentioned as a heads up,
+ * and to allow implementors deciding between implementation approaches to
+ * make the most informed choice.
+ *
+ * @section timing Timing
+ *
+ * Nanoapps should expect to be running on a highly constrained system, with
+ * little memory and little CPU.  Any single nanoapp should expect to
+ * be one of several nanoapps on the system, which also share the CPU with the
+ * CHRE and possibly other services as well.
+ *
+ * Thus, a nanoapp needs to be efficient in its memory and CPU usage.
+ * Also, as noted in the Threading Model section, a CHRE implementation may
+ * be single threaded.  As a result, all methods invoked in a nanoapp
+ * (like nanoappStart, nanoappHandleEvent, memory free callbacks, etc.)
+ * must run "quickly".  "Quickly" is difficult to define, as there is a
+ * diversity of Context Hub hardware.  Nanoapp authors are strongly recommended
+ * to limit their application to consuming no more than 1 second of CPU time
+ * prior to returning control to the CHRE implementation.  A CHRE implementation
+ * may consider a nanoapp as unresponsive if it spends more time than this to
+ * process a single event, and take corrective action.
+ *
+ * A nanoapp may have the need to occasionally perform a large block of
+ * calculations that exceeds the 1 second guidance.  The recommended approach in
+ * this case is to split up the large block of calculations into smaller
+ * batches.  In one call into the nanoapp, the nanoapp can perform the first
+ * batch, and then set a timer or send an event (chreSendEvent()) to itself
+ * indicating which batch should be done next. This will allow the nanoapp to
+ * perform the entire calculation over time, without monopolizing system
+ * resources.
+ *
+ * @section floats Floating point support
+ *
+ * The C type 'float' is used in this API, and thus a CHRE implementation
+ * is required to support 'float's.
+ *
+ * Support of the C types 'double' and 'long double' is optional for a
+ * CHRE implementation.  Note that if a CHRE decides to support them, unlike
+ * 'float' support, there is no requirement that this support is particularly
+ * efficient.  So nanoapp authors should be aware this may be inefficient.
+ *
+ * If a CHRE implementation chooses not to support 'double' or
+ * 'long double', then the build toolchain setup provided needs to set
+ * the preprocessor define CHRE_NO_DOUBLE_SUPPORT.
+ *
+ * @section compat CHRE and Nanoapp compatibility
+ *
+ * CHRE implementations must make affordances to maintain binary compatibility
+ * across minor revisions of the API version (e.g. v1.1 to v1.2).  This applies
+ * to both running a nanoapp compiled for a newer version of the API on a CHRE
+ * implementation built against an older version (backwards compatibility), and
+ * vice versa (forwards compatibility).  API changes that are acceptable in
+ * minor version changes that may require special measures to ensure binary
+ * compatibility include: addition of new functions; addition of arguments to
+ * existing functions when the default value used for nanoapps compiled against
+ * the old version is well-defined and does not affect existing functionality;
+ * and addition of fields to existing structures, even when this induces a
+ * binary layout change (this should be made rare via judicious use of reserved
+ * fields).  API changes that must only occur alongside a major version change
+ * and are therefore not compatible include: removal of any function, argument,
+ * field in a data structure, or mandatory functional behavior that a nanoapp
+ * may depend on; any change in the interpretation of an existing data structure
+ * field that alters the way it was defined previously (changing the units of a
+ * field would fall under this, but appropriating a previously reserved field
+ * for some new functionality would not); and any change in functionality or
+ * expected behavior that conflicts with the previous definition.
+ *
+ * Note that the CHRE API only specifies the software interface between a
+ * nanoapp and the CHRE system - the binary interface (ABI) between nanoapp and
+ * CHRE is necessarily implementation-dependent.  Therefore, the recommended
+ * approach to accomplish binary compatibility is to build a Nanoapp Support
+ * Library (NSL) that is specific to the CHRE implementation into the nanoapp
+ * binary, and use it to handle ABI details in a way that ensures compatibility.
+ * In addition, to accomplish forwards compatibility, the CHRE implementation is
+ * expected to recognize the CHRE API version that a nanoapp is targeting and
+ * engage compatibility behaviors where necessary.
+ *
+ * By definition, major API version changes (e.g. v1.1 to v2.0) break
+ * compatibility.  Therefore, a CHRE implementation must not attempt to load a
+ * nanoapp that is targeting a newer major API version.
+ */
+
+#endif  /* _CHRE_H_ */
+
diff --git a/chre_api/legacy/v1_6/chre/audio.h b/chre_api/legacy/v1_6/chre/audio.h
new file mode 100644
index 0000000..e8ec960
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/audio.h
@@ -0,0 +1,429 @@
+/*
+ * 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_AUDIO_H_
+#define _CHRE_AUDIO_H_
+
+/**
+ * @file
+ * The API for requesting audio in the Context Hub Runtime Environment.
+ *
+ * This includes the definition of audio data structures and the ability to
+ * request audio streams.
+ */
+
+#include <chre/event.h>
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The current compatibility version of the chreAudioDataEvent structure.
+ */
+#define CHRE_AUDIO_DATA_EVENT_VERSION  UINT8_C(1)
+
+/**
+ * Produce an event ID in the block of IDs reserved for audio
+ * @param offset Index into audio event ID block; valid range [0,15]
+ */
+#define CHRE_AUDIO_EVENT_ID(offset)  (CHRE_EVENT_AUDIO_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAudioSourceStatusEvent
+ *
+ * Indicates a change in the format and/or rate of audio data provided to a
+ * nanoapp.
+ */
+#define CHRE_EVENT_AUDIO_SAMPLING_CHANGE  CHRE_AUDIO_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreAudioDataEvent
+ *
+ * Provides a buffer of audio data to a nanoapp.
+ */
+#define CHRE_EVENT_AUDIO_DATA  CHRE_AUDIO_EVENT_ID(1)
+
+/**
+ * The maximum size of the name of an audio source including the
+ * null-terminator.
+ */
+#define CHRE_AUDIO_SOURCE_NAME_MAX_SIZE  (40)
+
+/**
+ * Helper values for sample rates.
+ *
+ * @defgroup CHRE_AUDIO_SAMPLE_RATES
+ * @{
+ */
+
+//! 16kHz Audio Sample Data
+#define CHRE_AUDIO_SAMPLE_RATE_16KHZ  (16000)
+
+/** @} */
+
+/**
+ * Formats for audio that can be provided to a nanoapp.
+ */
+enum chreAudioDataFormat {
+  /**
+   * Unsigned, 8-bit u-Law encoded data as specified by ITU-T G.711.
+   */
+  CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW = 0,
+
+  /**
+   * Signed, 16-bit linear PCM data. Endianness must be native to the local
+   * processor.
+   */
+  CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM = 1,
+};
+
+/**
+ * A description of an audio source available to a nanoapp.
+ *
+ * This provides a description of an audio source with a name and a
+ * description of the format of the provided audio data.
+ */
+struct chreAudioSource {
+  /**
+   * A human readable name for this audio source. This is a C-style,
+   * null-terminated string. The length must be less than or equal to
+   * CHRE_AUDIO_SOURCE_NAME_MAX_SIZE bytes (including the null-terminator) and
+   * is expected to describe the source of the audio in US English. All
+   * characters must be printable (i.e.: isprint would return true for all
+   * characters in the name for the EN-US locale). The typical use of this field
+   * is for a nanoapp to log the name of the audio source that it is using.
+   *
+   * Example: "Camcorder Microphone"
+   */
+  const char *name;
+
+  /**
+   * The sampling rate in hertz of this mode. This value is rounded to the
+   * nearest integer. Typical values might include 16000, 44100 and 44800.
+   *
+   * If the requested audio source is preempted by another feature of the system
+   * (e.g. hotword), a gap may occur in received audio data. This is indicated
+   * to the client by posting a CHRE_EVENT_AUDIO_SAMPLING_CHANGE event. The
+   * nanoapp will then receive another CHRE_EVENT_AUDIO_SAMPLING_CHANGE event
+   * once the audio source is available again.
+   */
+  uint32_t sampleRate;
+
+  /**
+   * The minimum amount of time that this audio source can be buffered, in
+   * nanoseconds. Audio data is delivered to nanoapps in buffers. This specifies
+   * the minimum amount of data that can be delivered to a nanoapp without
+   * losing data. A request for a buffer that is smaller than this will fail.
+   */
+  uint64_t minBufferDuration;
+
+  /**
+   * The maximum amount of time that this audio source can be buffered, in
+   * nanoseconds. Audio data is delivered to nanoapps in buffers. This specifies
+   * the maximum amount of data that can be stored by the system in one event
+   * without losing data. A request for a buffer that is larger than this will
+   * fail.
+   */
+  uint64_t maxBufferDuration;
+
+  /**
+   * The format for data provided to the nanoapp. This will be assigned to one
+   * of the enum chreAudioDataFormat values.
+   */
+  uint8_t format;
+};
+
+/**
+ * The current status of an audio source.
+ */
+struct chreAudioSourceStatus {
+  /**
+   * Set to true if the audio source is currently enabled by this nanoapp. If
+   * this struct is provided by a CHRE_EVENT_AUDIO_SAMPLING_CHANGE event, it
+   * must necessarily be set to true because sampling change events are only
+   * sent for sources which this nanoapp has actively subscribed to. If this
+   * struct is obtained from the chreAudioGetStatus API, it may be set to true
+   * or false depending on if audio is currently enabled.
+   */
+  bool enabled;
+
+  /**
+   * Set to true if the audio source is currently suspended and no audio data
+   * will be received from this source.
+   */
+  bool suspended;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_AUDIO_SAMPLING_CHANGE.
+ */
+struct chreAudioSourceStatusEvent {
+  /**
+   * The audio source which has completed a status change.
+   */
+  uint32_t handle;
+
+  /**
+   * The status of this audio source.
+   */
+  struct chreAudioSourceStatus status;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_AUDIO_DATA.
+ *
+ * One example of the sequence of events for a nanoapp to receive audio data is:
+ *
+ * 1. CHRE_EVENT_AUDIO_SAMPLING_CHANGE - Indicates that audio data is not
+ *                                       suspended.
+ * 2. CHRE_EVENT_AUDIO_DATA - One buffer of audio samples. Potentially repeated.
+ * 3. CHRE_EVENT_AUDIO_SAMPLING_CHANGE - Indicates that audio data has suspended
+ *                                       which indicates a gap in the audio.
+ * 4. CHRE_EVENT_AUDIO_SAMPLING_CHANGE - Indicates that audio data has resumed
+ *                                       and that audio data may be delivered
+ *                                       again if enough samples are buffered.
+ * 5. CHRE_EVENT_AUDIO_DATA - One buffer of audio samples. Potentially repeated.
+ *                            The nanoapp must tolerate a gap in the timestamps.
+ *
+ * This process repeats for as long as an active request is made for an audio
+ * source. A CHRE_EVENT_AUDIO_SAMPLING_CHANGE does not guarantee that the next
+ * event will be a CHRE_EVENT_AUDIO_DATA event when suspended is set to false.
+ * It may happen that the audio source is suspended before a complete buffer can
+ * be captured. This will cause another CHRE_EVENT_AUDIO_SAMPLING_CHANGE event
+ * to be dispatched with suspended set to true before a buffer is delivered.
+ *
+ * Audio events must be delivered to a nanoapp in order.
+ */
+struct chreAudioDataEvent {
+  /**
+   * Indicates the version of the structure, for compatibility purposes. Clients
+   * do not normally need to worry about this field; the CHRE implementation
+   * guarantees that the client only receives the structure version it expects.
+   */
+  uint8_t version;
+
+  /**
+   * Additional bytes reserved for future use; must be set to 0.
+   */
+  uint8_t reserved[3];
+
+  /**
+   * The handle for which this audio data originated from.
+   */
+  uint32_t handle;
+
+  /**
+   * The base timestamp for this buffer of audio data, from the same time base
+   * as chreGetTime() (in nanoseconds). The audio API does not provide
+   * timestamps for each audio sample. This timestamp corresponds to the first
+   * sample of the buffer. Even though the value is expressed in nanoseconds,
+   * there is an expectation that the sample clock may drift and nanosecond
+   * level accuracy may not be possible. The goal is to be as accurate as
+   * possible within reasonable limitations of a given system.
+   */
+  uint64_t timestamp;
+
+  /**
+   * The sample rate for this buffer of data in hertz, rounded to the nearest
+   * integer. Fractional sampling rates are not supported. Typical values might
+   * include 16000, 44100 and 48000.
+   */
+  uint32_t sampleRate;
+
+  /**
+   * The number of samples provided with this buffer.
+   */
+  uint32_t sampleCount;
+
+  /**
+   * The format of this audio data. This enumeration and union of pointers below
+   * form a tagged struct. The consumer of this API must use this enum to
+   * determine which samples pointer below to dereference. This will be assigned
+   * to one of the enum chreAudioDataFormat values.
+   */
+  uint8_t format;
+
+  /**
+   * A union of pointers to various formats of sample data. These correspond to
+   * the valid chreAudioDataFormat values.
+   */
+  union {
+    const uint8_t *samplesULaw8;
+    const int16_t *samplesS16;
+  };
+};
+
+/**
+ * Retrieves information about an audio source supported by the current CHRE
+ * implementation. The source returned by the runtime must not change for the
+ * entire lifecycle of the Nanoapp and hot-pluggable audio sources are not
+ * supported.
+ *
+ * A simple example of iterating all available audio sources is provided here:
+ *
+ * struct chreAudioSource audioSource;
+ * for (uint32_t i = 0; chreAudioGetSource(i, &audioSource); i++) {
+ *     chreLog(CHRE_LOG_INFO, "Found audio source: %s", audioSource.name);
+ * }
+ *
+ * Handles provided to this API must be a stable value for the entire duration
+ * of a nanoapp. Handles for all audio sources must be zero-indexed and
+ * contiguous. The following are examples of handles that could be provided to
+ * this API:
+ *
+ *   Valid: 0
+ *   Valid: 0, 1, 2, 3
+ * Invalid: 1, 2, 3
+ * Invalid: 0, 2
+ *
+ * @param handle The handle for an audio source to obtain details for. The
+ *     range of acceptable handles must be zero-indexed and contiguous.
+ * @param audioSource A struct to populate with details of the audio source.
+ * @return true if the query was successful, false if the provided handle is
+ *     invalid or the supplied audioSource is NULL.
+ *
+ * @since v1.2
+ */
+bool chreAudioGetSource(uint32_t handle, struct chreAudioSource *audioSource);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_AUDIO somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following audio APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to audio data by adding
+ * metadata to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_AUDIO) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Configures delivery of audio data to the current nanoapp. Note that this may
+ * not fully disable the audio source if it is used by other clients in the
+ * system but it will halt data delivery to the nanoapp.
+ *
+ * The bufferDuration and deliveryInterval parameters as described below are
+ * used together to determine both how much and how often to deliver data to a
+ * nanoapp, respectively. A nanoapp will always be provided the requested
+ * amount of data at the requested interval, even if another nanoapp in CHRE
+ * requests larger/more frequent buffers or smaller/less frequent buffers.
+ * These two buffering parameters allow describing the duty cycle of captured
+ * audio data. If a nanoapp wishes to receive all available audio data, it will
+ * specify a bufferDuration and deliveryInterval that are equal. A 50% duty
+ * cycle would be achieved by specifying a deliveryInterval that is double the
+ * value of the bufferDuration provided. These parameters allow the audio
+ * subsystem to operate at less than 100% duty cycle and permits use of
+ * incomplete audio data without periodic reconfiguration of the source.
+ *
+ * Two examples are illustrated below:
+ *
+ * Target duty cycle: 50%
+ * bufferDuration:    2
+ * deliveryInterval:  4
+ *
+ * Time       0   1   2   3   4   5   6   7
+ * Batch                  A               B
+ * Sample    --  --  a1  a2  --  --  b1  b2
+ * Duration          [    ]          [    ]
+ * Interval  [            ]  [            ]
+ *
+ *
+ * Target duty cycle: 100%
+ * bufferDuration:    4
+ * deliveryInterval:  4
+ *
+ * Time       0   1   2   3   4   5   6   7
+ * Batch                  A               B
+ * Sample    a1  a2  a3  a4  b1  b2  b3  b4
+ * Duration  [            ]  [            ]
+ * Interval  [            ]  [            ]
+ *
+ *
+ * This is expected to reduce power overall.
+ *
+ * The first audio buffer supplied to the nanoapp may contain data captured
+ * prior to the request. This could happen if the microphone was already enabled
+ * and reading into a buffer prior to the nanoapp requesting audio data for
+ * itself. The nanoapp must tolerate this.
+ *
+ * It is important to note that multiple logical audio sources (e.g. different
+ * sample rate, format, etc.) may map to one physical audio source. It is
+ * possible for a nanoapp to request audio data from more than one logical
+ * source at a time. Audio data may be suspended for either the current or other
+ * requests. The CHRE_EVENT_AUDIO_SAMPLING_CHANGE will be posted to all clients
+ * if such a change occurs. It is also possible for the request to succeed and
+ * all audio sources are serviced simultaneously. This is implementation defined
+ * but at least one audio source must function correctly if it is advertised,
+ * under normal conditions (e.g. not required for some other system function,
+ * such as hotword).
+ *
+ * @param handle The handle for this audio source. The handle for the desired
+ *     audio source can be determined using chreAudioGetSource().
+ * @param enable true if enabling the source, false otherwise. When passed as
+ *     false, the bufferDuration and deliveryInterval parameters are ignored.
+ * @param bufferDuration The amount of time to capture audio samples from this
+ *     audio source, in nanoseconds per delivery interval. This value must be
+ *     in the range of minBufferDuration/maxBufferDuration for this source or
+ *     the request will fail. The number of samples captured per buffer will be
+ *     derived from the sample rate of the source and the requested duration and
+ *     rounded down to the nearest sample boundary.
+ * @param deliveryInterval Desired time between each CHRE_EVENT_AUDIO_DATA
+ *     event. This allows specifying the complete duty cycle of a request
+ *     for audio data, in nanoseconds. This value must be greater than or equal
+ *     to bufferDuration or the request will fail due to an invalid
+ *     configuration.
+ * @return true if the configuration was successful, false if invalid parameters
+ *     were provided (non-existent handle, invalid buffering configuration).
+ *
+ * @since v1.2
+ * @note Requires audio permission
+ */
+bool chreAudioConfigureSource(uint32_t handle, bool enable,
+                              uint64_t bufferDuration,
+                              uint64_t deliveryInterval);
+
+/**
+ * Gets the current chreAudioSourceStatus struct for a given audio handle.
+ *
+ * @param handle The handle for the audio source to query. The provided handle
+ *     is obtained from a chreAudioSource which is requested from the
+ *     chreAudioGetSource API.
+ * @param status The current status of the supplied audio source.
+ * @return true if the provided handle is valid and the status was obtained
+ *     successfully, false if the handle was invalid or status is NULL.
+ *
+ * @since v1.2
+ * @note Requires audio permission
+ */
+bool chreAudioGetStatus(uint32_t handle, struct chreAudioSourceStatus *status);
+
+#else  /* defined(CHRE_NANOAPP_USES_AUDIO) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_AUDIO_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_AUDIO must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreAudioConfigureSource(...) \
+    CHRE_BUILD_ERROR(CHRE_AUDIO_PERM_ERROR_STRING "chreAudioConfigureSource")
+#define chreAudioGetStatus(...) \
+    CHRE_BUILD_ERROR(CHRE_AUDIO_PERM_ERROR_STRING "chreAudioGetStatus")
+#endif  /* defined(CHRE_NANOAPP_USES_AUDIO) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_AUDIO_H_ */
diff --git a/chre_api/legacy/v1_6/chre/ble.h b/chre_api/legacy/v1_6/chre/ble.h
new file mode 100644
index 0000000..5212891
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/ble.h
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2021 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_BLE_H_
+#define CHRE_BLE_H_
+
+/**
+ * @file
+ * CHRE BLE (Bluetooth Low Energy, Bluetooth LE) API.
+ * The CHRE BLE API currently supports BLE scanning features.
+ *
+ * The features in the CHRE BLE API are a subset and adaptation of Android
+ * capabilities as described in the Android BLE API and HCI requirements.
+ * ref:
+ * https://developer.android.com/guide/topics/connectivity/bluetooth/ble-overview
+ * ref: https://source.android.com/devices/bluetooth/hci_requirements
+ */
+
+#include <chre/common.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags returned by chreBleGetCapabilities().
+ *
+ * @defgroup CHRE_BLE_CAPABILITIES
+ * @{
+ */
+//! No BLE APIs are supported
+#define CHRE_BLE_CAPABILITIES_NONE UINT32_C(0)
+
+//! CHRE supports BLE scanning
+#define CHRE_BLE_CAPABILITIES_SCAN UINT32_C(1 << 0)
+
+//! CHRE BLE supports batching of scan results, either through Android-specific
+//! HCI (OCF: 0x156), or by the CHRE framework, internally.
+#define CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING UINT32_C(1 << 1)
+
+//! CHRE BLE scan supports best-effort hardware filtering. If filtering is
+//! available, chreBleGetFilterCapabilities() returns a bitmap indicating the
+//! specific filtering capabilities that are supported.
+//! To differentiate best-effort vs. no filtering, the following requirement
+//! must be met for this flag:
+//! If only one nanoapp is requesting BLE scans and there are no BLE scans from
+//! the AP, only filtered results will be provided to the nanoapp.
+#define CHRE_BLE_CAPABILITIES_SCAN_FILTER_BEST_EFFORT UINT32_C(1 << 2)
+/** @} */
+
+/**
+ * The set of flags returned by chreBleGetFilterCapabilities().
+ *
+ * The representative bit for each filtering capability is based on the sub-OCF
+ * of the Android filtering HCI vendor-specific command (LE_APCF_Command, OCF:
+ * 0x0157) for that particular filtering capability, as found in
+ * https://source.android.com/devices/bluetooth/hci_requirements
+ *
+ * For example, the Service Data filter has a sub-command of 0x7; hence
+ * the filtering capability is indicated by (1 << 0x7).
+ *
+ * @defgroup CHRE_BLE_FILTER_CAPABILITIES
+ * @{
+ */
+//! No CHRE BLE filters are supported
+#define CHRE_BLE_FILTER_CAPABILITIES_NONE UINT32_C(0)
+
+//! CHRE BLE supports RSSI filters
+#define CHRE_BLE_FILTER_CAPABILITIES_RSSI UINT32_C(1 << 1)
+
+//! CHRE BLE supports Service Data filters (Corresponding HCI OCF: 0x0157,
+//! Sub-command: 0x07)
+#define CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA UINT32_C(1 << 7)
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for BLE.
+ *
+ * Valid input range is [0, 15]. Do not add new events with ID > 15
+ * (see chre/event.h)
+ *
+ * @param offset Index into BLE event ID block; valid range is [0, 15].
+ *
+ * @defgroup CHRE_BLE_EVENT_ID
+ * @{
+ */
+#define CHRE_BLE_EVENT_ID(offset) (CHRE_EVENT_BLE_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Communicates the asynchronous result of a request to the BLE API. The
+ * requestType field in {@link #chreAsyncResult} is set to a value from enum
+ * chreBleRequestType.
+ *
+ * This is used for results of async config operations which need to
+ * interop with lower level code (potentially in a different thread) or send an
+ * HCI command to the FW and wait on the response.
+ */
+#define CHRE_EVENT_BLE_ASYNC_RESULT CHRE_BLE_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreBleAdvertisementEvent
+ *
+ * Provides results of a BLE scan.
+ */
+#define CHRE_EVENT_BLE_ADVERTISEMENT CHRE_BLE_EVENT_ID(1)
+
+// NOTE: Do not add new events with ID > 15
+/** @} */
+
+/**
+ * Maximum BLE (legacy) advertisement payload data length, in bytes
+ * This is calculated by subtracting 2 (type + len) from 31 (max payload).
+ */
+#define CHRE_BLE_DATA_LEN_MAX (29)
+
+/**
+ * BLE device address length, in bytes.
+ */
+#define CHRE_BLE_ADDRESS_LEN (6)
+
+/**
+ * RSSI value (int8_t) indicating no RSSI threshold.
+ */
+#define CHRE_BLE_RSSI_THRESHOLD_NONE (-128)
+
+/**
+ * RSSI value (int8_t) indicating no RSSI value available.
+ */
+#define CHRE_BLE_RSSI_NONE (127)
+
+/**
+ * Tx power value (int8_t) indicating no Tx power value available.
+ */
+#define CHRE_BLE_TX_POWER_NONE (127)
+
+/**
+ * Indicates ADI field was not provided in advertisement.
+ */
+#define CHRE_BLE_ADI_NONE (0xFF)
+
+/**
+ * The CHRE BLE advertising event type is based on the BT Core Spec v5.2,
+ * Vol 4, Part E, Section 7.7.65.13, LE Extended Advertising Report event,
+ * Event_Type.
+ *
+ * Note: helper functions are provided to avoid bugs, e.g. a nanoapp doing
+ * (eventTypeAndDataStatus == ADV_IND) instead of properly masking off reserved
+ * and irrelevant bits.
+ *
+ * @defgroup CHRE_BLE_EVENT
+ * @{
+ */
+// Extended event types
+#define CHRE_BLE_EVENT_MASK_TYPE (0x1f)
+#define CHRE_BLE_EVENT_TYPE_FLAG_CONNECTABLE (1 << 0)
+#define CHRE_BLE_EVENT_TYPE_FLAG_SCANNABLE (1 << 1)
+#define CHRE_BLE_EVENT_TYPE_FLAG_DIRECTED (1 << 2)
+#define CHRE_BLE_EVENT_TYPE_FLAG_SCAN_RSP (1 << 3)
+#define CHRE_BLE_EVENT_TYPE_FLAG_LEGACY (1 << 4)
+
+// Data status
+#define CHRE_BLE_EVENT_MASK_DATA_STATUS (0x3 << 5)
+#define CHRE_BLE_EVENT_DATA_STATUS_COMPLETE (0x0 << 5)
+#define CHRE_BLE_EVENT_DATA_STATUS_MORE_DATA_PENDING (0x1 << 5)
+#define CHRE_BLE_EVENT_DATA_STATUS_DATA_TRUNCATED (0x2 << 5)
+
+// Legacy event types
+#define CHRE_BLE_EVENT_TYPE_LEGACY_ADV_IND                                  \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY | CHRE_BLE_EVENT_TYPE_FLAG_CONNECTABLE | \
+   CHRE_BLE_EVENT_TYPE_FLAG_SCANNABLE)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_DIRECT_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY | CHRE_BLE_EVENT_TYPE_FLAG_CONNECTABLE)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_ADV_SCAN_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY | CHRE_BLE_EVENT_TYPE_FLAG_SCANNABLE)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_ADV_NONCONN_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_SCAN_RESP_ADV_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_SCAN_RSP | CHRE_BLE_EVENT_TYPE_LEGACY_ADV_IND)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_SCAN_RESP_ADV_SCAN_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_SCAN_RSP | CHRE_BLE_EVENT_TYPE_LEGACY_ADV_SCAN_IND)
+/** @} */
+
+/**
+ * Indicates a type of request made in this API. Used to populate the resultType
+ * field of struct chreAsyncResult sent with CHRE_EVENT_BLE_ASYNC_RESULT.
+ */
+enum chreBleRequestType {
+  CHRE_BLE_REQUEST_TYPE_START_SCAN = 1,
+  CHRE_BLE_REQUEST_TYPE_STOP_SCAN = 2,
+};
+
+/**
+ * CHRE BLE scan modes identify functional scan levels without specifying or
+ * guaranteeing particular scan parameters (e.g. duty cycle, interval, radio
+ * chain).
+ *
+ * The actual scan parameters may be platform dependent and may change without
+ * notice in real time based on contextual cues, etc.
+ *
+ * Scan modes should be selected based on use cases as described.
+ */
+enum chreBleScanMode {
+  //! A background scan level for always-running ambient applications.
+  //! A representative duty cycle may be between 3 - 10 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_BACKGROUND = 1,
+
+  //! A foreground scan level to be used for short periods.
+  //! A representative duty cycle may be between 10 - 20 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_FOREGROUND = 2,
+
+  //! A very high duty cycle scan level to be used for very short durations.
+  //! A representative duty cycle may be between 50 - 100 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_AGGRESSIVE = 3,
+};
+
+/**
+ * Selected AD Types are available among those defined in the Bluetooth spec.
+ * Assigned Numbers, Generic Access Profile.
+ * ref: https://www.bluetooth.com/specifications/assigned-numbers/
+ */
+enum chreBleAdType {
+  //! Service Data with 16-bit UUID
+  CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 = 0x16,
+};
+
+/**
+ * Generic scan filters definition based on AD Type, mask, and values. The
+ * maximum data length is limited to the maximum possible legacy advertisement
+ * payload data length (29 bytes).
+ *
+ * The filter is matched when
+ *   data & dataMask == advData & dataMask
+ * where advData is the advertisement packet data for the specified AD type.
+ *
+ * The CHRE generic filter structure represents a generic filter on an AD Type
+ * as defined in the Bluetooth spec Assigned Numbers, Generic Access Profile
+ * (ref: https://www.bluetooth.com/specifications/assigned-numbers/). This
+ * generic structure is used by the Advertising Packet Content Filter
+ * (APCF) HCI generic AD type sub-command 0x08 (ref:
+ * https://source.android.com/devices/bluetooth/hci_requirements#le_apcf_command).
+ *
+ * Note that the CHRE implementation may not support every kind of filter that
+ * can be represented by this structure. Use chreBleGetFilterCapabilities() to
+ * discover supported filtering capabilities at runtime.
+ *
+ * For example, to filter on a 16 bit service data UUID of 0xFE2C, the following
+ * settings would be used:
+ *   type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16
+ *   len = 2
+ *   data = {0xFE, 0x2C}
+ *   dataMask = {0xFF, 0xFF}
+ */
+struct chreBleGenericFilter {
+  //! Acceptable values among enum chreBleAdType
+  uint8_t type;
+
+  /**
+   * Length of data and dataMask. AD payloads shorter than this length will not
+   * be matched by the filter. Length must be greater than 0.
+   */
+  uint8_t len;
+
+  //! Used in combination with dataMask to filter an advertisement
+  char data[CHRE_BLE_DATA_LEN_MAX];
+
+  //! Used in combination with data to filter an advertisement
+  char dataMask[CHRE_BLE_DATA_LEN_MAX];
+};
+
+/**
+ * CHRE Bluetooth LE scan filters are based on a combination of an RSSI
+ * threshold and generic scan filters as defined by AD Type, mask, and values.
+ *
+ * CHRE-provided filters are implemented in a best-effort manner, depending on
+ * HW capabilities of the system and available resources. Therefore, provided
+ * scan results may be a superset of the specified filters. Nanoapps should try
+ * to take advantage of CHRE scan filters as much as possible, but must design
+ * their logic as to not depend on CHRE filtering.
+ *
+ * The syntax of CHRE scan filter definitions are based on the Android
+ * Advertising Packet Content Filter (APCF) HCI requirement subtype 0x08
+ * ref:
+ * https://source.android.com/devices/bluetooth/hci_requirements#le_apcf_command-set_filtering_parameters_sub_cmd
+ * and AD Types as defined in the Bluetooth spec Assigned Numbers, Generic
+ * Access Profile
+ * ref: https://www.bluetooth.com/specifications/assigned-numbers/
+ *
+ * Even though the scan filters are defined in a generic manner, CHRE Bluetooth
+ * is expected to initially support only a limited set of AD Types.
+ */
+struct chreBleScanFilter {
+  //! RSSI threshold filter (Corresponding HCI OCF: 0x0157, Sub: 0x01), where
+  //! advertisements with RSSI values below this threshold may be disregarded.
+  //! An rssiThreshold value of CHRE_BLE_RSSI_THRESHOLD_NONE indicates no RSSI
+  //! filtering.
+  int8_t rssiThreshold;
+
+  //! Number of generic scan filters provided in the scanFilters array.
+  //! A scanFilterCount value of 0 indicates no generic scan filters.
+  uint8_t scanFilterCount;
+
+  //! Pointer to an array of scan filters. If the array contains more than one
+  //! entry, advertisements matching any of the entries will be returned
+  //! (functional OR).
+  const struct chreBleGenericFilter *scanFilters;
+};
+
+/**
+ * CHRE BLE advertising address type is based on the BT Core Spec v5.2, Vol 4,
+ * Part E, Section 7.7.65.13, LE Extended Advertising Report event,
+ * Address_Type.
+ */
+enum chreBleAddressType {
+  //! Public device address.
+  CHRE_BLE_ADDRESS_TYPE_PUBLIC = 0x00,
+
+  //! Random device address.
+  CHRE_BLE_ADDRESS_TYPE_RANDOM = 0x01,
+
+  //! Public identity address (corresponds to resolved private address).
+  CHRE_BLE_ADDRESS_TYPE_PUBLIC_IDENTITY = 0x02,
+
+  //! Random (static) Identity Address (corresponds to resolved private
+  //! address)
+  CHRE_BLE_ADDRESS_TYPE_RANDOM_IDENTITY = 0x03,
+
+  //! No address provided (anonymous advertisement).
+  CHRE_BLE_ADDRESS_TYPE_NONE = 0xff,
+};
+
+/**
+ * CHRE BLE physical (PHY) channel encoding type, if supported, is based on the
+ * BT Core Spec v5.2, Vol 4, Part E, Section 7.7.65.13, LE Extended Advertising
+ * Report event, entries Primary_PHY and Secondary_PHY.
+ */
+enum chreBlePhyType {
+  //! No packets on this PHY (only on the secondary channel), or feature not
+  //! supported.
+  CHRE_BLE_PHY_NONE = 0x00,
+
+  //! LE 1 MBPS PHY encoding.
+  CHRE_BLE_PHY_1M = 0x01,
+
+  //! LE 2 MBPS PHY encoding (only on the secondary channel).
+  CHRE_BLE_PHY_2M = 0x02,
+
+  //! LE long-range coded PHY encoding.
+  CHRE_BLE_PHY_CODED = 0x03,
+};
+
+/**
+ * The CHRE BLE Advertising Report event is based on the BT Core Spec v5.2,
+ * Vol 4, Part E, Section 7.7.65.13, LE Extended Advertising Report event, with
+ * the following differences:
+ *
+ * 1) A CHRE timestamp field, which can be useful if CHRE is batching results.
+ * 2) Reordering of the rssi and periodicAdvertisingInterval fields for memory
+ *    alignment (prevent padding).
+ * 3) Addition of four reserved bytes to reclaim padding.
+ */
+struct chreBleAdvertisingReport {
+  //! The base timestamp, in nanoseconds, in the same time base as chreGetTime()
+  uint64_t timestamp;
+
+  //! @see CHRE_BLE_EVENT
+  uint8_t eventTypeAndDataStatus;
+
+  //! Advertising address type as defined in enum chreBleAddressType
+  uint8_t addressType;
+
+  //! Advertising device address
+  uint8_t address[CHRE_BLE_ADDRESS_LEN];
+
+  //! Advertiser PHY on primary advertising physical channel, if supported, as
+  //! defined in enum chreBlePhyType.
+  uint8_t primaryPhy;
+
+  //! Advertiser PHY on secondary advertising physical channel, if supported, as
+  //! defined in enum chreBlePhyType.
+  uint8_t secondaryPhy;
+
+  //! Value of the Advertising SID subfield in the ADI field of the PDU among
+  //! the range of [0, 0x0f].
+  //! CHRE_BLE_ADI_NONE indicates no ADI field was provided.
+  //! Other values are reserved.
+  uint8_t advertisingSid;
+
+  //! Transmit (Tx) power in dBm. Typical values are [-127, 20].
+  //! CHRE_BLE_TX_POWER_NONE indicates Tx power not available.
+  int8_t txPower;
+
+  //! Interval of the periodic advertising in 1.25 ms intervals, i.e.
+  //! time = periodicAdvertisingInterval * 1.25 ms
+  //! 0 means no periodic advertising. Minimum value is otherwise 6 (7.5 ms).
+  uint16_t periodicAdvertisingInterval;
+
+  //! RSSI in dBm. Typical values are [-127, 20].
+  //! CHRE_BLE_RSSI_NONE indicates RSSI is not available.
+  int8_t rssi;
+
+  //! Direct address type (i.e. only accept connection requests from a known
+  //! peer device) as defined in enum chreBleAddressType.
+  uint8_t directAddressType;
+
+  //! Direct address (i.e. only accept connection requests from a known peer
+  //! device).
+  uint8_t directAddress[CHRE_BLE_ADDRESS_LEN];
+
+  //! Length of data field. Acceptable range is [0, 31] for legacy and
+  //! [0, 229] for extended advertisements.
+  uint16_t dataLength;
+
+  //! dataLength bytes of data, or null if dataLength is 0
+  const uint8_t *data;
+
+  //! Reserved for future use; set to 0
+  uint32_t reserved;
+};
+
+/**
+ * A CHRE BLE Advertising Event can contain any number of CHRE BLE Advertising
+ * Reports (i.e. advertisements).
+ */
+struct chreBleAdvertisementEvent {
+  //! Reserved for future use; set to 0
+  uint16_t reserved;
+
+  //! Number of advertising reports in this event
+  uint16_t numReports;
+
+  //! Array of length numReports
+  const struct chreBleAdvertisingReport *reports;
+};
+
+/**
+ * Retrieves a set of flags indicating the BLE features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_BLE_CAPABILITIES_* flags set. @see
+ *         CHRE_BLE_CAPABILITIES
+ *
+ * @since v1.6
+ */
+uint32_t chreBleGetCapabilities(void);
+
+/**
+ * Retrieves a set of flags indicating the BLE filtering features supported by
+ * the current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_BLE_FILTER_CAPABILITIES_* flags set.
+ *         @see CHRE_BLE_FILTER_CAPABILITIES
+ *
+ * @since v1.6
+ */
+uint32_t chreBleGetFilterCapabilities(void);
+
+/**
+ * Helper function to extract event type from eventTypeAndDataStatus as defined
+ * in the BT Core Spec v5.2, Vol 4, Part E, Section 7.7.65.13, LE Extended
+ * Advertising Report event, entry Event_Type.
+ *
+ * @see CHRE_BLE_EVENT
+ *
+ * @param eventTypeAndDataStatus Combined event type and data status
+ *
+ * @return The event type portion of eventTypeAndDataStatus
+ */
+static inline uint8_t chreBleGetEventType(uint8_t eventTypeAndDataStatus) {
+  return (eventTypeAndDataStatus & CHRE_BLE_EVENT_MASK_TYPE);
+}
+
+/**
+ * Helper function to extract data status from eventTypeAndDataStatus as defined
+ * in the BT Core Spec v5.2, Vol 4, Part E, Section 7.7.65.13, LE Extended
+ * Advertising Report event, entry Event_Type.
+ *
+ * @see CHRE_BLE_EVENT
+ *
+ * @param eventTypeAndDataStatus Combined event type and data status
+ *
+ * @return The data status portion of eventTypeAndDataStatus
+ */
+static inline uint8_t chreBleGetDataStatus(uint8_t eventTypeAndDataStatus) {
+  return (eventTypeAndDataStatus & CHRE_BLE_EVENT_MASK_DATA_STATUS);
+}
+
+/**
+ * Helper function to to combine an event type with a data status to create
+ * eventTypeAndDataStatus as defined in the BT Core Spec v5.2, Vol 4, Part E,
+ * Section 7.7.65.13, LE Extended Advertising Report event, entry Event_Type.
+ *
+ * @see CHRE_BLE_EVENT
+ *
+ * @param eventType Event type
+ * @param dataStatus Data status
+ *
+ * @return A combined eventTypeAndDataStatus
+ */
+static inline uint8_t chreBleGetEventTypeAndDataStatus(uint8_t eventType,
+                                                       uint8_t dataStatus) {
+  return ((eventType & CHRE_BLE_EVENT_MASK_TYPE) |
+          (dataStatus & CHRE_BLE_EVENT_MASK_DATA_STATUS));
+}
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_BLE somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following BLE APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to access BLE functionality by adding
+ * metadata to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Start Bluetooth LE (BLE) scanning on CHRE.
+ *
+ * The result of the operation will be delivered asynchronously via the CHRE
+ * event CHRE_EVENT_BLE_ASYNC_RESULT.
+ *
+ * The scan results will be delivered asynchronously via the CHRE event
+ * CHRE_EVENT_BLE_ADVERTISEMENT.
+ *
+ * If the Bluetooth setting is disabled at the Android level, CHRE is expected
+ * to return a result with CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * If chreBleStartScanAsync() is called while a previous scan has been started,
+ * the previous scan will be stopped first and replaced with the new scan.
+ *
+ * Note that some corresponding Android parameters are missing from the CHRE
+ * API, where the following default or typical parameters are used:
+ * Callback type: CALLBACK_TYPE_ALL_MATCHES
+ * Result type: SCAN_RESULT_TYPE_FULL
+ * Match mode: MATCH_MODE_AGGRESSIVE
+ * Number of matches per filter: MATCH_NUM_MAX_ADVERTISEMENT
+ * Legacy-only: false
+ * PHY type: PHY_LE_ALL_SUPPORTED
+ *
+ * @param mode Scanning mode selected among enum chreBleScanMode
+ * @param reportDelayMs Maximum requested batching delay in ms. 0 indicates no
+ *                      batching. Note that the system may deliver results
+ *                      before the maximum specified delay is reached.
+ * @param filter Pointer to the requested best-effort filter configuration as
+ *               defined by struct chreBleScanFilter. The ownership of filter
+ *               and its nested elements remains with the caller, and the caller
+ *               may release it as soon as chreBleStartScanAsync() returns.
+ *
+ * @return True to indicate that the request was accepted. False otherwise.
+ *
+ * @since v1.6
+ */
+bool chreBleStartScanAsync(enum chreBleScanMode mode, uint32_t reportDelayMs,
+                           const struct chreBleScanFilter *filter);
+/**
+ * Stops a CHRE BLE scan.
+ *
+ * The result of the operation will be delivered asynchronously via the CHRE
+ * event CHRE_EVENT_BLE_ASYNC_RESULT.
+ *
+ * @return True to indicate that the request was accepted. False otherwise.
+ *
+ * @since v1.6
+ */
+bool chreBleStopScanAsync(void);
+
+/**
+ * Definitions for handling unsupported CHRE BLE scenarios.
+ */
+#else  // defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+#define CHRE_BLE_PERM_ERROR_STRING                                       \
+  "CHRE_NANOAPP_USES_BLE must be defined when building this nanoapp in " \
+  "order to refer to "
+
+#define chreBleStartScanAsync(...) \
+  CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleStartScanAsync")
+
+#define chreBleStopScanAsync(...) \
+  CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleStopScanAsync")
+
+#endif  // defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CHRE_BLE_H_ */
diff --git a/chre_api/legacy/v1_6/chre/common.h b/chre_api/legacy/v1_6/chre/common.h
new file mode 100644
index 0000000..8f5292e
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/common.h
@@ -0,0 +1,180 @@
+/*
+ * 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_COMMON_H_
+#define _CHRE_COMMON_H_
+
+/**
+ * @file
+ * Definitions shared across multiple CHRE header files
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Mask of the 5 most significant bytes in a 64-bit nanoapp or CHRE platform
+ * identifier, which represents the vendor ID portion of the ID.
+ */
+#define CHRE_VENDOR_ID_MASK  UINT64_C(0xFFFFFFFFFF000000)
+
+/**
+ * Vendor ID "Googl".  Used in nanoapp IDs and CHRE platform IDs developed and
+ * released by Google.
+ */
+#define CHRE_VENDOR_ID_GOOGLE  UINT64_C(0x476F6F676C000000)
+
+/**
+ * Vendor ID "GoogT".  Used for nanoapp IDs associated with testing done by
+ * Google.
+ */
+#define CHRE_VENDOR_ID_GOOGLE_TEST  UINT64_C(0x476F6F6754000000)
+
+/**
+ * Helper macro to mask off all bytes other than the vendor ID (most significant
+ * 5 bytes) in 64-bit nanoapp and CHRE platform identifiers.
+ *
+ * @see chreGetNanoappInfo()
+ * @see chreGetPlatformId()
+ */
+#define CHRE_EXTRACT_VENDOR_ID(id)  ((id) & CHRE_VENDOR_ID_MASK)
+
+/**
+ * Number of nanoseconds in one second, represented as an unsigned 64-bit
+ * integer
+ */
+#define CHRE_NSEC_PER_SEC  UINT64_C(1000000000)
+
+/**
+ * General timeout for asynchronous API requests. Unless specified otherwise, a
+ * function call that returns data asynchronously via an event, such as
+ * CHRE_EVENT_ASYNC_GNSS_RESULT, must do so within this amount of time.
+ */
+#define CHRE_ASYNC_RESULT_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC)
+
+
+/**
+ * A generic listing of error codes for use in {@link #chreAsyncResult} and
+ * elsewhere. In general, module-specific error codes may be added to this enum,
+ * but effort should be made to come up with a generic name that still captures
+ * the meaning of the error.
+ */
+// LINT.IfChange
+enum chreError {
+    //! No error occurred
+    CHRE_ERROR_NONE = 0,
+
+    //! An unspecified failure occurred
+    CHRE_ERROR = 1,
+
+    //! One or more supplied arguments are invalid
+    CHRE_ERROR_INVALID_ARGUMENT = 2,
+
+    //! Unable to satisfy request because the system is busy
+    CHRE_ERROR_BUSY = 3,
+
+    //! Unable to allocate memory
+    CHRE_ERROR_NO_MEMORY = 4,
+
+    //! The requested feature is not supported
+    CHRE_ERROR_NOT_SUPPORTED = 5,
+
+    //! A timeout occurred while processing the request
+    CHRE_ERROR_TIMEOUT = 6,
+
+    //! The relevant capability is disabled, for example due to a user
+    //! configuration that takes precedence over this request
+    CHRE_ERROR_FUNCTION_DISABLED = 7,
+
+    //! The request was rejected due to internal rate limiting of the requested
+    //! functionality - the client may try its request again after waiting an
+    //! unspecified amount of time
+    CHRE_ERROR_REJECTED_RATE_LIMIT = 8,
+
+    //! The requested functionality is not currently accessible from the CHRE,
+    //! because another client, such as the main applications processor, is
+    //! currently controlling it.
+    CHRE_ERROR_FUNCTION_RESTRICTED_TO_OTHER_MASTER = 9,
+    CHRE_ERROR_FUNCTION_RESTRICTED_TO_OTHER_CLIENT = 9,
+
+    //! This request is no longer valid. It may have been replaced by a newer
+    //! request before taking effect.
+    CHRE_ERROR_OBSOLETE_REQUEST = 10,
+
+    //!< Do not exceed this value when adding new error codes
+    CHRE_ERROR_LAST = UINT8_MAX,
+};
+// LINT.ThenChange(core/include/chre/core/api_manager_common.h)
+
+/**
+ * Generic data structure to indicate the result of an asynchronous operation.
+ *
+ * @note
+ * The general model followed by CHRE for asynchronous operations is that a
+ * request function returns a boolean value that indicates whether the request
+ * was accepted for further processing. The actual result of the operation is
+ * provided in a subsequent event sent with an event type that is defined in the
+ * specific API. Typically, a "cookie" parameter is supplied to allow the client
+ * to tie the response to a specific request, or pass data through, etc. The
+ * response is expected to be delivered within CHRE_ASYNC_RESULT_TIMEOUT_NS if
+ * not specified otherwise.
+ *
+ * The CHRE implementation must allow for multiple asynchronous requests to be
+ * outstanding at a given time, under reasonable resource constraints. Further,
+ * requests must be processed in the same order as supplied by the client of the
+ * API in order to maintain causality. Using GNSS as an example, if a client
+ * calls chreGnssLocationSessionStartAsync() and then immediately calls
+ * chreGnssLocationSessionStopAsync(), the final result must be that the
+ * location session is stopped. Whether requests always complete in the
+ * order that they are given is implementation-defined. For example, if a client
+ * calls chreGnssLocationSessionStart() and then immediately calls
+ * chreGnssMeasurementSessionStart(), it is possible for the
+ * CHRE_EVENT_GNSS_RESULT associated with the measurement session to be
+ * delivered before the one for the location session.
+ */
+struct chreAsyncResult {
+    //! Indicates the request associated with this result. The interpretation of
+    //! values in this field is dependent upon the event type provided when this
+    //! result was delivered.
+    uint8_t requestType;
+
+    //! Set to true if the request was successfully processed
+    bool success;
+
+    //! If the request failed (success is false), this is set to a value from
+    //! enum chreError (other than CHRE_ERROR_NONE), which may provide
+    //! additional information about the nature of the failure.
+    //! @see #chreError
+    uint8_t errorCode;
+
+    //! Reserved for future use, set to 0
+    uint8_t reserved;
+
+    //! Set to the cookie parameter given to the request function tied to this
+    //! result
+    const void *cookie;
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CHRE_COMMON_H_ */
diff --git a/chre_api/legacy/v1_6/chre/event.h b/chre_api/legacy/v1_6/chre/event.h
new file mode 100644
index 0000000..77811b8
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/event.h
@@ -0,0 +1,902 @@
+/*
+ * 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_EVENT_H_
+#define _CHRE_EVENT_H_
+
+/**
+ * @file
+ * Context Hub Runtime Environment API dealing with events and messages.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <chre/toolchain.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The CHRE implementation is required to provide the following preprocessor
+ * defines via the build system.
+ *
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE: The maximum size, in bytes, allowed for
+ *     a message sent to chreSendMessageToHostEndpoint().  This must be at least
+ *     CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE.
+ */
+
+#ifndef CHRE_MESSAGE_TO_HOST_MAX_SIZE
+#error CHRE_MESSAGE_TO_HOST_MAX_SIZE must be defined by the CHRE implementation
+#endif
+
+/**
+ * The minimum size, in bytes, any CHRE implementation will use for
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE is set to 1000 for v1.5+ CHRE implementations,
+ * and 128 for v1.0-v1.4 implementations (previously kept in
+ * CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, which has been removed).
+ *
+ * All CHRE implementations supporting v1.5+ must support the raised limit of
+ * 1000 bytes, however a nanoapp compiled against v1.5 cannot assume this
+ * limit if there is a possibility their binary will run on a v1.4 or earlier
+ * implementation that had a lower limit. To allow for nanoapp compilation in
+ * these situations, CHRE_MESSAGE_TO_HOST_MAX_SIZE must be set to the minimum
+ * value the nanoapp may encounter, and CHRE_NANOAPP_SUPPORTS_PRE_V1_5 can be
+ * defined to skip the compile-time check.
+ */
+#if (!defined(CHRE_NANOAPP_SUPPORTS_PRE_V1_5) && \
+     CHRE_MESSAGE_TO_HOST_MAX_SIZE < 1000) ||    \
+    (defined(CHRE_NANOAPP_SUPPORTS_PRE_V1_5) &&  \
+     CHRE_MESSAGE_TO_HOST_MAX_SIZE < 128)
+#error CHRE_MESSAGE_TO_HOST_MAX_SIZE is too small.
+#endif
+
+/**
+ * The lowest numerical value legal for a user-defined event.
+ *
+ * The system reserves all event values from 0 to 0x7FFF, inclusive.
+ * User events may use any value in the range 0x8000 to 0xFFFF, inclusive.
+ *
+ * Note that the same event values might be used by different nanoapps
+ * for different meanings.  This is not a concern, as these values only
+ * have meaning when paired with the originating nanoapp.
+ */
+#define CHRE_EVENT_FIRST_USER_VALUE  UINT16_C(0x8000)
+
+/**
+ * nanoappHandleEvent argument: struct chreMessageFromHostData
+ *
+ * The format of the 'message' part of this structure is left undefined,
+ * and it's up to the nanoapp and host to have an established protocol
+ * beforehand.
+ */
+#define CHRE_EVENT_MESSAGE_FROM_HOST  UINT16_C(0x0001)
+
+/**
+ * nanoappHandleEvent argument: 'cookie' given to chreTimerSet() method.
+ *
+ * Indicates that a timer has elapsed, in accordance with how chreTimerSet() was
+ * invoked.
+ */
+#define CHRE_EVENT_TIMER  UINT16_C(0x0002)
+
+/**
+ * nanoappHandleEvent argument: struct chreNanoappInfo
+ *
+ * Indicates that a nanoapp has successfully started (its nanoappStart()
+ * function has been called, and it returned true) and is able to receive events
+ * sent via chreSendEvent().  Note that this event is not sent for nanoapps that
+ * were started prior to the current nanoapp - use chreGetNanoappInfo() to
+ * determine if another nanoapp is already running.
+ *
+ * @see chreConfigureNanoappInfoEvents
+ * @since v1.1
+ */
+#define CHRE_EVENT_NANOAPP_STARTED  UINT16_C(0x0003)
+
+/**
+ * nanoappHandleEvent argument: struct chreNanoappInfo
+ *
+ * Indicates that a nanoapp has stopped executing and is no longer able to
+ * receive events sent via chreSendEvent().  Any events sent prior to receiving
+ * this event are not guaranteed to have been delivered.
+ *
+ * @see chreConfigureNanoappInfoEvents
+ * @since v1.1
+ */
+#define CHRE_EVENT_NANOAPP_STOPPED  UINT16_C(0x0004)
+
+/**
+ * nanoappHandleEvent argument: NULL
+ *
+ * Indicates that CHRE has observed the host wake from low-power sleep state.
+ *
+ * @see chreConfigureHostSleepStateEvents
+ * @since v1.2
+ */
+#define CHRE_EVENT_HOST_AWAKE  UINT16_C(0x0005)
+
+/**
+ * nanoappHandleEvent argument: NULL
+ *
+ * Indicates that CHRE has observed the host enter low-power sleep state.
+ *
+ * @see chreConfigureHostSleepStateEvents
+ * @since v1.2
+ */
+#define CHRE_EVENT_HOST_ASLEEP  UINT16_C(0x0006)
+
+/**
+ * nanoappHandleEvent argument: NULL
+ *
+ * Indicates that CHRE is collecting debug dumps. Nanoapps can call
+ * chreDebugDumpLog() to log their debug data while handling this event.
+ *
+ * @see chreConfigureDebugDumpEvent
+ * @see chreDebugDumpLog
+ * @since v1.4
+ */
+#define CHRE_EVENT_DEBUG_DUMP  UINT16_C(0x0007)
+
+/**
+ * nanoappHandleEvent argument: struct chreHostEndpointNotification
+ *
+ * Notifications event regarding a host endpoint.
+ *
+ * @see chreConfigureHostEndpointNotifications
+ * @since v1.6
+ */
+#define CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION UINT16_C(0x0008)
+
+/**
+ * First possible value for CHRE_EVENT_SENSOR events.
+ *
+ * This allows us to separately define our CHRE_EVENT_SENSOR_* events in
+ * chre/sensor.h, without fear of collision with other event values.
+ */
+#define CHRE_EVENT_SENSOR_FIRST_EVENT  UINT16_C(0x0100)
+
+/**
+ * Last possible value for CHRE_EVENT_SENSOR events.
+ *
+ * This allows us to separately define our CHRE_EVENT_SENSOR_* events in
+ * chre/sensor.h, without fear of collision with other event values.
+ */
+#define CHRE_EVENT_SENSOR_LAST_EVENT  UINT16_C(0x02FF)
+
+/**
+ * First event in the block reserved for GNSS. These events are defined in
+ * chre/gnss.h.
+ */
+#define CHRE_EVENT_GNSS_FIRST_EVENT  UINT16_C(0x0300)
+#define CHRE_EVENT_GNSS_LAST_EVENT   UINT16_C(0x030F)
+
+/**
+ * First event in the block reserved for WiFi. These events are defined in
+ * chre/wifi.h.
+ */
+#define CHRE_EVENT_WIFI_FIRST_EVENT  UINT16_C(0x0310)
+#define CHRE_EVENT_WIFI_LAST_EVENT   UINT16_C(0x031F)
+
+/**
+ * First event in the block reserved for WWAN. These events are defined in
+ * chre/wwan.h.
+ */
+#define CHRE_EVENT_WWAN_FIRST_EVENT  UINT16_C(0x0320)
+#define CHRE_EVENT_WWAN_LAST_EVENT   UINT16_C(0x032F)
+
+/**
+ * First event in the block reserved for audio. These events are defined in
+ * chre/audio.h.
+ */
+#define CHRE_EVENT_AUDIO_FIRST_EVENT UINT16_C(0x0330)
+#define CHRE_EVENT_AUDIO_LAST_EVENT  UINT16_C(0x033F)
+
+/**
+ * First event in the block reserved for settings changed notifications.
+ * These events are defined in chre/user_settings.h
+ *
+ * @since v1.5
+ */
+#define CHRE_EVENT_SETTING_CHANGED_FIRST_EVENT UINT16_C(0x340)
+#define CHRE_EVENT_SETTING_CHANGED_LAST_EVENT  UINT16_C(0x34F)
+
+/**
+ * First event in the block reserved for Bluetooth LE. These events are defined
+ * in chre/ble.h.
+ */
+#define CHRE_EVENT_BLE_FIRST_EVENT UINT16_C(0x0350)
+#define CHRE_EVENT_BLE_LAST_EVENT  UINT16_C(0x035F)
+
+/**
+ * First in the extended range of values dedicated for internal CHRE
+ * implementation usage.
+ *
+ * This range is semantically the same as the internal event range defined
+ * below, but has been extended to allow for more implementation-specific events
+ * to be used.
+ *
+ * @since v1.1
+ */
+#define CHRE_EVENT_INTERNAL_EXTENDED_FIRST_EVENT  UINT16_C(0x7000)
+
+/**
+ * First in a range of values dedicated for internal CHRE implementation usage.
+ *
+ * If a CHRE wishes to use events internally, any values within this range
+ * are assured not to be taken by future CHRE API additions.
+ */
+#define CHRE_EVENT_INTERNAL_FIRST_EVENT  UINT16_C(0x7E00)
+
+/**
+ * Last in a range of values dedicated for internal CHRE implementation usage.
+ *
+ * If a CHRE wishes to use events internally, any values within this range
+ * are assured not to be taken by future CHRE API additions.
+ */
+#define CHRE_EVENT_INTERNAL_LAST_EVENT  UINT16_C(0x7FFF)
+
+/**
+ * A special value for the hostEndpoint argument in
+ * chreSendMessageToHostEndpoint() that indicates that the message should be
+ * delivered to all host endpoints.  This value will not be used in the
+ * hostEndpoint field of struct chreMessageFromHostData supplied with
+ * CHRE_EVENT_MESSAGE_FROM_HOST.
+ *
+ * @since v1.1
+ */
+#define CHRE_HOST_ENDPOINT_BROADCAST  UINT16_C(0xFFFF)
+
+/**
+ * A special value for hostEndpoint in struct chreMessageFromHostData that
+ * indicates that a host endpoint is unknown or otherwise unspecified.  This
+ * value may be received in CHRE_EVENT_MESSAGE_FROM_HOST, but it is not valid to
+ * provide it to chreSendMessageToHostEndpoint().
+ *
+ * @since v1.1
+ */
+#define CHRE_HOST_ENDPOINT_UNSPECIFIED  UINT16_C(0xFFFE)
+
+/**
+ * Bitmask values that can be given as input to the messagePermissions parameter
+ * of chreSendMessageWithPermissions(). These values are typically used by
+ * nanoapps when they used data from the corresponding CHRE APIs to produce the
+ * message contents being sent and is used to attribute permissions usage on
+ * the Android side. See chreSendMessageWithPermissions() for more details on
+ * how these values are used when sending a message.
+ *
+ * Values in the range
+ * [CHRE_MESSAGE_PERMISSION_VENDOR_START, CHRE_MESSAGE_PERMISSION_VENDOR_END]
+ * are reserved for vendors to use when adding support for permission-gated APIs
+ * in their implementations.
+ *
+ * On the Android side, CHRE permissions are mapped as follows:
+ * - CHRE_MESSAGE_PERMISSION_AUDIO: android.permission.RECORD_AUDIO
+ * - CHRE_MESSAGE_PERMISSION_GNSS, CHRE_MESSAGE_PERMISSION_WIFI, and
+ *   CHRE_MESSAGE_PERMISSION_WWAN: android.permission.ACCESS_FINE_LOCATION, and
+ *   android.permissions.ACCESS_BACKGROUND_LOCATION
+ *
+ * @since v1.5
+ *
+ * @defgroup CHRE_MESSAGE_PERMISSION
+ * @{
+ */
+
+#define CHRE_MESSAGE_PERMISSION_NONE UINT32_C(0)
+#define CHRE_MESSAGE_PERMISSION_AUDIO UINT32_C(1)
+#define CHRE_MESSAGE_PERMISSION_GNSS (UINT32_C(1) << 1)
+#define CHRE_MESSAGE_PERMISSION_WIFI (UINT32_C(1) << 2)
+#define CHRE_MESSAGE_PERMISSION_WWAN (UINT32_C(1) << 3)
+#define CHRE_MESSAGE_PERMISSION_BLE (UINT32_C(1) << 4)
+#define CHRE_MESSAGE_PERMISSION_VENDOR_START (UINT32_C(1) << 24)
+#define CHRE_MESSAGE_PERMISSION_VENDOR_END (UINT32_C(1) << 31)
+
+/** @} */
+
+/**
+ * Data provided with CHRE_EVENT_MESSAGE_FROM_HOST.
+ */
+struct chreMessageFromHostData {
+    /**
+     * Message type supplied by the host.
+     *
+     * @note In CHRE API v1.0, support for forwarding this field from the host
+     * was not strictly required, and some implementations did not support it.
+     * However, its support is mandatory as of v1.1.
+     */
+    union {
+        /**
+         * The preferred name to use when referencing this field.
+         *
+         * @since v1.1
+         */
+        uint32_t messageType;
+
+        /**
+         * @deprecated This is the name for the messageType field used in v1.0.
+         * Left to allow code to compile against both v1.0 and v1.1 of the API
+         * definition without needing to use #ifdefs. This will be removed in a
+         * future API update - use messageType instead.
+         */
+        uint32_t reservedMessageType;
+    };
+
+    /**
+     * The size, in bytes of the following 'message'.
+     *
+     * This can be 0.
+     */
+    uint32_t messageSize;
+
+    /**
+     * The message from the host.
+     *
+     * These contents are of a format that the host and nanoapp must have
+     * established beforehand.
+     *
+     * This data is 'messageSize' bytes in length.  Note that if 'messageSize'
+     * is 0, this might be NULL.
+     */
+    const void *message;
+
+    /**
+     * An identifier for the host-side entity that sent this message.  Unless
+     * this is set to CHRE_HOST_ENDPOINT_UNSPECIFIED, it can be used in
+     * chreSendMessageToHostEndpoint() to send a directed reply that will only
+     * be received by the given entity on the host.  Endpoint identifiers are
+     * opaque values assigned at runtime, so they cannot be assumed to always
+     * describe a specific entity across restarts.
+     *
+     * If running on a CHRE API v1.0 implementation, this field will always be
+     * set to CHRE_HOST_ENDPOINT_UNSPECIFIED.
+     *
+     * @since v1.1
+     */
+    uint16_t hostEndpoint;
+};
+
+/**
+ * Provides metadata for a nanoapp in the system.
+ */
+struct chreNanoappInfo {
+    /**
+     * Nanoapp identifier. The convention for populating this value is to set
+     * the most significant 5 bytes to a value that uniquely identifies the
+     * vendor, and the lower 3 bytes identify the nanoapp.
+     */
+    uint64_t appId;
+
+    /**
+     * Nanoapp version.  The semantics of this field are defined by the nanoapp,
+     * however nanoapps are recommended to follow the same scheme used for the
+     * CHRE version exposed in chreGetVersion().  That is, the most significant
+     * byte represents the major version, the next byte the minor version, and
+     * the lower two bytes the patch version.
+     */
+    uint32_t version;
+
+    /**
+     * The instance ID of this nanoapp, which can be used in chreSendEvent() to
+     * address an event specifically to this nanoapp.  This identifier is
+     * guaranteed to be unique among all nanoapps in the system.
+     *
+     * @since v1.6
+     * Instance ID is guaranteed to never go beyond INT16_MAX. This helps the
+     * instance ID be packed into other information inside an int (useful for
+     * RPC routing).
+     */
+    uint32_t instanceId;
+};
+
+/**
+ * The types of notification events that can be included in struct
+ * chreHostEndpointNotification.
+ *
+ * @defgroup HOST_ENDPOINT_NOTIFICATION_TYPE
+ * @{
+ */
+#define HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT UINT8_C(0)
+/** @} */
+
+/**
+ * Data provided in CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION.
+ */
+struct chreHostEndpointNotification {
+    /**
+     * The ID of the host endpoint that this notification is for.
+     */
+    uint16_t hostEndpointId;
+
+    /**
+     * The type of notification this event represents, which should be
+     * one of the HOST_ENDPOINT_NOTIFICATION_TYPE_* values.
+     */
+    uint8_t notificationType;
+
+    /**
+     * Reserved for future use, must be zero.
+     */
+    uint8_t reserved;
+};
+
+//! The maximum length of a host endpoint's name.
+#define CHRE_MAX_ENDPOINT_NAME_LEN (51)
+
+//! The maximum length of a host endpoint's tag.
+#define CHRE_MAX_ENDPOINT_TAG_LEN (51)
+
+/**
+ * The type of host endpoint that can be used in the hostEndpointType field
+ * of chreHostEndpointInfo.
+ *
+ * @since v1.6
+ *
+ * @defgroup CHRE_HOST_ENDPOINT_TYPE_
+ * @{
+ */
+
+//! The host endpoint is part of the Android system framework.
+#define CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK UINT8_C(0)
+
+//! The host endpoint is an Android app.
+#define CHRE_HOST_ENDPOINT_TYPE_APP UINT8_C(1)
+
+//! Values in the range [CHRE_HOST_ENDPOINT_TYPE_VENDOR_START,
+//! CHRE_HOST_ENDPOINT_TYPE_VENDOR_END] can be a custom defined host endpoint
+//! type for platform-specific vendor use.
+#define CHRE_HOST_ENDPOINT_TYPE_VENDOR_START UINT8_C(128)
+#define CHRE_HOST_ENDPOINT_TYPE_VENDOR_END UINT8_C(255)
+
+/** @} */
+
+/**
+ * Provides metadata for a host endpoint.
+ *
+ * @since v1.6
+ */
+struct chreHostEndpointInfo {
+    //! The endpoint ID of this host.
+    uint16_t hostEndpointId;
+
+    //! The type of host endpoint, which must be set to one of the
+    //! CHRE_HOST_ENDPOINT_TYPE_* values or a value in the vendor-reserved
+    //! range.
+    uint8_t hostEndpointType;
+
+    //! Flag indicating if the packageName/endpointName field is valid.
+    uint8_t isNameValid : 1;
+
+    //! Flag indicating if the attributionTag/endpointTag field is valid.
+    uint8_t isTagValid : 1;
+
+    //! A union of null-terminated host name strings.
+    union {
+        //! The Android package name associated with this host, valid if the
+        //! hostEndpointType is CHRE_HOST_ENDPOINT_TYPE_APP or
+        //! CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK. Refer to the Android documentation
+        //! for the package attribute in the app manifest.
+        char packageName[CHRE_MAX_ENDPOINT_NAME_LEN];
+
+        //! A generic endpoint name that can be used for endpoints that
+        //! may not have a package name.
+        char endpointName[CHRE_MAX_ENDPOINT_NAME_LEN];
+    };
+
+    //! A union of null-terminated host tag strings for further identification.
+    union {
+        //! The attribution tag associated with this host that is used to audit
+        //! access to data, which can be valid if the hostEndpointType is
+        //! CHRE_HOST_ENDPOINT_TYPE_APP. Refer to the Android documentation
+        //! regarding data audit using attribution tags.
+        char attributionTag[CHRE_MAX_ENDPOINT_TAG_LEN];
+
+        //! A generic endpoint tag that can be used for endpoints that
+        //! may not have an attribution tag.
+        char endpointTag[CHRE_MAX_ENDPOINT_TAG_LEN];
+    };
+};
+
+/**
+ * An RPC service exposed by a nanoapp.
+ *
+ * The implementation of the RPC interface is not defined by the HAL, and is written
+ * at the messaging endpoint layers (Android app and/or CHRE nanoapp). NanoappRpcService
+ * contains the informational metadata to be consumed by the RPC interface layer.
+ */
+struct chreNanoappRpcService {
+    /**
+     * The unique 64-bit ID of an RPC service exposed by a nanoapp. Note that
+     * the uniqueness is only required within the nanoapp's domain (i.e. the
+     * combination of the nanoapp ID and service id must be unique).
+     */
+    uint64_t id;
+
+    /**
+     * The software version of this service, which follows the sematic
+     * versioning scheme (see semver.org). It follows the format
+     * major.minor.patch, where major and minor versions take up one byte
+     * each, and the patch version takes up the final 2 bytes.
+     */
+    uint32_t version;
+};
+
+/**
+ * Callback which frees data associated with an event.
+ *
+ * This callback is (optionally) provided to the chreSendEvent() method as
+ * a means for freeing the event data and performing any other cleanup
+ * necessary when the event is completed.  When this callback is invoked,
+ * 'eventData' is no longer needed and can be released.
+ *
+ * @param eventType  The 'eventType' argument from chreSendEvent().
+ * @param eventData  The 'eventData' argument from chreSendEvent().
+ *
+ * @see chreSendEvent
+ */
+typedef void (chreEventCompleteFunction)(uint16_t eventType, void *eventData);
+
+/**
+ * Callback which frees a message.
+ *
+ * This callback is (optionally) provided to the chreSendMessageToHostEndpoint()
+ * method as a means for freeing the message.  When this callback is invoked,
+ * 'message' is no longer needed and can be released.  Note that this in
+ * no way assures that said message did or did not make it to the host, simply
+ * that this memory is no longer needed.
+ *
+ * @param message  The 'message' argument from chreSendMessageToHostEndpoint().
+ * @param messageSize  The 'messageSize' argument from
+ *     chreSendMessageToHostEndpoint().
+ *
+ * @see chreSendMessageToHostEndpoint
+ */
+typedef void (chreMessageFreeFunction)(void *message, size_t messageSize);
+
+
+/**
+ * Enqueue an event to be sent to another nanoapp.
+ *
+ * @param eventType  This is a user-defined event type, of at least the
+ *     value CHRE_EVENT_FIRST_USER_VALUE.  It is illegal to attempt to use any
+ *     of the CHRE_EVENT_* values reserved for the CHRE.
+ * @param eventData  A pointer value that will be understood by the receiving
+ *     app.  Note that NULL is perfectly acceptable.  It also is not required
+ *     that this be a valid pointer, although if this nanoapp is intended to
+ *     work on arbitrary CHRE implementations, then the size of a
+ *     pointer cannot be assumed to be a certain size.  Note that the caller
+ *     no longer owns this memory after the call.
+ * @param freeCallback  A pointer to a callback function.  After the lifetime
+ *     of 'eventData' is over (either through successful delivery or the event
+ *     being dropped), this callback will be invoked.  This argument is allowed
+ *     to be NULL, in which case no callback will be invoked.
+ * @param targetInstanceId  The ID of the instance we're delivering this event
+ *     to.  Note that this is allowed to be our own instance.  The instance ID
+ *     of a nanoapp can be retrieved by using chreGetNanoappInfoByInstanceId().
+ * @return true if the event was enqueued, false otherwise.  Note that even
+ *     if this method returns 'false', the 'freeCallback' will be invoked,
+ *     if non-NULL.  Note in the 'false' case, the 'freeCallback' may be
+ *     invoked directly from within chreSendEvent(), so it's necessary
+ *     for nanoapp authors to avoid possible recursion with this.
+ *
+ * @see chreEventDataFreeFunction
+ */
+bool chreSendEvent(uint16_t eventType, void *eventData,
+                   chreEventCompleteFunction *freeCallback,
+                   uint32_t targetInstanceId);
+
+/**
+ * Send a message to the host, using the broadcast endpoint
+ * CHRE_HOST_ENDPOINT_BROADCAST.  Refer to chreSendMessageToHostEndpoint() for
+ * further details.
+ *
+ * @see chreSendMessageToHostEndpoint
+ *
+ * @deprecated New code should use chreSendMessageToHostEndpoint() instead of
+ * this function.  A future update to the API may cause references to this
+ * function to produce a compiler warning.
+ */
+bool chreSendMessageToHost(void *message, uint32_t messageSize,
+                           uint32_t messageType,
+                           chreMessageFreeFunction *freeCallback)
+    CHRE_DEPRECATED("Use chreSendMessageToHostEndpoint instead");
+
+/**
+ * Send a message to the host, using CHRE_MESSAGE_PERMISSION_NONE for the
+ * associated message permissions. This method must only be used if no data
+ * provided by CHRE's audio, GNSS, WiFi, and WWAN APIs was used to produce the
+ * contents of the message being sent. Refer to chreSendMessageWithPermissions()
+ * for further details.
+ *
+ * @see chreSendMessageWithPermissions
+ *
+ * @since v1.1
+ */
+bool chreSendMessageToHostEndpoint(void *message, size_t messageSize,
+                                   uint32_t messageType, uint16_t hostEndpoint,
+                                   chreMessageFreeFunction *freeCallback);
+
+/**
+ * Send a message to the host, waking it up if it is currently asleep.
+ *
+ * This message is by definition arbitrarily defined.  Since we're not
+ * just a passing a pointer to memory around the system, but need to copy
+ * this into various buffers to send it to the host, the CHRE
+ * implementation cannot be asked to support an arbitrarily large message
+ * size.  As a result, we have the CHRE implementation define
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE.
+ *
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE is not given a value by the Platform API.  The
+ * Platform API does define CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, and requires
+ * that CHRE_MESSAGE_TO_HOST_MAX_SIZE is at least that value.
+ *
+ * As a result, if your message sizes are all less than
+ * CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, then you have no concerns on any
+ * CHRE implementation.  If your message sizes are larger, you'll need to
+ * come up with a strategy for splitting your message across several calls
+ * to this method.  As long as that strategy works for
+ * CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, it will work across all CHRE
+ * implementations (although on some implementations less calls to this
+ * method may be necessary).
+ *
+ * When sending a message to the host, the ContextHub service will enforce
+ * the host client has been granted Android-level permissions corresponding to
+ * the ones the nanoapp declares it uses through CHRE_NANOAPP_USES_AUDIO, etc.
+ * In addition to this, the permissions bitmask provided as input to this method
+ * results in the Android framework using app-ops to verify and log access upon
+ * message delivery to an application. This is primarily useful for ensuring
+ * accurate attribution for messages generated using permission-controlled data.
+ * The bitmask declared by the nanoapp for this message must be a
+ * subset of the permissions it declared it would use at build time or the
+ * message will be rejected.
+ *
+ * Nanoapps must use this method if the data they are sending contains or was
+ * derived from any data sampled through CHRE's audio, GNSS, WiFi, or WWAN APIs.
+ * Additionally, if vendors add APIs to expose data that would be guarded by a
+ * permission in Android, vendors must support declaring a message permission
+ * through this method.
+ *
+ * @param message  Pointer to a block of memory to send to the host.
+ *     NULL is acceptable only if messageSize is 0.  If non-NULL, this
+ *     must be a legitimate pointer (that is, unlike chreSendEvent(), a small
+ *     integral value cannot be cast to a pointer for this).  Note that the
+ *     caller no longer owns this memory after the call.
+ * @param messageSize  The size, in bytes, of the given message. If this exceeds
+ *     CHRE_MESSAGE_TO_HOST_MAX_SIZE, the message will be rejected.
+ * @param messageType  Message type sent to the app on the host.
+ *     NOTE: In CHRE API v1.0, support for forwarding this field to the host was
+ *     not strictly required, and some implementations did not support it.
+ *     However, its support is mandatory as of v1.1.
+ * @param hostEndpoint  An identifier for the intended recipient of the message,
+ *     or CHRE_HOST_ENDPOINT_BROADCAST if all registered endpoints on the host
+ *     should receive the message.  Endpoint identifiers are assigned on the
+ *     host side, and nanoapps may learn of the host endpoint ID of an intended
+ *     recipient via an initial message sent by the host.  This parameter is
+ *     always treated as CHRE_HOST_ENDPOINT_BROADCAST if running on a CHRE API
+ *     v1.0 implementation. CHRE_HOST_ENDPOINT_BROADCAST isn't allowed to be
+ *     specified if anything other than CHRE_MESSAGE_PERMISSION_NONE is given
+ *     as messagePermissions since doing so would potentially attribute
+ *     permissions usage to host clients that don't intend to consume the data.
+ * @param messagePermissions Bitmasked CHRE_MESSAGE_PERMISSION_ values that will
+ *     be converted to corresponding Android-level permissions and attributed
+ *     the host endpoint upon consumption of the message.
+ * @param freeCallback  A pointer to a callback function.  After the lifetime
+ *     of 'message' is over (which does not assure that 'message' made it to
+ *     the host, just that the transport layer no longer needs this memory),
+ *     this callback will be invoked.  This argument is allowed
+ *     to be NULL, in which case no callback will be invoked.
+ * @return true if the message was accepted for transmission, false otherwise.
+ *     Note that even if this method returns 'false', the 'freeCallback' will
+ *     be invoked, if non-NULL.  In either case, the 'freeCallback' may be
+ *     invoked directly from within chreSendMessageToHostEndpoint(), so it's
+ *     necessary for nanoapp authors to avoid possible recursion with this.
+ *
+ * @see chreMessageFreeFunction
+ *
+ * @since v1.5
+ */
+bool chreSendMessageWithPermissions(void *message, size_t messageSize,
+                                    uint32_t messageType, uint16_t hostEndpoint,
+                                    uint32_t messagePermissions,
+                                    chreMessageFreeFunction *freeCallback);
+
+/**
+ * Queries for information about a nanoapp running in the system.
+ *
+ * In the current API, appId is required to be unique, i.e. there cannot be two
+ * nanoapps running concurrently with the same appId.  If this restriction is
+ * removed in a future API version and multiple instances of the same appId are
+ * present, this function must always return the first app to start.
+ *
+ * @param appId Identifier for the nanoapp that the caller is requesting
+ *     information about.
+ * @param info Output parameter.  If this function returns true, this structure
+ *     will be populated with details of the specified nanoapp.
+ * @return true if a nanoapp with the given ID is currently running, and the
+ *     supplied info parameter was populated with its information.
+ *
+ * @since v1.1
+ */
+bool chreGetNanoappInfoByAppId(uint64_t appId, struct chreNanoappInfo *info);
+
+/**
+ * Queries for information about a nanoapp running in the system, using the
+ * runtime unique identifier.  This method can be used to get information about
+ * the sender of an event.
+ *
+ * @param instanceId
+ * @param info Output parameter.  If this function returns true, this structure
+ *     will be populated with details of the specified nanoapp.
+ * @return true if a nanoapp with the given instance ID is currently running,
+ *     and the supplied info parameter was populated with its information.
+ *
+ * @since v1.1
+ */
+bool chreGetNanoappInfoByInstanceId(uint32_t instanceId,
+                                    struct chreNanoappInfo *info);
+
+/**
+ * Configures whether this nanoapp will be notified when other nanoapps in the
+ * system start and stop, via CHRE_EVENT_NANOAPP_STARTED and
+ * CHRE_EVENT_NANOAPP_STOPPED.  These events are disabled by default, and if a
+ * nanoapp is not interested in interacting with other nanoapps, then it does
+ * not need to register for them.  However, if inter-nanoapp communication is
+ * desired, nanoapps are recommended to call this function from nanoappStart().
+ *
+ * If running on a CHRE platform that only supports v1.0 of the CHRE API, this
+ * function has no effect.
+ *
+ * @param enable true to enable these events, false to disable
+ *
+ * @see CHRE_EVENT_NANOAPP_STARTED
+ * @see CHRE_EVENT_NANOAPP_STOPPED
+ *
+ * @since v1.1
+ */
+void chreConfigureNanoappInfoEvents(bool enable);
+
+/**
+ * Configures whether this nanoapp will be notified when the host (applications
+ * processor) transitions between wake and sleep, via CHRE_EVENT_HOST_AWAKE and
+ * CHRE_EVENT_HOST_ASLEEP.  As chreSendMessageToHostEndpoint() wakes the host if
+ * it is asleep, these events can be used to opportunistically send data to the
+ * host only when it wakes up for some other reason.  Note that this event is
+ * not instantaneous - there is an inherent delay in CHRE observing power state
+ * changes of the host processor, which may be significant depending on the
+ * implementation, especially in the wake to sleep direction.  Therefore,
+ * nanoapps are not guaranteed that messages sent to the host between AWAKE and
+ * ASLEEP events will not trigger a host wakeup.  However, implementations must
+ * ensure that the nominal wake-up notification latency is strictly less than
+ * the minimum wake-sleep time of the host processor.  Implementations are also
+ * encouraged to minimize this and related latencies where possible, to avoid
+ * unnecessary host wake-ups.
+ *
+ * These events are only sent on transitions, so the initial state will not be
+ * sent to the nanoapp as an event - use chreIsHostAwake().
+ *
+ * @param enable true to enable these events, false to disable
+ *
+ * @see CHRE_EVENT_HOST_AWAKE
+ * @see CHRE_EVENT_HOST_ASLEEP
+ *
+ * @since v1.2
+ */
+void chreConfigureHostSleepStateEvents(bool enable);
+
+/**
+ * Retrieves the current sleep/wake state of the host (applications processor).
+ * Note that, as with the CHRE_EVENT_HOST_AWAKE and CHRE_EVENT_HOST_ASLEEP
+ * events, there is no guarantee that CHRE's view of the host processor's sleep
+ * state is instantaneous, and it may also change between querying the state and
+ * performing a host-waking action like sending a message to the host.
+ *
+ * @return true if by CHRE's own estimation the host is currently awake,
+ *     false otherwise
+ *
+ * @since v1.2
+ */
+bool chreIsHostAwake(void);
+
+/**
+ * Configures whether this nanoapp will be notified when CHRE is collecting
+ * debug dumps, via CHRE_EVENT_DEBUG_DUMP. This event is disabled by default,
+ * and if a nanoapp is not interested in logging its debug data, then it does
+ * not need to register for it.
+ *
+ * @param enable true to enable receipt of this event, false to disable.
+ *
+ * @see CHRE_EVENT_DEBUG_DUMP
+ * @see chreDebugDumpLog
+ *
+ * @since v1.4
+ */
+void chreConfigureDebugDumpEvent(bool enable);
+
+/**
+ * Configures whether this nanoapp will receive updates regarding a host
+ * endpoint that is connected with the Context Hub.
+ *
+ * If this API succeeds, the nanoapp will receive disconnection notifications,
+ * via the CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION event with type
+ * HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT, which can be invoked if the host
+ * has disconnected from the Context Hub either explicitly or implicitly (e.g.
+ * crashes). Nanoapps can use this notifications to clean up any resources
+ * associated with this host endpoint.
+ *
+ * @param hostEndpointId The host endpoint ID to configure notifications for.
+ * @param enable true to enable notifications.
+ *
+ * @return true on success
+ *
+ * @see chreMessageFromHostData
+ * @see chreHostEndpointNotification
+ * @see CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION
+ *
+ * @since v1.6
+ */
+bool chreConfigureHostEndpointNotifications(uint16_t hostEndpointId,
+                                            bool enable);
+
+/**
+ * Publishes an RPC service from this nanoapp.
+ *
+ * When this API is invoked, the list of RPC services will be provided to
+ * host applications interacting with the nanoapp.
+ *
+ * This function must be invoked from nanoappStart(), to guarantee stable output
+ * of the list of RPC services supported by the nanoapp.
+ *
+ * @param services A non-null pointer to the list of RPC services to publish.
+ * @param numServices The number of services to publish, i.e. the length of the
+ *   services array.
+ *
+ * @return true if the publishing is successful.
+ *
+ * @since v1.6
+ */
+bool chrePublishRpcServices(struct chreNanoappRpcService *services,
+                            size_t numServices);
+
+/**
+ * Retrieves metadata for a given host endpoint ID.
+ *
+ * This API will provide metadata regarding an endpoint associated with a
+ * host endpoint ID. The nanoapp should use this API to determine more
+ * information about a host endpoint that has sent a message to the nanoapp,
+ * after receiving a chreMessageFromHostData (which includes the endpoint ID).
+ *
+ * If the given host endpoint ID is not associated with a valid host (or if the
+ * client has disconnected from the Android or CHRE framework, i.e. no longer
+ * able to send messages to CHRE), this method will return false and info will
+ * not be populated.
+ *
+ * @param hostEndpointId The endpoint ID of the host to get info for.
+ * @param info The non-null pointer to where the metadata will be stored.
+ *
+ * @return true if info has been successfully populated.
+ *
+ * @since v1.6
+ */
+bool chreGetHostEndpointInfo(uint16_t hostEndpointId,
+                             struct chreHostEndpointInfo *info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_EVENT_H_ */
+
diff --git a/chre_api/legacy/v1_6/chre/gnss.h b/chre_api/legacy/v1_6/chre/gnss.h
new file mode 100644
index 0000000..79a8f46
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/gnss.h
@@ -0,0 +1,604 @@
+/*
+ * 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_GNSS_H_
+#define _CHRE_GNSS_H_
+
+/**
+ * @file
+ * Global Navigation Satellite System (GNSS) API.
+ *
+ * These structures and definitions are based on the Android N GPS HAL.
+ * Refer to that header file (located at this path as of the time of this
+ * comment: hardware/libhardware/include/hardware/gps.h) and associated
+ * documentation for further details and explanations for these fields.
+ * References in comments like "(ref: GnssAccumulatedDeltaRangeState)" map to
+ * the relevant element in the GPS HAL where additional information can be
+ * found.
+ *
+ * In general, the parts of this API that are taken from the GPS HAL follow the
+ * naming conventions established in that interface rather than the CHRE API
+ * conventions, in order to avoid confusion and enable code re-use where
+ * applicable.
+ */
+
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <chre/common.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags that may be returned by chreGnssGetCapabilities()
+ * @defgroup CHRE_GNSS_CAPABILITIES
+ * @{
+ */
+
+//! A lack of flags indicates that GNSS is not supported in this CHRE
+#define CHRE_GNSS_CAPABILITIES_NONE          UINT32_C(0)
+
+//! GNSS position fixes are supported via chreGnssLocationSessionStartAsync()
+#define CHRE_GNSS_CAPABILITIES_LOCATION      UINT32_C(1 << 0)
+
+//! GNSS raw measurements are supported via
+//! chreGnssMeasurementSessionStartAsync()
+#define CHRE_GNSS_CAPABILITIES_MEASUREMENTS  UINT32_C(1 << 1)
+
+//! Location fixes supplied from chreGnssConfigurePassiveLocationListener()
+//! are tapped in at the GNSS engine level, so they include additional fixes
+//! such as those requested by the AP, and not just those requested by other
+//! nanoapps within CHRE (which is the case when this flag is not set)
+#define CHRE_GNSS_CAPABILITIES_GNSS_ENGINE_BASED_PASSIVE_LISTENER \
+                                             UINT32_C(1 << 2)
+
+/** @} */
+
+/**
+ * The current version of struct chreGnssDataEvent associated with this API
+ */
+#define CHRE_GNSS_DATA_EVENT_VERSION  UINT8_C(0)
+
+/**
+ * The maximum time the CHRE implementation is allowed to elapse before sending
+ * an event with the result of an asynchronous request, unless specified
+ * otherwise
+ */
+#define CHRE_GNSS_ASYNC_RESULT_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC)
+
+/**
+ * Produce an event ID in the block of IDs reserved for GNSS
+ * @param offset  Index into GNSS event ID block; valid range [0,15]
+ */
+#define CHRE_GNSS_EVENT_ID(offset)  (CHRE_EVENT_GNSS_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Communicates the asynchronous result of a request to the GNSS API, such as
+ * starting a location session via chreGnssLocationSessionStartAsync(). The
+ * requestType field in chreAsyncResult is set to a value from enum
+ * chreGnssRequestType.
+ */
+#define CHRE_EVENT_GNSS_ASYNC_RESULT  CHRE_GNSS_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreGnssLocationEvent
+ *
+ * Represents a location fix provided by the GNSS subsystem.
+ */
+#define CHRE_EVENT_GNSS_LOCATION      CHRE_GNSS_EVENT_ID(1)
+
+/**
+ * nanoappHandleEvent argument: struct chreGnssDataEvent
+ *
+ * Represents a set of GNSS measurements with associated clock data.
+ */
+#define CHRE_EVENT_GNSS_DATA          CHRE_GNSS_EVENT_ID(2)
+
+// NOTE: Do not add new events with ID > 15; only values 0-15 are reserved
+// (see chre/event.h)
+
+// Flags indicating the Accumulated Delta Range's states
+// (ref: GnssAccumulatedDeltaRangeState)
+#define CHRE_GNSS_ADR_STATE_UNKNOWN     UINT16_C(0)
+#define CHRE_GNSS_ADR_STATE_VALID       UINT16_C(1 << 0)
+#define CHRE_GNSS_ADR_STATE_RESET       UINT16_C(1 << 1)
+#define CHRE_GNSS_ADR_STATE_CYCLE_SLIP  UINT16_C(1 << 2)
+
+// Flags to indicate what fields in chreGnssClock are valid (ref: GnssClockFlags)
+#define CHRE_GNSS_CLOCK_HAS_LEAP_SECOND        UINT16_C(1 << 0)
+#define CHRE_GNSS_CLOCK_HAS_TIME_UNCERTAINTY   UINT16_C(1 << 1)
+#define CHRE_GNSS_CLOCK_HAS_FULL_BIAS          UINT16_C(1 << 2)
+#define CHRE_GNSS_CLOCK_HAS_BIAS               UINT16_C(1 << 3)
+#define CHRE_GNSS_CLOCK_HAS_BIAS_UNCERTAINTY   UINT16_C(1 << 4)
+#define CHRE_GNSS_CLOCK_HAS_DRIFT              UINT16_C(1 << 5)
+#define CHRE_GNSS_CLOCK_HAS_DRIFT_UNCERTAINTY  UINT16_C(1 << 6)
+
+// Flags to indicate which values are valid in a GpsLocation
+// (ref: GpsLocationFlags)
+#define CHRE_GPS_LOCATION_HAS_LAT_LONG           UINT16_C(1 << 0)
+#define CHRE_GPS_LOCATION_HAS_ALTITUDE           UINT16_C(1 << 1)
+#define CHRE_GPS_LOCATION_HAS_SPEED              UINT16_C(1 << 2)
+#define CHRE_GPS_LOCATION_HAS_BEARING            UINT16_C(1 << 3)
+#define CHRE_GPS_LOCATION_HAS_ACCURACY           UINT16_C(1 << 4)
+
+//! @since v1.3
+#define CHRE_GPS_LOCATION_HAS_ALTITUDE_ACCURACY  UINT16_C(1 << 5)
+//! @since v1.3
+#define CHRE_GPS_LOCATION_HAS_SPEED_ACCURACY     UINT16_C(1 << 6)
+//! @since v1.3
+#define CHRE_GPS_LOCATION_HAS_BEARING_ACCURACY   UINT16_C(1 << 7)
+
+/**
+ * The maximum number of instances of struct chreGnssMeasurement that may be
+ * included in a single struct chreGnssDataEvent.
+ *
+ * The value of this struct was increased from 64 to 128 in CHRE v1.5. For
+ * nanoapps targeting CHRE v1.4 or lower, the measurement_count will be capped
+ * at 64.
+ */
+#define CHRE_GNSS_MAX_MEASUREMENT  UINT8_C(128)
+#define CHRE_GNSS_MAX_MEASUREMENT_PRE_1_5  UINT8_C(64)
+
+// Flags indicating the GNSS measurement state (ref: GnssMeasurementState)
+#define CHRE_GNSS_MEASUREMENT_STATE_UNKNOWN                UINT16_C(0)
+#define CHRE_GNSS_MEASUREMENT_STATE_CODE_LOCK              UINT16_C(1 << 0)
+#define CHRE_GNSS_MEASUREMENT_STATE_BIT_SYNC               UINT16_C(1 << 1)
+#define CHRE_GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC          UINT16_C(1 << 2)
+#define CHRE_GNSS_MEASUREMENT_STATE_TOW_DECODED            UINT16_C(1 << 3)
+#define CHRE_GNSS_MEASUREMENT_STATE_MSEC_AMBIGUOUS         UINT16_C(1 << 4)
+#define CHRE_GNSS_MEASUREMENT_STATE_SYMBOL_SYNC            UINT16_C(1 << 5)
+#define CHRE_GNSS_MEASUREMENT_STATE_GLO_STRING_SYNC        UINT16_C(1 << 6)
+#define CHRE_GNSS_MEASUREMENT_STATE_GLO_TOD_DECODED        UINT16_C(1 << 7)
+#define CHRE_GNSS_MEASUREMENT_STATE_BDS_D2_BIT_SYNC        UINT16_C(1 << 8)
+#define CHRE_GNSS_MEASUREMENT_STATE_BDS_D2_SUBFRAME_SYNC   UINT16_C(1 << 9)
+#define CHRE_GNSS_MEASUREMENT_STATE_GAL_E1BC_CODE_LOCK     UINT16_C(1 << 10)
+#define CHRE_GNSS_MEASUREMENT_STATE_GAL_E1C_2ND_CODE_LOCK  UINT16_C(1 << 11)
+#define CHRE_GNSS_MEASUREMENT_STATE_GAL_E1B_PAGE_SYNC      UINT16_C(1 << 12)
+#define CHRE_GNSS_MEASUREMENT_STATE_SBAS_SYNC              UINT16_C(1 << 13)
+
+#define CHRE_GNSS_MEASUREMENT_CARRIER_FREQUENCY_UNKNOWN    0.f
+
+/**
+ * Indicates a type of request made in this API. Used to populate the resultType
+ * field of struct chreAsyncResult sent with CHRE_EVENT_GNSS_ASYNC_RESULT.
+ */
+enum chreGnssRequestType {
+    CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START    = 1,
+    CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP     = 2,
+    CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_START = 3,
+    CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_STOP  = 4,
+};
+
+/**
+ * Constellation type associated with an SV
+ */
+enum chreGnssConstellationType {
+    CHRE_GNSS_CONSTELLATION_UNKNOWN = 0,
+    CHRE_GNSS_CONSTELLATION_GPS     = 1,
+    CHRE_GNSS_CONSTELLATION_SBAS    = 2,
+    CHRE_GNSS_CONSTELLATION_GLONASS = 3,
+    CHRE_GNSS_CONSTELLATION_QZSS    = 4,
+    CHRE_GNSS_CONSTELLATION_BEIDOU  = 5,
+    CHRE_GNSS_CONSTELLATION_GALILEO = 6,
+};
+
+/**
+ * Enumeration of available values for the chreGnssMeasurement multipath indicator
+ */
+enum chreGnssMultipathIndicator {
+    //! The indicator is not available or unknown
+    CHRE_GNSS_MULTIPATH_INDICATOR_UNKNOWN     = 0,
+    //! The measurement is indicated to be affected by multipath
+    CHRE_GNSS_MULTIPATH_INDICATOR_PRESENT     = 1,
+    //! The measurement is indicated to be not affected by multipath
+    CHRE_GNSS_MULTIPATH_INDICATOR_NOT_PRESENT = 2,
+};
+
+/**
+ * Represents an estimate of the GNSS clock time (see the Android GPS HAL for
+ * more detailed information)
+ */
+struct chreGnssClock {
+    //! The GNSS receiver hardware clock value in nanoseconds, including
+    //! uncertainty
+    int64_t time_ns;
+
+    //! The difference between hardware clock inside GNSS receiver and the
+    //! estimated GNSS time in nanoseconds; contains bias uncertainty
+    int64_t full_bias_ns;
+
+    //! Sub-nanosecond bias, adds to full_bias_ns
+    float bias_ns;
+
+    //! The clock's drift in nanoseconds per second
+    float drift_nsps;
+
+    //! 1-sigma uncertainty associated with the clock's bias in nanoseconds
+    float bias_uncertainty_ns;
+
+    //! 1-sigma uncertainty associated with the clock's drift in nanoseconds
+    //! per second
+    float drift_uncertainty_nsps;
+
+    //! While this number stays the same, timeNs should flow continuously
+    uint32_t hw_clock_discontinuity_count;
+
+    //! A set of flags indicating the validity of the fields in this data
+    //! structure (see GNSS_CLOCK_HAS_*)
+    uint16_t flags;
+
+    //! Reserved for future use; set to 0
+    uint8_t reserved[2];
+};
+
+/**
+ * Represents a GNSS measurement; contains raw and computed information (see the
+ * Android GPS HAL for more detailed information)
+ */
+struct chreGnssMeasurement {
+    //! Hardware time offset from time_ns for this measurement, in nanoseconds
+    int64_t time_offset_ns;
+
+    //! Accumulated delta range since the last channel reset in micro-meters
+    int64_t accumulated_delta_range_um;
+
+    //! Received GNSS satellite time at the time of measurement, in nanoseconds
+    int64_t received_sv_time_in_ns;
+
+    //! 1-sigma uncertainty of received GNSS satellite time, in nanoseconds
+    int64_t received_sv_time_uncertainty_in_ns;
+
+    //! Pseudorange rate at the timestamp in meters per second (uncorrected)
+    float pseudorange_rate_mps;
+
+    //! 1-sigma uncertainty of pseudorange rate in meters per second
+    float pseudorange_rate_uncertainty_mps;
+
+    //! 1-sigma uncertainty of the accumulated delta range in meters
+    float accumulated_delta_range_uncertainty_m;
+
+    //! Carrier-to-noise density in dB-Hz, in the range of [0, 63]
+    float c_n0_dbhz;
+
+    //! Signal to noise ratio (dB), power above observed noise at correlators
+    float snr_db;
+
+    //! Satellite sync state flags (GNSS_MEASUREMENT_STATE_*) - sets modulus for
+    //! received_sv_time_in_ns
+    uint16_t state;
+
+    //! Set of ADR state flags (GNSS_ADR_STATE_*)
+    uint16_t accumulated_delta_range_state;
+
+    //! Satellite vehicle ID number
+    int16_t svid;
+
+    //! Constellation of the given satellite vehicle
+    //! @see #chreGnssConstellationType
+    uint8_t constellation;
+
+    //! @see #chreGnssMultipathIndicator
+    uint8_t multipath_indicator;
+
+    //! Carrier frequency of the signal tracked in Hz.
+    //! For example, it can be the GPS central frequency for L1 = 1575.45 MHz,
+    //! or L2 = 1227.60 MHz, L5 = 1176.45 MHz, various GLO channels, etc.
+    //!
+    //! Set to CHRE_GNSS_MEASUREMENT_CARRIER_FREQUENCY_UNKNOWN if not reported.
+    //!
+    //! For an L1, L5 receiver tracking a satellite on L1 and L5 at the same
+    //! time, two chreGnssMeasurement structs must be reported for this same
+    //! satellite, in one of the measurement structs, all the values related to
+    //! L1 must be filled, and in the other all of the values related to L5
+    //! must be filled.
+    //! @since v1.4
+    float carrier_frequency_hz;
+};
+
+/**
+ * Data structure sent with events associated with CHRE_EVENT_GNSS_DATA, enabled
+ * via chreGnssMeasurementSessionStartAsync()
+ */
+struct chreGnssDataEvent {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that it only sends the client the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! Number of chreGnssMeasurement entries included in this event. Must be in
+    //! the range [0, CHRE_GNSS_MAX_MEASUREMENT]
+    uint8_t measurement_count;
+
+    //! Reserved for future use; set to 0
+    uint8_t reserved[6];
+
+    struct chreGnssClock clock;
+
+    //! Pointer to an array containing measurement_count measurements
+    const struct chreGnssMeasurement *measurements;
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_GNSS_LOCATION, enabled via
+ * chreGnssLocationSessionStartAsync(). This is modeled after GpsLocation in the
+ * GPS HAL, but does not use the double data type.
+ */
+struct chreGnssLocationEvent {
+    //! UTC timestamp for location fix in milliseconds since January 1, 1970
+    uint64_t timestamp;
+
+    //! Fixed point latitude, degrees times 10^7 (roughly centimeter resolution)
+    int32_t latitude_deg_e7;
+
+    //! Fixed point longitude, degrees times 10^7 (roughly centimeter
+    //! resolution)
+    int32_t longitude_deg_e7;
+
+    //! Altitude in meters above the WGS 84 reference ellipsoid
+    float altitude;
+
+    //! Horizontal speed in meters per second
+    float speed;
+
+    //! Clockwise angle between north and current heading, in degrees; range
+    //! [0, 360)
+    float bearing;
+
+    //! Expected horizontal accuracy in meters such that a circle with a radius
+    //! of length 'accuracy' from the latitude and longitude has a 68%
+    //! probability of including the true location.
+    float accuracy;
+
+    //! A set of flags indicating which fields in this structure are valid.
+    //! If any fields are not available, the flag must not be set and the field
+    //! must be initialized to 0.
+    //! @see #GpsLocationFlags
+    uint16_t flags;
+
+    //! Reserved for future use; set to 0
+    //! @since v1.3
+    uint8_t reserved[2];
+
+    //! Expected vertical accuracy in meters such that a range of
+    //! 2 * altitude_accuracy centered around altitude has a 68% probability of
+    //! including the true altitude.
+    //! @since v1.3
+    float altitude_accuracy;
+
+    //! Expected speed accuracy in meters per second such that a range of
+    //! 2 * speed_accuracy centered around speed has a 68% probability of
+    //! including the true speed.
+    //! @since v1.3
+    float speed_accuracy;
+
+    //! Expected bearing accuracy in degrees such that a range of
+    //! 2 * bearing_accuracy centered around bearing has a 68% probability of
+    //! including the true bearing.
+    //! @since v1.3
+    float bearing_accuracy;
+};
+
+
+/**
+ * Retrieves a set of flags indicating the GNSS features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the Nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_GNSS_CAPABILITIES_* flags set
+ *
+ * @since v1.1
+ */
+uint32_t chreGnssGetCapabilities(void);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_GNSS somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following GNSS APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to GNSS data by adding metadata
+ * to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_GNSS) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Initiates a GNSS positioning session, or changes the requested interval of an
+ * existing session. If starting or modifying the session was successful, then
+ * the GNSS engine will work on determining the device's position.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details. If the "Location" setting is disabled at the Android level,
+ * the CHRE implementation is expected to return a result with
+ * CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_LOCATION flag set, then this method will return false.
+ *
+ * @param minIntervalMs The desired minimum interval between location fixes
+ *        delivered to the client via CHRE_EVENT_GNSS_LOCATION, in milliseconds.
+ *        The requesting client must allow for fixes to be delivered at shorter
+ *        or longer interval than requested. For example, adverse RF conditions
+ *        may result in fixes arriving at a longer interval, etc.
+ * @param minTimeToNextFixMs The desired minimum time to the next location fix.
+ *        If this is 0, the GNSS engine should start working on the next fix
+ *        immediately. If greater than 0, the GNSS engine should not spend
+ *        measurable power to produce a location fix until this amount of time
+ *        has elapsed.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssLocationSessionStartAsync(uint32_t minIntervalMs,
+                                       uint32_t minTimeToNextFixMs,
+                                       const void *cookie);
+
+/**
+ * Terminates an existing GNSS positioning session. If no positioning session
+ * is active at the time of this request, it is treated as if an active session
+ * was successfully ended.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details.
+ *
+ * After CHRE_EVENT_GNSS_ASYNC_RESULT is delivered to the client, no more
+ * CHRE_EVENT_GNSS_LOCATION events will be delievered until a new location
+ * session is started.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_LOCATION flag set, then this method will return false.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssLocationSessionStopAsync(const void *cookie);
+
+/**
+ * Initiates a request to receive raw GNSS measurements. A GNSS measurement
+ * session can exist independently of location sessions. In other words, a
+ * Nanoapp is able to receive measurements at its requested interval both with
+ * and without an active location session.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details. If the "Location" setting is disabled at the Android level,
+ * the CHRE implementation is expected to return a result with
+ * CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_MEASUREMENTS flag set, then this method will return
+ * false.
+ *
+ * @param minIntervalMs The desired minimum interval between measurement reports
+ *        delivered via CHRE_EVENT_GNSS_DATA. When requested at 1000ms or
+ *        faster, and GNSS measurements are tracked, device should report
+ *        measurements as fast as requested, and shall report no slower than
+ *        once every 1000ms, on average.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssMeasurementSessionStartAsync(uint32_t minIntervalMs,
+                                          const void *cookie);
+
+/**
+ * Terminates an existing raw GNSS measurement session. If no measurement
+ * session is active at the time of this request, it is treated as if an active
+ * session was successfully ended.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_MEASUREMENTS flag set, then this method will return
+ * false.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssMeasurementSessionStopAsync(const void *cookie);
+
+/**
+ * Controls whether this nanoapp will passively receive GNSS-based location
+ * fixes produced as a result of location sessions initiated by other entities.
+ * This function allows a nanoapp to opportunistically receive location fixes
+ * via CHRE_EVENT_GNSS_LOCATION events without imposing additional power cost,
+ * though with no guarantees as to when or how often those events will arrive.
+ * There will be no duplication of events if a passive location listener and
+ * location session are enabled in parallel.
+ *
+ * Enabling passive location listening is not required to receive events for an
+ * active location session started via chreGnssLocationSessionStartAsync(). This
+ * setting is independent of the active location session, so modifying one does
+ * not have an effect on the other.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_LOCATION flag set or the value returned by
+ * chreGetApiVersion() is less than CHRE_API_VERSION_1_2, then this method will
+ * return false.
+ *
+ * If chreGnssGetCapabilities() includes
+ * CHRE_GNSS_CAPABILITIES_GNSS_ENGINE_BASED_PASSIVE_LISTENER, the passive
+ * registration is recorded at the GNSS engine level, so events include fixes
+ * requested by the applications processor and potentially other non-CHRE
+ * clients. If this flag is not set, then only fixes requested by other nanoapps
+ * within CHRE are provided.
+ *
+ * @param enable true to receive opportunistic location fixes, false to disable
+ *
+ * @return true if the configuration was processed successfully, false on error
+ *     or if this feature is not supported
+ *
+ * @since v1.2
+ * @note Requires GNSS permission
+ */
+bool chreGnssConfigurePassiveLocationListener(bool enable);
+
+#else  /* defined(CHRE_NANOAPP_USES_GNSS) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_GNSS_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_GNSS must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreGnssLocationSessionStartAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssLocationSessionStartAsync")
+#define chreGnssLocationSessionStopAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssLocationSessionStopAsync")
+#define chreGnssMeasurementSessionStartAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssMeasurementSessionStartAsync")
+#define chreGnssMeasurementSessionStopAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssMeasurementSessionStopAsync")
+#define chreGnssConfigurePassiveLocationListener(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssConfigurePassiveLocationListener")
+#endif  /* defined(CHRE_NANOAPP_USES_GNSS) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_GNSS_H_ */
diff --git a/chre_api/legacy/v1_6/chre/nanoapp.h b/chre_api/legacy/v1_6/chre/nanoapp.h
new file mode 100644
index 0000000..da199ee
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/nanoapp.h
@@ -0,0 +1,93 @@
+/*
+ * 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_NANOAPP_H_
+#define _CHRE_NANOAPP_H_
+
+/**
+ * @file
+ * Methods in the Context Hub Runtime Environment which must be implemented
+ * by the nanoapp.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Method invoked by the CHRE when loading the nanoapp.
+ *
+ * Every CHRE method is legal to call from this method.
+ *
+ * @return  'true' if the nanoapp successfully started.  'false' if the nanoapp
+ *     failed to properly initialize itself (for example, could not obtain
+ *     sufficient memory from the heap).  If this method returns 'false', the
+ *     nanoapp will be unloaded by the CHRE (and nanoappEnd will
+ *     _not_ be invoked in that case).
+ * @see nanoappEnd
+ */
+bool nanoappStart(void);
+
+/**
+ * Method invoked by the CHRE when there is an event for this nanoapp.
+ *
+ * Every CHRE method is legal to call from this method.
+ *
+ * @param senderInstanceId  The Instance ID for the source of this event.
+ *     Note that this may be CHRE_INSTANCE_ID, indicating that the event
+ *     was generated by the CHRE.
+ * @param eventType  The event type.  This might be one of the CHRE_EVENT_*
+ *     types defined in this API.  But it might also be a user-defined event.
+ * @param eventData  The associated data, if any, for this specific type of
+ *     event.  From the nanoapp's perspective, this eventData's lifetime ends
+ *     when this method returns, and thus any data the nanoapp wishes to
+ *     retain must be copied.  Note that interpretation of event data is
+ *     given by the event type, and for some events may not be a valid
+ *     pointer.  See documentation of the specific CHRE_EVENT_* types for how to
+ *     interpret this data for those.  Note that for user events, you will
+ *     need to establish what this data means.
+ */
+void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                        const void *eventData);
+
+/**
+ * Method invoked by the CHRE when unloading the nanoapp.
+ *
+ * It is not valid to attempt to send events or messages, or to invoke functions
+ * which will generate events to this app, within the nanoapp implementation of
+ * this function.  That means it is illegal for the nanoapp invoke any of the
+ * following:
+ *
+ * - chreSendEvent()
+ * - chreSendMessageToHost()
+ * - chreSensorConfigure()
+ * - chreSensorConfigureModeOnly()
+ * - chreTimerSet()
+ * - etc.
+ *
+ * @see nanoappStart
+ */
+void nanoappEnd(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_NANOAPP_H_ */
diff --git a/chre_api/legacy/v1_6/chre/re.h b/chre_api/legacy/v1_6/chre/re.h
new file mode 100644
index 0000000..20a69b6
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/re.h
@@ -0,0 +1,437 @@
+/*
+ * 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_RE_H_
+#define _CHRE_RE_H_
+
+/**
+ * @file
+ * Some of the core Runtime Environment utilities of the Context Hub
+ * Runtime Environment.
+ *
+ * This includes functions for memory allocation, logging, and timers.
+ */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <chre/toolchain.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The instance ID for the CHRE.
+ *
+ * This ID is used to identify events generated by the CHRE (as
+ * opposed to events generated by another nanoapp).
+ */
+#define CHRE_INSTANCE_ID  UINT32_C(0)
+
+/**
+ * A timer ID representing an invalid timer.
+ *
+ * This valid is returned by chreTimerSet() if a timer cannot be
+ * started.
+ */
+#define CHRE_TIMER_INVALID  UINT32_C(-1)
+
+
+/**
+ * The maximum size, in characters including null terminator, guaranteed for
+ * logging debug data with one call of chreDebugDumpLog() without getting
+ * truncated.
+ *
+ * @see chreDebugDumpLog
+ * @since v1.4
+ */
+#define CHRE_DEBUG_DUMP_MINIMUM_MAX_SIZE 1000
+
+/**
+ * Logging levels used to indicate severity level of logging messages.
+ *
+ * CHRE_LOG_ERROR: Something fatal has happened, i.e. something that will have
+ *     user-visible consequences and won't be recoverable without explicitly
+ *     deleting some data, uninstalling applications, wiping the data
+ *     partitions or reflashing the entire phone (or worse).
+ * CHRE_LOG_WARN: Something that will have user-visible consequences but is
+ *     likely to be recoverable without data loss by performing some explicit
+ *     action, ranging from waiting or restarting an app all the way to
+ *     re-downloading a new version of an application or rebooting the device.
+ * CHRE_LOG_INFO: Something interesting to most people happened, i.e. when a
+ *     situation is detected that is likely to have widespread impact, though
+ *     isn't necessarily an error.
+ * CHRE_LOG_DEBUG: Used to further note what is happening on the device that
+ *     could be relevant to investigate and debug unexpected behaviors. You
+ *     should log only what is needed to gather enough information about what
+ *     is going on about your component.
+ *
+ * There is currently no API to turn on/off logging by level, but we anticipate
+ * adding such in future releases.
+ *
+ * @see chreLog
+ */
+enum chreLogLevel {
+    CHRE_LOG_ERROR,
+    CHRE_LOG_WARN,
+    CHRE_LOG_INFO,
+    CHRE_LOG_DEBUG
+};
+
+
+/**
+ * Get the application ID.
+ *
+ * The application ID is set by the loader of the nanoapp.  This is not
+ * assured to be unique among all nanoapps running in the system.
+ *
+ * @return The application ID.
+ */
+uint64_t chreGetAppId(void);
+
+/**
+ * Get the instance ID.
+ *
+ * The instance ID is the CHRE handle to this nanoapp.  This is assured
+ * to be unique among all nanoapps running in the system, and to be
+ * different from the CHRE_INSTANCE_ID.  This is the ID used to communicate
+ * between nanoapps.
+ *
+ * @return The instance ID
+ */
+uint32_t chreGetInstanceId(void);
+
+/**
+ * A method for logging information about the system.
+ *
+ * The chreLog logging activity alone must not cause host wake-ups. For
+ * example, logs could be buffered in internal memory when the host is asleep,
+ * and delivered when appropriate (e.g. the host wakes up). If done this way,
+ * the internal buffer is recommended to be large enough (at least a few KB), so
+ * that multiple messages can be buffered. When these logs are sent to the host,
+ * they are strongly recommended to be made visible under the tag 'CHRE' in
+ * logcat - a future version of the CHRE API may make this a hard requirement.
+ *
+ * A log entry can have a variety of levels (@see LogLevel).  This function
+ * allows a variable number of arguments, in a printf-style format.
+ *
+ * A nanoapp needs to be able to rely upon consistent printf format
+ * recognition across any platform, and thus we establish formats which
+ * are required to be handled by every CHRE implementation.  Some of the
+ * integral formats may seem obscure, but this API heavily uses types like
+ * uint32_t and uint16_t.  The platform independent macros for those printf
+ * formats, like PRId32 or PRIx16, end up using some of these "obscure"
+ * formats on some platforms, and thus are required.
+ *
+ * For the initial N release, our emphasis is on correctly getting information
+ * into the log, and minimizing the requirements for CHRE implementations
+ * beyond that.  We're not as concerned about how the information is visually
+ * displayed.  As a result, there are a number of format sub-specifiers which
+ * are "OPTIONAL" for the N implementation.  "OPTIONAL" in this context means
+ * that a CHRE implementation is allowed to essentially ignore the specifier,
+ * but it must understand the specifier enough in order to properly skip it.
+ *
+ * For a nanoapp author, an OPTIONAL format means you might not get exactly
+ * what you want on every CHRE implementation, but you will always get
+ * something valid.
+ *
+ * To be clearer, here's an example with the OPTIONAL 0-padding for integers
+ * for different hypothetical CHRE implementations.
+ * Compliant, chose to implement OPTIONAL format:
+ *   chreLog(level, "%04x", 20) ==> "0014"
+ * Compliant, chose not to implement OPTIONAL format:
+ *   chreLog(level, "%04x", 20) ==> "14"
+ * Non-compliant, discarded format because the '0' was assumed to be incorrect:
+ *   chreLog(level, "%04x", 20) ==> ""
+ *
+ * Note that some of the OPTIONAL specifiers will probably become
+ * required in future APIs.
+ *
+ * We also have NOT_SUPPORTED specifiers.  Nanoapp authors should not use any
+ * NOT_SUPPORTED specifiers, as unexpected things could happen on any given
+ * CHRE implementation.  A CHRE implementation is allowed to support this
+ * (for example, when using shared code which already supports this), but
+ * nanoapp authors need to avoid these.
+ *
+ * Unless specifically noted as OPTIONAL or NOT_SUPPORTED, format
+ * (sub-)specifiers listed below are required.
+ *
+ * OPTIONAL format sub-specifiers:
+ * - '-' (left-justify within the given field width)
+ * - '+' (precede the result with a '+' sign if it is positive)
+ * - ' ' (precede the result with a blank space if no sign is going to be
+ *        output)
+ * - '#' (For 'o', 'x' or 'X', precede output with "0", "0x" or "0X",
+ *        respectively.  For floating point, unconditionally output a decimal
+ *        point.)
+ * - '0' (left pad the number with zeroes instead of spaces when <width>
+ *        needs padding)
+ * - <width> (A number representing the minimum number of characters to be
+ *            output, left-padding with blank spaces if needed to meet the
+ *            minimum)
+ * - '.'<precision> (A number which has different meaning depending on context.)
+ *    - Integer context: Minimum number of digits to output, padding with
+ *          leading zeros if needed to meet the minimum.
+ *    - 'f' context: Number of digits to output after the decimal
+ *          point (to the right of it).
+ *    - 's' context: Maximum number of characters to output.
+ *
+ * Integral format specifiers:
+ * - 'd' (signed)
+ * - 'u' (unsigned)
+ * - 'o' (octal)
+ * - 'x' (hexadecimal, lower case)
+ * - 'X' (hexadecimal, upper case)
+ *
+ * Integral format sub-specifiers (as prefixes to an above integral format):
+ * - 'hh' (char)
+ * - 'h' (short)
+ * - 'l' (long)
+ * - 'll' (long long)
+ * - 'z' (size_t)
+ * - 't' (ptrdiff_t)
+ *
+ * Other format specifiers:
+ * - 'f' (floating point)
+ * - 'c' (character)
+ * - 's' (character string, terminated by '\0')
+ * - 'p' (pointer)
+ * - '%' (escaping the percent sign (i.e. "%%" becomes "%"))
+ *
+ * NOT_SUPPORTED specifiers:
+ * - 'n' (output nothing, but fill in a given pointer with the number
+ *        of characters written so far)
+ * - '*' (indicates that the width/precision value comes from one of the
+ *        arguments to the function)
+ * - 'e', 'E' (scientific notation output)
+ * - 'g', 'G' (Shortest floating point representation)
+ *
+ * @param level  The severity level for this message.
+ * @param formatStr  Either the entirety of the message, or a printf-style
+ *     format string of the format documented above.
+ * @param ...  A variable number of arguments necessary for the given
+ *     'formatStr' (there may be no additional arguments for some 'formatStr's).
+ */
+CHRE_PRINTF_ATTR(2, 3)
+void chreLog(enum chreLogLevel level, const char *formatStr, ...);
+
+/**
+ * Get the system time.
+ *
+ * This returns a time in nanoseconds in reference to some arbitrary
+ * time in the past.  This method is only useful for determining timing
+ * between events on the system, and is not useful for determining
+ * any sort of absolute time.
+ *
+ * This value must always increase (and must never roll over).  This
+ * value has no meaning across CHRE reboots.
+ *
+ * @return The system time, in nanoseconds.
+ */
+uint64_t chreGetTime(void);
+
+/**
+ * Retrieves CHRE's current estimated offset between the local CHRE clock
+ * exposed in chreGetTime(), and the host-side clock exposed in the Android API
+ * SystemClock.elapsedRealtimeNanos().  This offset is formed as host time minus
+ * CHRE time, so that it can be added to the value returned by chreGetTime() to
+ * determine the current estimate of the host time.
+ *
+ * A call to this function must not require waking up the host and should return
+ * quickly.
+ *
+ * This function must always return a valid value from the earliest point that
+ * it can be called by a nanoapp.  In other words, it is not valid to return
+ * some fixed/invalid value while waiting for the initial offset estimate to be
+ * determined - this initial offset must be ready before nanoapps are started.
+ *
+ * @return An estimate of the offset between CHRE's time returned in
+ *     chreGetTime() and the time on the host given in the Android API
+ *     SystemClock.elapsedRealtimeNanos(), accurate to within +/- 10
+ *     milliseconds, such that adding this offset to chreGetTime() produces the
+ *     estimated current time on the host.  This value may change over time to
+ *     account for drift, etc., so multiple calls to this API may produce
+ *     different results.
+ *
+ * @since v1.1
+ */
+int64_t chreGetEstimatedHostTimeOffset(void);
+
+/**
+ * Convenience function to retrieve CHRE's estimate of the current time on the
+ * host, corresponding to the Android API SystemClock.elapsedRealtimeNanos().
+ *
+ * @return An estimate of the current time on the host, accurate to within
+ *     +/- 10 milliseconds.  This estimate is *not* guaranteed to be
+ *     monotonically increasing, and may move backwards as a result of receiving
+ *     new information from the host.
+ *
+ * @since v1.1
+ */
+static inline uint64_t chreGetEstimatedHostTime(void) {
+    int64_t offset = chreGetEstimatedHostTimeOffset();
+    uint64_t time = chreGetTime();
+
+    // Just casting time to int64_t and adding the (potentially negative) offset
+    // should be OK under most conditions, but this way avoids issues if
+    // time >= 2^63, which is technically allowed since we don't specify a start
+    // value for chreGetTime(), though one would assume 0 is roughly boot time.
+    if (offset >= 0) {
+        time += (uint64_t) offset;
+    } else {
+        // Assuming chreGetEstimatedHostTimeOffset() is implemented properly,
+        // this will never underflow, because offset = hostTime - chreTime,
+        // and both times are monotonically increasing (e.g. when determining
+        // the offset, if hostTime is 0 and chreTime is 100 we'll have
+        // offset = -100, but chreGetTime() will always return >= 100 after that
+        // point).
+        time -= (uint64_t) (offset * -1);
+    }
+
+    return time;
+}
+
+/**
+ * Set a timer.
+ *
+ * When the timer fires, nanoappHandleEvent will be invoked with
+ * CHRE_EVENT_TIMER and with the given 'cookie'.
+ *
+ * A CHRE implementation is required to provide at least 32
+ * timers.  However, there's no assurance there will be any available
+ * for any given nanoapp (if it's loaded late, etc).
+ *
+ * @param duration  Time, in nanoseconds, before the timer fires.
+ * @param cookie  Argument that will be sent to nanoappHandleEvent upon the
+ *     timer firing.  This is allowed to be NULL and does not need to be
+ *     a valid pointer (assuming the nanoappHandleEvent code is expecting such).
+ * @param oneShot  If true, the timer will just fire once.  If false, the
+ *     timer will continue to refire every 'duration', until this timer is
+ *     canceled (@see chreTimerCancel).
+ *
+ * @return  The timer ID.  If the system is unable to set a timer
+ *     (no more available timers, etc.) then CHRE_TIMER_INVALID will
+ *     be returned.
+ *
+ * @see nanoappHandleEvent
+ */
+uint32_t chreTimerSet(uint64_t duration, const void *cookie, bool oneShot);
+
+/**
+ * Cancel a timer.
+ *
+ * After this method returns, the CHRE assures there will be no more
+ * events sent from this timer, and any enqueued events from this timer
+ * will need to be evicted from the queue by the CHRE.
+ *
+ * @param timerId  A timer ID obtained by this nanoapp via chreTimerSet().
+ * @return true if the timer was cancelled, false otherwise.  We may
+ *     fail to cancel the timer if it's a one shot which (just) fired,
+ *     or if the given timer ID is not owned by the calling app.
+ */
+bool chreTimerCancel(uint32_t timerId);
+
+/**
+ * Terminate this nanoapp.
+ *
+ * This takes effect immediately.
+ *
+ * The CHRE will no longer execute this nanoapp.  The CHRE will not invoke
+ * nanoappEnd(), nor will it call any memory free callbacks in the nanoapp.
+ *
+ * The CHRE will unload/evict this nanoapp's code.
+ *
+ * @param abortCode  A value indicating the reason for aborting.  (Note that
+ *    in this version of the API, there is no way for anyone to access this
+ *    code, but future APIs may expose it.)
+ * @return Never.  This method does not return, as the CHRE stops nanoapp
+ *    execution immediately.
+ */
+void chreAbort(uint32_t abortCode);
+
+/**
+ * Allocate a given number of bytes from the system heap.
+ *
+ * The nanoapp is required to free this memory via chreHeapFree() prior to
+ * the nanoapp ending.
+ *
+ * While the CHRE implementation is required to free up heap resources of
+ * a nanoapp when unloading it, future requirements and tests focused on
+ * nanoapps themselves may check for memory leaks, and will require nanoapps
+ * to properly manage their heap resources.
+ *
+ * @param bytes  The number of bytes requested.
+ * @return  A pointer to 'bytes' contiguous bytes of heap memory, or NULL
+ *     if the allocation could not be performed.  This pointer must be suitably
+ *     aligned for any kind of variable.
+ *
+ * @see chreHeapFree.
+ */
+void *chreHeapAlloc(uint32_t bytes);
+
+/**
+ * Free a heap allocation.
+ *
+ * This allocation must be from a value returned from a chreHeapAlloc() call
+ * made by this nanoapp.  In other words, it is illegal to free memory
+ * allocated by another nanoapp (or the CHRE).
+ *
+ * @param ptr  'ptr' is required to be a value returned from chreHeapAlloc().
+ *     Note that since chreHeapAlloc can return NULL, CHRE
+ *     implementations must safely handle 'ptr' being NULL.
+ *
+ * @see chreHeapAlloc.
+ */
+void chreHeapFree(void *ptr);
+
+/**
+ * Logs the nanoapp's debug data into debug dumps.
+ *
+ * A debug dump is a string representation of information that can be used to
+ * diagnose and debug issues. While chreLog() is useful for logging events as
+ * they happen, the debug dump is a complementary function typically used to
+ * output a snapshot of a nanoapp's state, history, vital statistics, etc. The
+ * CHRE framework is required to pass this information to the debug method in
+ * the Context Hub HAL, where it can be captured in Android bugreports, etc.
+ *
+ * This function must only be called while handling CHRE_DEBUG_DUMP_EVENT,
+ * otherwise it will have no effect. A nanoapp can call this function multiple
+ * times while handling the event. If the resulting formatted string from a
+ * single call to this function is longer than CHRE_DEBUG_DUMP_MINIMUM_MAX_SIZE
+ * characters, it may get truncated.
+ *
+ * @param formatStr A printf-style format string of the format documented in
+ *     chreLog().
+ * @param ... A variable number of arguments necessary for the given 'formatStr'
+ *     (there may be no additional arguments for some 'formatStr's).
+ *
+ * @see chreConfigureDebugDumpEvent
+ * @see chreLog
+ *
+ * @since v1.4
+ */
+CHRE_PRINTF_ATTR(1, 2)
+void chreDebugDumpLog(const char *formatStr, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_RE_H_ */
+
diff --git a/chre_api/legacy/v1_6/chre/sensor.h b/chre_api/legacy/v1_6/chre/sensor.h
new file mode 100644
index 0000000..7ead7ee
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/sensor.h
@@ -0,0 +1,1111 @@
+/*
+ * 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_SENSOR_H_
+#define _CHRE_SENSOR_H_
+
+/**
+ * @file
+ * API dealing with sensor interaction in the Context Hub Runtime
+ * Environment.
+ *
+ * This includes the definition of our sensor types and the ability to
+ * configure them for receiving events.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <chre/common.h>
+#include <chre/event.h>
+#include <chre/sensor_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * Base value for all of the data events for sensors.
+ *
+ * The value for a data event FOO is
+ * CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_FOO
+ *
+ * This allows for easy mapping, and also explains why there are gaps
+ * in our values since we don't have all possible sensor types assigned.
+ */
+#define CHRE_EVENT_SENSOR_DATA_EVENT_BASE  CHRE_EVENT_SENSOR_FIRST_EVENT
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in SI units (m/s^2) and measure the acceleration applied to
+ * the device.
+ */
+#define CHRE_EVENT_SENSOR_ACCELEROMETER_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_ACCELEROMETER)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorOccurrenceData
+ *
+ * Since this is a one-shot sensor, after this event is delivered to the
+ * nanoapp, the sensor automatically goes into DONE mode.  Sensors of this
+ * type must be configured with a ONE_SHOT mode.
+ */
+#define CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorOccurrenceData
+ *
+ * Since this is a one-shot sensor, after this event is delivered to the
+ * nanoapp, the sensor automatically goes into DONE mode.  Sensors of this
+ * type must be configured with a ONE_SHOT mode.
+ */
+#define CHRE_EVENT_SENSOR_STATIONARY_DETECT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_STATIONARY_DETECT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_GYROSCOPE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GYROSCOPE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in micro-Tesla (uT) and measure the geomagnetic
+ * field in the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'pressure' field within 'readings'.
+ * This value is in hectopascals (hPa).
+ */
+#define CHRE_EVENT_SENSOR_PRESSURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_PRESSURE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'light' field within 'readings'.
+ * This value is in SI lux units.
+ */
+#define CHRE_EVENT_SENSOR_LIGHT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_LIGHT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorByteData
+ *
+ * The data is interpreted from the following fields in 'readings':
+ * o 'isNear': If set to 1, we are nearby (on the order of centimeters);
+ *       if set to 0, we are far. The meaning of near/far in this field must be
+ *       consistent with the Android definition.
+ * o 'invalid': If set to 1, this is not a valid reading of this data.
+ *       As of CHRE API v1.2, this field is deprecated and must always be set to
+ *       0.  If an invalid reading is generated by the sensor hardware, it must
+ *       be dropped and not delivered to any nanoapp.
+ *
+ * In prior versions of the CHRE API, there can be an invalid event generated
+ * upon configuring this sensor.  Thus, the 'invalid' field must be checked on
+ * the first event before interpreting 'isNear'.
+ */
+#define CHRE_EVENT_SENSOR_PROXIMITY_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_PROXIMITY)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorOccurrenceData
+ *
+ * This data is generated every time a step is taken by the user.
+ *
+ * This is backed by the same algorithm that feeds Android's
+ * SENSOR_TYPE_STEP_DETECTOR, and therefore sacrifices some accuracy to target
+ * an update latency of under 2 seconds.
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_STEP_DETECT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_STEP_DETECT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorUint64Data
+ *
+ * The value of the data is the cumulative number of steps taken by the user
+ * since the last reboot while the sensor is active. This data is generated
+ * every time a step is taken by the user.
+ *
+ * This is backed by the same algorithm that feeds Android's
+ * SENSOR_TYPE_STEP_COUNTER, and therefore targets high accuracy with under
+ * 10 seconds of update latency.
+ *
+ * @since v1.5
+ */
+#define CHRE_EVENT_SENSOR_STEP_COUNTER_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_STEP_COUNTER)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The value of the data is the measured hinge angle between 0 and 360 degrees
+ * inclusive.
+ *
+ * This is backed by the same algorithm that feeds Android's
+ * SENSOR_TYPE_HINGE_ANGLE.
+ *
+ * @since v1.5
+ */
+#define CHRE_EVENT_SENSOR_HINGE_ANGLE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_HINGE_ANGLE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in SI units (m/s^2) and measure the acceleration applied to
+ * the device.
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in micro-Tesla (uT) and measure the geomagnetic
+ * field in the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'temperature' field within 'readings'.
+ * This value is in degrees Celsius.
+ */
+#define CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'temperature' field within 'readings'.
+ * This value is in degrees Celsius.
+ */
+#define CHRE_EVENT_SENSOR_GYROSCOPE_TEMPERATURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'temperature' field within 'readings'.
+ * This value is in degrees Celsius.
+ */
+#define CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE)
+
+/**
+ * First value for sensor events which are not data from the sensor.
+ *
+ * Unlike the data event values, these other event values don't have any
+ * mapping to sensor types.
+ */
+#define CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE \
+    (CHRE_EVENT_SENSOR_FIRST_EVENT + 0x0100)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorSamplingStatusEvent
+ *
+ * Indicates that the interval and/or the latency which this sensor is
+ * sampling at has changed.
+ */
+#define CHRE_EVENT_SENSOR_SAMPLING_CHANGE \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 0)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x_bias', 'y_bias', and 'z_bias'
+ * field within 'readings', or by the 3D array 'bias' (bias[0] == x_bias;
+ * bias[1] == y_bias; bias[2] == z_bias). Bias is subtracted from uncalibrated
+ * data to generate calibrated data.
+ *
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis.
+ *
+ * If bias delivery is supported, this event is generated by default when
+ * chreSensorConfigure is called to enable for the sensor of type
+ * CHRE_SENSOR_TYPE_GYROSCOPE, or if bias delivery is explicitly enabled
+ * through chreSensorConfigureBiasEvents() for the sensor.
+ */
+#define CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 1)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x_bias', 'y_bias', and 'z_bias'
+ * field within 'readings', or by the 3D array 'bias' (bias[0] == x_bias;
+ * bias[1] == y_bias; bias[2] == z_bias). Bias is subtracted from uncalibrated
+ * data to generate calibrated data.
+ *
+ * All values are in micro-Tesla (uT) and measure the geomagnetic
+ * field in the X, Y and Z axis.
+ *
+ * If bias delivery is supported, this event is generated by default when
+ * chreSensorConfigure is called to enable for the sensor of type
+ * CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD, or if bias delivery is explicitly enabled
+ * through chreSensorConfigureBiasEvents() for the sensor.
+ */
+#define CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 2)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x_bias', 'y_bias', and 'z_bias'
+ * field within 'readings', or by the 3D array 'bias' (bias[0] == x_bias;
+ * bias[1] == y_bias; bias[2] == z_bias). Bias is subtracted from uncalibrated
+ * data to generate calibrated data.
+ *
+ * All values are in SI units (m/s^2) and measure the acceleration applied to
+ * the device.
+ *
+ * If bias delivery is supported, this event is generated by default when
+ * chreSensorConfigure is called to enable for the sensor of type
+ * CHRE_SENSOR_TYPE_ACCELEROMETER, or if bias delivery is explicitly enabled
+ * through chreSensorConfigureBiasEvents() for the sensor.
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 3)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFlushCompleteEvent
+ *
+ * An event indicating that a flush request made by chreSensorFlushAsync has
+ * completed.
+ *
+ * @see chreSensorFlushAsync
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_FLUSH_COMPLETE \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 4)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data of this event is the same as that of
+ * CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO, except the sensorHandle field of
+ * chreSensorDataHeader contains the handle of the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE.
+ *
+ * This event is only generated if the bias reporting is explicitly enabled
+ * for a nanoapp through chreSensorConfigureBiasEvents() for the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE.
+ *
+ * @see CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 5)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data of this event is the same as that of
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO, except the sensorHandle field
+ * of chreSensorDataHeader contains the handle of the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD.
+ *
+ * This event is only generated if the bias reporting is explicitly enabled
+ * for a nanoapp through chreSensorConfigureBiasEvents() for the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD.
+ *
+ * @see CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 6)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data of this event is the same as that of
+ * CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO, except the sensorHandle field
+ * of chreSensorDataHeader contains the handle of the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER.
+ *
+ * This event is only generated if the bias reporting is explicitly enabled
+ * for a nanoapp through chreSensorConfigureBiasEvents for the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER.
+ *
+ * @see CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 7)
+
+#if CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_BIAS_INFO > \
+    CHRE_EVENT_SENSOR_LAST_EVENT
+#error Too many sensor events.
+#endif
+
+/**
+ * Value indicating we want the smallest possible latency for a sensor.
+ *
+ * This literally translates to 0 nanoseconds for the chreSensorConfigure()
+ * argument.  While we won't get exactly 0 nanoseconds, the CHRE will
+ * queue up this event As Soon As Possible.
+ */
+#define CHRE_SENSOR_LATENCY_ASAP  UINT64_C(0)
+
+/**
+ * Special value indicating non-importance, or non-applicability of the sampling
+ * interval.
+ *
+ * @see chreSensorConfigure
+ * @see chreSensorSamplingStatus
+ */
+#define CHRE_SENSOR_INTERVAL_DEFAULT  UINT64_C(-1)
+
+/**
+ * Special value indicating non-importance of the latency.
+ *
+ * @see chreSensorConfigure
+ * @see chreSensorSamplingStatus
+ */
+#define CHRE_SENSOR_LATENCY_DEFAULT  UINT64_C(-1)
+
+/**
+ * A sensor index value indicating that it is the default sensor.
+ *
+ * @see chreSensorFind
+ */
+#define CHRE_SENSOR_INDEX_DEFAULT  UINT8_C(0)
+
+/**
+ * Special value indicating non-importance of the batch interval.
+ *
+ * @see chreSensorConfigureWithBatchInterval
+ */
+#define CHRE_SENSOR_BATCH_INTERVAL_DEFAULT  UINT64_C(-1)
+
+// This is used to define elements of enum chreSensorConfigureMode.
+#define CHRE_SENSOR_CONFIGURE_RAW_POWER_ON           (1 << 0)
+
+// This is used to define elements of enum chreSensorConfigureMode.
+#define CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS  (1 << 1)
+
+// This is used to define elements of enum chreSensorConfigureMode.
+#define CHRE_SENSOR_CONFIGURE_RAW_REPORT_ONE_SHOT    (2 << 1)
+
+/**
+ * The maximum amount of time allowed to elapse between the call to
+ * chreSensorFlushAsync() and when CHRE_EVENT_SENSOR_FLUSH_COMPLETE is delivered
+ * to the nanoapp on a successful flush.
+ */
+#define CHRE_SENSOR_FLUSH_COMPLETE_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC)
+
+/**
+ * Modes we can configure a sensor to use.
+ *
+ * Our mode will affect not only how/if we receive events, but
+ * also whether or not the sensor will be powered on our behalf.
+ *
+ * @see chreSensorConfigure
+ */
+enum chreSensorConfigureMode {
+    /**
+     * Get events from the sensor.
+     *
+     * Power: Turn on if not already on.
+     * Reporting: Continuous.  Send each new event as it comes (subject to
+     *     batching and latency).
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS =
+        (CHRE_SENSOR_CONFIGURE_RAW_POWER_ON |
+         CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS),
+
+    /**
+     * Get a single event from the sensor and then become DONE.
+     *
+     * Once the event is sent, the sensor automatically
+     * changes to CHRE_SENSOR_CONFIGURE_MODE_DONE mode.
+     *
+     * Power: Turn on if not already on.
+     * Reporting: One shot.  Send the next event and then be DONE.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT =
+        (CHRE_SENSOR_CONFIGURE_RAW_POWER_ON |
+         CHRE_SENSOR_CONFIGURE_RAW_REPORT_ONE_SHOT),
+
+    /**
+     * Get events from a sensor that are generated for any client in the system.
+     *
+     * This is considered passive because the sensor will not be powered on for
+     * the sake of our nanoapp.  If and only if another client in the system has
+     * requested this sensor power on will we get events.
+     *
+     * This can be useful for something which is interested in seeing data, but
+     * not interested enough to be responsible for powering on the sensor.
+     *
+     * Power: Do not power the sensor on our behalf.
+     * Reporting: Continuous.  Send each event as it comes.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_CONTINUOUS =
+        CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS,
+
+    /**
+     * Get a single event from a sensor that is generated for any client in the
+     * system.
+     *
+     * See CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_CONTINUOUS for more details on
+     * what the "passive" means.
+     *
+     * Power: Do not power the sensor on our behalf.
+     * Reporting: One shot.  Send only the next event and then be DONE.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_ONE_SHOT =
+        CHRE_SENSOR_CONFIGURE_RAW_REPORT_ONE_SHOT,
+
+    /**
+     * Indicate we are done using this sensor and no longer interested in it.
+     *
+     * See chreSensorConfigure for more details on expressing interest or
+     * lack of interest in a sensor.
+     *
+     * Power: Do not power the sensor on our behalf.
+     * Reporting: None.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_DONE = 0,
+};
+
+/**
+ * A structure containing information about a Sensor.
+ *
+ * See documentation of individual fields below.
+ */
+struct chreSensorInfo {
+    /**
+     * The name of the sensor.
+     *
+     * A text name, useful for logging/debugging, describing the Sensor.  This
+     * is not assured to be unique (i.e. there could be multiple sensors with
+     * the name "Temperature").
+     *
+     * CHRE implementations may not set this as NULL.  An empty
+     * string, while discouraged, is legal.
+     */
+    const char *sensorName;
+
+    /**
+     * One of the CHRE_SENSOR_TYPE_* defines above.
+     */
+    uint8_t sensorType;
+
+    /**
+     * Flag indicating if this sensor is on-change.
+     *
+     * An on-change sensor only generates events when underlying state
+     * changes.  This has the same meaning as on-change does in the Android
+     * Sensors HAL.  See sensors.h for much more details.
+     *
+     * A value of 1 indicates this is on-change.  0 indicates this is not
+     * on-change.
+     */
+    uint8_t isOnChange          : 1;
+
+    /**
+     * Flag indicating if this sensor is one-shot.
+     *
+     * A one-shot sensor only triggers a single event, and then automatically
+     * disables itself.
+     *
+     * A value of 1 indicates this is one-shot.  0 indicates this is not
+     * on-change.
+     */
+    uint8_t isOneShot           : 1;
+
+    /**
+     * Flag indicating if this sensor supports reporting bias info events.
+     *
+     * This field will be set to 0 when running on CHRE API versions prior to
+     * v1.3, but must be ignored (i.e. does not mean bias info event is not
+     * supported).
+     *
+     * @see chreSensorConfigureBiasEvents
+     *
+     * @since v1.3
+     */
+    uint8_t reportsBiasEvents   : 1;
+
+    /**
+     * Flag indicating if this sensor supports passive mode requests.
+     *
+     * This field will be set to 0 when running on CHRE API versions prior to
+     * v1.4, and must be ignored (i.e. does not mean passive mode requests are
+     * not supported).
+     *
+     * @see chreSensorConfigure
+     *
+     * @since v1.4
+     */
+    uint8_t supportsPassiveMode : 1;
+
+    uint8_t unusedFlags         : 4;
+
+    /**
+     * The minimum sampling interval supported by this sensor, in nanoseconds.
+     *
+     * Requests to chreSensorConfigure with a lower interval than this will
+     * fail.  If the sampling interval is not applicable to this sensor, this
+     * will be set to CHRE_SENSOR_INTERVAL_DEFAULT.
+     *
+     * This field will be set to 0 when running on CHRE API versions prior to
+     * v1.1, indicating that the minimum interval is not known.
+     *
+     * @since v1.1
+     */
+    uint64_t minInterval;
+
+    /**
+     * Uniquely identifies the sensor for a given type. A value of 0 indicates
+     * that this is the "default" sensor, which is returned by
+     * chreSensorFindDefault().
+     *
+     * The sensor index of a given type must be stable across boots (i.e. must
+     * not change), and a different sensor of the same type must have different
+     * sensor index values, and the set of sensorIndex values for a given sensor
+     * type must be continuguous.
+     *
+     * @since v1.5
+     */
+    uint8_t sensorIndex;
+};
+
+/**
+ * The status of a sensor's sampling configuration.
+ */
+struct chreSensorSamplingStatus {
+    /**
+     * The interval, in nanoseconds, at which the sensor is now sampling.
+     *
+     * If this is CHRE_SENSOR_INTERVAL_DEFAULT, then a sampling interval
+     * isn't meaningful for this sensor.
+     *
+     * Note that if 'enabled' is false, this value is not meaningful.
+     */
+    uint64_t interval;
+
+    /**
+     * The latency, in nanoseconds, at which the senor is now reporting.
+     *
+     * If this is CHRE_SENSOR_LATENCY_DEFAULT, then a latency
+     * isn't meaningful for this sensor.
+     *
+     * The effective batch interval can be derived from this value by
+     * adding the current sampling interval.
+     *
+     * Note that if 'enabled' is false, this value is not meaningful.
+     */
+    uint64_t latency;
+
+    /**
+     * True if the sensor is actively powered and sampling; false otherwise.
+     */
+    bool enabled;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_SENSOR_SAMPLING_CHANGE.
+ *
+ * Note that only at least one of 'interval' or 'latency' must be
+ * different than it was prior to this event.  Thus, one of these
+ * fields may be (but doesn't need to be) the same as before.
+ */
+struct chreSensorSamplingStatusEvent {
+    /**
+     * The handle of the sensor which has experienced a change in sampling.
+     */
+    uint32_t sensorHandle;
+
+    /**
+     * The new sampling status.
+     *
+     * At least one of the field in this struct will be different from
+     * the previous sampling status event.
+     */
+    struct chreSensorSamplingStatus status;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_SENSOR_FLUSH_COMPLETE.
+ *
+ * @see chreSensorFlushAsync
+ *
+ * @since v1.3
+ */
+struct chreSensorFlushCompleteEvent {
+    /**
+     * The handle of the sensor which a flush was completed.
+     */
+    uint32_t sensorHandle;
+
+    /**
+     * Populated with a value from enum {@link #chreError}, indicating whether
+     * the flush failed, and if so, provides the cause of the failure.
+     */
+    uint8_t errorCode;
+
+    /**
+     * Reserved for future use. Set to 0.
+     */
+    uint8_t reserved[3];
+
+    /**
+     * Set to the cookie parameter given to chreSensorFlushAsync.
+     */
+    const void *cookie;
+};
+
+/**
+ * Find the default sensor for a given sensor type.
+ *
+ * @param sensorType One of the CHRE_SENSOR_TYPE_* constants.
+ * @param handle  If a sensor is found, then the memory will be filled with
+ *     the value for the sensor's handle.  This argument must be non-NULL.
+ * @return true if a sensor was found, false otherwise.
+ */
+bool chreSensorFindDefault(uint8_t sensorType, uint32_t *handle);
+
+/**
+ * Finds a sensor of a given index and sensor type.
+ *
+ * For CHRE implementations that support multiple sensors of the same sensor
+ * type, this method can be used to get the non-default sensor(s). The default
+ * sensor, as defined in the chreSensorFindDefault(), will be returned if
+ * a sensor index of zero is specified.
+ *
+ * A simple example of iterating all available sensors of a given type is
+ * provided here:
+ *
+ * uint32_t handle;
+ * for (uint8_t i = 0; chreSensorFind(sensorType, i, &handle); i++) {
+ *   chreLog(CHRE_LOG_INFO,
+ *           "Found sensor index %" PRIu8 ", which has handle %" PRIu32,
+ *           i, handle);
+ * }
+ *
+ * If this method is invoked for CHRE versions prior to v1.5, invocations with
+ * sensorIndex value of 0 will be equivalent to using chreSensorFindDefault, and
+ * if sensorIndex is non-zero will return false.
+ *
+ * In cases where multiple sensors are supported in both the Android sensors
+ * framework and CHRE, the sensorName of the chreSensorInfo struct for a given
+ * sensor instance must match exactly with that of the
+ * android.hardware.Sensor#getName() return value. This can be used to match a
+ * sensor instance between the Android and CHRE sensors APIs.
+ *
+ * @param sensorType One of the CHRE_SENSOR_TYPE_* constants.
+ * @param sensorIndex The index of the desired sensor.
+ * @param handle  If a sensor is found, then the memory will be filled with
+ *     the value for the sensor's handle.  This argument must be non-NULL.
+ * @return true if a sensor was found, false otherwise.
+ *
+ * @since v1.5
+ */
+bool chreSensorFind(uint8_t sensorType, uint8_t sensorIndex, uint32_t *handle);
+
+/**
+ * Get the chreSensorInfo struct for a given sensor.
+ *
+ * @param sensorHandle  The sensor handle, as obtained from
+ *     chreSensorFindDefault() or passed to nanoappHandleEvent().
+ * @param info  If the sensor is valid, then this memory will be filled with
+ *     the SensorInfo contents for this sensor.  This argument must be
+ *     non-NULL.
+ * @return true if the senor handle is valid and 'info' was filled in;
+ *     false otherwise.
+ */
+bool chreGetSensorInfo(uint32_t sensorHandle, struct chreSensorInfo *info);
+
+/**
+ * Get the chreSensorSamplingStatus struct for a given sensor.
+ *
+ * Note that this may be different from what was requested in
+ * chreSensorConfigure(), for multiple reasons.  It's possible that the sensor
+ * does not exactly support the interval requested in chreSensorConfigure(), so
+ * a faster one was chosen.
+ *
+ * It's also possible that there is another user of this sensor who has
+ * requested a faster interval and/or lower latency.  This latter scenario
+ * should be noted, because it means the sensor rate can change due to no
+ * interaction from this nanoapp.  Note that the
+ * CHRE_EVENT_SENSOR_SAMPLING_CHANGE event will trigger in this case, so it's
+ * not necessary to poll for such a change.
+ *
+ * @param sensorHandle  The sensor handle, as obtained from
+ *     chreSensorFindDefault() or passed to nanoappHandleEvent().
+ * @param status  If the sensor is valid, then this memory will be filled with
+ *     the sampling status contents for this sensor.  This argument must be
+ *     non-NULL.
+ * @return true if the senor handle is valid and 'status' was filled in;
+ *     false otherwise.
+ */
+bool chreGetSensorSamplingStatus(uint32_t sensorHandle,
+                                 struct chreSensorSamplingStatus *status);
+
+/**
+ * Configures a given sensor at a specific interval and latency and mode.
+ *
+ * If this sensor's chreSensorInfo has isOneShot set to 1,
+ * then the mode must be one of the ONE_SHOT modes, or this method will fail.
+ *
+ * The CHRE wants to power as few sensors as possible, in keeping with its
+ * low power design.  As such, it only turns on sensors when there are clients
+ * actively interested in that sensor data, and turns off sensors as soon as
+ * there are no clients interested in them.  Calling this method generally
+ * indicates an interest, and using CHRE_SENSOR_CONFIGURE_MODE_DONE shows
+ * when we are no longer interested.
+ *
+ * Thus, each initial Configure of a sensor (per nanoapp) needs to eventually
+ * have a DONE call made, either directly or on its behalf.  Subsequent calls
+ * to a Configure method within the same nanoapp, when there has been no DONE
+ * in between, still only require a single DONE call.
+ *
+ * For example, the following is valid usage:
+ * <code>
+ *   chreSensorConfigure(myHandle, mode, interval0, latency0);
+ *   [...]
+ *   chreSensorConfigure(myHandle, mode, interval1, latency0);
+ *   [...]
+ *   chreSensorConfigure(myHandle, mode, interval1, latency1);
+ *   [...]
+ *   chreSensorConfigureModeOnly(myHandle, CHRE_SENSOR_CONFIGURE_MODE_DONE);
+ * </code>
+ *
+ * The first call to Configure is the one which creates the requirement
+ * to eventually call with DONE.  The subsequent calls are just changing the
+ * interval/latency.  They have not changed the fact that this nanoapp is
+ * still interested in output from the sensor 'myHandle'.  Thus, only one
+ * single call for DONE is needed.
+ *
+ * There is a special case.  One-shot sensors, sensors which
+ * just trigger a single event and never trigger again, implicitly go into
+ * DONE mode after that single event triggers.  Thus, the
+ * following are legitimate usages:
+ * <code>
+ *   chreSensorConfigure(myHandle, MODE_ONE_SHOT, interval, latency);
+ *   [...]
+ *   [myHandle triggers an event]
+ *   [no need to configure to DONE].
+ * </code>
+ *
+ * And:
+ * <code>
+ *   chreSensorConfigure(myHandle, MODE_ONE_SHOT, interval, latency);
+ *   [...]
+ *   chreSensorConfigureModeOnly(myHandle, MODE_DONE);
+ *   [we cancelled myHandle before it ever triggered an event]
+ * </code>
+ *
+ * Note that while PASSIVE modes, by definition, don't express an interest in
+ * powering the sensor, DONE is still necessary to silence the event reporting.
+ * Starting with CHRE API v1.4, for sensors that do not support passive mode, a
+ * request with mode set to CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_CONTINUOUS or
+ * CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_ONE_SHOT will be rejected. CHRE API
+ * versions 1.3 and older implicitly assume that passive mode is supported
+ * across all sensors, however this is not necessarily the case. Clients can
+ * call chreSensorInfo to identify whether a sensor supports passive mode.
+ *
+ * When a calibrated sensor (e.g. CHRE_SENSOR_TYPE_ACCELEROMETER) is
+ * successfully enabled through this method and if bias delivery is supported,
+ * by default CHRE will start delivering bias events for the sensor
+ * (e.g. CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO) to the nanoapp. If the
+ * nanoapp does not wish to receive these events, they can be disabled through
+ * chreSensorConfigureBiasEvents after enabling the sensor.
+ *
+ * @param sensorHandle  The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param mode  The mode to use.  See descriptions within the
+ *     chreSensorConfigureMode enum.
+ * @param interval  The interval, in nanoseconds, at which we want events from
+ *     the sensor.  On success, the sensor will be set to 'interval', or a value
+ *     less than 'interval'.  There is a special value
+ *     CHRE_SENSOR_INTERVAL_DEFAULT, in which we don't express a preference for
+ *     the interval, and allow the sensor to choose what it wants.  Note that
+ *     due to batching, we may receive events less frequently than
+ *     'interval'.
+ * @param latency  The maximum latency, in nanoseconds, allowed before the
+ *     CHRE begins delivery of an event.  This will control how many events
+ *     can be queued by the sensor before requiring a delivery event.
+ *     Latency is defined as the "timestamp when event is queued by the CHRE"
+ *     minus "timestamp of oldest unsent data reading".
+ *     There is a special value CHRE_SENSOR_LATENCY_DEFAULT, in which we don't
+ *     express a preference for the latency, and allow the sensor to choose what
+ *     it wants.
+ *     Note that there is no assurance of how long it will take an event to
+ *     get through a CHRE's queueing system, and thus there is no ability to
+ *     request a minimum time from the occurrence of a phenomenon to when the
+ *     nanoapp receives the information.  The current CHRE API has no
+ *     real-time elements, although future versions may introduce some to
+ *     help with this issue.
+ * @return true if the configuration succeeded, false otherwise.
+ *
+ * @see chreSensorConfigureMode
+ * @see chreSensorFindDefault
+ * @see chreSensorInfo
+ * @see chreSensorConfigureBiasEvents
+ */
+bool chreSensorConfigure(uint32_t sensorHandle,
+                         enum chreSensorConfigureMode mode,
+                         uint64_t interval, uint64_t latency);
+
+/**
+ * Short cut for chreSensorConfigure where we only want to configure the mode
+ * and do not care about interval/latency.
+ *
+ * @see chreSensorConfigure
+ */
+static inline bool chreSensorConfigureModeOnly(
+        uint32_t sensorHandle, enum chreSensorConfigureMode mode) {
+    return chreSensorConfigure(sensorHandle,
+                               mode,
+                               CHRE_SENSOR_INTERVAL_DEFAULT,
+                               CHRE_SENSOR_LATENCY_DEFAULT);
+}
+
+/**
+ * Convenience function that wraps chreSensorConfigure but enables batching to
+ * be controlled by specifying the desired maximum batch interval rather
+ * than maximum sample latency.  Users may find the batch interval to be a more
+ * intuitive method of expressing the desired batching behavior.
+ *
+ * Batch interval is different from latency as the batch interval time is
+ * counted starting when the prior event containing a batch of sensor samples is
+ * delivered, while latency starts counting when the first sample is deferred to
+ * start collecting a batch.  In other words, latency ignores the time between
+ * the last sample in a batch to the first sample of the next batch, while it's
+ * included in the batch interval, as illustrated below.
+ *
+ *  Time      0   1   2   3   4   5   6   7   8
+ *  Batch             A           B           C
+ *  Sample   a1  a2  a3  b1  b2  b3  c1  c2  c3
+ *  Latency  [        ]  [        ]  [        ]
+ *  BatchInt          |           |           |
+ *
+ * In the diagram, the effective sample interval is 1 time unit, latency is 2
+ * time units, and batch interval is 3 time units.
+ *
+ * @param sensorHandle See chreSensorConfigure#sensorHandle
+ * @param mode See chreSensorConfigure#mode
+ * @param sampleInterval See chreSensorConfigure#interval, but note that
+ *     CHRE_SENSOR_INTERVAL_DEFAULT is not a supported input to this method.
+ * @param batchInterval The desired maximum interval, in nanoseconds, between
+ *     CHRE enqueuing each batch of sensor samples.
+ * @return Same as chreSensorConfigure
+ *
+ * @see chreSensorConfigure
+ *
+ * @since v1.1
+ */
+static inline bool chreSensorConfigureWithBatchInterval(
+        uint32_t sensorHandle, enum chreSensorConfigureMode mode,
+        uint64_t sampleInterval, uint64_t batchInterval) {
+    bool result = false;
+
+    if (sampleInterval != CHRE_SENSOR_INTERVAL_DEFAULT) {
+        uint64_t latency;
+        if (batchInterval == CHRE_SENSOR_BATCH_INTERVAL_DEFAULT) {
+            latency = CHRE_SENSOR_LATENCY_DEFAULT;
+        } else if (batchInterval > sampleInterval) {
+            latency = batchInterval - sampleInterval;
+        } else {
+            latency = CHRE_SENSOR_LATENCY_ASAP;
+        }
+        result = chreSensorConfigure(sensorHandle, mode, sampleInterval,
+                                     latency);
+    }
+
+    return result;
+}
+
+/**
+ * Configures the reception of bias events for a specific sensor.
+ *
+ * If bias event delivery is supported for a sensor, the sensor's chreSensorInfo
+ * has reportsBiasEvents set to 1. If supported, it must be supported for both
+ * calibrated and uncalibrated versions of the sensor. If supported, CHRE must
+ * provide bias events to the nanoapp by default when chreSensorConfigure is
+ * called to enable the calibrated version of the sensor (for backwards
+ * compatibility reasons, as this is the defined behavior for CHRE API v1.0).
+ * When configuring uncalibrated sensors, nanoapps must explicitly configure an
+ * enable request through this method to receive bias events. If bias event
+ * delivery is not supported for the sensor, this method will return false and
+ * no bias events will be generated.
+ *
+ * To enable bias event delivery (enable=true), the nanoapp must be registered
+ * to the sensor through chreSensorConfigure, and bias events will only be
+ * generated when the sensor is powered on. To disable the bias event delivery,
+ * this method can be invoked with enable=false.
+ *
+ * If an enable configuration is successful, the calling nanoapp will receive
+ * bias info events, e.g. CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO, when the
+ * bias status changes (or first becomes available). Calibrated data
+ * (e.g. CHRE_SENSOR_TYPE_ACCELEROMETER) is generated by subracting bias from
+ * uncalibrated data (e.g. CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER).
+ * Calibrated sensor events are generated by applying the most recent bias
+ * available (i.e. timestamp of calibrated data are greater than or equal to the
+ * timestamp of the bias data that has been applied to it). The configuration of
+ * bias event delivery persists until the sensor is unregistered by the nanoapp
+ * through chreSensorConfigure or modified through this method.
+ *
+ * To get an initial bias before new bias events, the nanoapp should get the
+ * bias synchronously after this method is invoked, e.g.:
+ *
+ * if (chreSensorConfigure(handle, ...)) {
+ *   chreSensorConfigureBiasEvents(handle, true);
+ *   chreSensorGetThreeAxisBias(handle, &bias);
+ * }
+ *
+ * Note that chreSensorGetThreeAxisBias() should be called after
+ * chreSensorConfigureBiasEvents() to ensure that no bias events are lost.
+ *
+ * If called while running on a CHRE API version below v1.3, this function
+ * returns false and has no effect. The default behavior regarding bias events
+ * is unchanged, meaning that the implementation may still send bias events
+ * when a calibrated sensor is registered (if supported), and will not send bias
+ * events when an uncalibrated sensor is registered.
+ *
+ * @param sensorHandle The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param enable true to receive bias events, false otherwise
+ *
+ * @return true if the configuration succeeded, false otherwise
+ *
+ * @since v1.3
+ */
+bool chreSensorConfigureBiasEvents(uint32_t sensorHandle, bool enable);
+
+/**
+ * Synchronously provides the most recent bias info available for a sensor. The
+ * bias will only be provided for a sensor that supports bias event delivery
+ * using the chreSensorThreeAxisData type. If the bias is not yet available
+ * (but is supported), this method will store data with a bias of 0 and the
+ * accuracy field in chreSensorDataHeader set to CHRE_SENSOR_ACCURACY_UNKNOWN.
+ *
+ * If called while running on a CHRE API version below v1.3, this function
+ * returns false.
+ *
+ * @param sensorHandle The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param bias A pointer to where the bias will be stored.
+ *
+ * @return true if the bias was successfully stored, false if sensorHandle was
+ *     invalid or the sensor does not support three axis bias delivery
+ *
+ * @since v1.3
+ *
+ * @see chreSensorConfigureBiasEvents
+ */
+bool chreSensorGetThreeAxisBias(uint32_t sensorHandle,
+                                struct chreSensorThreeAxisData *bias);
+
+/**
+ * Makes a request to flush all samples stored for batching. The nanoapp must be
+ * registered to the sensor through chreSensorConfigure, and the sensor must be
+ * powered on. If the request is accepted, all batched samples of the sensor
+ * are sent to nanoapps registered to the sensor. During a flush, it is treated
+ * as though the latency as given in chreSensorConfigure has expired. When all
+ * batched samples have been flushed (or the flush fails), the nanoapp will
+ * receive a unicast CHRE_EVENT_SENSOR_FLUSH_COMPLETE event. The time to deliver
+ * this event must not exceed CHRE_SENSOR_FLUSH_COMPLETE_TIMEOUT_NS after this
+ * method is invoked. If there are no samples in the batch buffer (either in
+ * hardware FIFO or software), then this method will return true and a
+ * CHRE_EVENT_SENSOR_FLUSH_COMPLETE event is delivered immediately.
+ *
+ * If a flush request is invalid (e.g. the sensor refers to a one-shot sensor,
+ * or the sensor was not enabled), and this API will return false and no
+ * CHRE_EVENT_SENSOR_FLUSH_COMPLETE event will be delivered.
+ *
+ * If multiple flush requests are made for a sensor prior to flush completion,
+ * then the requesting nanoapp will receive all batched samples existing at the
+ * time of the latest flush request. In this case, the number of
+ * CHRE_EVENT_SENSOR_FLUSH_COMPLETE events received must equal the number of
+ * flush requests made.
+ *
+ * If a sensor request is disabled after a flush request is made through this
+ * method but before the flush operation is completed, the nanoapp will receive
+ * a CHRE_EVENT_SENSOR_FLUSH_COMPLETE with the error code
+ * CHRE_ERROR_FUNCTION_DISABLED for any pending flush requests.
+ *
+ * Starting with CHRE API v1.3, implementations must support this capability
+ * across all exposed sensor types.
+ *
+ * @param sensorHandle  The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param cookie  An opaque value that will be included in the
+ *     chreSensorFlushCompleteEvent sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.3
+ */
+bool chreSensorFlushAsync(uint32_t sensorHandle, const void *cookie);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_SENSOR_H_ */
diff --git a/chre_api/legacy/v1_6/chre/sensor_types.h b/chre_api/legacy/v1_6/chre/sensor_types.h
new file mode 100644
index 0000000..63b495b
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/sensor_types.h
@@ -0,0 +1,468 @@
+/*
+ * 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_SENSOR_TYPES_H_
+#define _CHRE_SENSOR_TYPES_H_
+
+/**
+ * @file
+ * Standalone definition of sensor types, and the data structures of the sample
+ * events they emit.
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * The CHRE_SENSOR_TYPE_* defines are the sensor types supported.
+ *
+ * Unless otherwise noted, each of these sensor types is based off of a
+ * corresponding sensor type in the Android API's sensors.h interface.
+ * For a given CHRE_SENSOR_TYPE_FOO, it corresponds to the SENSOR_TYPE_FOO in
+ * hardware/libhardware/include/hardware/sensors.h of the Android code base.
+ *
+ * Unless otherwise noted below, a CHRE_SENSOR_TYPE_FOO should be assumed
+ * to work the same as the Android SENSOR_TYPE_FOO, as documented in the
+ * sensors.h documentation and as detailed within the Android Compatibility
+ * Definition Document.
+ *
+ * Note that every sensor will generate CHRE_EVENT_SENSOR_SAMPLING_CHANGE
+ * events, so it is not listed with each individual sensor.
+ */
+
+/**
+ * Start value for all of the vendor-defined private sensors.
+ *
+ * @since v1.2
+ */
+#define CHRE_SENSOR_TYPE_VENDOR_START  UINT8_C(192)
+
+/**
+ * Accelerometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_ACCELEROMETER_DATA and
+ *     optionally CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO
+ *
+ * Note that the ACCELEROMETER_DATA is always the fully calibrated data,
+ * including factory calibration and runtime calibration if available.
+ *
+ * @see chreConfigureSensorBiasEvents
+ */
+#define CHRE_SENSOR_TYPE_ACCELEROMETER  UINT8_C(1)
+
+/**
+ * Instantaneous motion detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA
+ *
+ * This is a one-shot sensor.
+ *
+ * This does not have a direct analogy within sensors.h.  This is similar
+ * to SENSOR_TYPE_MOTION_DETECT, but this triggers instantly upon any
+ * motion, instead of waiting for a period of continuous motion.
+ */
+#define CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT  UINT8_C(2)
+
+/**
+ * Stationary detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_STATIONARY_DETECT_DATA
+ *
+ * This is a one-shot sensor.
+ */
+#define CHRE_SENSOR_TYPE_STATIONARY_DETECT  UINT8_C(3)
+
+/**
+ * Gyroscope.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GYROSCOPE_DATA and
+ *     optionally CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO
+ *
+ * Note that the GYROSCOPE_DATA is always the fully calibrated data, including
+ * factory calibration and runtime calibration if available.
+ *
+ * @see chreConfigureSensorBiasEvents
+ */
+#define CHRE_SENSOR_TYPE_GYROSCOPE  UINT8_C(6)
+
+/**
+ * Uncalibrated gyroscope.
+ *
+ * Generates: CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA
+ *
+ * Note that the UNCALIBRATED_GYROSCOPE_DATA must be factory calibrated data,
+ * but not runtime calibrated.
+ */
+#define CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE  UINT8_C(7)
+
+/**
+ * Magnetometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA and
+ *     optionally CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO
+ *
+ * Note that the GEOMAGNETIC_FIELD_DATA is always the fully calibrated data,
+ * including factory calibration and runtime calibration if available.
+ *
+ * @see chreConfigureSensorBiasEvents
+ */
+#define CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD  UINT8_C(8)
+
+/**
+ * Uncalibrated magnetometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA
+ *
+ * Note that the UNCALIBRATED_GEOMAGNETIC_FIELD_DATA must be factory calibrated
+ * data, but not runtime calibrated.
+ */
+#define CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD  UINT8_C(9)
+
+/**
+ * Barometric pressure sensor.
+ *
+ * Generates: CHRE_EVENT_SENSOR_PRESSURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_PRESSURE  UINT8_C(10)
+
+/**
+ * Ambient light sensor.
+ *
+ * Generates: CHRE_EVENT_SENSOR_LIGHT_DATA
+ *
+ * This is an on-change sensor.
+ */
+#define CHRE_SENSOR_TYPE_LIGHT  UINT8_C(12)
+
+/**
+ * Proximity detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_PROXIMITY_DATA
+ *
+ * This is an on-change sensor.
+ */
+#define CHRE_SENSOR_TYPE_PROXIMITY  UINT8_C(13)
+
+/**
+ * Step detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_STEP_DETECT_DATA
+ *
+ * @since v1.3
+ */
+#define CHRE_SENSOR_TYPE_STEP_DETECT  UINT8_C(23)
+
+/**
+ * Step counter.
+ *
+ * Generates: CHRE_EVENT_SENSOR_STEP_COUNTER_DATA
+ *
+ * This is an on-change sensor. Note that the data returned by this sensor must
+ * match the value that can be obtained via the Android sensors framework at the
+ * same point in time. This means, if CHRE reboots from the rest of the system,
+ * the counter must not reset to 0.
+ *
+ * @since v1.5
+ */
+#define CHRE_SENSOR_TYPE_STEP_COUNTER UINT8_C(24)
+
+/**
+ * Hinge angle sensor.
+ *
+ * Generates: CHRE_EVENT_SENSOR_HINGE_ANGLE_DATA
+ *
+ * This is an on-change sensor.
+ *
+ * A sensor of this type measures the angle, in degrees, between two
+ * integral parts of the device. Movement of a hinge measured by this sensor
+ * type is expected to alter the ways in which the user may interact with
+ * the device, for example by unfolding or revealing a display.
+ *
+ * @since v1.5
+ */
+#define CHRE_SENSOR_TYPE_HINGE_ANGLE UINT8_C(36)
+
+/**
+ * Uncalibrated accelerometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA
+ *
+ * Note that the UNCALIBRATED_ACCELEROMETER_DATA must be factory calibrated
+ * data, but not runtime calibrated.
+ */
+#define CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER  UINT8_C(55)
+
+/**
+ * Accelerometer temperature.
+ *
+ * Generates: CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE  UINT8_C(56)
+
+/**
+ * Gyroscope temperature.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GYROSCOPE_TEMPERATURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE  UINT8_C(57)
+
+/**
+ * Magnetometer temperature.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE  UINT8_C(58)
+
+#if CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE >= CHRE_SENSOR_TYPE_VENDOR_START
+#error Too many sensor types
+#endif
+
+/**
+ * Values that can be stored in the accuracy field of chreSensorDataHeader.
+ * If CHRE_SENSOR_ACCURACY_UNKNOWN is returned, then the driver did not provide
+ * accuracy information with the data. Values in the range
+ * [CHRE_SENSOR_ACCURACY_VENDOR_START, CHRE_SENSOR_ACCURACY_VENDOR_END] are
+ * reserved for vendor-specific values for vendor sensor types, and are not used
+ * by CHRE for standard sensor types.
+ *
+ * Otherwise, the values have the same meaning as defined in the Android
+ * Sensors definition:
+ * https://developer.android.com/reference/android/hardware/SensorManager
+ *
+ * @since v1.3
+ *
+ * @defgroup CHRE_SENSOR_ACCURACY
+ * @{
+ */
+
+#define CHRE_SENSOR_ACCURACY_UNKNOWN       UINT8_C(0)
+#define CHRE_SENSOR_ACCURACY_UNRELIABLE    UINT8_C(1)
+#define CHRE_SENSOR_ACCURACY_LOW           UINT8_C(2)
+#define CHRE_SENSOR_ACCURACY_MEDIUM        UINT8_C(3)
+#define CHRE_SENSOR_ACCURACY_HIGH          UINT8_C(4)
+#define CHRE_SENSOR_ACCURACY_VENDOR_START  UINT8_C(192)
+#define CHRE_SENSOR_ACCURACY_VENDOR_END    UINT8_MAX
+
+/** @} */
+
+/**
+ * Header used in every structure containing batchable data from a sensor.
+ *
+ * The typical structure for sensor data looks like:
+ *
+ *   struct chreSensorTypeData {
+ *       struct chreSensorDataHeader header;
+ *       struct chreSensorTypeSampleData {
+ *           uint32_t timestampDelta;
+ *           union {
+ *               <type> value;
+ *               <type> interpretation0;
+ *               <type> interpretation1;
+ *           };
+ *       } readings[1];
+ *   };
+ *
+ * Despite 'readings' being declared as an array of 1 element,
+ * an instance of the struct will actually have 'readings' as
+ * an array of header.readingCount elements (which may be 1).
+ * The 'timestampDelta' is in relation to the previous 'readings' (or
+ * the baseTimestamp for readings[0].  So,
+ * Timestamp for readings[0] == header.baseTimestamp +
+ *     readings[0].timestampDelta.
+ * Timestamp for readings[1] == timestamp for readings[0] +
+ *     readings[1].timestampDelta.
+ * And thus, in order to determine the timestamp for readings[N], it's
+ * necessary to process through all of the N-1 readings.  The advantage,
+ * though, is that our entire readings can span an arbitrary length of time,
+ * just as long as any two consecutive readings differ by no more than
+ * 4.295 seconds (timestampDelta, like all time in the CHRE, is in
+ * nanoseconds).
+ *
+ * If a sensor has batched readings where two consecutive readings differ by
+ * more than 4.295 seconds, the CHRE will split them across multiple
+ * instances of the struct, and send multiple events.
+ *
+ * The value from the sensor is typically expressed in a union,
+ * allowing a generic access to the data ('value'), along with
+ * differently named access giving a more natural interpretation
+ * of the data for the specific sensor types which use this
+ * structure.  This allows, for example, barometer code to
+ * reference readings[N].pressure, and an ambient light sensor
+ * to reference readings[N].light, while both use the same
+ * structure.
+ */
+struct chreSensorDataHeader {
+    /**
+     * The base timestamp, in nanoseconds; must be in the same time base as
+     * chreGetTime().
+     */
+    uint64_t baseTimestamp;
+
+    /**
+     * The handle of the sensor producing this event.
+     */
+    uint32_t sensorHandle;
+
+    /**
+     * The number elements in the 'readings' array.
+     *
+     * This must be at least 1.
+     */
+    uint16_t readingCount;
+
+    /**
+     * The accuracy of the sensor data.
+     *
+     * @ref CHRE_SENSOR_ACCURACY
+     *
+     * @since v1.3
+     */
+    uint8_t accuracy;
+
+    /**
+     * Reserved bytes.
+     *
+     * This must be 0.
+     */
+    uint8_t reserved;
+};
+
+/**
+ * Data for a sensor which reports on three axes.
+ *
+ * This is used by CHRE_EVENT_SENSOR_ACCELEROMETER_DATA,
+ * CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO,
+ * CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA,
+ * CHRE_EVENT_SENSOR_GYROSCOPE_DATA,
+ * CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO,
+ * CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA,
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA,
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO, and
+ * CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA.
+ */
+struct chreSensorThreeAxisData {
+    /**
+     * @see chreSensorDataHeader
+     */
+    struct chreSensorDataHeader header;
+    struct chreSensorThreeAxisSampleData {
+        /**
+         * @see chreSensorDataHeader
+         */
+        uint32_t timestampDelta;
+        union {
+            float values[3];
+            float v[3];
+            struct {
+                float x;
+                float y;
+                float z;
+            };
+            float bias[3];
+            struct {
+                float x_bias;
+                float y_bias;
+                float z_bias;
+            };
+        };
+    } readings[1];
+};
+
+/**
+ * Data from a sensor where we only care about a event occurring.
+ *
+ * This is a bit unusual in that our readings have no data in addition
+ * to the timestamp.  But since we only care about the occurrence, we
+ * don't need to know anything else.
+ *
+ * Used by: CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA,
+ *     CHRE_EVENT_SENSOR_STATIONARY_DETECT_DATA, and
+ *     CHRE_EVENT_SENSOR_STEP_DETECT_DATA.
+ */
+struct chreSensorOccurrenceData {
+    struct chreSensorDataHeader header;
+    struct chreSensorOccurrenceSampleData {
+        uint32_t timestampDelta;
+        // This space intentionally left blank.
+        // Only the timestamp is meaningful here, there
+        // is no additional data.
+    } readings[1];
+};
+
+/**
+ * This is used by CHRE_EVENT_SENSOR_LIGHT_DATA,
+ * CHRE_EVENT_SENSOR_PRESSURE_DATA,
+ * CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA,
+ * CHRE_EVENT_SENSOR_GYROSCOPE_TEMPERATURE_DATA,
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA, and
+ * CHRE_EVENT_SENSOR_HINGE_ANGLE_DATA.
+ */
+struct chreSensorFloatData {
+    struct chreSensorDataHeader header;
+    struct chreSensorFloatSampleData {
+        uint32_t timestampDelta;
+        union {
+            float value;
+            float light;        //!< Unit: lux
+            float pressure;     //!< Unit: hectopascals (hPa)
+            float temperature;  //!< Unit: degrees Celsius
+            float angle;        //!< Unit: angular degrees
+        };
+    } readings[1];
+};
+
+/**
+ * CHRE_EVENT_SENSOR_PROXIMITY_DATA.
+ */
+struct chreSensorByteData {
+    struct chreSensorDataHeader header;
+    struct chreSensorByteSampleData {
+        uint32_t timestampDelta;
+        union {
+            uint8_t value;
+            struct {
+                uint8_t isNear : 1;
+                //! @deprecated As of v1.2, this field is deprecated and must
+                //! always be set to 0
+                uint8_t invalid : 1;
+                uint8_t padding0 : 6;
+            };
+        };
+    } readings[1];
+};
+
+/**
+ * Data for a sensor which reports a single uint64 value.
+ *
+ * This is used by CHRE_EVENT_SENSOR_STEP_COUNTER_DATA.
+ */
+struct chreSensorUint64Data {
+    struct chreSensorDataHeader header;
+    struct chreSensorUint64SampleData {
+        uint32_t timestampDelta;
+        uint64_t value;
+    } readings[1];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_SENSOR_TYPES_H_ */
diff --git a/chre_api/legacy/v1_6/chre/toolchain.h b/chre_api/legacy/v1_6/chre/toolchain.h
new file mode 100644
index 0000000..c2b8722
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/toolchain.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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_TOOLCHAIN_H_
+#define CHRE_TOOLCHAIN_H_
+
+/**
+ * @file
+ * Compiler/build toolchain-specific macros used by the CHRE API
+ */
+
+#if defined(__GNUC__) || defined(__clang__)
+// For GCC and clang
+
+#define CHRE_DEPRECATED(message) \
+  __attribute__((deprecated(message)))
+
+// Enable printf-style compiler warnings for mismatched format string and args
+#define CHRE_PRINTF_ATTR(formatPos, argStart) \
+  __attribute__((format(printf, formatPos, argStart)))
+
+#define CHRE_BUILD_ERROR(message) CHRE_DO_PRAGMA(GCC error message)
+#define CHRE_DO_PRAGMA(message) _Pragma(#message)
+
+#elif defined(__ICCARM__) || defined(__CC_ARM)
+// For IAR ARM and Keil MDK-ARM compilers
+
+#define CHRE_PRINTF_ATTR(formatPos, argStart)
+
+#elif defined(_MSC_VER)
+// For Microsoft Visual Studio
+
+#define CHRE_PRINTF_ATTR(formatPos, argStart)
+
+#else  // if !defined(__GNUC__) && !defined(__clang__)
+
+#error Need to add support for new compiler
+
+#endif
+
+// For platforms that don't support error pragmas, utilize the best method of
+// showing an error depending on the platform support.
+#ifndef CHRE_BUILD_ERROR
+#ifdef __cplusplus  // C++17 or greater assumed
+#define CHRE_BUILD_ERROR(message) static_assert(0, message)
+#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+#define CHRE_BUILD_ERROR(message) _Static_assert(0, message)
+#else
+#define CHRE_BUILD_ERROR(message) char buildError[-1] = message
+#endif
+#endif
+
+#endif  // CHRE_TOOLCHAIN_H_
diff --git a/chre_api/legacy/v1_6/chre/user_settings.h b/chre_api/legacy/v1_6/chre/user_settings.h
new file mode 100644
index 0000000..3780a3d
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/user_settings.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 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_USER_SETTINGS_H_
+#define _CHRE_USER_SETTINGS_H_
+
+/**
+ * @file
+ * The API for requesting notifications on changes in the settings of the
+ * active user. If the device is set up with one or more secondary users
+ * (see https://source.android.com/devices/tech/admin/multi-user), the user
+ * settings in CHRE reflect that of the currently active user.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <chre/event.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The user settings that nanoapps can request notifications for on a status
+ * change.
+ *
+ * NOTE: The WIFI available setting indicates the overall availability
+ * of WIFI related functionality. For example, if wifi is disabled for
+ * connectivity but enabled for location, the WIFI available setting is
+ * enabled.
+ *
+ * NOTE: The BLE available setting indicates the overall availability of
+ * BLE related functionality. For example, if BLE is disabled for connectivity,
+ * but enabled for location, the BLE available setting is enabled.
+ *
+ * @defgroup CHRE_USER_SETTINGS
+ * @{
+ */
+#define CHRE_USER_SETTING_LOCATION             UINT8_C(0)
+#define CHRE_USER_SETTING_WIFI_AVAILABLE       UINT8_C(1)
+#define CHRE_USER_SETTING_AIRPLANE_MODE        UINT8_C(2)
+#define CHRE_USER_SETTING_MICROPHONE           UINT8_C(3)
+#define CHRE_USER_SETTING_BLE_AVAILABLE        UINT8_C(4)
+
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for settings notifications.
+ *
+ * @param offset Index into the event ID block, valid in the range [0,15]
+ */
+#define CHRE_SETTING_EVENT_ID(offset) (CHRE_EVENT_SETTING_CHANGED_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreUserSettingChangedEvent
+ *
+ * Notify nanoapps of a change in the associated setting. Nanoapps must first
+ * register (via chreUserSettingConfigureEvents) for events before they are
+ * sent out.
+ */
+#define CHRE_EVENT_SETTING_CHANGED_LOCATION         CHRE_SETTING_EVENT_ID(0)
+#define CHRE_EVENT_SETTING_CHANGED_WIFI_AVAILABLE   CHRE_SETTING_EVENT_ID(1)
+#define CHRE_EVENT_SETTING_CHANGED_AIRPLANE_MODE    CHRE_SETTING_EVENT_ID(2)
+#define CHRE_EVENT_SETTING_CHANGED_MICROPHONE       CHRE_SETTING_EVENT_ID(3)
+#define CHRE_EVENT_SETTING_CHANGED_BLE_AVAILABLE    CHRE_SETTING_EVENT_ID(4)
+
+#if CHRE_EVENT_SETTING_CHANGED_BLE_AVAILABLE > CHRE_EVENT_SETTING_CHANGED_LAST_EVENT
+#error Too many setting changed events.
+#endif
+
+/**
+ * Indicates the current state of a setting.
+ * The setting state is 'unknown' only in the following scenarios:
+ *  - CHRE hasn't received the initial state yet on a restart.
+ *  - The nanoapp is running on CHRE v1.4 or older
+ *  - Nanoapp provided in invalid setting ID to chreUserSettingGetStatus.
+ */
+enum chreUserSettingState {
+  CHRE_USER_SETTING_STATE_UNKNOWN = -1,
+  CHRE_USER_SETTING_STATE_DISABLED = 0,
+  CHRE_USER_SETTING_STATE_ENABLED = 1
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE settings changed notifications.
+ */
+struct chreUserSettingChangedEvent {
+  //! Indicates the setting whose state has changed.
+  uint8_t setting;
+
+  //! A value that corresponds to a member in enum chreUserSettingState,
+  // indicating the latest value of the setting.
+  int8_t settingState;
+};
+
+/**
+ * Get the current state of a given setting.
+ *
+ * @param setting The setting to get the current status of.
+ *
+ * @return The current state of the requested setting. The state is returned
+ * as an int8_t to be consistent with the associated event data, but is
+ * guaranteed to be a valid enum chreUserSettingState member.
+ *
+ * @since v1.5
+ */
+int8_t chreUserSettingGetState(uint8_t setting);
+
+/**
+ * Register or deregister for a notification on a status change for a given
+ * setting. Note that registration does not produce an event with the initial
+ * (or current) state, though nanoapps can use chreUserSettingGetState() for
+ * this purpose.
+ *
+ * @param setting The setting on whose change a notification is desired.
+ * @param enable The nanoapp is registered to receive notifications on a
+ * change in the user settings if this parameter is true, otherwise the
+ * nanoapp receives no further notifications for this setting.
+ *
+ * @since v1.5
+ */
+void chreUserSettingConfigureEvents(uint8_t setting, bool enable);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_USER_SETTINGS_H_ */
diff --git a/chre_api/legacy/v1_6/chre/version.h b/chre_api/legacy/v1_6/chre/version.h
new file mode 100644
index 0000000..3cb714b
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/version.h
@@ -0,0 +1,235 @@
+/*
+ * 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_VERSION_H_
+#define _CHRE_VERSION_H_
+
+/**
+ * @file
+ * Definitions and methods for the versioning of the Context Hub Runtime
+ * Environment.
+ *
+ * The CHRE API versioning pertains to all header files in the CHRE API.
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Value for version 0.1 of the Context Hub Runtime Environment API interface.
+ *
+ * This is a legacy version of the CHRE API. Version 1.0 is considered the first
+ * official CHRE API version.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_0_1 UINT32_C(0x00010000)
+
+/**
+ * Value for version 1.0 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android Nougat release.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_0 UINT32_C(0x01000000)
+
+/**
+ * Value for version 1.1 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android O release. It adds
+ * initial support for new GNSS, WiFi, and WWAN modules.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_1 UINT32_C(0x01010000)
+
+/**
+ * Value for version 1.2 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android P release. It adds
+ * initial support for the new audio module.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_2 UINT32_C(0x01020000)
+
+/**
+ * Value for version 1.3 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android Q release. It adds
+ * support for GNSS location altitude/speed/bearing accuracy. It also adds step
+ * detect as a standard CHRE sensor and supports bias event delivery and sensor
+ * data flushing.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_3 UINT32_C(0x01030000)
+
+/**
+ * Value for version 1.4 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android R release. It adds
+ * support for collecting debug dump information from nanoapps, receiving L5
+ * GNSS measurements, determining if a sensor supports passive requests,
+ * receiving 5G cell info, and deprecates chreSendMessageToHost.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_4 UINT32_C(0x01040000)
+
+/**
+ * Value for version 1.5 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android S release. It adds
+ * support for multiple sensors of the same type, permissions for sensitive CHRE
+ * APIs / data usage, ability to receive user settings updates, step counter and
+ * hinge angle sensors, improved WiFi scan preferences to support power
+ * optimization, new WiFi security types, increased the lower bound for the
+ * maximum CHRE to host message size, and increased GNSS measurements in
+ * chreGnssDataEvent.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_5 UINT32_C(0x01050000)
+
+/**
+ * Value for version 1.6 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API is shipped with the Android T release. It adds
+ * support for BLE scanning, subscribing to the WiFi NAN discovery engine,
+ * subscribing to host endpoint notifications, requesting metadata for a host
+ * endpoint ID, nanoapps publishing RPC services they support, and limits the
+ * nanoapp instance ID size to INT16_MAX.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_6 UINT32_C(0x01060000)
+
+/**
+ * Major and Minor Version of this Context Hub Runtime Environment API.
+ *
+ * The major version changes when there is an incompatible API change.
+ *
+ * The minor version changes when there is an addition in functionality
+ * in a backwards-compatible manner.
+ *
+ * We define the version number as an unsigned 32-bit value.  The most
+ * significant byte is the Major Version.  The second-most significant byte
+ * is the Minor Version.  The two least significant bytes are the Patch
+ * Version.  The Patch Version is not defined by this header API, but
+ * is provided by a specific CHRE implementation (see chreGetVersion()).
+ *
+ * Note that version numbers can always be numerically compared with
+ * expected results, so 1.0.0 < 1.0.4 < 1.1.0 < 2.0.300 < 3.5.0.
+ */
+#define CHRE_API_VERSION CHRE_API_VERSION_1_6
+
+/**
+ * Utility macro to extract only the API major version of a composite CHRE
+ * version.
+ *
+ * @param version A uint32_t version, e.g. the value returned by
+ *     chreGetApiVersion()
+ *
+ * @return The API major version in the least significant byte, e.g. 0x01
+ */
+#define CHRE_EXTRACT_MAJOR_VERSION(version) \
+  (uint32_t)(((version) & UINT32_C(0xFF000000)) >> 24)
+
+/**
+ * Utility macro to extract only the API minor version of a composite CHRE
+ * version.
+ *
+ * @param version A uint32_t version, e.g. the CHRE_API_VERSION constant
+ *
+ * @return The API minor version in the least significant byte, e.g. 0x01
+ */
+#define CHRE_EXTRACT_MINOR_VERSION(version) \
+  (uint32_t)(((version) & UINT32_C(0x00FF0000)) >> 16)
+
+/**
+ * Utility macro to extract only the API minor version of a composite CHRE
+ * version.
+ *
+ * @param version A complete uint32_t version, e.g. the value returned by
+ *     chreGetVersion()
+ *
+ * @return The implementation patch version in the least significant two bytes,
+ *     e.g. 0x0123, with all other bytes set to 0
+ */
+#define CHRE_EXTRACT_PATCH_VERSION(version) (uint32_t)((version) & UINT32_C(0xFFFF))
+
+/**
+ * Get the API version the CHRE implementation was compiled against.
+ *
+ * This is not necessarily the CHRE_API_VERSION in the header the nanoapp was
+ * built against, and indeed may not have even appeared in the context_hub_os.h
+ * header which this nanoapp was built against.
+ *
+ * By definition, this will have the two least significant bytes set to 0,
+ * and only contain the major and minor version number.
+ *
+ * @return The API version.
+ */
+uint32_t chreGetApiVersion(void);
+
+/**
+ * Get the version of this CHRE implementation.
+ *
+ * By definition, ((chreGetApiVersion() & UINT32_C(0xFFFF0000)) ==
+ *                 (chreGetVersion()    & UINT32_C(0xFFFF0000))).
+ *
+ * The Patch Version, in the lower two bytes, only have meaning in context
+ * of this specific platform ID.  It is increased by the platform every time
+ * a backwards-compatible bug fix is released.
+ *
+ * @return The version.
+ *
+ * @see chreGetPlatformId()
+ */
+uint32_t chreGetVersion(void);
+
+/**
+ * Get the Platform ID of this CHRE.
+ *
+ * The most significant five bytes are the vendor ID as set out by the
+ * NANOAPP_VENDOR convention in the original context hub HAL header file
+ * (context_hub.h), also used by nanoapp IDs.
+ *
+ * The least significant three bytes are set by the vendor, but must be
+ * unique for each different CHRE implementation/hardware that the vendor
+ * supplies.
+ *
+ * The idea is that in the case of known bugs in the field, a new nanoapp could
+ * be shipped with a workaround that would use this value, and chreGetVersion(),
+ * to have code that can conditionally work around the bug on a buggy version.
+ * Thus, we require this uniqueness to allow such a setup to work.
+ *
+ * @return The platform ID.
+ *
+ * @see CHRE_EXTRACT_VENDOR_ID
+ */
+uint64_t chreGetPlatformId(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CHRE_VERSION_H_ */
diff --git a/chre_api/legacy/v1_6/chre/wifi.h b/chre_api/legacy/v1_6/chre/wifi.h
new file mode 100644
index 0000000..b4bda9c
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/wifi.h
@@ -0,0 +1,1313 @@
+/*
+ * 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_WIFI_H_
+#define _CHRE_WIFI_H_
+
+/**
+ * @file
+ * WiFi (IEEE 802.11) API, currently covering scanning features useful for
+ * determining location and offloading certain connectivity scans.
+ *
+ * In this file, specification references use the following shorthand:
+ *
+ *    Shorthand | Full specification name
+ *   ---------- | ------------------------
+ *     "802.11" | IEEE Std 802.11-2007
+ *     "HT"     | IEEE Std 802.11n-2009
+ *     "VHT"    | IEEE Std 802.11ac-2013
+ *     "WiFi 6" | IEEE Std 802.11ax draft
+ *     "NAN"    | Wi-Fi Neighbor Awareness Networking (NAN) Technical
+ *                Specification (v3.2)
+ *
+ * In the current version of CHRE API, the 6GHz band introduced in WiFi 6 is
+ * not supported. A scan request from CHRE should not result in scanning 6GHz
+ * channels. In particular, if a 6GHz channel is specified in scanning or
+ * ranging request parameter, CHRE should return an error code of
+ * CHRE_ERROR_NOT_SUPPORTED. Additionally, CHRE implementations must not include
+ * observations of access points on 6GHz channels in scan results, especially
+ * those produced due to scan monitoring.
+ */
+
+#include "common.h"
+#include <chre/common.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags returned by chreWifiGetCapabilities().
+ * @defgroup CHRE_WIFI_CAPABILITIES
+ * @{
+ */
+
+//! No WiFi APIs are supported
+#define CHRE_WIFI_CAPABILITIES_NONE              UINT32_C(0)
+
+//! Listening to scan results is supported, as enabled via
+//! chreWifiConfigureScanMonitorAsync()
+#define CHRE_WIFI_CAPABILITIES_SCAN_MONITORING   UINT32_C(1 << 0)
+
+//! Requesting WiFi scans on-demand is supported via chreWifiRequestScanAsync()
+#define CHRE_WIFI_CAPABILITIES_ON_DEMAND_SCAN    UINT32_C(1 << 1)
+
+//! Specifying the radio chain preference in on-demand scan requests, and
+//! reporting it in scan events is supported
+//! @since v1.2
+#define CHRE_WIFI_CAPABILITIES_RADIO_CHAIN_PREF  UINT32_C(1 << 2)
+
+//! Requesting RTT ranging is supported via chreWifiRequestRangingAsync()
+//! @since v1.2
+#define CHRE_WIFI_CAPABILITIES_RTT_RANGING       UINT32_C(1 << 3)
+
+//! Specifies if WiFi NAN service subscription is supported. If a platform
+//! supports subscriptions, then it must also support RTT ranging for NAN
+//! services via chreWifiNanRequestRangingAsync()
+//! @since v1.6
+#define CHRE_WIFI_CAPABILITIES_NAN_SUB           UINT32_C(1 << 4)
+
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for WiFi
+ * @param offset  Index into WiFi event ID block; valid range [0,15]
+ */
+#define CHRE_WIFI_EVENT_ID(offset)  (CHRE_EVENT_WIFI_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Communicates the asynchronous result of a request to the WiFi API. The
+ * requestType field in {@link #chreAsyncResult} is set to a value from enum
+ * chreWifiRequestType.
+ */
+#define CHRE_EVENT_WIFI_ASYNC_RESULT  CHRE_WIFI_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiScanEvent
+ *
+ * Provides results of a WiFi scan.
+ */
+#define CHRE_EVENT_WIFI_SCAN_RESULT  CHRE_WIFI_EVENT_ID(1)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiRangingEvent
+ *
+ * Provides results of an RTT ranging request.
+ */
+#define CHRE_EVENT_WIFI_RANGING_RESULT  CHRE_WIFI_EVENT_ID(2)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanIdentifierEvent
+ *
+ * Lets the client know if the NAN engine was able to successfully assign
+ * an identifier to the subscribe call. The 'cookie' field in the event
+ * argument struct can be used to track which subscribe request this identifier
+ * maps to.
+ */
+#define CHRE_EVENT_WIFI_NAN_IDENTIFIER_RESULT   CHRE_WIFI_EVENT_ID(3)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanDiscoveryEvent
+ *
+ * Event that is sent whenever a NAN service matches the criteria specified
+ * in a subscription request.
+ */
+#define CHRE_EVENT_WIFI_NAN_DISCOVERY_RESULT  CHRE_WIFI_EVENT_ID(4)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanSessionLostEvent
+ *
+ * Informs the client that a discovered service is no longer available or
+ * visible.
+ * The ID of the service on the client that was communicating with the extinct
+ * service is indicated by the event argument.
+ */
+#define CHRE_EVENT_WIFI_NAN_SESSION_LOST  CHRE_WIFI_EVENT_ID(5)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanSessionTerminatedEvent
+ *
+ * Signals the end of a NAN subscription session. The termination can be due to
+ * the user turning the WiFi off, or other platform reasons like not being able
+ * to support NAN concurrency with the host. The terminated event will have a
+ * reason code appropriately populated to denote why the event was sent.
+ */
+#define CHRE_EVENT_WIFI_NAN_SESSION_TERMINATED  CHRE_WIFI_EVENT_ID(6)
+
+// NOTE: Do not add new events with ID > 15; only values 0-15 are reserved
+// (see chre/event.h)
+
+/**
+ * The maximum amount of time that is allowed to elapse between a call to
+ * chreWifiRequestScanAsync() that returns true, and the associated
+ * CHRE_EVENT_WIFI_ASYNC_RESULT used to indicate whether the scan completed
+ * successfully or not.
+ */
+#define CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS  (30 * CHRE_NSEC_PER_SEC)
+
+/**
+ * The maximum amount of time that is allowed to elapse between a call to
+ * chreWifiRequestRangingAsync() that returns true, and the associated
+ * CHRE_EVENT_WIFI_RANGING_RESULT used to indicate whether the ranging operation
+ * completed successfully or not.
+ */
+#define CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS  (30 * CHRE_NSEC_PER_SEC)
+
+/**
+ * The current compatibility version of the chreWifiScanEvent structure,
+ * including nested structures.
+ */
+#define CHRE_WIFI_SCAN_EVENT_VERSION  UINT8_C(1)
+
+/**
+ * The current compatibility version of the chreWifiRangingEvent structure,
+ * including nested structures.
+ */
+#define CHRE_WIFI_RANGING_EVENT_VERSION  UINT8_C(0)
+
+/**
+ * Maximum number of frequencies that can be explicitly specified when
+ * requesting a scan
+ * @see #chreWifiScanParams
+ */
+#define CHRE_WIFI_FREQUENCY_LIST_MAX_LEN  (20)
+
+/**
+ * Maximum number of SSIDs that can be explicitly specified when requesting a
+ * scan
+ * @see #chreWifiScanParams
+ */
+#define CHRE_WIFI_SSID_LIST_MAX_LEN  (20)
+
+/**
+ * The maximum number of devices that can be specified in a single RTT ranging
+ * request.
+ * @see #chreWifiRangingParams
+ */
+#define CHRE_WIFI_RANGING_LIST_MAX_LEN  (10)
+
+/**
+ * The maximum number of octets in an SSID (see 802.11 7.3.2.1)
+ */
+#define CHRE_WIFI_SSID_MAX_LEN  (32)
+
+/**
+ * The number of octets in a BSSID (see 802.11 7.1.3.3.3)
+ */
+#define CHRE_WIFI_BSSID_LEN  (6)
+
+/**
+ * Set of flags which can either indicate a frequency band. Specified as a bit
+ * mask to allow for combinations in future API versions.
+ * @defgroup CHRE_WIFI_BAND_MASK
+ * @{
+ */
+
+#define CHRE_WIFI_BAND_MASK_2_4_GHZ  UINT8_C(1 << 0)  //!< 2.4 GHz
+#define CHRE_WIFI_BAND_MASK_5_GHZ    UINT8_C(1 << 1)  //!< 5 GHz
+
+/** @} */
+
+/**
+ * Characteristics of a scanned device given in struct chreWifiScanResult.flags
+ * @defgroup CHRE_WIFI_SCAN_RESULT_FLAGS
+ * @{
+ */
+
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_NONE                         UINT8_C(0)
+
+//! Element ID 61 (HT Operation) is present (see HT 7.3.2)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_HT_OPS_PRESENT               UINT8_C(1 << 0)
+
+//! Element ID 192 (VHT Operation) is present (see VHT 8.4.2)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_VHT_OPS_PRESENT              UINT8_C(1 << 1)
+
+//! Element ID 127 (Extended Capabilities) is present, and bit 70 (Fine Timing
+//! Measurement Responder) is set to 1 (see IEEE Std 802.11-2016 9.4.2.27)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER             UINT8_C(1 << 2)
+
+//! Retained for backwards compatibility
+//! @see CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_IS_80211MC_RTT_RESPONDER \
+    CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER
+
+//! HT Operation element indicates that a secondary channel is present
+//! (see HT 7.3.2.57)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_HAS_SECONDARY_CHANNEL_OFFSET UINT8_C(1 << 3)
+
+//! HT Operation element indicates that the secondary channel is below the
+//! primary channel (see HT 7.3.2.57)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_SECONDARY_CHANNEL_OFFSET_IS_BELOW  \
+                                                                 UINT8_C(1 << 4)
+
+/** @} */
+
+/**
+ * Identifies the authentication methods supported by an AP. Note that not every
+ * combination of flags may be possible. Based on WIFI_PNO_AUTH_CODE_* from
+ * hardware/libhardware_legacy/include/hardware_legacy/gscan.h in Android.
+ * @defgroup CHRE_WIFI_SECURITY_MODE_FLAGS
+ * @{
+ */
+
+#define CHRE_WIFI_SECURITY_MODE_UNKONWN  UINT8_C(0)
+
+#define CHRE_WIFI_SECURITY_MODE_OPEN  UINT8_C(1 << 0)  //!< No auth/security
+#define CHRE_WIFI_SECURITY_MODE_WEP   UINT8_C(1 << 1)
+#define CHRE_WIFI_SECURITY_MODE_PSK   UINT8_C(1 << 2)  //!< WPA-PSK or WPA2-PSK
+#define CHRE_WIFI_SECURITY_MODE_EAP   UINT8_C(1 << 3)  //!< WPA-EAP or WPA2-EAP
+
+//! @since v1.5
+#define CHRE_WIFI_SECURITY_MODE_SAE   UINT8_C(1 << 4)
+
+//! @since v1.5
+#define CHRE_WIFI_SECURITY_MODE_EAP_SUITE_B  UINT8_C(1 << 5)
+
+//! @since v1.5
+#define CHRE_WIFI_SECURITY_MODE_OWE   UINT8_C(1 << 6)
+
+/** @} */
+
+/**
+ * Identifies which radio chain was used to discover an AP. The underlying
+ * hardware does not necessarily support more than one radio chain.
+ * @defgroup CHRE_WIFI_RADIO_CHAIN_FLAGS
+ * @{
+ */
+
+#define CHRE_WIFI_RADIO_CHAIN_UNKNOWN  UINT8_C(0)
+#define CHRE_WIFI_RADIO_CHAIN_0        UINT8_C(1 << 0)
+#define CHRE_WIFI_RADIO_CHAIN_1        UINT8_C(1 << 1)
+
+/** @} */
+
+//! Special value indicating that an LCI uncertainty fields is not provided
+//! Ref: RFC 6225
+#define CHRE_WIFI_LCI_UNCERTAINTY_UNKNOWN  UINT8_C(0)
+
+/**
+ * Defines the flags that may be returned in
+ * {@link #chreWifiRangingResult.flags}. Undefined bits are reserved for future
+ * use and must be ignored by nanoapps.
+ * @defgroup CHRE_WIFI_RTT_RESULT_FLAGS
+ * @{
+ */
+
+//! If set, the nested chreWifiLci structure is populated; otherwise it is
+//! invalid and must be ignored
+#define CHRE_WIFI_RTT_RESULT_HAS_LCI  UINT8_C(1 << 0)
+
+/** @} */
+
+/**
+ * Identifies a WiFi frequency band
+ */
+enum chreWifiBand {
+    CHRE_WIFI_BAND_2_4_GHZ = CHRE_WIFI_BAND_MASK_2_4_GHZ,
+    CHRE_WIFI_BAND_5_GHZ   = CHRE_WIFI_BAND_MASK_5_GHZ,
+};
+
+/**
+ * Indicates the BSS operating channel width determined from the VHT and/or HT
+ * Operation elements. Refer to VHT 8.4.2.161 and HT 7.3.2.57.
+ */
+enum chreWifiChannelWidth {
+    CHRE_WIFI_CHANNEL_WIDTH_20_MHZ         = 0,
+    CHRE_WIFI_CHANNEL_WIDTH_40_MHZ         = 1,
+    CHRE_WIFI_CHANNEL_WIDTH_80_MHZ         = 2,
+    CHRE_WIFI_CHANNEL_WIDTH_160_MHZ        = 3,
+    CHRE_WIFI_CHANNEL_WIDTH_80_PLUS_80_MHZ = 4,
+};
+
+/**
+ * Indicates the type of scan requested or performed
+ */
+enum chreWifiScanType {
+    //! Perform a purely active scan using probe requests. Do not scan channels
+    //! restricted to use via Dynamic Frequency Selection (DFS) only.
+    CHRE_WIFI_SCAN_TYPE_ACTIVE = 0,
+
+    //! Perform an active scan on unrestricted channels, and also perform a
+    //! passive scan on channels that are restricted to use via Dynamic
+    //! Frequency Selection (DFS), e.g. the U-NII bands 5250-5350MHz and
+    //! 5470-5725MHz in the USA as mandated by FCC regulation.
+    CHRE_WIFI_SCAN_TYPE_ACTIVE_PLUS_PASSIVE_DFS = 1,
+
+    //! Perform a passive scan, only listening for beacons.
+    CHRE_WIFI_SCAN_TYPE_PASSIVE = 2,
+
+    //! Client has no preference for a particular scan type.
+    //! Only valid in a {@link #chreWifiScanParams}.
+    //!
+    //! On a v1.4 or earlier platform, this will fall back to
+    //! CHRE_WIFI_SCAN_TYPE_ACTIVE if {@link #chreWifiScanParams.channelSet} is
+    //! set to CHRE_WIFI_CHANNEL_SET_NON_DFS, and to
+    //! CHRE_WIFI_SCAN_TYPE_ACTIVE_PLUS_PASSIVE_DFS otherwise.
+    //!
+    //! If CHRE_WIFI_CAPABILITIES_RADIO_CHAIN_PREF is supported, a v1.5 or
+    //! later platform shall perform a type of scan optimized for {@link
+    //! #chreWifiScanParams.radioChainPref}.
+    //!
+    //! Clients are strongly encouraged to set this value in {@link
+    //! #chreWifiScanParams.scanType} and instead express their preferences
+    //! through {@link #chreWifiRadioChainPref} and {@link #chreWifiChannelSet}
+    //! so the platform can best optimize power and performance.
+    //!
+    //! @since v1.5
+    CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE = 3,
+};
+
+/**
+ * Indicates whether RTT ranging with a specific device succeeded
+ */
+enum chreWifiRangingStatus {
+    //! Ranging completed successfully
+    CHRE_WIFI_RANGING_STATUS_SUCCESS = 0,
+
+    //! Ranging failed due to an unspecified error
+    CHRE_WIFI_RANGING_STATUS_ERROR   = 1,
+};
+
+/**
+ * Possible values for {@link #chreWifiLci.altitudeType}. Ref: RFC 6225 2.4
+ */
+enum chreWifiLciAltitudeType {
+    CHRE_WIFI_LCI_ALTITUDE_TYPE_UNKNOWN = 0,
+    CHRE_WIFI_LCI_ALTITUDE_TYPE_METERS  = 1,
+    CHRE_WIFI_LCI_ALTITUDE_TYPE_FLOORS  = 2,
+};
+
+/**
+ * Indicates a type of request made in this API. Used to populate the resultType
+ * field of struct chreAsyncResult sent with CHRE_EVENT_WIFI_ASYNC_RESULT.
+ */
+enum chreWifiRequestType {
+    CHRE_WIFI_REQUEST_TYPE_CONFIGURE_SCAN_MONITOR = 1,
+    CHRE_WIFI_REQUEST_TYPE_REQUEST_SCAN           = 2,
+    CHRE_WIFI_REQUEST_TYPE_RANGING                = 3,
+    CHRE_WIFI_REQUEST_TYPE_NAN_SUBSCRIBE          = 4,
+};
+
+/**
+ * Allows a nanoapp to express its preference for how multiple available
+ * radio chains should be used when performing an on-demand scan. This is only a
+ * preference from the nanoapp and is not guaranteed to be honored by the WiFi
+ * firmware.
+ */
+enum chreWifiRadioChainPref {
+    //! No preference for radio chain usage
+    CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT = 0,
+
+    //! In a scan result, indicates that the radio chain preference used for the
+    //! scan is not known
+    CHRE_WIFI_RADIO_CHAIN_PREF_UNKNOWN = CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT,
+
+    //! Prefer to use available radio chains in a way that minimizes time to
+    //! complete the scan
+    CHRE_WIFI_RADIO_CHAIN_PREF_LOW_LATENCY = 1,
+
+    //! Prefer to use available radio chains in a way that minimizes total power
+    //! consumed for the scan
+    CHRE_WIFI_RADIO_CHAIN_PREF_LOW_POWER = 2,
+
+    //! Prefer to use available radio chains in a way that maximizes accuracy of
+    //! the scan result, e.g. RSSI measurements
+    CHRE_WIFI_RADIO_CHAIN_PREF_HIGH_ACCURACY = 3,
+};
+
+/**
+ * WiFi NAN subscription type.
+ */
+enum chreWifiNanSubscribeType {
+    //! In the active mode, explicit transmission of a subscribe message is
+    //! requested, and publish messages are processed.
+    CHRE_WIFI_NAN_SUBSCRIBE_TYPE_ACTIVE = 0,
+
+    //! In the passive mode, no transmission of a subscribe message is
+    //! requested, but received publish messages are checked for matches.
+    CHRE_WIFI_NAN_SUBSCRIBE_TYPE_PASSIVE = 1,
+};
+
+/**
+ * Indicates the reason for a subscribe session termination.
+ */
+enum chreWifiNanTerminatedReason {
+    CHRE_WIFI_NAN_TERMINATED_BY_USER_REQUEST = 0,
+    CHRE_WIFI_NAN_TERMINATED_BY_TIMEOUT = 1,
+    CHRE_WIFI_NAN_TERMINATED_BY_FAILURE = 2,
+};
+
+/**
+ * SSID with an explicit length field, used when an array of SSIDs is supplied.
+ */
+struct chreWifiSsidListItem {
+    //! Number of valid bytes in ssid. Valid range [0, CHRE_WIFI_SSID_MAX_LEN]
+    uint8_t ssidLen;
+
+    //! Service Set Identifier (SSID)
+    uint8_t ssid[CHRE_WIFI_SSID_MAX_LEN];
+};
+
+/**
+ * Indicates the set of channels to be scanned.
+ *
+ * @since v1.5
+ */
+enum chreWifiChannelSet {
+    //! The set of channels that allows active scan using probe request.
+    CHRE_WIFI_CHANNEL_SET_NON_DFS = 0,
+
+    //! The set of all channels supported.
+    CHRE_WIFI_CHANNEL_SET_ALL = 1,
+};
+
+/**
+ * Data structure passed to chreWifiRequestScanAsync
+ */
+struct chreWifiScanParams {
+    //! Set to a value from @ref enum chreWifiScanType
+    uint8_t scanType;
+
+    //! Indicates whether the client is willing to tolerate receiving cached
+    //! results of a previous scan, and if so, the maximum age of the scan that
+    //! the client will accept. "Age" in this case is defined as the elapsed
+    //! time between when the most recent scan was completed and the request is
+    //! received, in milliseconds. If set to 0, no cached results may be
+    //! provided, and all scan results must come from a "fresh" WiFi scan, i.e.
+    //! one that completes strictly after this request is received. If more than
+    //! one scan is cached and meets this age threshold, only the newest scan is
+    //! provided.
+    uint32_t maxScanAgeMs;
+
+    //! If set to 0, scan all frequencies. Otherwise, this indicates the number
+    //! of frequencies to scan, as specified in the frequencyList array. Valid
+    //! range [0, CHRE_WIFI_FREQUENCY_LIST_MAX_LEN].
+    uint16_t frequencyListLen;
+
+    //! Pointer to an array of frequencies to scan, given as channel center
+    //! frequencies in MHz. This field may be NULL if frequencyListLen is 0.
+    const uint32_t *frequencyList;
+
+    //! If set to 0, do not restrict scan to any SSIDs. Otherwise, this
+    //! indicates the number of SSIDs in the ssidList array to be used for
+    //! directed probe requests. Not applicable and ignore when scanType is
+    //! CHRE_WIFI_SCAN_TYPE_PASSIVE.
+    uint8_t ssidListLen;
+
+    //! Pointer to an array of SSIDs to use for directed probe requests. May be
+    //! NULL if ssidListLen is 0.
+    const struct chreWifiSsidListItem *ssidList;
+
+    //! Set to a value from enum chreWifiRadioChainPref to specify the desired
+    //! trade-off between power consumption, accuracy, etc. If
+    //! chreWifiGetCapabilities() does not have the applicable bit set, this
+    //! parameter is ignored.
+    //! @since v1.2
+    uint8_t radioChainPref;
+
+    //! Set to a value from enum chreWifiChannelSet to specify the set of
+    //! channels to be scanned. This field is considered by the platform only
+    //! if scanType is CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE and frequencyListLen
+    //! is equal to zero.
+    //!
+    //! @since v1.5
+    uint8_t channelSet;
+};
+
+/**
+ * Provides information about a single access point (AP) detected in a scan.
+ */
+struct chreWifiScanResult {
+    //! Number of milliseconds prior to referenceTime in the enclosing
+    //! chreWifiScanEvent struct when the probe response or beacon frame that
+    //! was used to populate this structure was received.
+    uint32_t ageMs;
+
+    //! Capability Information field sent by the AP (see 802.11 7.3.1.4). This
+    //! field must reflect native byte order and bit ordering, such that
+    //! (capabilityInfo & 1) gives the bit for the ESS subfield.
+    uint16_t capabilityInfo;
+
+    //! Number of valid bytes in ssid. Valid range [0, CHRE_WIFI_SSID_MAX_LEN]
+    uint8_t ssidLen;
+
+    //! Service Set Identifier (SSID), a series of 0 to 32 octets identifying
+    //! the access point. Note that this is commonly a human-readable ASCII
+    //! string, but this is not the required encoding per the standard.
+    uint8_t ssid[CHRE_WIFI_SSID_MAX_LEN];
+
+    //! Basic Service Set Identifier (BSSID), represented in big-endian byte
+    //! order, such that the first octet of the OUI is accessed in byte index 0.
+    uint8_t bssid[CHRE_WIFI_BSSID_LEN];
+
+    //! A set of flags from CHRE_WIFI_SCAN_RESULT_FLAGS_*
+    uint8_t flags;
+
+    //! RSSI (Received Signal Strength Indicator), in dBm. Typically negative.
+    //! If multiple radio chains were used to scan this AP, this is a "best
+    //! available" measure that may be a composite of measurements taken across
+    //! the radio chains.
+    int8_t  rssi;
+
+    //! Operating band, set to a value from enum chreWifiBand
+    uint8_t band;
+
+    /**
+     * Indicates the center frequency of the primary 20MHz channel, given in
+     * MHz. This value is derived from the channel number via the formula:
+     *
+     *     primaryChannel (MHz) = CSF + 5 * primaryChannelNumber
+     *
+     * Where CSF is the channel starting frequency (in MHz) given by the
+     * operating class/band (i.e. 2407 or 5000), and primaryChannelNumber is the
+     * channel number in the range [1, 200].
+     *
+     * Refer to VHT 22.3.14.
+     */
+    uint32_t primaryChannel;
+
+    /**
+     * If the channel width is 20 MHz, this field is not relevant and set to 0.
+     * If the channel width is 40, 80, or 160 MHz, then this denotes the channel
+     * center frequency (in MHz). If the channel is 80+80 MHz, then this denotes
+     * the center frequency of segment 0, which contains the primary channel.
+     * This value is derived from the frequency index using the same formula as
+     * for primaryChannel.
+     *
+     * Refer to VHT 8.4.2.161, and VHT 22.3.14.
+     *
+     * @see #primaryChannel
+     */
+    uint32_t centerFreqPrimary;
+
+    /**
+     * If the channel width is 80+80MHz, then this denotes the center frequency
+     * of segment 1, which does not contain the primary channel. Otherwise, this
+     * field is not relevant and set to 0.
+     *
+     * @see #centerFreqPrimary
+     */
+    uint32_t centerFreqSecondary;
+
+    //! @see #chreWifiChannelWidth
+    uint8_t channelWidth;
+
+    //! Flags from CHRE_WIFI_SECURITY_MODE_* indicating supported authentication
+    //! and associated security modes
+    //! @see CHRE_WIFI_SECURITY_MODE_FLAGS
+    uint8_t securityMode;
+
+    //! Identifies the radio chain(s) used to discover this AP
+    //! @see CHRE_WIFI_RADIO_CHAIN_FLAGS
+    //! @since v1.2
+    uint8_t radioChain;
+
+    //! If the CHRE_WIFI_RADIO_CHAIN_0 bit is set in radioChain, gives the RSSI
+    //! measured on radio chain 0 in dBm; otherwise invalid and set to 0. This
+    //! field, along with its relative rssiChain1, can be used to determine RSSI
+    //! measurements from each radio chain when multiple chains were used to
+    //! discover this AP.
+    //! @see #radioChain
+    //! @since v1.2
+    int8_t rssiChain0;
+    int8_t rssiChain1;  //!< @see #rssiChain0
+
+    //! Reserved; set to 0
+    uint8_t reserved[7];
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_WIFI_SCAN_RESULT.
+ */
+struct chreWifiScanEvent {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that the client only receives the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! The number of entries in the results array in this event. The CHRE
+    //! implementation may split scan results across multiple events for memory
+    //! concerns, etc.
+    uint8_t resultCount;
+
+    //! The total number of results returned by the scan. Allows an event
+    //! consumer to identify when it has received all events associated with a
+    //! scan.
+    uint8_t resultTotal;
+
+    //! Sequence number for this event within the series of events comprising a
+    //! complete scan result. Scan events are delivered strictly in order, i.e.
+    //! this is monotonically increasing for the results of a single scan. Valid
+    //! range [0, <number of events for scan> - 1]. The number of events for a
+    //! scan is typically given by
+    //! ceil(resultTotal / <max results per event supported by platform>).
+    uint8_t eventIndex;
+
+    //! A value from enum chreWifiScanType indicating the type of scan performed
+    uint8_t scanType;
+
+    //! If a directed scan was performed to a limited set of SSIDs, then this
+    //! identifies the number of unique SSIDs included in the probe requests.
+    //! Otherwise, this is set to 0, indicating that the scan was not limited by
+    //! SSID. Note that if this is non-zero, the list of SSIDs used is not
+    //! included in the scan event.
+    uint8_t ssidSetSize;
+
+    //! If 0, indicates that all frequencies applicable for the scanType were
+    //! scanned. Otherwise, indicates the number of frequencies scanned, as
+    //! specified in scannedFreqList.
+    uint16_t scannedFreqListLen;
+
+    //! Timestamp when the scan was completed, from the same time base as
+    //! chreGetTime() (in nanoseconds)
+    uint64_t referenceTime;
+
+    //! Pointer to an array containing scannedFreqListLen values comprising the
+    //! set of frequencies that were scanned. Frequencies are specified as
+    //! channel center frequencies in MHz. May be NULL if scannedFreqListLen is
+    //! 0.
+    const uint32_t *scannedFreqList;
+
+    //! Pointer to an array containing resultCount entries. May be NULL if
+    //! resultCount is 0.
+    const struct chreWifiScanResult *results;
+
+    //! Set to a value from enum chreWifiRadioChainPref indicating the radio
+    //! chain preference used for the scan. If the applicable bit is not set in
+    //! chreWifiGetCapabilities(), this will always be set to
+    //! CHRE_WIFI_RADIO_CHAIN_PREF_UNKNOWN.
+    //! @since v1.2
+    uint8_t radioChainPref;
+};
+
+/**
+ * Identifies a device to perform RTT ranging against. These values are normally
+ * populated based on the contents of a scan result.
+ * @see #chreWifiScanResult
+ * @see chreWifiRangingTargetFromScanResult()
+ */
+struct chreWifiRangingTarget {
+    //! Device MAC address, specified in the same byte order as
+    //! {@link #chreWifiScanResult.bssid}
+    uint8_t macAddress[CHRE_WIFI_BSSID_LEN];
+
+    //! Center frequency of the primary 20MHz channel, in MHz
+    //! @see #chreWifiScanResult.primaryChannel
+    uint32_t primaryChannel;
+
+    //! Channel center frequency, in MHz, or 0 if not relevant
+    //! @see #chreWifiScanResult.centerFreqPrimary
+    uint32_t centerFreqPrimary;
+
+    //! Channel center frequency of segment 1 if channel width is 80+80MHz,
+    //! otherwise 0
+    //! @see #chreWifiScanResult.centerFreqSecondary
+    uint32_t centerFreqSecondary;
+
+    //! @see #chreWifiChannelWidth
+    uint8_t channelWidth;
+
+    //! Reserved for future use and ignored by CHRE
+    uint8_t reserved[3];
+};
+
+/**
+ * Parameters for an RTT ("Fine Timing Measurement" in terms of 802.11-2016)
+ * ranging request, supplied to chreWifiRequestRangingAsync().
+ */
+struct chreWifiRangingParams {
+    //! Number of devices to perform ranging against and the length of
+    //! targetList, in range [1, CHRE_WIFI_RANGING_LIST_MAX_LEN].
+    uint8_t targetListLen;
+
+    //! Array of macAddressListLen MAC addresses (e.g. BSSIDs) with which to
+    //! attempt RTT ranging.
+    const struct chreWifiRangingTarget *targetList;
+};
+
+/**
+ * Provides the result of RTT ranging with a single device.
+ */
+struct chreWifiRangingResult {
+    //! Time when the ranging operation on this device was performed, in the
+    //! same time base as chreGetTime() (in nanoseconds)
+    uint64_t timestamp;
+
+    //! MAC address of the device for which ranging was requested
+    uint8_t macAddress[CHRE_WIFI_BSSID_LEN];
+
+    //! Gives the result of ranging to this device. If not set to
+    //! CHRE_WIFI_RANGING_STATUS_SUCCESS, the ranging attempt to this device
+    //! failed, and other fields in this structure may be invalid.
+    //! @see #chreWifiRangingStatus
+    uint8_t status;
+
+    //! The mean RSSI measured during the RTT burst, in dBm. Typically negative.
+    //! If status is not CHRE_WIFI_RANGING_STATUS_SUCCESS, will be set to 0.
+    int8_t rssi;
+
+    //! Estimated distance to the device with the given BSSID, in millimeters.
+    //! Generally the mean of multiple measurements performed in a single burst.
+    //! If status is not CHRE_WIFI_RANGING_STATUS_SUCCESS, will be set to 0.
+    uint32_t distance;
+
+    //! Standard deviation of estimated distance across multiple measurements
+    //! performed in a single RTT burst, in millimeters. If status is not
+    //! CHRE_WIFI_RANGING_STATUS_SUCCESS, will be set to 0.
+    uint32_t distanceStdDev;
+
+    //! Location Configuration Information (LCI) information optionally returned
+    //! during the ranging procedure. Only valid if {@link #flags} has the
+    //! CHRE_WIFI_RTT_RESULT_HAS_LCI bit set. Refer to IEEE 802.11-2016
+    //! 9.4.2.22.10, 11.24.6.7, and RFC 6225 (July 2011) for more information.
+    //! Coordinates are to be interpreted according to the WGS84 datum.
+    struct chreWifiLci {
+        //! Latitude in degrees as 2's complement fixed-point with 25 fractional
+        //! bits, i.e. degrees * 2^25. Ref: RFC 6225 2.3
+        int64_t latitude;
+
+        //! Longitude, same format as {@link #latitude}
+        int64_t longitude;
+
+        //! Altitude represented as a 2's complement fixed-point value with 8
+        //! fractional bits. Interpretation depends on {@link #altitudeType}. If
+        //! UNKNOWN, this field must be ignored. If *METERS, distance relative
+        //! to the zero point in the vertical datum. If *FLOORS, a floor value
+        //! relative to the ground floor, potentially fractional, e.g. to
+        //! indicate mezzanine levels. Ref: RFC 6225 2.4
+        int32_t altitude;
+
+        //! Maximum extent of latitude uncertainty in degrees, decoded via this
+        //! formula: 2 ^ (8 - x) where "x" is the encoded value passed in this
+        //! field. Unknown if set to CHRE_WIFI_LCI_UNCERTAINTY_UNKNOWN.
+        //! Ref: RFC 6225 2.3.2
+        uint8_t latitudeUncertainty;
+
+        //! @see #latitudeUncertainty
+        uint8_t longitudeUncertainty;
+
+        //! Defines how to interpret altitude, set to a value from enum
+        //! chreWifiLciAltitudeType
+        uint8_t altitudeType;
+
+        //! Uncertainty in altitude, decoded via this formula: 2 ^ (21 - x)
+        //! where "x" is the encoded value passed in this field. Unknown if set
+        //! to CHRE_WIFI_LCI_UNCERTAINTY_UNKNOWN. Only applies when altitudeType
+        //! is CHRE_WIFI_LCI_ALTITUDE_TYPE_METERS. Ref: RFC 6225 2.4.5
+        uint8_t altitudeUncertainty;
+    } lci;
+
+    //! Refer to CHRE_WIFI_RTT_RESULT_FLAGS
+    uint8_t flags;
+
+    //! Reserved; set to 0
+    uint8_t reserved[7];
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_WIFI_RANGING_RESULT.
+ */
+struct chreWifiRangingEvent {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that the client only receives the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! The number of ranging results included in the results array; matches the
+    //! number of MAC addresses specified in the request
+    uint8_t resultCount;
+
+    //! Reserved; set to 0
+    uint8_t reserved[2];
+
+    //! Pointer to an array containing resultCount entries
+    const struct chreWifiRangingResult *results;
+};
+
+/**
+ * Indicates the WiFi NAN capabilities of the device. Must contain non-zero
+ * values if WiFi NAN is supported.
+ */
+struct chreWifiNanCapabilities {
+    //! Maximum length of the match filter arrays (applies to both tx and rx
+    //! match filters).
+    uint32_t maxMatchFilterLength;
+
+    //! Maximum length of the service specific information byte array.
+    uint32_t maxServiceSpecificInfoLength;
+
+    //! Maximum length of the service name. Includes the NULL terminator.
+    uint8_t maxServiceNameLength;
+
+    //! Reserved for future use.
+    uint8_t reserved[3];
+};
+
+/**
+ * Data structure sent with events of type
+ * CHRE_EVENT_WIFI_NAN_IDENTIFIER_RESULT
+ */
+struct chreWifiNanIdentifierEvent {
+    //! A unique ID assigned by the NAN engine for the subscribe request
+    //! associated with the cookie encapsulated in the async result below. The
+    //! ID is set to 0 if there was a request failure in which case the async
+    //! result below contains the appropriate error code indicating the failure
+    //! reason.
+    uint32_t id;
+
+    //! Structure which contains the cookie associated with the publish/
+    //! subscribe request, along with an error code that indicates request
+    //! success or failure.
+    struct chreAsyncResult result;
+};
+
+/**
+ * Indicates the desired configuration for a WiFi NAN ranging request.
+ */
+struct chreWifiNanRangingParams {
+    //! MAC address of the NAN device for which range is to be determined.
+    uint8_t macAddress[CHRE_WIFI_BSSID_LEN];
+};
+
+/**
+ * Configuration parameters specific to the Subscribe Function (Spec 4.1.1.1)
+ */
+struct chreWifiNanSubscribeConfig {
+    //! Indicates the subscribe type, set to a value from @ref
+    //! chreWifiNanSubscribeType.
+    uint8_t subscribeType;
+
+    //! UTF-8 name string that identifies the service/application. Must be NULL
+    //! terminated. Note that the string length cannot be greater than the
+    //! maximum length specified by @ref chreWifiNanCapabilities. No
+    //! restriction is placed on the string case, since the service name
+    //! matching is expected to be case insensitive.
+    const char *service;
+
+    //! An array of bytes (and the associated array length) of service-specific
+    //! information. Note that the array length must be less than the
+    //! maxServiceSpecificInfoLength parameter obtained from the NAN
+    //! capabilities (@see struct chreWifiNanCapabilities).
+    const uint8_t *serviceSpecificInfo;
+    uint32_t serviceSpecificInfoSize;
+
+    //! Ordered sequence of {length | value} pairs that specify match criteria
+    //! beyond the service name. 'length' uses 1 byte, and its value indicates
+    //! the number of bytes of the match criteria that follow. The length of
+    //! the match filter array should not exceed the maximum match filter
+    //! length obtained from @ref chreWifiNanGetCapabilities. When a service
+    //! publish message discovery frame containing the Service ID being
+    //! subscribed to is received, the matching is done as follows:
+    //! Each {length | value} pair in the kth position (1 <= k <= #length-value
+    //! pairs) is compared against the kth {length | value} pair in the
+    //! matching filter field of the publish message.
+    //! - For a kth position {length | value} pair in the rx match filter with
+    //!   a length of 0, a match is declared regardless of the tx match filter
+    //!   contents.
+    //! - For a kth position {length | value} pair in the rx match with a non-
+    //!   zero length, there must be an exact match with the kth position pair
+    //!    in the match filter field of the received service descriptor for a
+    //!    match to be found.
+    //! Please refer to Appendix H of the NAN spec for examples on matching.
+    //! The match filter length should not exceed the maxMatchFilterLength
+    //! obtained from @ref chreWifiNanCapabilities.
+    const uint8_t *matchFilter;
+    uint32_t matchFilterLength;
+};
+
+/**
+ * Data structure sent with events of type
+ * CHRE_EVENT_WIFI_NAN_DISCOVERY_RESULT.
+ */
+struct chreWifiNanDiscoveryEvent {
+    //! Identifier of the subscribe function instance that requested a
+    //! discovery.
+    uint32_t subscribeId;
+
+    //! Identifier of the publisher on the remote NAN device.
+    uint32_t publishId;
+
+    //! NAN interface address of the publisher
+    uint8_t publisherAddress[CHRE_WIFI_BSSID_LEN];
+
+    //! An array of bytes (and the associated array length) of service-specific
+    //! information. Note that the array length must be less than the
+    //! maxServiceSpecificInfoLength parameter obtained from the NAN
+    //! capabilities (@see struct chreWifiNanCapabilities).
+    const uint8_t *serviceSpecificInfo;
+    uint32_t serviceSpecificInfoSize;
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_WIFI_NAN_SESSION_LOST.
+ */
+struct chreWifiNanSessionLostEvent {
+    //! The original ID (returned by the NAN discovery engine) of the subscriber
+    //! instance.
+    uint32_t id;
+
+    //! The ID of the previously discovered publisher on a peer NAN device that
+    //! is no longer connected.
+    uint32_t peerId;
+};
+
+/**
+ * Data structure sent with events of type
+ * CHRE_EVENT_WIFI_NAN_SESSION_TERMINATED.
+ */
+struct chreWifiNanSessionTerminatedEvent {
+    //! The original ID (returned by the NAN discovery engine) of the subscriber
+    //! instance that was terminated.
+    uint32_t id;
+
+    //! A value that maps to one of the termination reasons in @ref enum
+    //! chreWifiNanTerminatedReason.
+    uint8_t reason;
+
+    //! Reserved for future use.
+    uint8_t reserved[3];
+};
+
+/**
+ * Retrieves a set of flags indicating the WiFi features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the Nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_WIFI_CAPABILITIES_* flags set
+ *
+ * @since v1.1
+ */
+uint32_t chreWifiGetCapabilities(void);
+
+/**
+ * Retrieves device-specific WiFi NAN capabilities, and populates them in
+ * the @ref chreWifiNanCapabilities structure.
+ *
+ * @param capabilities Structure into which the WiFi NAN capabilities of
+ *        the device are populated into. Must not be NULL.
+ * @return true if WiFi NAN is supported, false otherwise.
+ *
+ * @since v1.6
+ */
+bool chreWifiNanGetCapabilities(struct chreWifiNanCapabilities *capabilities);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_WIFI somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following WiFi APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to WiFi data by adding metadata
+ * to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_WIFI) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Manages a client's request to receive the results of WiFi scans performed for
+ * other purposes, for example scans done to maintain connectivity and scans
+ * requested by other clients. The presence of this request has no effect on the
+ * frequency or configuration of the WiFi scans performed - it is purely a
+ * registration by the client to receive the results of scans that would
+ * otherwise occur normally. This should include all available scan results,
+ * including those that are not normally sent to the applications processor,
+ * such as Preferred Network Offload (PNO) scans. Scan results provided because
+ * of this registration must not contain cached results - they are always
+ * expected to contain the fresh results from a recent scan.
+ *
+ * An active scan monitor subscription must persist across temporary conditions
+ * under which no WiFi scans will be performed, for example if WiFi is
+ * completely disabled via user-controlled settings, or if the WiFi system
+ * restarts independently of CHRE. Likewise, a request to enable a scan monitor
+ * subscription must succeed under normal conditions, even in circumstances
+ * where no WiFi scans will be performed. In these cases, the scan monitor
+ * implementation must produce scan results once the temporary condition is
+ * cleared, for example after WiFi is enabled by the user.
+ *
+ * These scan results are delivered to the Nanoapp's handle event callback using
+ * CHRE_EVENT_WIFI_SCAN_RESULT.
+ *
+ * An active scan monitor subscription is not necessary to receive the results
+ * of an on-demand scan request sent via chreWifiRequestScanAsync(), and it does
+ * not result in duplicate delivery of scan results generated from
+ * chreWifiRequestScanAsync().
+ *
+ * If no monitor subscription is active at the time of a request with
+ * enable=false, it is treated as if an active subscription was successfully
+ * ended.
+ *
+ * The result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_WIFI_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details.
+ *
+ * @param enable Set to true to enable monitoring scan results, false to
+ *        disable
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WiFi permission
+ */
+bool chreWifiConfigureScanMonitorAsync(bool enable, const void *cookie);
+
+/**
+ * Sends an on-demand request for WiFi scan results. This may trigger a new
+ * scan, or be entirely serviced from cache, depending on the maxScanAgeMs
+ * parameter.
+ *
+ * This resulting status of this request is delivered asynchronously via an
+ * event of type CHRE_EVENT_WIFI_ASYNC_RESULT. The result must be delivered
+ * within CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS of the this request. Refer to the
+ * note in {@link #chreAsyncResult} for more details.
+ *
+ * A successful result provided in CHRE_EVENT_WIFI_ASYNC_RESULT indicates that
+ * the scan results are ready to be delivered in a subsequent event (or events,
+ * which arrive consecutively without any other scan results in between)
+ * of type CHRE_EVENT_WIFI_SCAN_RESULT.
+ *
+ * WiFi scanning must be disabled if both "WiFi scanning" and "WiFi" settings
+ * are disabled at the Android level. In this case, the CHRE implementation is
+ * expected to return a result with CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * It is not valid for a client to request a new scan while a result is pending
+ * based on a previous scan request from the same client. In this situation, the
+ * CHRE implementation is expected to return a result with CHRE_ERROR_BUSY.
+ * However, if a scan is currently pending or in progress due to a request from
+ * another client, whether within the CHRE or otherwise, the implementation must
+ * not fail the request for this reason. If the pending scan satisfies the
+ * client's request parameters, then the implementation should use its results
+ * to satisfy the request rather than scheduling a new scan.
+ *
+ * @param params A set of parameters for the scan request. Must not be NULL.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WiFi permission
+ */
+bool chreWifiRequestScanAsync(const struct chreWifiScanParams *params,
+                              const void *cookie);
+
+/**
+ * Convenience function which calls chreWifiRequestScanAsync() with a default
+ * set of scan parameters.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WiFi permission
+ */
+static inline bool chreWifiRequestScanAsyncDefault(const void *cookie) {
+    static const struct chreWifiScanParams params = {
+        /*.scanType=*/         CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE,
+        /*.maxScanAgeMs=*/     5000,  // 5 seconds
+        /*.frequencyListLen=*/ 0,
+        /*.frequencyList=*/    NULL,
+        /*.ssidListLen=*/      0,
+        /*.ssidList=*/         NULL,
+        /*.radioChainPref=*/   CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT,
+        /*.channelSet=*/       CHRE_WIFI_CHANNEL_SET_NON_DFS
+    };
+    return chreWifiRequestScanAsync(&params, cookie);
+}
+
+/**
+ * Issues a request to initiate distance measurements using round-trip time
+ * (RTT), aka Fine Timing Measurement (FTM), to one or more devices identified
+ * by MAC address. Within CHRE, MACs are typically the BSSIDs of scanned APs
+ * that have the CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER flag set.
+ *
+ * This resulting status of this request is delivered asynchronously via an
+ * event of type CHRE_EVENT_WIFI_ASYNC_RESULT. The result must be delivered
+ * within CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS of the this request. Refer to the
+ * note in {@link #chreAsyncResult} for more details.
+ *
+ * WiFi RTT ranging must be disabled if any of the following is true:
+ * - Both "WiFi" and "WiFi Scanning" settings are disabled at the Android level.
+ * - The "Location" setting is disabled at the Android level.
+ * In this case, the CHRE implementation is expected to return a result with
+ * CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * A successful result provided in CHRE_EVENT_WIFI_ASYNC_RESULT indicates that
+ * the results of ranging will be delivered in a subsequent event of type
+ * CHRE_EVENT_WIFI_RANGING_RESULT. Note that the CHRE_EVENT_WIFI_ASYNC_RESULT
+ * gives an overall status - for example, it is used to indicate failure if the
+ * entire ranging request was rejected because WiFi is disabled. However, it is
+ * valid for this event to indicate success, but RTT ranging to fail for all
+ * requested devices - for example, they may be out of range. Therefore, it is
+ * also necessary to check the status field in {@link #chreWifiRangingResult}.
+ *
+ * @param params Structure containing the parameters of the scan request,
+ *        including the list of devices to attempt ranging.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.2
+ * @note Requires WiFi permission
+ */
+bool chreWifiRequestRangingAsync(const struct chreWifiRangingParams *params,
+                                 const void *cookie);
+
+/**
+ * Helper function to populate an instance of struct chreWifiRangingTarget with
+ * the contents of a scan result provided in struct chreWifiScanResult.
+ * Populates other parameters that are not directly derived from the scan result
+ * with default values.
+ *
+ * @param scanResult The scan result to parse as input
+ * @param rangingTarget The RTT ranging target to populate as output
+ *
+ * @note Requires WiFi permission
+ */
+static inline void chreWifiRangingTargetFromScanResult(
+        const struct chreWifiScanResult *scanResult,
+        struct chreWifiRangingTarget *rangingTarget) {
+    memcpy(rangingTarget->macAddress, scanResult->bssid,
+           sizeof(rangingTarget->macAddress));
+    rangingTarget->primaryChannel      = scanResult->primaryChannel;
+    rangingTarget->centerFreqPrimary   = scanResult->centerFreqPrimary;
+    rangingTarget->centerFreqSecondary = scanResult->centerFreqSecondary;
+    rangingTarget->channelWidth        = scanResult->channelWidth;
+
+    // Note that this is not strictly necessary (CHRE can see which API version
+    // the nanoapp was built against, so it knows to ignore these fields), but
+    // we do it here to keep things nice and tidy
+    memset(rangingTarget->reserved, 0, sizeof(rangingTarget->reserved));
+}
+
+/**
+ * Subscribe to a NAN service.
+ *
+ * Sends a subscription request to the NAN discovery engine with the
+ * specified configration parameters. If successful, a unique non-zero
+ * subscription ID associated with this instance of the subscription
+ * request is assigned by the NAN discovery engine. The subscription request
+ * is active until explicitly canceled, or if the connection was interrupted.
+ *
+ * Note that CHRE forwards any discovery events that it receives to the
+ * subscribe function instance, and does no duplicate filtering. If
+ * multiple events of the same discovery are undesirable, it is up to the
+ * platform NAN discovery engine implementation to implement redundancy
+ * detection mechanisms.
+ *
+ * If WiFi is turned off by the user at the Android level, an existing
+ * subscribe session is canceled, and a CHRE_EVENT_WIFI_ASYNC_RESULT event is
+ * event is sent to the subscriber. Nanoapps are expected to register for user
+ * settings notifications (@see chreUserSettingConfigureEvents), and
+ * re-establish a subscribe session on a WiFi re-enabled settings changed
+ * notification.
+ *
+ * @param config Service subscription configuration
+ * @param cookie A value that the nanoapp uses to track this particular
+ *        subscription request.
+ * @return true if NAN is enabled and a subscription request was successfully
+ *         made to the NAN engine. The actual result of the service discovery
+ *         is sent via a CHRE_EVENT_WIFI_NAN_DISCOVERY_RESULT event.
+ *
+ * @since v1.6
+ * @note Requires WiFi permission
+ */
+bool chreWifiNanSubscribe(struct chreWifiNanSubscribeConfig *config,
+                          const void *cookie);
+
+/**
+ * Cancel a subscribe function instance.
+ *
+ * @param subscriptionId The ID that was originally assigned to this instance
+ *        of the subscribe function.
+ * @return true if NAN is enabled, the subscribe ID  was found and the instance
+ *         successfully canceled.
+ *
+ * @since v1.6
+ * @note Requires WiFi permission
+ */
+bool chreWifiNanSubscribeCancel(uint32_t subscriptionID);
+
+/**
+ * Request RTT ranging from a peer NAN device.
+ *
+ * Nanoapps can use this API to explicitly request measurement reports from
+ * the peer device. Note that both end points have to support ranging for a
+ * successful request. The MAC address of the peer NAN device for which ranging
+ * is desired may be obtained either from a NAN service discovery or from an
+ * out-of-band source (HAL service, BLE, etc.).
+ *
+ * If WiFi is turned off by the user at the Android level, an existing
+ * ranging session is canceled, and a CHRE_EVENT_WIFI_ASYNC_RESULT event is
+ * sent to the subscriber. Nanoapps are expected to register for user settings
+ * notifications (@see chreUserSettingConfigureEvents), and perform another
+ * ranging request on a WiFi re-enabled settings changed notification.
+ *
+ * A successful result provided in CHRE_EVENT_WIFI_ASYNC_RESULT indicates that
+ * the results of ranging will be delivered in a subsequent event of type
+ * CHRE_EVENT_WIFI_RANGING_RESULT.
+ *
+ * @param params Structure containing the parameters of the ranging request,
+ *        including the MAC address of the peer NAN device to attempt ranging.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise.
+ */
+bool chreWifiNanRequestRangingAsync(const struct chreWifiNanRangingParams *params,
+                                    const void *cookie);
+
+#else  /* defined(CHRE_NANOAPP_USES_WIFI) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_WIFI_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_WIFI must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreWifiConfigureScanMonitorAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiConfigureScanMonitorAsync")
+#define chreWifiRequestScanAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiRequestScanAsync")
+#define chreWifiRequestScanAsyncDefault(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiRequestScanAsyncDefault")
+#define chreWifiRequestRangingAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiRequestRangingAsync")
+#define chreWifiRangingTargetFromScanResult(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiRangingTargetFromScanResult")
+#define chreWifiNanSubscribe(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiNanSubscribe")
+#define chreWifiNanSubscribeCancel(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiNanSubscribeCancel")
+#define chreWifiNanRequestRangingAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiNanRequestRangingAsync")
+#endif  /* defined(CHRE_NANOAPP_USES_WIFI) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_WIFI_H_ */
diff --git a/chre_api/legacy/v1_6/chre/wwan.h b/chre_api/legacy/v1_6/chre/wwan.h
new file mode 100644
index 0000000..51cf5f9
--- /dev/null
+++ b/chre_api/legacy/v1_6/chre/wwan.h
@@ -0,0 +1,591 @@
+/*
+ * 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_WWAN_H_
+#define _CHRE_WWAN_H_
+
+/**
+ * @file
+ * Wireless Wide Area Network (WWAN, i.e. mobile/cellular network) API relevant
+ * for querying cell tower identity and associated information that can be
+ * useful in determining location.
+ *
+ * Based on Android N RIL definitions (located at this path as of the time of
+ * this comment: hardware/ril/include/telephony/ril.h), version 12. Updated
+ * based on Android radio HAL definition (hardware/interfaces/radio) for more
+ * recent Android builds. Refer to those files and associated documentation for
+ * further details.
+ *
+ * In general, the parts of this API that are taken from the RIL follow the
+ * field naming conventions established in that interface rather than the CHRE
+ * API conventions, in order to avoid confusion and enable code re-use where
+ * applicable. Note that structure names include the chreWwan* prefix rather
+ * than RIL_*, but field names are the same. If necessary to enable code
+ * sharing, it is recommended to create typedefs that map from the CHRE
+ * structures to the associated RIL type names, for example "typedef struct
+ * chreWwanCellIdentityGsm RIL_CellIdentityGsm_v12", etc.
+ */
+
+#include <chre/common.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags returned by chreWwanGetCapabilities().
+ * @defgroup CHRE_WWAN_CAPABILITIES
+ * @{
+ */
+
+//! No WWAN APIs are supported
+#define CHRE_WWAN_CAPABILITIES_NONE  UINT32_C(0)
+
+//! Current cell information can be queried via chreWwanGetCellInfoAsync()
+#define CHRE_WWAN_GET_CELL_INFO      UINT32_C(1 << 0)
+
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for WWAN
+ * @param offset  Index into WWAN event ID block; valid range [0,15]
+ */
+#define CHRE_WWAN_EVENT_ID(offset)  (CHRE_EVENT_WWAN_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreWwanCellInfoResult
+ *
+ * Provides the result of an asynchronous request for cell info sent via
+ * chreWwanGetCellInfoAsync().
+ */
+#define CHRE_EVENT_WWAN_CELL_INFO_RESULT  CHRE_WWAN_EVENT_ID(0)
+
+// NOTE: Do not add new events with ID > 15; only values 0-15 are reserved
+// (see chre/event.h)
+
+/**
+ * The current version of struct chreWwanCellInfoResult associated with this
+ * API definition.
+ */
+#define CHRE_WWAN_CELL_INFO_RESULT_VERSION  UINT8_C(1)
+
+//! Reference: RIL_CellIdentityGsm_v12
+struct chreWwanCellIdentityGsm {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 16-bit Location Area Code, 0..65535, INT32_MAX if unknown
+    int32_t lac;
+
+    //! 16-bit GSM Cell Identity described in TS 27.007, 0..65535,
+    //! INT32_MAX if unknown
+    int32_t cid;
+
+    //! 16-bit GSM Absolute RF channel number, INT32_MAX if unknown
+    int32_t arfcn;
+
+    //! 6-bit Base Station Identity Code, UINT8_MAX if unknown
+    uint8_t bsic;
+
+    //! Reserved for future use; must be set to 0
+    uint8_t reserved[3];
+};
+
+//! Reference: RIL_CellIdentityWcdma_v12
+struct chreWwanCellIdentityWcdma {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 16-bit Location Area Code, 0..65535, INT32_MAX if unknown
+    int32_t lac;
+
+    //! 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455,
+    //! INT32_MAX if unknown
+    int32_t cid;
+
+    //! 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511,
+    //! INT32_MAX if unknown
+    int32_t psc;
+
+    //! 16-bit UMTS Absolute RF Channel Number, INT32_MAX if unknown
+    int32_t uarfcn;
+};
+
+//! Reference: RIL_CellIdentityCdma
+struct chreWwanCellIdentityCdma {
+    //! Network Id 0..65535, INT32_MAX if unknown
+    int32_t networkId;
+
+    //! CDMA System Id 0..32767, INT32_MAX if unknown
+    int32_t systemId;
+
+    //! Base Station Id 0..65535, INT32_MAX if unknown
+    int32_t basestationId;
+
+    //! Longitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0.
+    //! It is represented in units of 0.25 seconds and ranges from -2592000
+    //! to 2592000, both values inclusive (corresponding to a range of -180
+    //! to +180 degrees). INT32_MAX if unknown
+    int32_t longitude;
+
+    //! Latitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0.
+    //! It is represented in units of 0.25 seconds and ranges from -1296000
+    //! to 1296000, both values inclusive (corresponding to a range of -90
+    //! to +90 degrees). INT32_MAX if unknown
+    int32_t latitude;
+};
+
+//! Reference: RIL_CellIdentityLte_v12
+struct chreWwanCellIdentityLte {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 28-bit Cell Identity described in TS ???, INT32_MAX if unknown
+    int32_t ci;
+
+    //! physical cell id 0..503, INT32_MAX if unknown
+    int32_t pci;
+
+    //! 16-bit tracking area code, INT32_MAX if unknown
+    int32_t tac;
+
+    //! 18-bit LTE Absolute RF Channel Number, INT32_MAX if unknown
+    int32_t earfcn;
+};
+
+//! Reference: RIL_CellIdentityTdscdma
+struct chreWwanCellIdentityTdscdma {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 16-bit Location Area Code, 0..65535, INT32_MAX if unknown
+    int32_t lac;
+
+    //! 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455,
+    //! INT32_MAX if unknown
+    int32_t cid;
+
+    //! 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT32_MAX if
+    //! unknown
+    int32_t cpid;
+};
+
+//! Reference: [email protected] CellIdentityNr
+//! @since v1.4
+struct chreWwanCellIdentityNr {
+    //! 3-digit Mobile Country Code, in range [0, 999]. This value must be valid
+    //! for registered or camped cells. INT32_MAX means invalid/unreported.
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, in range [0, 999]. This value must be
+    //! valid for registered or camped cells. INT32_MAX means
+    //! invalid/unreported.
+    int32_t mnc;
+
+    //! NR Cell Identity in range [0, 68719476735] (36 bits), which
+    //! unambiguously identifies a cell within a public land mobile network
+    //! (PLMN). This value must be valid for registered or camped cells.
+    //! Reference: TS 38.413 section 9.3.1.7.
+    //!
+    //! Note: for backward compatibility reasons, the nominally int64_t nci is
+    //! split into two uint32_t values, with nci0 being the least significant 4
+    //! bytes. If chreWwanUnpackNrNci returns INT64_MAX, it means nci is
+    //! invalid/unreported.
+    //!
+    //! Users are recommended to use the helper accessor chreWwanUnpackNrNci to
+    //! access the nci field.
+    //!
+    //! @see chreWwanUnpackNrNci
+    uint32_t nci0;
+    uint32_t nci1;
+
+    //! Physical cell id in range [0, 1007]. This value must be valid.
+    //! Reference: TS 38.331 section 6.3.2.
+    int32_t pci;
+
+    //! 24-bit tracking area code in range [0, 16777215]. INT32_MAX means
+    //! invalid/unreported.
+    //! Reference: TS 38.413 section 9.3.3.10 and TS 29.571 section 5.4.2.
+    int32_t tac;
+
+    //! NR Absolute Radio Frequency Channel Number, in range [0, 3279165]. This
+    //! value must be valid.
+    //! Reference: TS 38.101-1 section 5.4.2.1 and TS 38.101-2 section 5.4.2.1.
+    int32_t nrarfcn;
+};
+
+//! Reference: RIL_GSM_SignalStrength_v12
+struct chreWwanSignalStrengthGsm {
+    //! Valid values are (0-31, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t signalStrength;
+
+    //! bit error rate (0-7, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t bitErrorRate;
+
+    //! Timing Advance in bit periods. 1 bit period = 48.13 us.
+    //! INT32_MAX means invalid/unreported.
+    int32_t timingAdvance;
+};
+
+//! Reference: RIL_SignalStrengthWcdma
+struct chreWwanSignalStrengthWcdma {
+    //! Valid values are (0-31, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t signalStrength;
+
+    //! bit error rate (0-7, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t bitErrorRate;
+};
+
+//! Reference: RIL_CDMA_SignalStrength
+struct chreWwanSignalStrengthCdma {
+    //! Valid values are positive integers.  This value is the actual RSSI value
+    //! multiplied by -1.  Example: If the actual RSSI is -75, then this
+    //! response value will be 75.
+    //! INT32_MAX means invalid/unreported.
+    int32_t dbm;
+
+    //! Valid values are positive integers.  This value is the actual Ec/Io
+    //! multiplied by -10.  Example: If the actual Ec/Io is -12.5 dB, then this
+    //! response value will be 125.
+    //! INT32_MAX means invalid/unreported.
+    int32_t ecio;
+};
+
+//! Reference: RIL_EVDO_SignalStrength
+struct chreWwanSignalStrengthEvdo {
+    //! Valid values are positive integers.  This value is the actual RSSI value
+    //! multiplied by -1.  Example: If the actual RSSI is -75, then this
+    //! response value will be 75.
+    //! INT32_MAX means invalid/unreported.
+    int32_t dbm;
+
+    //! Valid values are positive integers.  This value is the actual Ec/Io
+    //! multiplied by -10.  Example: If the actual Ec/Io is -12.5 dB, then this
+    //! response value will be 125.
+    //! INT32_MAX means invalid/unreported.
+    int32_t ecio;
+
+    //! Valid values are 0-8.  8 is the highest signal to noise ratio.
+    //! INT32_MAX means invalid/unreported.
+    int32_t signalNoiseRatio;
+};
+
+//! Reference: RIL_LTE_SignalStrength_v8
+struct chreWwanSignalStrengthLte {
+    //! Valid values are (0-31, 99) as defined in TS 27.007 8.5
+    int32_t signalStrength;
+
+    //! The current Reference Signal Receive Power in dBm multiplied by -1.
+    //! Range: 44 to 140 dBm
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.133 9.1.4
+    int32_t rsrp;
+
+    //! The current Reference Signal Receive Quality in dB multiplied by -1.
+    //! Range: 3 to 20 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.133 9.1.7
+    int32_t rsrq;
+
+    //! The current reference signal signal-to-noise ratio in 0.1 dB units.
+    //! Range: -200 to +300 (-200 = -20.0 dB, +300 = 30dB).
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.101 8.1.1
+    int32_t rssnr;
+
+    //! The current Channel Quality Indicator.
+    //! Range: 0 to 15.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.101 9.2, 9.3, A.4
+    int32_t cqi;
+
+    //! timing advance in micro seconds for a one way trip from cell to device.
+    //! Approximate distance can be calculated using 300m/us * timingAdvance.
+    //! Range: 0 to 0x7FFFFFFE
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP 36.321 section 6.1.3.5
+    //! also: http://www.cellular-planningoptimization.com/2010/02/timing-advance-with-calculation.html
+    int32_t timingAdvance;
+};
+
+//! Reference: RIL_TD_SCDMA_SignalStrength
+struct chreWwanSignalStrengthTdscdma {
+    //! The Received Signal Code Power in dBm multiplied by -1.
+    //! Range : 25 to 120
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 25.123, section 9.1.1.1
+    int32_t rscp;
+};
+
+//! Reference: [email protected] NrSignalStrength
+//! @since v1.4
+struct chreWwanSignalStrengthNr {
+    //! SS (second synchronization) reference signal received power in dBm
+    //! multiplied by -1.
+    //! Range [44, 140], INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.1 and TS 38.133 section 10.1.6.
+    int32_t ssRsrp;
+
+    //! SS reference signal received quality in 0.5 dB units.
+    //! Range [-86, 41] with -86 = -43.0 dB and 41 = 20.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.3 and TS 38.133 section 10.1.11.1.
+    int32_t ssRsrq;
+
+    //! SS signal-to-noise and interference ratio in 0.5 dB units.
+    //! Range [-46, 81] with -46 = -23.0 dB and 81 = 40.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.5 and TS 38.133 section 10.1.16.1.
+    int32_t ssSinr;
+
+    //! CSI reference signal received power in dBm multiplied by -1.
+    //! Range [44, 140], INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.2 and TS 38.133 section 10.1.6.
+    int32_t csiRsrp;
+
+    //! CSI reference signal received quality in 0.5 dB units.
+    //! Range [-86, 41] with -86 = -43.0 dB and 41 = 20.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.4 and TS 38.133 section 10.1.11.1.
+    int32_t csiRsrq;
+
+    //! CSI signal-to-noise and interference ratio in 0.5 dB units.
+    //! Range [-46, 81] with -46 = -23.0 dB and 81 = 40.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.6 and TS 38.133 section 10.1.16.1.
+    int32_t csiSinr;
+};
+
+//! Reference: RIL_CellInfoGsm_v12
+struct chreWwanCellInfoGsm {
+    struct chreWwanCellIdentityGsm    cellIdentityGsm;
+    struct chreWwanSignalStrengthGsm  signalStrengthGsm;
+};
+
+//! Reference: RIL_CellInfoWcdma_v12
+struct chreWwanCellInfoWcdma {
+    struct chreWwanCellIdentityWcdma    cellIdentityWcdma;
+    struct chreWwanSignalStrengthWcdma  signalStrengthWcdma;
+};
+
+//! Reference: RIL_CellInfoCdma
+struct chreWwanCellInfoCdma {
+    struct chreWwanCellIdentityCdma    cellIdentityCdma;
+    struct chreWwanSignalStrengthCdma  signalStrengthCdma;
+    struct chreWwanSignalStrengthEvdo  signalStrengthEvdo;
+};
+
+//! Reference: RIL_CellInfoLte_v12
+struct chreWwanCellInfoLte {
+    struct chreWwanCellIdentityLte    cellIdentityLte;
+    struct chreWwanSignalStrengthLte  signalStrengthLte;
+};
+
+//! Reference: RIL_CellInfoTdscdma
+struct chreWwanCellInfoTdscdma {
+    struct chreWwanCellIdentityTdscdma    cellIdentityTdscdma;
+    struct chreWwanSignalStrengthTdscdma  signalStrengthTdscdma;
+};
+
+//! Reference: [email protected] CellInfoNr
+//! @since v1.4
+struct chreWwanCellInfoNr {
+    struct chreWwanCellIdentityNr    cellIdentityNr;
+    struct chreWwanSignalStrengthNr  signalStrengthNr;
+};
+
+//! Reference: RIL_CellInfoType
+//! All other values are reserved and should be ignored by nanoapps.
+enum chreWwanCellInfoType {
+    CHRE_WWAN_CELL_INFO_TYPE_GSM      = 1,
+    CHRE_WWAN_CELL_INFO_TYPE_CDMA     = 2,
+    CHRE_WWAN_CELL_INFO_TYPE_LTE      = 3,
+    CHRE_WWAN_CELL_INFO_TYPE_WCDMA    = 4,
+    CHRE_WWAN_CELL_INFO_TYPE_TD_SCDMA = 5,
+    CHRE_WWAN_CELL_INFO_TYPE_NR       = 6,  //! @since v1.4
+};
+
+//! Reference: RIL_TimeStampType
+enum chreWwanCellTimeStampType {
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_UNKNOWN  = 0,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_ANTENNA  = 1,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_MODEM    = 2,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_OEM_RIL  = 3,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_JAVA_RIL = 4,
+};
+
+//! Reference: RIL_CellInfo_v12
+struct chreWwanCellInfo {
+    //! Timestamp in nanoseconds; must be in the same time base as chreGetTime()
+    uint64_t timeStamp;
+
+    //! A value from enum {@link #CellInfoType} indicating the radio access
+    //! technology of the cell, and which field in union CellInfo can be used
+    //! to retrieve additional information
+    uint8_t cellInfoType;
+
+    //! A value from enum {@link #CellTimeStampType} that identifies the source
+    //! of the value in timeStamp. This is typically set to
+    //! CHRE_WWAN_CELL_TIMESTAMP_TYPE_OEM_RIL, and indicates the time given by
+    //! chreGetTime() that an intermediate module received the data from the
+    //! modem and forwarded it to the requesting CHRE client.
+    uint8_t timeStampType;
+
+    //! !0 if this cell is registered, 0 if not registered
+    uint8_t registered;
+
+    //! Reserved for future use; must be set to 0
+    uint8_t reserved;
+
+    //! The value in cellInfoType indicates which field in this union is valid
+    union chreWwanCellInfoPerRat {
+        struct chreWwanCellInfoGsm     gsm;
+        struct chreWwanCellInfoCdma    cdma;
+        struct chreWwanCellInfoLte     lte;
+        struct chreWwanCellInfoWcdma   wcdma;
+        struct chreWwanCellInfoTdscdma tdscdma;
+        struct chreWwanCellInfoNr      nr;  //! @since v1.4
+    } CellInfo;
+};
+
+/**
+ * Data structure provided with events of type CHRE_EVENT_WWAN_CELL_INFO_RESULT.
+ */
+struct chreWwanCellInfoResult {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that the client only receives the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! Populated with a value from enum {@link #chreError}, indicating whether
+    //! the request failed, and if so, provides the cause of the failure
+    uint8_t errorCode;
+
+    //! The number of valid entries in cells[]
+    uint8_t cellInfoCount;
+
+    //! Reserved for future use; must be set to 0
+    uint8_t reserved;
+
+    //! Set to the cookie parameter given to chreWwanGetCellInfoAsync()
+    const void *cookie;
+
+    //! Pointer to an array of cellInfoCount elements containing information
+    //! about serving and neighbor cells
+    const struct chreWwanCellInfo *cells;
+};
+
+
+/**
+ * Retrieves a set of flags indicating the WWAN features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the Nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_WWAN_CAPABILITIES_* flags set
+ *
+ * @since v1.1
+ */
+uint32_t chreWwanGetCapabilities(void);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_WWAN somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following WWAN APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to WWAN data by adding metadata
+ * to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_WWAN) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Query information about the current serving cell and its neighbors. This does
+ * not perform a network scan, but should return state from the current network
+ * registration data stored in the cellular modem. This is effectively the same
+ * as a request for RIL_REQUEST_GET_CELL_INFO_LIST in the RIL.
+ *
+ * The requested cellular information is returned asynchronously via
+ * CHRE_EVENT_WWAN_CELL_INFO_RESULT. The implementation must send this event,
+ * either with successful data or an error status, within
+ * CHRE_ASYNC_RESULT_TIMEOUT_NS.
+ *
+ * If the airplane mode setting is enabled at the Android level, the CHRE
+ * implementation is expected to return a successful asynchronous result with an
+ * empty cell info list.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WWAN permission
+ */
+bool chreWwanGetCellInfoAsync(const void *cookie);
+
+/**
+ * Helper accessor for nci in the chreWwanCellIdentityNr struct.
+ *
+ * @return nci or INT64_MAX if invalid/unreported.
+ *
+ * @see chreWwanCellIdentityNr
+ *
+ * @since v1.4
+ * @note Requires WWAN permission
+ */
+static inline int64_t chreWwanUnpackNrNci(
+    const struct chreWwanCellIdentityNr *nrCellId) {
+  return (int64_t) (((uint64_t) nrCellId->nci1 << 32) | nrCellId->nci0);
+}
+
+#else  /* defined(CHRE_NANOAPP_USES_WWAN) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_WWAN_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_WWAN must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreWwanGetCellInfoAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WWAN_PERM_ERROR_STRING "chreWwanGetCellInfoAsync")
+#define chreWwanUnpackNrNci(...) \
+    CHRE_BUILD_ERROR(CHRE_WWAN_PERM_ERROR_STRING "chreWwanUnpackNrNci")
+#endif  /* defined(CHRE_NANOAPP_USES_WWAN) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_WWAN_H_ */
diff --git a/chre_api/legacy/v1_7/chre.h b/chre_api/legacy/v1_7/chre.h
new file mode 100644
index 0000000..9b87d08
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre.h
@@ -0,0 +1,201 @@
+/*
+ * 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_H_
+#define _CHRE_H_
+
+/**
+ * @file
+ * This header file includes all the headers which combine to fully define the
+ * interface for the Context Hub Runtime Environment (CHRE).  This interface is
+ * of interest to both implementers of CHREs and authors of nanoapps.  The API
+ * documentation attempts to address concerns of both.
+ *
+ * See individual header files for API details, and general comments below
+ * for overall platform information.
+ */
+
+#include <chre/audio.h>
+#include <chre/ble.h>
+#include <chre/common.h>
+#include <chre/event.h>
+#include <chre/gnss.h>
+#include <chre/nanoapp.h>
+#include <chre/re.h>
+#include <chre/sensor.h>
+#include <chre/toolchain.h>
+#include <chre/user_settings.h>
+#include <chre/version.h>
+#include <chre/wifi.h>
+#include <chre/wwan.h>
+
+/**
+ * @mainpage
+ * CHRE is the Context Hub Runtime Environment.  CHRE is used in Android to run
+ * contextual applications, called nanoapps, in a low-power processing domain
+ * other than the applications processor that runs Android itself.  The CHRE
+ * API, documented herein, is the common interface exposed to nanoapps for any
+ * compatible CHRE implementation.  The CHRE API provides the ability for
+ * creating nanoapps that are code-compatible across different CHRE
+ * implementations and underlying platforms. Refer to the following sections for
+ * a discussion on some important details of CHRE that aren't explicitly exposed
+ * in the API itself.
+ *
+ * @section entry_points Entry points
+ *
+ * The following entry points are used to bind a nanoapp to the CHRE system, and
+ * all three must be implemented by any nanoapp (see chre/nanoapp.h):
+ * - nanoappStart: initialization
+ * - nanoappHandleEvent: hook for event-driven processing
+ * - nanoappEnd: graceful teardown
+ *
+ * The CHRE implementation must also ensure that it performs these functions
+ * prior to invoking nanoappStart, or after nanoappEnd returns:
+ * - bss section zeroed out (prior to nanoappStart)
+ * - static variables initialized (prior to nanoappStart)
+ * - global C++ constructors called (prior to nanoappStart)
+ * - global C++ destructors called (after nanoappEnd)
+ *
+ * @section threading Threading model
+ *
+ * A CHRE implementation is free to choose among many different
+ * threading models, including a single-threaded system or a multi-threaded
+ * system with preemption.  The current platform definition is agnostic to this
+ * underlying choice.  However, the CHRE implementation must ensure that time
+ * spent executing within a nanoapp does not significantly degrade or otherwise
+ * interfere with other functions of the system in which CHRE is implemented,
+ * especially latency-sensitive tasks such as sensor event delivery to the AP.
+ * In other words, it must ensure that these functions can either occur in
+ * parallel or preempt a nanoapp's execution.  The current version of the API
+ * does not specify whether the implementation allows for CPU sharing between
+ * nanoapps on a more granular level than the handling of individual events [1].
+ * In any case, event ordering from the perspective of an individual nanoapp
+ * must be FIFO, but the CHRE implementation may choose to violate total
+ * ordering of events across all nanoapps to achieve more fair resource sharing,
+ * but this is not required.
+ *
+ * This version of the CHRE API does require that all nanoapps are treated as
+ * non-reentrant, meaning that only one instance of program flow can be inside
+ * an individual nanoapp at any given time.  That is, any of the functions of
+ * the nanoapp, including the entry points and all other callbacks, cannot be
+ * invoked if a previous invocation to the same or any other function in the
+ * nanoapp has not completed yet.
+ *
+ * For example, if a nanoapp is currently in nanoappHandleEvent(), the CHRE is
+ * not allowed to call nanoappHandleEvent() again, or to call a memory freeing
+ * callback.  Similarly, if a nanoapp is currently in a memory freeing
+ * callback, the CHRE is not allowed to call nanoappHandleEvent(), or invoke
+ * another memory freeing callback.
+ *
+ * There are two exceptions to this rule: If an invocation of chreSendEvent()
+ * fails (returns 'false'), it is allowed to immediately invoke the memory
+ * freeing callback passed into that function.  This is a rare case, and one
+ * where otherwise a CHRE implementation is likely to leak memory. Similarly,
+ * chreSendMessageToHost() is allowed to invoke the memory freeing callback
+ * directly, whether it returns 'true' or 'false'.  This is because the CHRE
+ * implementation may copy the message data to its own buffer, and therefore
+ * wouldn't need the nanoapp-supplied buffer after chreSendMessageToHost()
+ * returns.
+ *
+ * For a nanoapp author, this means no thought needs to be given to
+ * synchronization issues with global objects, as they will, by definition,
+ * only be accessed by a single thread at once.
+ *
+ * [1]: Note to CHRE implementers: A future version of the CHRE platform may
+ * require multi-threading with preemption.  This is mentioned as a heads up,
+ * and to allow implementors deciding between implementation approaches to
+ * make the most informed choice.
+ *
+ * @section timing Timing
+ *
+ * Nanoapps should expect to be running on a highly constrained system, with
+ * little memory and little CPU.  Any single nanoapp should expect to
+ * be one of several nanoapps on the system, which also share the CPU with the
+ * CHRE and possibly other services as well.
+ *
+ * Thus, a nanoapp needs to be efficient in its memory and CPU usage.
+ * Also, as noted in the Threading Model section, a CHRE implementation may
+ * be single threaded.  As a result, all methods invoked in a nanoapp
+ * (like nanoappStart, nanoappHandleEvent, memory free callbacks, etc.)
+ * must run "quickly".  "Quickly" is difficult to define, as there is a
+ * diversity of Context Hub hardware.  Nanoapp authors are strongly recommended
+ * to limit their application to consuming no more than 1 second of CPU time
+ * prior to returning control to the CHRE implementation.  A CHRE implementation
+ * may consider a nanoapp as unresponsive if it spends more time than this to
+ * process a single event, and take corrective action.
+ *
+ * A nanoapp may have the need to occasionally perform a large block of
+ * calculations that exceeds the 1 second guidance.  The recommended approach in
+ * this case is to split up the large block of calculations into smaller
+ * batches.  In one call into the nanoapp, the nanoapp can perform the first
+ * batch, and then set a timer or send an event (chreSendEvent()) to itself
+ * indicating which batch should be done next. This will allow the nanoapp to
+ * perform the entire calculation over time, without monopolizing system
+ * resources.
+ *
+ * @section floats Floating point support
+ *
+ * The C type 'float' is used in this API, and thus a CHRE implementation
+ * is required to support 'float's.
+ *
+ * Support of the C types 'double' and 'long double' is optional for a
+ * CHRE implementation.  Note that if a CHRE decides to support them, unlike
+ * 'float' support, there is no requirement that this support is particularly
+ * efficient.  So nanoapp authors should be aware this may be inefficient.
+ *
+ * If a CHRE implementation chooses not to support 'double' or
+ * 'long double', then the build toolchain setup provided needs to set
+ * the preprocessor define CHRE_NO_DOUBLE_SUPPORT.
+ *
+ * @section compat CHRE and Nanoapp compatibility
+ *
+ * CHRE implementations must make affordances to maintain binary compatibility
+ * across minor revisions of the API version (e.g. v1.1 to v1.2).  This applies
+ * to both running a nanoapp compiled for a newer version of the API on a CHRE
+ * implementation built against an older version (backwards compatibility), and
+ * vice versa (forwards compatibility).  API changes that are acceptable in
+ * minor version changes that may require special measures to ensure binary
+ * compatibility include: addition of new functions; addition of arguments to
+ * existing functions when the default value used for nanoapps compiled against
+ * the old version is well-defined and does not affect existing functionality;
+ * and addition of fields to existing structures, even when this induces a
+ * binary layout change (this should be made rare via judicious use of reserved
+ * fields).  API changes that must only occur alongside a major version change
+ * and are therefore not compatible include: removal of any function, argument,
+ * field in a data structure, or mandatory functional behavior that a nanoapp
+ * may depend on; any change in the interpretation of an existing data structure
+ * field that alters the way it was defined previously (changing the units of a
+ * field would fall under this, but appropriating a previously reserved field
+ * for some new functionality would not); and any change in functionality or
+ * expected behavior that conflicts with the previous definition.
+ *
+ * Note that the CHRE API only specifies the software interface between a
+ * nanoapp and the CHRE system - the binary interface (ABI) between nanoapp and
+ * CHRE is necessarily implementation-dependent.  Therefore, the recommended
+ * approach to accomplish binary compatibility is to build a Nanoapp Support
+ * Library (NSL) that is specific to the CHRE implementation into the nanoapp
+ * binary, and use it to handle ABI details in a way that ensures compatibility.
+ * In addition, to accomplish forwards compatibility, the CHRE implementation is
+ * expected to recognize the CHRE API version that a nanoapp is targeting and
+ * engage compatibility behaviors where necessary.
+ *
+ * By definition, major API version changes (e.g. v1.1 to v2.0) break
+ * compatibility.  Therefore, a CHRE implementation must not attempt to load a
+ * nanoapp that is targeting a newer major API version.
+ */
+
+#endif  /* _CHRE_H_ */
+
diff --git a/chre_api/legacy/v1_7/chre/audio.h b/chre_api/legacy/v1_7/chre/audio.h
new file mode 100644
index 0000000..e8ec960
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/audio.h
@@ -0,0 +1,429 @@
+/*
+ * 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_AUDIO_H_
+#define _CHRE_AUDIO_H_
+
+/**
+ * @file
+ * The API for requesting audio in the Context Hub Runtime Environment.
+ *
+ * This includes the definition of audio data structures and the ability to
+ * request audio streams.
+ */
+
+#include <chre/event.h>
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The current compatibility version of the chreAudioDataEvent structure.
+ */
+#define CHRE_AUDIO_DATA_EVENT_VERSION  UINT8_C(1)
+
+/**
+ * Produce an event ID in the block of IDs reserved for audio
+ * @param offset Index into audio event ID block; valid range [0,15]
+ */
+#define CHRE_AUDIO_EVENT_ID(offset)  (CHRE_EVENT_AUDIO_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAudioSourceStatusEvent
+ *
+ * Indicates a change in the format and/or rate of audio data provided to a
+ * nanoapp.
+ */
+#define CHRE_EVENT_AUDIO_SAMPLING_CHANGE  CHRE_AUDIO_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreAudioDataEvent
+ *
+ * Provides a buffer of audio data to a nanoapp.
+ */
+#define CHRE_EVENT_AUDIO_DATA  CHRE_AUDIO_EVENT_ID(1)
+
+/**
+ * The maximum size of the name of an audio source including the
+ * null-terminator.
+ */
+#define CHRE_AUDIO_SOURCE_NAME_MAX_SIZE  (40)
+
+/**
+ * Helper values for sample rates.
+ *
+ * @defgroup CHRE_AUDIO_SAMPLE_RATES
+ * @{
+ */
+
+//! 16kHz Audio Sample Data
+#define CHRE_AUDIO_SAMPLE_RATE_16KHZ  (16000)
+
+/** @} */
+
+/**
+ * Formats for audio that can be provided to a nanoapp.
+ */
+enum chreAudioDataFormat {
+  /**
+   * Unsigned, 8-bit u-Law encoded data as specified by ITU-T G.711.
+   */
+  CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW = 0,
+
+  /**
+   * Signed, 16-bit linear PCM data. Endianness must be native to the local
+   * processor.
+   */
+  CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM = 1,
+};
+
+/**
+ * A description of an audio source available to a nanoapp.
+ *
+ * This provides a description of an audio source with a name and a
+ * description of the format of the provided audio data.
+ */
+struct chreAudioSource {
+  /**
+   * A human readable name for this audio source. This is a C-style,
+   * null-terminated string. The length must be less than or equal to
+   * CHRE_AUDIO_SOURCE_NAME_MAX_SIZE bytes (including the null-terminator) and
+   * is expected to describe the source of the audio in US English. All
+   * characters must be printable (i.e.: isprint would return true for all
+   * characters in the name for the EN-US locale). The typical use of this field
+   * is for a nanoapp to log the name of the audio source that it is using.
+   *
+   * Example: "Camcorder Microphone"
+   */
+  const char *name;
+
+  /**
+   * The sampling rate in hertz of this mode. This value is rounded to the
+   * nearest integer. Typical values might include 16000, 44100 and 44800.
+   *
+   * If the requested audio source is preempted by another feature of the system
+   * (e.g. hotword), a gap may occur in received audio data. This is indicated
+   * to the client by posting a CHRE_EVENT_AUDIO_SAMPLING_CHANGE event. The
+   * nanoapp will then receive another CHRE_EVENT_AUDIO_SAMPLING_CHANGE event
+   * once the audio source is available again.
+   */
+  uint32_t sampleRate;
+
+  /**
+   * The minimum amount of time that this audio source can be buffered, in
+   * nanoseconds. Audio data is delivered to nanoapps in buffers. This specifies
+   * the minimum amount of data that can be delivered to a nanoapp without
+   * losing data. A request for a buffer that is smaller than this will fail.
+   */
+  uint64_t minBufferDuration;
+
+  /**
+   * The maximum amount of time that this audio source can be buffered, in
+   * nanoseconds. Audio data is delivered to nanoapps in buffers. This specifies
+   * the maximum amount of data that can be stored by the system in one event
+   * without losing data. A request for a buffer that is larger than this will
+   * fail.
+   */
+  uint64_t maxBufferDuration;
+
+  /**
+   * The format for data provided to the nanoapp. This will be assigned to one
+   * of the enum chreAudioDataFormat values.
+   */
+  uint8_t format;
+};
+
+/**
+ * The current status of an audio source.
+ */
+struct chreAudioSourceStatus {
+  /**
+   * Set to true if the audio source is currently enabled by this nanoapp. If
+   * this struct is provided by a CHRE_EVENT_AUDIO_SAMPLING_CHANGE event, it
+   * must necessarily be set to true because sampling change events are only
+   * sent for sources which this nanoapp has actively subscribed to. If this
+   * struct is obtained from the chreAudioGetStatus API, it may be set to true
+   * or false depending on if audio is currently enabled.
+   */
+  bool enabled;
+
+  /**
+   * Set to true if the audio source is currently suspended and no audio data
+   * will be received from this source.
+   */
+  bool suspended;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_AUDIO_SAMPLING_CHANGE.
+ */
+struct chreAudioSourceStatusEvent {
+  /**
+   * The audio source which has completed a status change.
+   */
+  uint32_t handle;
+
+  /**
+   * The status of this audio source.
+   */
+  struct chreAudioSourceStatus status;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_AUDIO_DATA.
+ *
+ * One example of the sequence of events for a nanoapp to receive audio data is:
+ *
+ * 1. CHRE_EVENT_AUDIO_SAMPLING_CHANGE - Indicates that audio data is not
+ *                                       suspended.
+ * 2. CHRE_EVENT_AUDIO_DATA - One buffer of audio samples. Potentially repeated.
+ * 3. CHRE_EVENT_AUDIO_SAMPLING_CHANGE - Indicates that audio data has suspended
+ *                                       which indicates a gap in the audio.
+ * 4. CHRE_EVENT_AUDIO_SAMPLING_CHANGE - Indicates that audio data has resumed
+ *                                       and that audio data may be delivered
+ *                                       again if enough samples are buffered.
+ * 5. CHRE_EVENT_AUDIO_DATA - One buffer of audio samples. Potentially repeated.
+ *                            The nanoapp must tolerate a gap in the timestamps.
+ *
+ * This process repeats for as long as an active request is made for an audio
+ * source. A CHRE_EVENT_AUDIO_SAMPLING_CHANGE does not guarantee that the next
+ * event will be a CHRE_EVENT_AUDIO_DATA event when suspended is set to false.
+ * It may happen that the audio source is suspended before a complete buffer can
+ * be captured. This will cause another CHRE_EVENT_AUDIO_SAMPLING_CHANGE event
+ * to be dispatched with suspended set to true before a buffer is delivered.
+ *
+ * Audio events must be delivered to a nanoapp in order.
+ */
+struct chreAudioDataEvent {
+  /**
+   * Indicates the version of the structure, for compatibility purposes. Clients
+   * do not normally need to worry about this field; the CHRE implementation
+   * guarantees that the client only receives the structure version it expects.
+   */
+  uint8_t version;
+
+  /**
+   * Additional bytes reserved for future use; must be set to 0.
+   */
+  uint8_t reserved[3];
+
+  /**
+   * The handle for which this audio data originated from.
+   */
+  uint32_t handle;
+
+  /**
+   * The base timestamp for this buffer of audio data, from the same time base
+   * as chreGetTime() (in nanoseconds). The audio API does not provide
+   * timestamps for each audio sample. This timestamp corresponds to the first
+   * sample of the buffer. Even though the value is expressed in nanoseconds,
+   * there is an expectation that the sample clock may drift and nanosecond
+   * level accuracy may not be possible. The goal is to be as accurate as
+   * possible within reasonable limitations of a given system.
+   */
+  uint64_t timestamp;
+
+  /**
+   * The sample rate for this buffer of data in hertz, rounded to the nearest
+   * integer. Fractional sampling rates are not supported. Typical values might
+   * include 16000, 44100 and 48000.
+   */
+  uint32_t sampleRate;
+
+  /**
+   * The number of samples provided with this buffer.
+   */
+  uint32_t sampleCount;
+
+  /**
+   * The format of this audio data. This enumeration and union of pointers below
+   * form a tagged struct. The consumer of this API must use this enum to
+   * determine which samples pointer below to dereference. This will be assigned
+   * to one of the enum chreAudioDataFormat values.
+   */
+  uint8_t format;
+
+  /**
+   * A union of pointers to various formats of sample data. These correspond to
+   * the valid chreAudioDataFormat values.
+   */
+  union {
+    const uint8_t *samplesULaw8;
+    const int16_t *samplesS16;
+  };
+};
+
+/**
+ * Retrieves information about an audio source supported by the current CHRE
+ * implementation. The source returned by the runtime must not change for the
+ * entire lifecycle of the Nanoapp and hot-pluggable audio sources are not
+ * supported.
+ *
+ * A simple example of iterating all available audio sources is provided here:
+ *
+ * struct chreAudioSource audioSource;
+ * for (uint32_t i = 0; chreAudioGetSource(i, &audioSource); i++) {
+ *     chreLog(CHRE_LOG_INFO, "Found audio source: %s", audioSource.name);
+ * }
+ *
+ * Handles provided to this API must be a stable value for the entire duration
+ * of a nanoapp. Handles for all audio sources must be zero-indexed and
+ * contiguous. The following are examples of handles that could be provided to
+ * this API:
+ *
+ *   Valid: 0
+ *   Valid: 0, 1, 2, 3
+ * Invalid: 1, 2, 3
+ * Invalid: 0, 2
+ *
+ * @param handle The handle for an audio source to obtain details for. The
+ *     range of acceptable handles must be zero-indexed and contiguous.
+ * @param audioSource A struct to populate with details of the audio source.
+ * @return true if the query was successful, false if the provided handle is
+ *     invalid or the supplied audioSource is NULL.
+ *
+ * @since v1.2
+ */
+bool chreAudioGetSource(uint32_t handle, struct chreAudioSource *audioSource);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_AUDIO somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following audio APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to audio data by adding
+ * metadata to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_AUDIO) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Configures delivery of audio data to the current nanoapp. Note that this may
+ * not fully disable the audio source if it is used by other clients in the
+ * system but it will halt data delivery to the nanoapp.
+ *
+ * The bufferDuration and deliveryInterval parameters as described below are
+ * used together to determine both how much and how often to deliver data to a
+ * nanoapp, respectively. A nanoapp will always be provided the requested
+ * amount of data at the requested interval, even if another nanoapp in CHRE
+ * requests larger/more frequent buffers or smaller/less frequent buffers.
+ * These two buffering parameters allow describing the duty cycle of captured
+ * audio data. If a nanoapp wishes to receive all available audio data, it will
+ * specify a bufferDuration and deliveryInterval that are equal. A 50% duty
+ * cycle would be achieved by specifying a deliveryInterval that is double the
+ * value of the bufferDuration provided. These parameters allow the audio
+ * subsystem to operate at less than 100% duty cycle and permits use of
+ * incomplete audio data without periodic reconfiguration of the source.
+ *
+ * Two examples are illustrated below:
+ *
+ * Target duty cycle: 50%
+ * bufferDuration:    2
+ * deliveryInterval:  4
+ *
+ * Time       0   1   2   3   4   5   6   7
+ * Batch                  A               B
+ * Sample    --  --  a1  a2  --  --  b1  b2
+ * Duration          [    ]          [    ]
+ * Interval  [            ]  [            ]
+ *
+ *
+ * Target duty cycle: 100%
+ * bufferDuration:    4
+ * deliveryInterval:  4
+ *
+ * Time       0   1   2   3   4   5   6   7
+ * Batch                  A               B
+ * Sample    a1  a2  a3  a4  b1  b2  b3  b4
+ * Duration  [            ]  [            ]
+ * Interval  [            ]  [            ]
+ *
+ *
+ * This is expected to reduce power overall.
+ *
+ * The first audio buffer supplied to the nanoapp may contain data captured
+ * prior to the request. This could happen if the microphone was already enabled
+ * and reading into a buffer prior to the nanoapp requesting audio data for
+ * itself. The nanoapp must tolerate this.
+ *
+ * It is important to note that multiple logical audio sources (e.g. different
+ * sample rate, format, etc.) may map to one physical audio source. It is
+ * possible for a nanoapp to request audio data from more than one logical
+ * source at a time. Audio data may be suspended for either the current or other
+ * requests. The CHRE_EVENT_AUDIO_SAMPLING_CHANGE will be posted to all clients
+ * if such a change occurs. It is also possible for the request to succeed and
+ * all audio sources are serviced simultaneously. This is implementation defined
+ * but at least one audio source must function correctly if it is advertised,
+ * under normal conditions (e.g. not required for some other system function,
+ * such as hotword).
+ *
+ * @param handle The handle for this audio source. The handle for the desired
+ *     audio source can be determined using chreAudioGetSource().
+ * @param enable true if enabling the source, false otherwise. When passed as
+ *     false, the bufferDuration and deliveryInterval parameters are ignored.
+ * @param bufferDuration The amount of time to capture audio samples from this
+ *     audio source, in nanoseconds per delivery interval. This value must be
+ *     in the range of minBufferDuration/maxBufferDuration for this source or
+ *     the request will fail. The number of samples captured per buffer will be
+ *     derived from the sample rate of the source and the requested duration and
+ *     rounded down to the nearest sample boundary.
+ * @param deliveryInterval Desired time between each CHRE_EVENT_AUDIO_DATA
+ *     event. This allows specifying the complete duty cycle of a request
+ *     for audio data, in nanoseconds. This value must be greater than or equal
+ *     to bufferDuration or the request will fail due to an invalid
+ *     configuration.
+ * @return true if the configuration was successful, false if invalid parameters
+ *     were provided (non-existent handle, invalid buffering configuration).
+ *
+ * @since v1.2
+ * @note Requires audio permission
+ */
+bool chreAudioConfigureSource(uint32_t handle, bool enable,
+                              uint64_t bufferDuration,
+                              uint64_t deliveryInterval);
+
+/**
+ * Gets the current chreAudioSourceStatus struct for a given audio handle.
+ *
+ * @param handle The handle for the audio source to query. The provided handle
+ *     is obtained from a chreAudioSource which is requested from the
+ *     chreAudioGetSource API.
+ * @param status The current status of the supplied audio source.
+ * @return true if the provided handle is valid and the status was obtained
+ *     successfully, false if the handle was invalid or status is NULL.
+ *
+ * @since v1.2
+ * @note Requires audio permission
+ */
+bool chreAudioGetStatus(uint32_t handle, struct chreAudioSourceStatus *status);
+
+#else  /* defined(CHRE_NANOAPP_USES_AUDIO) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_AUDIO_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_AUDIO must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreAudioConfigureSource(...) \
+    CHRE_BUILD_ERROR(CHRE_AUDIO_PERM_ERROR_STRING "chreAudioConfigureSource")
+#define chreAudioGetStatus(...) \
+    CHRE_BUILD_ERROR(CHRE_AUDIO_PERM_ERROR_STRING "chreAudioGetStatus")
+#endif  /* defined(CHRE_NANOAPP_USES_AUDIO) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_AUDIO_H_ */
diff --git a/chre_api/legacy/v1_7/chre/ble.h b/chre_api/legacy/v1_7/chre/ble.h
new file mode 100644
index 0000000..5941da2
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/ble.h
@@ -0,0 +1,678 @@
+/*
+ * Copyright (C) 2021 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_BLE_H_
+#define CHRE_BLE_H_
+
+/**
+ * @file
+ * CHRE BLE (Bluetooth Low Energy, Bluetooth LE) API.
+ * The CHRE BLE API currently supports BLE scanning features.
+ *
+ * The features in the CHRE BLE API are a subset and adaptation of Android
+ * capabilities as described in the Android BLE API and HCI requirements.
+ * ref:
+ * https://developer.android.com/guide/topics/connectivity/bluetooth/ble-overview
+ * ref: https://source.android.com/devices/bluetooth/hci_requirements
+ */
+
+#include <chre/common.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags returned by chreBleGetCapabilities().
+ *
+ * @defgroup CHRE_BLE_CAPABILITIES
+ * @{
+ */
+//! No BLE APIs are supported
+#define CHRE_BLE_CAPABILITIES_NONE UINT32_C(0)
+
+//! CHRE supports BLE scanning
+#define CHRE_BLE_CAPABILITIES_SCAN UINT32_C(1 << 0)
+
+//! CHRE BLE supports batching of scan results, either through Android-specific
+//! HCI (OCF: 0x156), or by the CHRE framework, internally.
+//! @since v1.7 Platforms with this capability must also support flushing scan
+//! results during a batched scan.
+#define CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING UINT32_C(1 << 1)
+
+//! CHRE BLE scan supports best-effort hardware filtering. If filtering is
+//! available, chreBleGetFilterCapabilities() returns a bitmap indicating the
+//! specific filtering capabilities that are supported.
+//! To differentiate best-effort vs. no filtering, the following requirement
+//! must be met for this flag:
+//! If only one nanoapp is requesting BLE scans and there are no BLE scans from
+//! the AP, only filtered results will be provided to the nanoapp.
+#define CHRE_BLE_CAPABILITIES_SCAN_FILTER_BEST_EFFORT UINT32_C(1 << 2)
+/** @} */
+
+/**
+ * The set of flags returned by chreBleGetFilterCapabilities().
+ *
+ * The representative bit for each filtering capability is based on the sub-OCF
+ * of the Android filtering HCI vendor-specific command (LE_APCF_Command, OCF:
+ * 0x0157) for that particular filtering capability, as found in
+ * https://source.android.com/devices/bluetooth/hci_requirements
+ *
+ * For example, the Service Data filter has a sub-command of 0x7; hence
+ * the filtering capability is indicated by (1 << 0x7).
+ *
+ * @defgroup CHRE_BLE_FILTER_CAPABILITIES
+ * @{
+ */
+//! No CHRE BLE filters are supported
+#define CHRE_BLE_FILTER_CAPABILITIES_NONE UINT32_C(0)
+
+//! CHRE BLE supports RSSI filters
+#define CHRE_BLE_FILTER_CAPABILITIES_RSSI UINT32_C(1 << 1)
+
+//! CHRE BLE supports Service Data filters (Corresponding HCI OCF: 0x0157,
+//! Sub-command: 0x07)
+#define CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA UINT32_C(1 << 7)
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for BLE.
+ *
+ * Valid input range is [0, 15]. Do not add new events with ID > 15
+ * (see chre/event.h)
+ *
+ * @param offset Index into BLE event ID block; valid range is [0, 15].
+ *
+ * @defgroup CHRE_BLE_EVENT_ID
+ * @{
+ */
+#define CHRE_BLE_EVENT_ID(offset) (CHRE_EVENT_BLE_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Communicates the asynchronous result of a request to the BLE API. The
+ * requestType field in {@link #chreAsyncResult} is set to a value from enum
+ * chreBleRequestType.
+ *
+ * This is used for results of async config operations which need to
+ * interop with lower level code (potentially in a different thread) or send an
+ * HCI command to the FW and wait on the response.
+ */
+#define CHRE_EVENT_BLE_ASYNC_RESULT CHRE_BLE_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreBleAdvertisementEvent
+ *
+ * Provides results of a BLE scan.
+ */
+#define CHRE_EVENT_BLE_ADVERTISEMENT CHRE_BLE_EVENT_ID(1)
+
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Indicates that a flush request made via chreBleFlushAsync() is complete, and
+ * all batched advertisements resulting from the flush have been delivered via
+ * preceding CHRE_EVENT_BLE_ADVERTISEMENT events.
+ *
+ * @since v1.7
+ */
+#define CHRE_EVENT_BLE_FLUSH_COMPLETE CHRE_BLE_EVENT_ID(2)
+
+// NOTE: Do not add new events with ID > 15
+/** @} */
+
+/**
+ * Maximum BLE (legacy) advertisement payload data length, in bytes
+ * This is calculated by subtracting 2 (type + len) from 31 (max payload).
+ */
+#define CHRE_BLE_DATA_LEN_MAX (29)
+
+/**
+ * BLE device address length, in bytes.
+ */
+#define CHRE_BLE_ADDRESS_LEN (6)
+
+/**
+ * RSSI value (int8_t) indicating no RSSI threshold.
+ */
+#define CHRE_BLE_RSSI_THRESHOLD_NONE (-128)
+
+/**
+ * RSSI value (int8_t) indicating no RSSI value available.
+ */
+#define CHRE_BLE_RSSI_NONE (127)
+
+/**
+ * Tx power value (int8_t) indicating no Tx power value available.
+ */
+#define CHRE_BLE_TX_POWER_NONE (127)
+
+/**
+ * Indicates ADI field was not provided in advertisement.
+ */
+#define CHRE_BLE_ADI_NONE (0xFF)
+
+/**
+ * The CHRE BLE advertising event type is based on the BT Core Spec v5.2,
+ * Vol 4, Part E, Section 7.7.65.13, LE Extended Advertising Report event,
+ * Event_Type.
+ *
+ * Note: helper functions are provided to avoid bugs, e.g. a nanoapp doing
+ * (eventTypeAndDataStatus == ADV_IND) instead of properly masking off reserved
+ * and irrelevant bits.
+ *
+ * @defgroup CHRE_BLE_EVENT
+ * @{
+ */
+// Extended event types
+#define CHRE_BLE_EVENT_MASK_TYPE (0x1f)
+#define CHRE_BLE_EVENT_TYPE_FLAG_CONNECTABLE (1 << 0)
+#define CHRE_BLE_EVENT_TYPE_FLAG_SCANNABLE (1 << 1)
+#define CHRE_BLE_EVENT_TYPE_FLAG_DIRECTED (1 << 2)
+#define CHRE_BLE_EVENT_TYPE_FLAG_SCAN_RSP (1 << 3)
+#define CHRE_BLE_EVENT_TYPE_FLAG_LEGACY (1 << 4)
+
+// Data status
+#define CHRE_BLE_EVENT_MASK_DATA_STATUS (0x3 << 5)
+#define CHRE_BLE_EVENT_DATA_STATUS_COMPLETE (0x0 << 5)
+#define CHRE_BLE_EVENT_DATA_STATUS_MORE_DATA_PENDING (0x1 << 5)
+#define CHRE_BLE_EVENT_DATA_STATUS_DATA_TRUNCATED (0x2 << 5)
+
+// Legacy event types
+#define CHRE_BLE_EVENT_TYPE_LEGACY_ADV_IND                                  \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY | CHRE_BLE_EVENT_TYPE_FLAG_CONNECTABLE | \
+   CHRE_BLE_EVENT_TYPE_FLAG_SCANNABLE)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_DIRECT_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY | CHRE_BLE_EVENT_TYPE_FLAG_CONNECTABLE)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_ADV_SCAN_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY | CHRE_BLE_EVENT_TYPE_FLAG_SCANNABLE)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_ADV_NONCONN_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_LEGACY)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_SCAN_RESP_ADV_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_SCAN_RSP | CHRE_BLE_EVENT_TYPE_LEGACY_ADV_IND)
+#define CHRE_BLE_EVENT_TYPE_LEGACY_SCAN_RESP_ADV_SCAN_IND \
+  (CHRE_BLE_EVENT_TYPE_FLAG_SCAN_RSP | CHRE_BLE_EVENT_TYPE_LEGACY_ADV_SCAN_IND)
+/** @} */
+
+/**
+ * The maximum amount of time allowed to elapse between the call to
+ * chreBleFlushAsync() and when CHRE_EVENT_BLE_FLUSH_COMPLETE is delivered to
+ * the nanoapp on a successful flush.
+ */
+#define CHRE_BLE_FLUSH_COMPLETE_TIMEOUT_NS (5 * CHRE_NSEC_PER_SEC)
+
+/**
+ * Indicates a type of request made in this API. Used to populate the resultType
+ * field of struct chreAsyncResult sent with CHRE_EVENT_BLE_ASYNC_RESULT.
+ */
+enum chreBleRequestType {
+  CHRE_BLE_REQUEST_TYPE_START_SCAN = 1,
+  CHRE_BLE_REQUEST_TYPE_STOP_SCAN = 2,
+  CHRE_BLE_REQUEST_TYPE_FLUSH = 3,  //!< @since v1.7
+};
+
+/**
+ * CHRE BLE scan modes identify functional scan levels without specifying or
+ * guaranteeing particular scan parameters (e.g. duty cycle, interval, radio
+ * chain).
+ *
+ * The actual scan parameters may be platform dependent and may change without
+ * notice in real time based on contextual cues, etc.
+ *
+ * Scan modes should be selected based on use cases as described.
+ */
+enum chreBleScanMode {
+  //! A background scan level for always-running ambient applications.
+  //! A representative duty cycle may be between 3 - 10 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_BACKGROUND = 1,
+
+  //! A foreground scan level to be used for short periods.
+  //! A representative duty cycle may be between 10 - 20 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_FOREGROUND = 2,
+
+  //! A very high duty cycle scan level to be used for very short durations.
+  //! A representative duty cycle may be between 50 - 100 % (tentative, and
+  //! with no guarantees).
+  CHRE_BLE_SCAN_MODE_AGGRESSIVE = 3,
+};
+
+/**
+ * Selected AD Types are available among those defined in the Bluetooth spec.
+ * Assigned Numbers, Generic Access Profile.
+ * ref: https://www.bluetooth.com/specifications/assigned-numbers/
+ */
+enum chreBleAdType {
+  //! Service Data with 16-bit UUID
+  CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 = 0x16,
+};
+
+/**
+ * Generic scan filters definition based on AD Type, mask, and values. The
+ * maximum data length is limited to the maximum possible legacy advertisement
+ * payload data length (29 bytes).
+ *
+ * The filter is matched when
+ *   data & dataMask == advData & dataMask
+ * where advData is the advertisement packet data for the specified AD type.
+ *
+ * The CHRE generic filter structure represents a generic filter on an AD Type
+ * as defined in the Bluetooth spec Assigned Numbers, Generic Access Profile
+ * (ref: https://www.bluetooth.com/specifications/assigned-numbers/). This
+ * generic structure is used by the Advertising Packet Content Filter
+ * (APCF) HCI generic AD type sub-command 0x08 (ref:
+ * https://source.android.com/devices/bluetooth/hci_requirements#le_apcf_command).
+ *
+ * Note that the CHRE implementation may not support every kind of filter that
+ * can be represented by this structure. Use chreBleGetFilterCapabilities() to
+ * discover supported filtering capabilities at runtime.
+ *
+ * For example, to filter on a 16 bit service data UUID of 0xFE2C, the following
+ * settings would be used:
+ *   type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16
+ *   len = 2
+ *   data = {0xFE, 0x2C}
+ *   dataMask = {0xFF, 0xFF}
+ */
+struct chreBleGenericFilter {
+  //! Acceptable values among enum chreBleAdType
+  uint8_t type;
+
+  /**
+   * Length of data and dataMask. AD payloads shorter than this length will not
+   * be matched by the filter. Length must be greater than 0.
+   */
+  uint8_t len;
+
+  //! Used in combination with dataMask to filter an advertisement
+  uint8_t data[CHRE_BLE_DATA_LEN_MAX];
+
+  //! Used in combination with data to filter an advertisement
+  uint8_t dataMask[CHRE_BLE_DATA_LEN_MAX];
+};
+
+/**
+ * CHRE Bluetooth LE scan filters are based on a combination of an RSSI
+ * threshold and generic scan filters as defined by AD Type, mask, and values.
+ *
+ * CHRE-provided filters are implemented in a best-effort manner, depending on
+ * HW capabilities of the system and available resources. Therefore, provided
+ * scan results may be a superset of the specified filters. Nanoapps should try
+ * to take advantage of CHRE scan filters as much as possible, but must design
+ * their logic as to not depend on CHRE filtering.
+ *
+ * The syntax of CHRE scan filter definitions are based on the Android
+ * Advertising Packet Content Filter (APCF) HCI requirement subtype 0x08
+ * ref:
+ * https://source.android.com/devices/bluetooth/hci_requirements#le_apcf_command-set_filtering_parameters_sub_cmd
+ * and AD Types as defined in the Bluetooth spec Assigned Numbers, Generic
+ * Access Profile
+ * ref: https://www.bluetooth.com/specifications/assigned-numbers/
+ *
+ * Even though the scan filters are defined in a generic manner, CHRE Bluetooth
+ * is expected to initially support only a limited set of AD Types.
+ */
+struct chreBleScanFilter {
+  //! RSSI threshold filter (Corresponding HCI OCF: 0x0157, Sub: 0x01), where
+  //! advertisements with RSSI values below this threshold may be disregarded.
+  //! An rssiThreshold value of CHRE_BLE_RSSI_THRESHOLD_NONE indicates no RSSI
+  //! filtering.
+  int8_t rssiThreshold;
+
+  //! Number of generic scan filters provided in the scanFilters array.
+  //! A scanFilterCount value of 0 indicates no generic scan filters.
+  uint8_t scanFilterCount;
+
+  //! Pointer to an array of scan filters. If the array contains more than one
+  //! entry, advertisements matching any of the entries will be returned
+  //! (functional OR).
+  const struct chreBleGenericFilter *scanFilters;
+};
+
+/**
+ * CHRE BLE advertising address type is based on the BT Core Spec v5.2, Vol 4,
+ * Part E, Section 7.7.65.13, LE Extended Advertising Report event,
+ * Address_Type.
+ */
+enum chreBleAddressType {
+  //! Public device address.
+  CHRE_BLE_ADDRESS_TYPE_PUBLIC = 0x00,
+
+  //! Random device address.
+  CHRE_BLE_ADDRESS_TYPE_RANDOM = 0x01,
+
+  //! Public identity address (corresponds to resolved private address).
+  CHRE_BLE_ADDRESS_TYPE_PUBLIC_IDENTITY = 0x02,
+
+  //! Random (static) Identity Address (corresponds to resolved private
+  //! address)
+  CHRE_BLE_ADDRESS_TYPE_RANDOM_IDENTITY = 0x03,
+
+  //! No address provided (anonymous advertisement).
+  CHRE_BLE_ADDRESS_TYPE_NONE = 0xff,
+};
+
+/**
+ * CHRE BLE physical (PHY) channel encoding type, if supported, is based on the
+ * BT Core Spec v5.2, Vol 4, Part E, Section 7.7.65.13, LE Extended Advertising
+ * Report event, entries Primary_PHY and Secondary_PHY.
+ */
+enum chreBlePhyType {
+  //! No packets on this PHY (only on the secondary channel), or feature not
+  //! supported.
+  CHRE_BLE_PHY_NONE = 0x00,
+
+  //! LE 1 MBPS PHY encoding.
+  CHRE_BLE_PHY_1M = 0x01,
+
+  //! LE 2 MBPS PHY encoding (only on the secondary channel).
+  CHRE_BLE_PHY_2M = 0x02,
+
+  //! LE long-range coded PHY encoding.
+  CHRE_BLE_PHY_CODED = 0x03,
+};
+
+/**
+ * The CHRE BLE Advertising Report event is based on the BT Core Spec v5.2,
+ * Vol 4, Part E, Section 7.7.65.13, LE Extended Advertising Report event, with
+ * the following differences:
+ *
+ * 1) A CHRE timestamp field, which can be useful if CHRE is batching results.
+ * 2) Reordering of the rssi and periodicAdvertisingInterval fields for memory
+ *    alignment (prevent padding).
+ * 3) Addition of four reserved bytes to reclaim padding.
+ */
+struct chreBleAdvertisingReport {
+  //! The base timestamp, in nanoseconds, in the same time base as chreGetTime()
+  uint64_t timestamp;
+
+  //! @see CHRE_BLE_EVENT
+  uint8_t eventTypeAndDataStatus;
+
+  //! Advertising address type as defined in enum chreBleAddressType
+  uint8_t addressType;
+
+  //! Advertising device address
+  uint8_t address[CHRE_BLE_ADDRESS_LEN];
+
+  //! Advertiser PHY on primary advertising physical channel, if supported, as
+  //! defined in enum chreBlePhyType.
+  uint8_t primaryPhy;
+
+  //! Advertiser PHY on secondary advertising physical channel, if supported, as
+  //! defined in enum chreBlePhyType.
+  uint8_t secondaryPhy;
+
+  //! Value of the Advertising SID subfield in the ADI field of the PDU among
+  //! the range of [0, 0x0f].
+  //! CHRE_BLE_ADI_NONE indicates no ADI field was provided.
+  //! Other values are reserved.
+  uint8_t advertisingSid;
+
+  //! Transmit (Tx) power in dBm. Typical values are [-127, 20].
+  //! CHRE_BLE_TX_POWER_NONE indicates Tx power not available.
+  int8_t txPower;
+
+  //! Interval of the periodic advertising in 1.25 ms intervals, i.e.
+  //! time = periodicAdvertisingInterval * 1.25 ms
+  //! 0 means no periodic advertising. Minimum value is otherwise 6 (7.5 ms).
+  uint16_t periodicAdvertisingInterval;
+
+  //! RSSI in dBm. Typical values are [-127, 20].
+  //! CHRE_BLE_RSSI_NONE indicates RSSI is not available.
+  int8_t rssi;
+
+  //! Direct address type (i.e. only accept connection requests from a known
+  //! peer device) as defined in enum chreBleAddressType.
+  uint8_t directAddressType;
+
+  //! Direct address (i.e. only accept connection requests from a known peer
+  //! device).
+  uint8_t directAddress[CHRE_BLE_ADDRESS_LEN];
+
+  //! Length of data field. Acceptable range is [0, 31] for legacy and
+  //! [0, 229] for extended advertisements.
+  uint16_t dataLength;
+
+  //! dataLength bytes of data, or null if dataLength is 0
+  const uint8_t *data;
+
+  //! Reserved for future use; set to 0
+  uint32_t reserved;
+};
+
+/**
+ * A CHRE BLE Advertising Event can contain any number of CHRE BLE Advertising
+ * Reports (i.e. advertisements).
+ */
+struct chreBleAdvertisementEvent {
+  //! Reserved for future use; set to 0
+  uint16_t reserved;
+
+  //! Number of advertising reports in this event
+  uint16_t numReports;
+
+  //! Array of length numReports
+  const struct chreBleAdvertisingReport *reports;
+};
+
+/**
+ * Retrieves a set of flags indicating the BLE features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_BLE_CAPABILITIES_* flags set. @see
+ *         CHRE_BLE_CAPABILITIES
+ *
+ * @since v1.6
+ */
+uint32_t chreBleGetCapabilities(void);
+
+/**
+ * Retrieves a set of flags indicating the BLE filtering features supported by
+ * the current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_BLE_FILTER_CAPABILITIES_* flags set.
+ *         @see CHRE_BLE_FILTER_CAPABILITIES
+ *
+ * @since v1.6
+ */
+uint32_t chreBleGetFilterCapabilities(void);
+
+/**
+ * Helper function to extract event type from eventTypeAndDataStatus as defined
+ * in the BT Core Spec v5.2, Vol 4, Part E, Section 7.7.65.13, LE Extended
+ * Advertising Report event, entry Event_Type.
+ *
+ * @see CHRE_BLE_EVENT
+ *
+ * @param eventTypeAndDataStatus Combined event type and data status
+ *
+ * @return The event type portion of eventTypeAndDataStatus
+ */
+static inline uint8_t chreBleGetEventType(uint8_t eventTypeAndDataStatus) {
+  return (eventTypeAndDataStatus & CHRE_BLE_EVENT_MASK_TYPE);
+}
+
+/**
+ * Helper function to extract data status from eventTypeAndDataStatus as defined
+ * in the BT Core Spec v5.2, Vol 4, Part E, Section 7.7.65.13, LE Extended
+ * Advertising Report event, entry Event_Type.
+ *
+ * @see CHRE_BLE_EVENT
+ *
+ * @param eventTypeAndDataStatus Combined event type and data status
+ *
+ * @return The data status portion of eventTypeAndDataStatus
+ */
+static inline uint8_t chreBleGetDataStatus(uint8_t eventTypeAndDataStatus) {
+  return (eventTypeAndDataStatus & CHRE_BLE_EVENT_MASK_DATA_STATUS);
+}
+
+/**
+ * Helper function to to combine an event type with a data status to create
+ * eventTypeAndDataStatus as defined in the BT Core Spec v5.2, Vol 4, Part E,
+ * Section 7.7.65.13, LE Extended Advertising Report event, entry Event_Type.
+ *
+ * @see CHRE_BLE_EVENT
+ *
+ * @param eventType Event type
+ * @param dataStatus Data status
+ *
+ * @return A combined eventTypeAndDataStatus
+ */
+static inline uint8_t chreBleGetEventTypeAndDataStatus(uint8_t eventType,
+                                                       uint8_t dataStatus) {
+  return ((eventType & CHRE_BLE_EVENT_MASK_TYPE) |
+          (dataStatus & CHRE_BLE_EVENT_MASK_DATA_STATUS));
+}
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_BLE somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following BLE APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to access BLE functionality by adding
+ * metadata to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Start Bluetooth LE (BLE) scanning on CHRE.
+ *
+ * The result of the operation will be delivered asynchronously via the CHRE
+ * event CHRE_EVENT_BLE_ASYNC_RESULT.
+ *
+ * The scan results will be delivered asynchronously via the CHRE event
+ * CHRE_EVENT_BLE_ADVERTISEMENT.
+ *
+ * If the Bluetooth setting is disabled at the Android level, CHRE is expected
+ * to return a result with CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * If chreBleStartScanAsync() is called while a previous scan has been started,
+ * the previous scan will be stopped first and replaced with the new scan.
+ *
+ * Note that some corresponding Android parameters are missing from the CHRE
+ * API, where the following default or typical parameters are used:
+ * Callback type: CALLBACK_TYPE_ALL_MATCHES
+ * Result type: SCAN_RESULT_TYPE_FULL
+ * Match mode: MATCH_MODE_AGGRESSIVE
+ * Number of matches per filter: MATCH_NUM_MAX_ADVERTISEMENT
+ * Legacy-only: false
+ * PHY type: PHY_LE_ALL_SUPPORTED
+ *
+ * @param mode Scanning mode selected among enum chreBleScanMode
+ * @param reportDelayMs Maximum requested batching delay in ms. 0 indicates no
+ *                      batching. Note that the system may deliver results
+ *                      before the maximum specified delay is reached.
+ * @param filter Pointer to the requested best-effort filter configuration as
+ *               defined by struct chreBleScanFilter. The ownership of filter
+ *               and its nested elements remains with the caller, and the caller
+ *               may release it as soon as chreBleStartScanAsync() returns.
+ *
+ * @return True to indicate that the request was accepted. False otherwise.
+ *
+ * @since v1.6
+ */
+bool chreBleStartScanAsync(enum chreBleScanMode mode, uint32_t reportDelayMs,
+                           const struct chreBleScanFilter *filter);
+/**
+ * Stops a CHRE BLE scan.
+ *
+ * The result of the operation will be delivered asynchronously via the CHRE
+ * event CHRE_EVENT_BLE_ASYNC_RESULT.
+ *
+ * @return True to indicate that the request was accepted. False otherwise.
+ *
+ * @since v1.6
+ */
+bool chreBleStopScanAsync(void);
+
+/**
+ * Requests to immediately deliver batched scan results. The nanoapp must
+ * have an active BLE scan request. If a request is accepted, it will be treated
+ * as though the reportDelayMs has expired for a batched scan. Upon accepting
+ * the request, CHRE works to immediately deliver scan results currently kept in
+ * batching memory, if any, via regular CHRE_EVENT_BLE_ADVERTISEMENT events,
+ * followed by a CHRE_EVENT_BLE_FLUSH_COMPLETE event.
+ *
+ * If the underlying system fails to complete the flush operation within
+ * CHRE_BLE_FLUSH_COMPLETE_TIMEOUT_NS, CHRE will send a
+ * CHRE_EVENT_BLE_FLUSH_COMPLETE event with CHRE_ERROR_TIMEOUT.
+ *
+ * If multiple flush requests are made prior to flush completion, then the
+ * requesting nanoapp will receive all batched samples existing at the time of
+ * the latest flush request. In this case, the number of
+ * CHRE_EVENT_BLE_FLUSH_COMPLETE events received must equal the number of flush
+ * requests made.
+ *
+ * If chreBleStopScanAsync() is called while a flush operation is in progress,
+ * it is unspecified whether the flush operation will complete successfully or
+ * return an error, such as CHRE_ERROR_FUNCTION_DISABLED, but in any case,
+ * CHRE_EVENT_BLE_FLUSH_COMPLETE must still be delivered. The same applies if
+ * the Bluetooth user setting is disabled during a flush operation.
+ *
+ * If called while running on a CHRE API version below v1.7, this function
+ * returns false and has no effect.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *               sent as a response to this request.
+ *
+ * @return True to indicate the request was accepted. False otherwise.
+ *
+ * @since v1.7
+ */
+bool chreBleFlushAsync(const void *cookie);
+
+/**
+ * Definitions for handling unsupported CHRE BLE scenarios.
+ */
+#else  // defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+#define CHRE_BLE_PERM_ERROR_STRING                                       \
+  "CHRE_NANOAPP_USES_BLE must be defined when building this nanoapp in " \
+  "order to refer to "
+
+#define chreBleStartScanAsync(...) \
+  CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleStartScanAsync")
+
+#define chreBleStopScanAsync(...) \
+  CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleStopScanAsync")
+
+#define chreBleFlushAsync(...) \
+  CHRE_BUILD_ERROR(CHRE_BLE_PERM_ERROR_STRING "chreBleFlushAsync")
+
+#endif  // defined(CHRE_NANOAPP_USES_BLE) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CHRE_BLE_H_ */
diff --git a/chre_api/legacy/v1_7/chre/common.h b/chre_api/legacy/v1_7/chre/common.h
new file mode 100644
index 0000000..8f5292e
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/common.h
@@ -0,0 +1,180 @@
+/*
+ * 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_COMMON_H_
+#define _CHRE_COMMON_H_
+
+/**
+ * @file
+ * Definitions shared across multiple CHRE header files
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Mask of the 5 most significant bytes in a 64-bit nanoapp or CHRE platform
+ * identifier, which represents the vendor ID portion of the ID.
+ */
+#define CHRE_VENDOR_ID_MASK  UINT64_C(0xFFFFFFFFFF000000)
+
+/**
+ * Vendor ID "Googl".  Used in nanoapp IDs and CHRE platform IDs developed and
+ * released by Google.
+ */
+#define CHRE_VENDOR_ID_GOOGLE  UINT64_C(0x476F6F676C000000)
+
+/**
+ * Vendor ID "GoogT".  Used for nanoapp IDs associated with testing done by
+ * Google.
+ */
+#define CHRE_VENDOR_ID_GOOGLE_TEST  UINT64_C(0x476F6F6754000000)
+
+/**
+ * Helper macro to mask off all bytes other than the vendor ID (most significant
+ * 5 bytes) in 64-bit nanoapp and CHRE platform identifiers.
+ *
+ * @see chreGetNanoappInfo()
+ * @see chreGetPlatformId()
+ */
+#define CHRE_EXTRACT_VENDOR_ID(id)  ((id) & CHRE_VENDOR_ID_MASK)
+
+/**
+ * Number of nanoseconds in one second, represented as an unsigned 64-bit
+ * integer
+ */
+#define CHRE_NSEC_PER_SEC  UINT64_C(1000000000)
+
+/**
+ * General timeout for asynchronous API requests. Unless specified otherwise, a
+ * function call that returns data asynchronously via an event, such as
+ * CHRE_EVENT_ASYNC_GNSS_RESULT, must do so within this amount of time.
+ */
+#define CHRE_ASYNC_RESULT_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC)
+
+
+/**
+ * A generic listing of error codes for use in {@link #chreAsyncResult} and
+ * elsewhere. In general, module-specific error codes may be added to this enum,
+ * but effort should be made to come up with a generic name that still captures
+ * the meaning of the error.
+ */
+// LINT.IfChange
+enum chreError {
+    //! No error occurred
+    CHRE_ERROR_NONE = 0,
+
+    //! An unspecified failure occurred
+    CHRE_ERROR = 1,
+
+    //! One or more supplied arguments are invalid
+    CHRE_ERROR_INVALID_ARGUMENT = 2,
+
+    //! Unable to satisfy request because the system is busy
+    CHRE_ERROR_BUSY = 3,
+
+    //! Unable to allocate memory
+    CHRE_ERROR_NO_MEMORY = 4,
+
+    //! The requested feature is not supported
+    CHRE_ERROR_NOT_SUPPORTED = 5,
+
+    //! A timeout occurred while processing the request
+    CHRE_ERROR_TIMEOUT = 6,
+
+    //! The relevant capability is disabled, for example due to a user
+    //! configuration that takes precedence over this request
+    CHRE_ERROR_FUNCTION_DISABLED = 7,
+
+    //! The request was rejected due to internal rate limiting of the requested
+    //! functionality - the client may try its request again after waiting an
+    //! unspecified amount of time
+    CHRE_ERROR_REJECTED_RATE_LIMIT = 8,
+
+    //! The requested functionality is not currently accessible from the CHRE,
+    //! because another client, such as the main applications processor, is
+    //! currently controlling it.
+    CHRE_ERROR_FUNCTION_RESTRICTED_TO_OTHER_MASTER = 9,
+    CHRE_ERROR_FUNCTION_RESTRICTED_TO_OTHER_CLIENT = 9,
+
+    //! This request is no longer valid. It may have been replaced by a newer
+    //! request before taking effect.
+    CHRE_ERROR_OBSOLETE_REQUEST = 10,
+
+    //!< Do not exceed this value when adding new error codes
+    CHRE_ERROR_LAST = UINT8_MAX,
+};
+// LINT.ThenChange(core/include/chre/core/api_manager_common.h)
+
+/**
+ * Generic data structure to indicate the result of an asynchronous operation.
+ *
+ * @note
+ * The general model followed by CHRE for asynchronous operations is that a
+ * request function returns a boolean value that indicates whether the request
+ * was accepted for further processing. The actual result of the operation is
+ * provided in a subsequent event sent with an event type that is defined in the
+ * specific API. Typically, a "cookie" parameter is supplied to allow the client
+ * to tie the response to a specific request, or pass data through, etc. The
+ * response is expected to be delivered within CHRE_ASYNC_RESULT_TIMEOUT_NS if
+ * not specified otherwise.
+ *
+ * The CHRE implementation must allow for multiple asynchronous requests to be
+ * outstanding at a given time, under reasonable resource constraints. Further,
+ * requests must be processed in the same order as supplied by the client of the
+ * API in order to maintain causality. Using GNSS as an example, if a client
+ * calls chreGnssLocationSessionStartAsync() and then immediately calls
+ * chreGnssLocationSessionStopAsync(), the final result must be that the
+ * location session is stopped. Whether requests always complete in the
+ * order that they are given is implementation-defined. For example, if a client
+ * calls chreGnssLocationSessionStart() and then immediately calls
+ * chreGnssMeasurementSessionStart(), it is possible for the
+ * CHRE_EVENT_GNSS_RESULT associated with the measurement session to be
+ * delivered before the one for the location session.
+ */
+struct chreAsyncResult {
+    //! Indicates the request associated with this result. The interpretation of
+    //! values in this field is dependent upon the event type provided when this
+    //! result was delivered.
+    uint8_t requestType;
+
+    //! Set to true if the request was successfully processed
+    bool success;
+
+    //! If the request failed (success is false), this is set to a value from
+    //! enum chreError (other than CHRE_ERROR_NONE), which may provide
+    //! additional information about the nature of the failure.
+    //! @see #chreError
+    uint8_t errorCode;
+
+    //! Reserved for future use, set to 0
+    uint8_t reserved;
+
+    //! Set to the cookie parameter given to the request function tied to this
+    //! result
+    const void *cookie;
+};
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CHRE_COMMON_H_ */
diff --git a/chre_api/legacy/v1_7/chre/event.h b/chre_api/legacy/v1_7/chre/event.h
new file mode 100644
index 0000000..a1cba75
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/event.h
@@ -0,0 +1,903 @@
+/*
+ * 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_EVENT_H_
+#define _CHRE_EVENT_H_
+
+/**
+ * @file
+ * Context Hub Runtime Environment API dealing with events and messages.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <chre/toolchain.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The CHRE implementation is required to provide the following preprocessor
+ * defines via the build system.
+ *
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE: The maximum size, in bytes, allowed for
+ *     a message sent to chreSendMessageToHostEndpoint().  This must be at least
+ *     CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE.
+ */
+
+#ifndef CHRE_MESSAGE_TO_HOST_MAX_SIZE
+#error CHRE_MESSAGE_TO_HOST_MAX_SIZE must be defined by the CHRE implementation
+#endif
+
+/**
+ * The minimum size, in bytes, any CHRE implementation will use for
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE is set to 1000 for v1.5+ CHRE implementations,
+ * and 128 for v1.0-v1.4 implementations (previously kept in
+ * CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, which has been removed).
+ *
+ * All CHRE implementations supporting v1.5+ must support the raised limit of
+ * 1000 bytes, however a nanoapp compiled against v1.5 cannot assume this
+ * limit if there is a possibility their binary will run on a v1.4 or earlier
+ * implementation that had a lower limit. To allow for nanoapp compilation in
+ * these situations, CHRE_MESSAGE_TO_HOST_MAX_SIZE must be set to the minimum
+ * value the nanoapp may encounter, and CHRE_NANOAPP_SUPPORTS_PRE_V1_5 can be
+ * defined to skip the compile-time check.
+ */
+#if (!defined(CHRE_NANOAPP_SUPPORTS_PRE_V1_5) && \
+     CHRE_MESSAGE_TO_HOST_MAX_SIZE < 1000) ||    \
+    (defined(CHRE_NANOAPP_SUPPORTS_PRE_V1_5) &&  \
+     CHRE_MESSAGE_TO_HOST_MAX_SIZE < 128)
+#error CHRE_MESSAGE_TO_HOST_MAX_SIZE is too small.
+#endif
+
+/**
+ * The lowest numerical value legal for a user-defined event.
+ *
+ * The system reserves all event values from 0 to 0x7FFF, inclusive.
+ * User events may use any value in the range 0x8000 to 0xFFFF, inclusive.
+ *
+ * Note that the same event values might be used by different nanoapps
+ * for different meanings.  This is not a concern, as these values only
+ * have meaning when paired with the originating nanoapp.
+ */
+#define CHRE_EVENT_FIRST_USER_VALUE  UINT16_C(0x8000)
+
+/**
+ * nanoappHandleEvent argument: struct chreMessageFromHostData
+ *
+ * The format of the 'message' part of this structure is left undefined,
+ * and it's up to the nanoapp and host to have an established protocol
+ * beforehand.
+ */
+#define CHRE_EVENT_MESSAGE_FROM_HOST  UINT16_C(0x0001)
+
+/**
+ * nanoappHandleEvent argument: 'cookie' given to chreTimerSet() method.
+ *
+ * Indicates that a timer has elapsed, in accordance with how chreTimerSet() was
+ * invoked.
+ */
+#define CHRE_EVENT_TIMER  UINT16_C(0x0002)
+
+/**
+ * nanoappHandleEvent argument: struct chreNanoappInfo
+ *
+ * Indicates that a nanoapp has successfully started (its nanoappStart()
+ * function has been called, and it returned true) and is able to receive events
+ * sent via chreSendEvent().  Note that this event is not sent for nanoapps that
+ * were started prior to the current nanoapp - use chreGetNanoappInfo() to
+ * determine if another nanoapp is already running.
+ *
+ * @see chreConfigureNanoappInfoEvents
+ * @since v1.1
+ */
+#define CHRE_EVENT_NANOAPP_STARTED  UINT16_C(0x0003)
+
+/**
+ * nanoappHandleEvent argument: struct chreNanoappInfo
+ *
+ * Indicates that a nanoapp has stopped executing and is no longer able to
+ * receive events sent via chreSendEvent().  Any events sent prior to receiving
+ * this event are not guaranteed to have been delivered.
+ *
+ * @see chreConfigureNanoappInfoEvents
+ * @since v1.1
+ */
+#define CHRE_EVENT_NANOAPP_STOPPED  UINT16_C(0x0004)
+
+/**
+ * nanoappHandleEvent argument: NULL
+ *
+ * Indicates that CHRE has observed the host wake from low-power sleep state.
+ *
+ * @see chreConfigureHostSleepStateEvents
+ * @since v1.2
+ */
+#define CHRE_EVENT_HOST_AWAKE  UINT16_C(0x0005)
+
+/**
+ * nanoappHandleEvent argument: NULL
+ *
+ * Indicates that CHRE has observed the host enter low-power sleep state.
+ *
+ * @see chreConfigureHostSleepStateEvents
+ * @since v1.2
+ */
+#define CHRE_EVENT_HOST_ASLEEP  UINT16_C(0x0006)
+
+/**
+ * nanoappHandleEvent argument: NULL
+ *
+ * Indicates that CHRE is collecting debug dumps. Nanoapps can call
+ * chreDebugDumpLog() to log their debug data while handling this event.
+ *
+ * @see chreConfigureDebugDumpEvent
+ * @see chreDebugDumpLog
+ * @since v1.4
+ */
+#define CHRE_EVENT_DEBUG_DUMP  UINT16_C(0x0007)
+
+/**
+ * nanoappHandleEvent argument: struct chreHostEndpointNotification
+ *
+ * Notifications event regarding a host endpoint.
+ *
+ * @see chreConfigureHostEndpointNotifications
+ * @since v1.6
+ */
+#define CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION UINT16_C(0x0008)
+
+/**
+ * First possible value for CHRE_EVENT_SENSOR events.
+ *
+ * This allows us to separately define our CHRE_EVENT_SENSOR_* events in
+ * chre/sensor.h, without fear of collision with other event values.
+ */
+#define CHRE_EVENT_SENSOR_FIRST_EVENT  UINT16_C(0x0100)
+
+/**
+ * Last possible value for CHRE_EVENT_SENSOR events.
+ *
+ * This allows us to separately define our CHRE_EVENT_SENSOR_* events in
+ * chre/sensor.h, without fear of collision with other event values.
+ */
+#define CHRE_EVENT_SENSOR_LAST_EVENT  UINT16_C(0x02FF)
+
+/**
+ * First event in the block reserved for GNSS. These events are defined in
+ * chre/gnss.h.
+ */
+#define CHRE_EVENT_GNSS_FIRST_EVENT  UINT16_C(0x0300)
+#define CHRE_EVENT_GNSS_LAST_EVENT   UINT16_C(0x030F)
+
+/**
+ * First event in the block reserved for WiFi. These events are defined in
+ * chre/wifi.h.
+ */
+#define CHRE_EVENT_WIFI_FIRST_EVENT  UINT16_C(0x0310)
+#define CHRE_EVENT_WIFI_LAST_EVENT   UINT16_C(0x031F)
+
+/**
+ * First event in the block reserved for WWAN. These events are defined in
+ * chre/wwan.h.
+ */
+#define CHRE_EVENT_WWAN_FIRST_EVENT  UINT16_C(0x0320)
+#define CHRE_EVENT_WWAN_LAST_EVENT   UINT16_C(0x032F)
+
+/**
+ * First event in the block reserved for audio. These events are defined in
+ * chre/audio.h.
+ */
+#define CHRE_EVENT_AUDIO_FIRST_EVENT UINT16_C(0x0330)
+#define CHRE_EVENT_AUDIO_LAST_EVENT  UINT16_C(0x033F)
+
+/**
+ * First event in the block reserved for settings changed notifications.
+ * These events are defined in chre/user_settings.h
+ *
+ * @since v1.5
+ */
+#define CHRE_EVENT_SETTING_CHANGED_FIRST_EVENT UINT16_C(0x340)
+#define CHRE_EVENT_SETTING_CHANGED_LAST_EVENT  UINT16_C(0x34F)
+
+/**
+ * First event in the block reserved for Bluetooth LE. These events are defined
+ * in chre/ble.h.
+ */
+#define CHRE_EVENT_BLE_FIRST_EVENT UINT16_C(0x0350)
+#define CHRE_EVENT_BLE_LAST_EVENT  UINT16_C(0x035F)
+
+/**
+ * First in the extended range of values dedicated for internal CHRE
+ * implementation usage.
+ *
+ * This range is semantically the same as the internal event range defined
+ * below, but has been extended to allow for more implementation-specific events
+ * to be used.
+ *
+ * @since v1.1
+ */
+#define CHRE_EVENT_INTERNAL_EXTENDED_FIRST_EVENT  UINT16_C(0x7000)
+
+/**
+ * First in a range of values dedicated for internal CHRE implementation usage.
+ *
+ * If a CHRE wishes to use events internally, any values within this range
+ * are assured not to be taken by future CHRE API additions.
+ */
+#define CHRE_EVENT_INTERNAL_FIRST_EVENT  UINT16_C(0x7E00)
+
+/**
+ * Last in a range of values dedicated for internal CHRE implementation usage.
+ *
+ * If a CHRE wishes to use events internally, any values within this range
+ * are assured not to be taken by future CHRE API additions.
+ */
+#define CHRE_EVENT_INTERNAL_LAST_EVENT  UINT16_C(0x7FFF)
+
+/**
+ * A special value for the hostEndpoint argument in
+ * chreSendMessageToHostEndpoint() that indicates that the message should be
+ * delivered to all host endpoints.  This value will not be used in the
+ * hostEndpoint field of struct chreMessageFromHostData supplied with
+ * CHRE_EVENT_MESSAGE_FROM_HOST.
+ *
+ * @since v1.1
+ */
+#define CHRE_HOST_ENDPOINT_BROADCAST  UINT16_C(0xFFFF)
+
+/**
+ * A special value for hostEndpoint in struct chreMessageFromHostData that
+ * indicates that a host endpoint is unknown or otherwise unspecified.  This
+ * value may be received in CHRE_EVENT_MESSAGE_FROM_HOST, but it is not valid to
+ * provide it to chreSendMessageToHostEndpoint().
+ *
+ * @since v1.1
+ */
+#define CHRE_HOST_ENDPOINT_UNSPECIFIED  UINT16_C(0xFFFE)
+
+/**
+ * Bitmask values that can be given as input to the messagePermissions parameter
+ * of chreSendMessageWithPermissions(). These values are typically used by
+ * nanoapps when they used data from the corresponding CHRE APIs to produce the
+ * message contents being sent and is used to attribute permissions usage on
+ * the Android side. See chreSendMessageWithPermissions() for more details on
+ * how these values are used when sending a message.
+ *
+ * Values in the range
+ * [CHRE_MESSAGE_PERMISSION_VENDOR_START, CHRE_MESSAGE_PERMISSION_VENDOR_END]
+ * are reserved for vendors to use when adding support for permission-gated APIs
+ * in their implementations.
+ *
+ * On the Android side, CHRE permissions are mapped as follows:
+ * - CHRE_MESSAGE_PERMISSION_AUDIO: android.permission.RECORD_AUDIO
+ * - CHRE_MESSAGE_PERMISSION_GNSS, CHRE_MESSAGE_PERMISSION_WIFI, and
+ *   CHRE_MESSAGE_PERMISSION_WWAN: android.permission.ACCESS_FINE_LOCATION, and
+ *   android.permissions.ACCESS_BACKGROUND_LOCATION
+ *
+ * @since v1.5
+ *
+ * @defgroup CHRE_MESSAGE_PERMISSION
+ * @{
+ */
+
+#define CHRE_MESSAGE_PERMISSION_NONE UINT32_C(0)
+#define CHRE_MESSAGE_PERMISSION_AUDIO UINT32_C(1)
+#define CHRE_MESSAGE_PERMISSION_GNSS (UINT32_C(1) << 1)
+#define CHRE_MESSAGE_PERMISSION_WIFI (UINT32_C(1) << 2)
+#define CHRE_MESSAGE_PERMISSION_WWAN (UINT32_C(1) << 3)
+#define CHRE_MESSAGE_PERMISSION_BLE (UINT32_C(1) << 4)
+#define CHRE_MESSAGE_PERMISSION_VENDOR_START (UINT32_C(1) << 24)
+#define CHRE_MESSAGE_PERMISSION_VENDOR_END (UINT32_C(1) << 31)
+
+/** @} */
+
+/**
+ * Data provided with CHRE_EVENT_MESSAGE_FROM_HOST.
+ */
+struct chreMessageFromHostData {
+    /**
+     * Message type supplied by the host.
+     *
+     * @note In CHRE API v1.0, support for forwarding this field from the host
+     * was not strictly required, and some implementations did not support it.
+     * However, its support is mandatory as of v1.1.
+     */
+    union {
+        /**
+         * The preferred name to use when referencing this field.
+         *
+         * @since v1.1
+         */
+        uint32_t messageType;
+
+        /**
+         * @deprecated This is the name for the messageType field used in v1.0.
+         * Left to allow code to compile against both v1.0 and v1.1 of the API
+         * definition without needing to use #ifdefs. This will be removed in a
+         * future API update - use messageType instead.
+         */
+        uint32_t reservedMessageType;
+    };
+
+    /**
+     * The size, in bytes of the following 'message'.
+     *
+     * This can be 0.
+     */
+    uint32_t messageSize;
+
+    /**
+     * The message from the host.
+     *
+     * These contents are of a format that the host and nanoapp must have
+     * established beforehand.
+     *
+     * This data is 'messageSize' bytes in length.  Note that if 'messageSize'
+     * is 0, this might be NULL.
+     */
+    const void *message;
+
+    /**
+     * An identifier for the host-side entity that sent this message.  Unless
+     * this is set to CHRE_HOST_ENDPOINT_UNSPECIFIED, it can be used in
+     * chreSendMessageToHostEndpoint() to send a directed reply that will only
+     * be received by the given entity on the host.  Endpoint identifiers are
+     * opaque values assigned at runtime, so they cannot be assumed to always
+     * describe a specific entity across restarts.
+     *
+     * If running on a CHRE API v1.0 implementation, this field will always be
+     * set to CHRE_HOST_ENDPOINT_UNSPECIFIED.
+     *
+     * @since v1.1
+     */
+    uint16_t hostEndpoint;
+};
+
+/**
+ * Provides metadata for a nanoapp in the system.
+ */
+struct chreNanoappInfo {
+    /**
+     * Nanoapp identifier. The convention for populating this value is to set
+     * the most significant 5 bytes to a value that uniquely identifies the
+     * vendor, and the lower 3 bytes identify the nanoapp.
+     */
+    uint64_t appId;
+
+    /**
+     * Nanoapp version.  The semantics of this field are defined by the nanoapp,
+     * however nanoapps are recommended to follow the same scheme used for the
+     * CHRE version exposed in chreGetVersion().  That is, the most significant
+     * byte represents the major version, the next byte the minor version, and
+     * the lower two bytes the patch version.
+     */
+    uint32_t version;
+
+    /**
+     * The instance ID of this nanoapp, which can be used in chreSendEvent() to
+     * address an event specifically to this nanoapp.  This identifier is
+     * guaranteed to be unique among all nanoapps in the system.
+     *
+     * @since v1.6
+     * Instance ID is guaranteed to never go beyond INT16_MAX. This helps the
+     * instance ID be packed into other information inside an int (useful for
+     * RPC routing).
+     */
+    uint32_t instanceId;
+};
+
+/**
+ * The types of notification events that can be included in struct
+ * chreHostEndpointNotification.
+ *
+ * @defgroup HOST_ENDPOINT_NOTIFICATION_TYPE
+ * @{
+ */
+#define HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT UINT8_C(0)
+/** @} */
+
+/**
+ * Data provided in CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION.
+ */
+struct chreHostEndpointNotification {
+    /**
+     * The ID of the host endpoint that this notification is for.
+     */
+    uint16_t hostEndpointId;
+
+    /**
+     * The type of notification this event represents, which should be
+     * one of the HOST_ENDPOINT_NOTIFICATION_TYPE_* values.
+     */
+    uint8_t notificationType;
+
+    /**
+     * Reserved for future use, must be zero.
+     */
+    uint8_t reserved;
+};
+
+//! The maximum length of a host endpoint's name.
+#define CHRE_MAX_ENDPOINT_NAME_LEN (51)
+
+//! The maximum length of a host endpoint's tag.
+#define CHRE_MAX_ENDPOINT_TAG_LEN (51)
+
+/**
+ * The type of host endpoint that can be used in the hostEndpointType field
+ * of chreHostEndpointInfo.
+ *
+ * @since v1.6
+ *
+ * @defgroup CHRE_HOST_ENDPOINT_TYPE_
+ * @{
+ */
+
+//! The host endpoint is part of the Android system framework.
+#define CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK UINT8_C(0)
+
+//! The host endpoint is an Android app.
+#define CHRE_HOST_ENDPOINT_TYPE_APP UINT8_C(1)
+
+//! Values in the range [CHRE_HOST_ENDPOINT_TYPE_VENDOR_START,
+//! CHRE_HOST_ENDPOINT_TYPE_VENDOR_END] can be a custom defined host endpoint
+//! type for platform-specific vendor use.
+#define CHRE_HOST_ENDPOINT_TYPE_VENDOR_START UINT8_C(128)
+#define CHRE_HOST_ENDPOINT_TYPE_VENDOR_END UINT8_C(255)
+
+/** @} */
+
+/**
+ * Provides metadata for a host endpoint.
+ *
+ * @since v1.6
+ */
+struct chreHostEndpointInfo {
+    //! The endpoint ID of this host.
+    uint16_t hostEndpointId;
+
+    //! The type of host endpoint, which must be set to one of the
+    //! CHRE_HOST_ENDPOINT_TYPE_* values or a value in the vendor-reserved
+    //! range.
+    uint8_t hostEndpointType;
+
+    //! Flag indicating if the packageName/endpointName field is valid.
+    uint8_t isNameValid : 1;
+
+    //! Flag indicating if the attributionTag/endpointTag field is valid.
+    uint8_t isTagValid : 1;
+
+    //! A union of null-terminated host name strings.
+    union {
+        //! The Android package name associated with this host, valid if the
+        //! hostEndpointType is CHRE_HOST_ENDPOINT_TYPE_APP or
+        //! CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK. Refer to the Android documentation
+        //! for the package attribute in the app manifest.
+        char packageName[CHRE_MAX_ENDPOINT_NAME_LEN];
+
+        //! A generic endpoint name that can be used for endpoints that
+        //! may not have a package name.
+        char endpointName[CHRE_MAX_ENDPOINT_NAME_LEN];
+    };
+
+    //! A union of null-terminated host tag strings for further identification.
+    union {
+        //! The attribution tag associated with this host that is used to audit
+        //! access to data, which can be valid if the hostEndpointType is
+        //! CHRE_HOST_ENDPOINT_TYPE_APP. Refer to the Android documentation
+        //! regarding data audit using attribution tags.
+        char attributionTag[CHRE_MAX_ENDPOINT_TAG_LEN];
+
+        //! A generic endpoint tag that can be used for endpoints that
+        //! may not have an attribution tag.
+        char endpointTag[CHRE_MAX_ENDPOINT_TAG_LEN];
+    };
+};
+
+/**
+ * An RPC service exposed by a nanoapp.
+ *
+ * The implementation of the RPC interface is not defined by the HAL, and is written
+ * at the messaging endpoint layers (Android app and/or CHRE nanoapp). NanoappRpcService
+ * contains the informational metadata to be consumed by the RPC interface layer.
+ */
+struct chreNanoappRpcService {
+    /**
+     * The unique 64-bit ID of an RPC service exposed by a nanoapp. Note that
+     * the uniqueness is only required within the nanoapp's domain (i.e. the
+     * combination of the nanoapp ID and service id must be unique).
+     */
+    uint64_t id;
+
+    /**
+     * The software version of this service, which follows the sematic
+     * versioning scheme (see semver.org). It follows the format
+     * major.minor.patch, where major and minor versions take up one byte
+     * each, and the patch version takes up the final 2 bytes.
+     */
+    uint32_t version;
+};
+
+/**
+ * Callback which frees data associated with an event.
+ *
+ * This callback is (optionally) provided to the chreSendEvent() method as
+ * a means for freeing the event data and performing any other cleanup
+ * necessary when the event is completed.  When this callback is invoked,
+ * 'eventData' is no longer needed and can be released.
+ *
+ * @param eventType  The 'eventType' argument from chreSendEvent().
+ * @param eventData  The 'eventData' argument from chreSendEvent().
+ *
+ * @see chreSendEvent
+ */
+typedef void (chreEventCompleteFunction)(uint16_t eventType, void *eventData);
+
+/**
+ * Callback which frees a message.
+ *
+ * This callback is (optionally) provided to the chreSendMessageToHostEndpoint()
+ * method as a means for freeing the message.  When this callback is invoked,
+ * 'message' is no longer needed and can be released.  Note that this in
+ * no way assures that said message did or did not make it to the host, simply
+ * that this memory is no longer needed.
+ *
+ * @param message  The 'message' argument from chreSendMessageToHostEndpoint().
+ * @param messageSize  The 'messageSize' argument from
+ *     chreSendMessageToHostEndpoint().
+ *
+ * @see chreSendMessageToHostEndpoint
+ */
+typedef void (chreMessageFreeFunction)(void *message, size_t messageSize);
+
+
+/**
+ * Enqueue an event to be sent to another nanoapp.
+ *
+ * @param eventType  This is a user-defined event type, of at least the
+ *     value CHRE_EVENT_FIRST_USER_VALUE.  It is illegal to attempt to use any
+ *     of the CHRE_EVENT_* values reserved for the CHRE.
+ * @param eventData  A pointer value that will be understood by the receiving
+ *     app.  Note that NULL is perfectly acceptable.  It also is not required
+ *     that this be a valid pointer, although if this nanoapp is intended to
+ *     work on arbitrary CHRE implementations, then the size of a
+ *     pointer cannot be assumed to be a certain size.  Note that the caller
+ *     no longer owns this memory after the call.
+ * @param freeCallback  A pointer to a callback function.  After the lifetime
+ *     of 'eventData' is over (either through successful delivery or the event
+ *     being dropped), this callback will be invoked.  This argument is allowed
+ *     to be NULL, in which case no callback will be invoked.
+ * @param targetInstanceId  The ID of the instance we're delivering this event
+ *     to.  Note that this is allowed to be our own instance.  The instance ID
+ *     of a nanoapp can be retrieved by using chreGetNanoappInfoByInstanceId().
+ * @return true if the event was enqueued, false otherwise.  Note that even
+ *     if this method returns 'false', the 'freeCallback' will be invoked,
+ *     if non-NULL.  Note in the 'false' case, the 'freeCallback' may be
+ *     invoked directly from within chreSendEvent(), so it's necessary
+ *     for nanoapp authors to avoid possible recursion with this.
+ *
+ * @see chreEventDataFreeFunction
+ */
+bool chreSendEvent(uint16_t eventType, void *eventData,
+                   chreEventCompleteFunction *freeCallback,
+                   uint32_t targetInstanceId);
+
+/**
+ * Send a message to the host, using the broadcast endpoint
+ * CHRE_HOST_ENDPOINT_BROADCAST.  Refer to chreSendMessageToHostEndpoint() for
+ * further details.
+ *
+ * @see chreSendMessageToHostEndpoint
+ *
+ * @deprecated New code should use chreSendMessageToHostEndpoint() instead of
+ * this function.  A future update to the API may cause references to this
+ * function to produce a compiler warning.
+ */
+bool chreSendMessageToHost(void *message, uint32_t messageSize,
+                           uint32_t messageType,
+                           chreMessageFreeFunction *freeCallback)
+    CHRE_DEPRECATED("Use chreSendMessageToHostEndpoint instead");
+
+/**
+ * Send a message to the host, using CHRE_MESSAGE_PERMISSION_NONE for the
+ * associated message permissions. This method must only be used if no data
+ * provided by CHRE's audio, GNSS, WiFi, and WWAN APIs was used to produce the
+ * contents of the message being sent. Refer to chreSendMessageWithPermissions()
+ * for further details.
+ *
+ * @see chreSendMessageWithPermissions
+ *
+ * @since v1.1
+ */
+bool chreSendMessageToHostEndpoint(void *message, size_t messageSize,
+                                   uint32_t messageType, uint16_t hostEndpoint,
+                                   chreMessageFreeFunction *freeCallback);
+
+/**
+ * Send a message to the host, waking it up if it is currently asleep.
+ *
+ * This message is by definition arbitrarily defined.  Since we're not
+ * just a passing a pointer to memory around the system, but need to copy
+ * this into various buffers to send it to the host, the CHRE
+ * implementation cannot be asked to support an arbitrarily large message
+ * size.  As a result, we have the CHRE implementation define
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE.
+ *
+ * CHRE_MESSAGE_TO_HOST_MAX_SIZE is not given a value by the Platform API.  The
+ * Platform API does define CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, and requires
+ * that CHRE_MESSAGE_TO_HOST_MAX_SIZE is at least that value.
+ *
+ * As a result, if your message sizes are all less than
+ * CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, then you have no concerns on any
+ * CHRE implementation.  If your message sizes are larger, you'll need to
+ * come up with a strategy for splitting your message across several calls
+ * to this method.  As long as that strategy works for
+ * CHRE_MESSAGE_TO_HOST_MINIMUM_MAX_SIZE, it will work across all CHRE
+ * implementations (although on some implementations less calls to this
+ * method may be necessary).
+ *
+ * When sending a message to the host, the ContextHub service will enforce
+ * the host client has been granted Android-level permissions corresponding to
+ * the ones the nanoapp declares it uses through CHRE_NANOAPP_USES_AUDIO, etc.
+ * In addition to this, the permissions bitmask provided as input to this method
+ * results in the Android framework using app-ops to verify and log access upon
+ * message delivery to an application. This is primarily useful for ensuring
+ * accurate attribution for messages generated using permission-controlled data.
+ * The bitmask declared by the nanoapp for this message must be a
+ * subset of the permissions it declared it would use at build time or the
+ * message will be rejected.
+ *
+ * Nanoapps must use this method if the data they are sending contains or was
+ * derived from any data sampled through CHRE's audio, GNSS, WiFi, or WWAN APIs.
+ * Additionally, if vendors add APIs to expose data that would be guarded by a
+ * permission in Android, vendors must support declaring a message permission
+ * through this method.
+ *
+ * @param message  Pointer to a block of memory to send to the host.
+ *     NULL is acceptable only if messageSize is 0.  If non-NULL, this
+ *     must be a legitimate pointer (that is, unlike chreSendEvent(), a small
+ *     integral value cannot be cast to a pointer for this).  Note that the
+ *     caller no longer owns this memory after the call.
+ * @param messageSize  The size, in bytes, of the given message. If this exceeds
+ *     CHRE_MESSAGE_TO_HOST_MAX_SIZE, the message will be rejected.
+ * @param messageType  Message type sent to the app on the host.
+ *     NOTE: In CHRE API v1.0, support for forwarding this field to the host was
+ *     not strictly required, and some implementations did not support it.
+ *     However, its support is mandatory as of v1.1.
+ * @param hostEndpoint  An identifier for the intended recipient of the message,
+ *     or CHRE_HOST_ENDPOINT_BROADCAST if all registered endpoints on the host
+ *     should receive the message.  Endpoint identifiers are assigned on the
+ *     host side, and nanoapps may learn of the host endpoint ID of an intended
+ *     recipient via an initial message sent by the host.  This parameter is
+ *     always treated as CHRE_HOST_ENDPOINT_BROADCAST if running on a CHRE API
+ *     v1.0 implementation. CHRE_HOST_ENDPOINT_BROADCAST isn't allowed to be
+ *     specified if anything other than CHRE_MESSAGE_PERMISSION_NONE is given
+ *     as messagePermissions since doing so would potentially attribute
+ *     permissions usage to host clients that don't intend to consume the data.
+ * @param messagePermissions Bitmasked CHRE_MESSAGE_PERMISSION_ values that will
+ *     be converted to corresponding Android-level permissions and attributed
+ *     the host endpoint upon consumption of the message.
+ * @param freeCallback  A pointer to a callback function.  After the lifetime
+ *     of 'message' is over (which does not assure that 'message' made it to
+ *     the host, just that the transport layer no longer needs this memory),
+ *     this callback will be invoked.  This argument is allowed
+ *     to be NULL, in which case no callback will be invoked.
+ * @return true if the message was accepted for transmission, false otherwise.
+ *     Note that even if this method returns 'false', the 'freeCallback' will
+ *     be invoked, if non-NULL.  In either case, the 'freeCallback' may be
+ *     invoked directly from within chreSendMessageToHostEndpoint(), so it's
+ *     necessary for nanoapp authors to avoid possible recursion with this.
+ *
+ * @see chreMessageFreeFunction
+ *
+ * @since v1.5
+ */
+bool chreSendMessageWithPermissions(void *message, size_t messageSize,
+                                    uint32_t messageType, uint16_t hostEndpoint,
+                                    uint32_t messagePermissions,
+                                    chreMessageFreeFunction *freeCallback);
+
+/**
+ * Queries for information about a nanoapp running in the system.
+ *
+ * In the current API, appId is required to be unique, i.e. there cannot be two
+ * nanoapps running concurrently with the same appId.  If this restriction is
+ * removed in a future API version and multiple instances of the same appId are
+ * present, this function must always return the first app to start.
+ *
+ * @param appId Identifier for the nanoapp that the caller is requesting
+ *     information about.
+ * @param info Output parameter.  If this function returns true, this structure
+ *     will be populated with details of the specified nanoapp.
+ * @return true if a nanoapp with the given ID is currently running, and the
+ *     supplied info parameter was populated with its information.
+ *
+ * @since v1.1
+ */
+bool chreGetNanoappInfoByAppId(uint64_t appId, struct chreNanoappInfo *info);
+
+/**
+ * Queries for information about a nanoapp running in the system, using the
+ * runtime unique identifier.  This method can be used to get information about
+ * the sender of an event.
+ *
+ * @param instanceId
+ * @param info Output parameter.  If this function returns true, this structure
+ *     will be populated with details of the specified nanoapp.
+ * @return true if a nanoapp with the given instance ID is currently running,
+ *     and the supplied info parameter was populated with its information.
+ *
+ * @since v1.1
+ */
+bool chreGetNanoappInfoByInstanceId(uint32_t instanceId,
+                                    struct chreNanoappInfo *info);
+
+/**
+ * Configures whether this nanoapp will be notified when other nanoapps in the
+ * system start and stop, via CHRE_EVENT_NANOAPP_STARTED and
+ * CHRE_EVENT_NANOAPP_STOPPED.  These events are disabled by default, and if a
+ * nanoapp is not interested in interacting with other nanoapps, then it does
+ * not need to register for them.  However, if inter-nanoapp communication is
+ * desired, nanoapps are recommended to call this function from nanoappStart().
+ *
+ * If running on a CHRE platform that only supports v1.0 of the CHRE API, this
+ * function has no effect.
+ *
+ * @param enable true to enable these events, false to disable
+ *
+ * @see CHRE_EVENT_NANOAPP_STARTED
+ * @see CHRE_EVENT_NANOAPP_STOPPED
+ *
+ * @since v1.1
+ */
+void chreConfigureNanoappInfoEvents(bool enable);
+
+/**
+ * Configures whether this nanoapp will be notified when the host (applications
+ * processor) transitions between wake and sleep, via CHRE_EVENT_HOST_AWAKE and
+ * CHRE_EVENT_HOST_ASLEEP.  As chreSendMessageToHostEndpoint() wakes the host if
+ * it is asleep, these events can be used to opportunistically send data to the
+ * host only when it wakes up for some other reason.  Note that this event is
+ * not instantaneous - there is an inherent delay in CHRE observing power state
+ * changes of the host processor, which may be significant depending on the
+ * implementation, especially in the wake to sleep direction.  Therefore,
+ * nanoapps are not guaranteed that messages sent to the host between AWAKE and
+ * ASLEEP events will not trigger a host wakeup.  However, implementations must
+ * ensure that the nominal wake-up notification latency is strictly less than
+ * the minimum wake-sleep time of the host processor.  Implementations are also
+ * encouraged to minimize this and related latencies where possible, to avoid
+ * unnecessary host wake-ups.
+ *
+ * These events are only sent on transitions, so the initial state will not be
+ * sent to the nanoapp as an event - use chreIsHostAwake().
+ *
+ * @param enable true to enable these events, false to disable
+ *
+ * @see CHRE_EVENT_HOST_AWAKE
+ * @see CHRE_EVENT_HOST_ASLEEP
+ *
+ * @since v1.2
+ */
+void chreConfigureHostSleepStateEvents(bool enable);
+
+/**
+ * Retrieves the current sleep/wake state of the host (applications processor).
+ * Note that, as with the CHRE_EVENT_HOST_AWAKE and CHRE_EVENT_HOST_ASLEEP
+ * events, there is no guarantee that CHRE's view of the host processor's sleep
+ * state is instantaneous, and it may also change between querying the state and
+ * performing a host-waking action like sending a message to the host.
+ *
+ * @return true if by CHRE's own estimation the host is currently awake,
+ *     false otherwise
+ *
+ * @since v1.2
+ */
+bool chreIsHostAwake(void);
+
+/**
+ * Configures whether this nanoapp will be notified when CHRE is collecting
+ * debug dumps, via CHRE_EVENT_DEBUG_DUMP. This event is disabled by default,
+ * and if a nanoapp is not interested in logging its debug data, then it does
+ * not need to register for it.
+ *
+ * @param enable true to enable receipt of this event, false to disable.
+ *
+ * @see CHRE_EVENT_DEBUG_DUMP
+ * @see chreDebugDumpLog
+ *
+ * @since v1.4
+ */
+void chreConfigureDebugDumpEvent(bool enable);
+
+/**
+ * Configures whether this nanoapp will receive updates regarding a host
+ * endpoint that is connected with the Context Hub.
+ *
+ * If this API succeeds, the nanoapp will receive disconnection notifications,
+ * via the CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION event with an eventData of type
+ * chreHostEndpointNotification with its notificationType set to
+ * HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT, which can be invoked if the host
+ * has disconnected from the Context Hub either explicitly or implicitly (e.g.
+ * crashes). Nanoapps can use this notifications to clean up any resources
+ * associated with this host endpoint.
+ *
+ * @param hostEndpointId The host endpoint ID to configure notifications for.
+ * @param enable true to enable notifications.
+ *
+ * @return true on success
+ *
+ * @see chreMessageFromHostData
+ * @see chreHostEndpointNotification
+ * @see CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION
+ *
+ * @since v1.6
+ */
+bool chreConfigureHostEndpointNotifications(uint16_t hostEndpointId,
+                                            bool enable);
+
+/**
+ * Publishes an RPC service from this nanoapp.
+ *
+ * When this API is invoked, the list of RPC services will be provided to
+ * host applications interacting with the nanoapp.
+ *
+ * This function must be invoked from nanoappStart(), to guarantee stable output
+ * of the list of RPC services supported by the nanoapp.
+ *
+ * @param services A non-null pointer to the list of RPC services to publish.
+ * @param numServices The number of services to publish, i.e. the length of the
+ *   services array.
+ *
+ * @return true if the publishing is successful.
+ *
+ * @since v1.6
+ */
+bool chrePublishRpcServices(struct chreNanoappRpcService *services,
+                            size_t numServices);
+
+/**
+ * Retrieves metadata for a given host endpoint ID.
+ *
+ * This API will provide metadata regarding an endpoint associated with a
+ * host endpoint ID. The nanoapp should use this API to determine more
+ * information about a host endpoint that has sent a message to the nanoapp,
+ * after receiving a chreMessageFromHostData (which includes the endpoint ID).
+ *
+ * If the given host endpoint ID is not associated with a valid host (or if the
+ * client has disconnected from the Android or CHRE framework, i.e. no longer
+ * able to send messages to CHRE), this method will return false and info will
+ * not be populated.
+ *
+ * @param hostEndpointId The endpoint ID of the host to get info for.
+ * @param info The non-null pointer to where the metadata will be stored.
+ *
+ * @return true if info has been successfully populated.
+ *
+ * @since v1.6
+ */
+bool chreGetHostEndpointInfo(uint16_t hostEndpointId,
+                             struct chreHostEndpointInfo *info);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_EVENT_H_ */
+
diff --git a/chre_api/legacy/v1_7/chre/gnss.h b/chre_api/legacy/v1_7/chre/gnss.h
new file mode 100644
index 0000000..79a8f46
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/gnss.h
@@ -0,0 +1,604 @@
+/*
+ * 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_GNSS_H_
+#define _CHRE_GNSS_H_
+
+/**
+ * @file
+ * Global Navigation Satellite System (GNSS) API.
+ *
+ * These structures and definitions are based on the Android N GPS HAL.
+ * Refer to that header file (located at this path as of the time of this
+ * comment: hardware/libhardware/include/hardware/gps.h) and associated
+ * documentation for further details and explanations for these fields.
+ * References in comments like "(ref: GnssAccumulatedDeltaRangeState)" map to
+ * the relevant element in the GPS HAL where additional information can be
+ * found.
+ *
+ * In general, the parts of this API that are taken from the GPS HAL follow the
+ * naming conventions established in that interface rather than the CHRE API
+ * conventions, in order to avoid confusion and enable code re-use where
+ * applicable.
+ */
+
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <chre/common.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags that may be returned by chreGnssGetCapabilities()
+ * @defgroup CHRE_GNSS_CAPABILITIES
+ * @{
+ */
+
+//! A lack of flags indicates that GNSS is not supported in this CHRE
+#define CHRE_GNSS_CAPABILITIES_NONE          UINT32_C(0)
+
+//! GNSS position fixes are supported via chreGnssLocationSessionStartAsync()
+#define CHRE_GNSS_CAPABILITIES_LOCATION      UINT32_C(1 << 0)
+
+//! GNSS raw measurements are supported via
+//! chreGnssMeasurementSessionStartAsync()
+#define CHRE_GNSS_CAPABILITIES_MEASUREMENTS  UINT32_C(1 << 1)
+
+//! Location fixes supplied from chreGnssConfigurePassiveLocationListener()
+//! are tapped in at the GNSS engine level, so they include additional fixes
+//! such as those requested by the AP, and not just those requested by other
+//! nanoapps within CHRE (which is the case when this flag is not set)
+#define CHRE_GNSS_CAPABILITIES_GNSS_ENGINE_BASED_PASSIVE_LISTENER \
+                                             UINT32_C(1 << 2)
+
+/** @} */
+
+/**
+ * The current version of struct chreGnssDataEvent associated with this API
+ */
+#define CHRE_GNSS_DATA_EVENT_VERSION  UINT8_C(0)
+
+/**
+ * The maximum time the CHRE implementation is allowed to elapse before sending
+ * an event with the result of an asynchronous request, unless specified
+ * otherwise
+ */
+#define CHRE_GNSS_ASYNC_RESULT_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC)
+
+/**
+ * Produce an event ID in the block of IDs reserved for GNSS
+ * @param offset  Index into GNSS event ID block; valid range [0,15]
+ */
+#define CHRE_GNSS_EVENT_ID(offset)  (CHRE_EVENT_GNSS_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Communicates the asynchronous result of a request to the GNSS API, such as
+ * starting a location session via chreGnssLocationSessionStartAsync(). The
+ * requestType field in chreAsyncResult is set to a value from enum
+ * chreGnssRequestType.
+ */
+#define CHRE_EVENT_GNSS_ASYNC_RESULT  CHRE_GNSS_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreGnssLocationEvent
+ *
+ * Represents a location fix provided by the GNSS subsystem.
+ */
+#define CHRE_EVENT_GNSS_LOCATION      CHRE_GNSS_EVENT_ID(1)
+
+/**
+ * nanoappHandleEvent argument: struct chreGnssDataEvent
+ *
+ * Represents a set of GNSS measurements with associated clock data.
+ */
+#define CHRE_EVENT_GNSS_DATA          CHRE_GNSS_EVENT_ID(2)
+
+// NOTE: Do not add new events with ID > 15; only values 0-15 are reserved
+// (see chre/event.h)
+
+// Flags indicating the Accumulated Delta Range's states
+// (ref: GnssAccumulatedDeltaRangeState)
+#define CHRE_GNSS_ADR_STATE_UNKNOWN     UINT16_C(0)
+#define CHRE_GNSS_ADR_STATE_VALID       UINT16_C(1 << 0)
+#define CHRE_GNSS_ADR_STATE_RESET       UINT16_C(1 << 1)
+#define CHRE_GNSS_ADR_STATE_CYCLE_SLIP  UINT16_C(1 << 2)
+
+// Flags to indicate what fields in chreGnssClock are valid (ref: GnssClockFlags)
+#define CHRE_GNSS_CLOCK_HAS_LEAP_SECOND        UINT16_C(1 << 0)
+#define CHRE_GNSS_CLOCK_HAS_TIME_UNCERTAINTY   UINT16_C(1 << 1)
+#define CHRE_GNSS_CLOCK_HAS_FULL_BIAS          UINT16_C(1 << 2)
+#define CHRE_GNSS_CLOCK_HAS_BIAS               UINT16_C(1 << 3)
+#define CHRE_GNSS_CLOCK_HAS_BIAS_UNCERTAINTY   UINT16_C(1 << 4)
+#define CHRE_GNSS_CLOCK_HAS_DRIFT              UINT16_C(1 << 5)
+#define CHRE_GNSS_CLOCK_HAS_DRIFT_UNCERTAINTY  UINT16_C(1 << 6)
+
+// Flags to indicate which values are valid in a GpsLocation
+// (ref: GpsLocationFlags)
+#define CHRE_GPS_LOCATION_HAS_LAT_LONG           UINT16_C(1 << 0)
+#define CHRE_GPS_LOCATION_HAS_ALTITUDE           UINT16_C(1 << 1)
+#define CHRE_GPS_LOCATION_HAS_SPEED              UINT16_C(1 << 2)
+#define CHRE_GPS_LOCATION_HAS_BEARING            UINT16_C(1 << 3)
+#define CHRE_GPS_LOCATION_HAS_ACCURACY           UINT16_C(1 << 4)
+
+//! @since v1.3
+#define CHRE_GPS_LOCATION_HAS_ALTITUDE_ACCURACY  UINT16_C(1 << 5)
+//! @since v1.3
+#define CHRE_GPS_LOCATION_HAS_SPEED_ACCURACY     UINT16_C(1 << 6)
+//! @since v1.3
+#define CHRE_GPS_LOCATION_HAS_BEARING_ACCURACY   UINT16_C(1 << 7)
+
+/**
+ * The maximum number of instances of struct chreGnssMeasurement that may be
+ * included in a single struct chreGnssDataEvent.
+ *
+ * The value of this struct was increased from 64 to 128 in CHRE v1.5. For
+ * nanoapps targeting CHRE v1.4 or lower, the measurement_count will be capped
+ * at 64.
+ */
+#define CHRE_GNSS_MAX_MEASUREMENT  UINT8_C(128)
+#define CHRE_GNSS_MAX_MEASUREMENT_PRE_1_5  UINT8_C(64)
+
+// Flags indicating the GNSS measurement state (ref: GnssMeasurementState)
+#define CHRE_GNSS_MEASUREMENT_STATE_UNKNOWN                UINT16_C(0)
+#define CHRE_GNSS_MEASUREMENT_STATE_CODE_LOCK              UINT16_C(1 << 0)
+#define CHRE_GNSS_MEASUREMENT_STATE_BIT_SYNC               UINT16_C(1 << 1)
+#define CHRE_GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC          UINT16_C(1 << 2)
+#define CHRE_GNSS_MEASUREMENT_STATE_TOW_DECODED            UINT16_C(1 << 3)
+#define CHRE_GNSS_MEASUREMENT_STATE_MSEC_AMBIGUOUS         UINT16_C(1 << 4)
+#define CHRE_GNSS_MEASUREMENT_STATE_SYMBOL_SYNC            UINT16_C(1 << 5)
+#define CHRE_GNSS_MEASUREMENT_STATE_GLO_STRING_SYNC        UINT16_C(1 << 6)
+#define CHRE_GNSS_MEASUREMENT_STATE_GLO_TOD_DECODED        UINT16_C(1 << 7)
+#define CHRE_GNSS_MEASUREMENT_STATE_BDS_D2_BIT_SYNC        UINT16_C(1 << 8)
+#define CHRE_GNSS_MEASUREMENT_STATE_BDS_D2_SUBFRAME_SYNC   UINT16_C(1 << 9)
+#define CHRE_GNSS_MEASUREMENT_STATE_GAL_E1BC_CODE_LOCK     UINT16_C(1 << 10)
+#define CHRE_GNSS_MEASUREMENT_STATE_GAL_E1C_2ND_CODE_LOCK  UINT16_C(1 << 11)
+#define CHRE_GNSS_MEASUREMENT_STATE_GAL_E1B_PAGE_SYNC      UINT16_C(1 << 12)
+#define CHRE_GNSS_MEASUREMENT_STATE_SBAS_SYNC              UINT16_C(1 << 13)
+
+#define CHRE_GNSS_MEASUREMENT_CARRIER_FREQUENCY_UNKNOWN    0.f
+
+/**
+ * Indicates a type of request made in this API. Used to populate the resultType
+ * field of struct chreAsyncResult sent with CHRE_EVENT_GNSS_ASYNC_RESULT.
+ */
+enum chreGnssRequestType {
+    CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START    = 1,
+    CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_STOP     = 2,
+    CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_START = 3,
+    CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_STOP  = 4,
+};
+
+/**
+ * Constellation type associated with an SV
+ */
+enum chreGnssConstellationType {
+    CHRE_GNSS_CONSTELLATION_UNKNOWN = 0,
+    CHRE_GNSS_CONSTELLATION_GPS     = 1,
+    CHRE_GNSS_CONSTELLATION_SBAS    = 2,
+    CHRE_GNSS_CONSTELLATION_GLONASS = 3,
+    CHRE_GNSS_CONSTELLATION_QZSS    = 4,
+    CHRE_GNSS_CONSTELLATION_BEIDOU  = 5,
+    CHRE_GNSS_CONSTELLATION_GALILEO = 6,
+};
+
+/**
+ * Enumeration of available values for the chreGnssMeasurement multipath indicator
+ */
+enum chreGnssMultipathIndicator {
+    //! The indicator is not available or unknown
+    CHRE_GNSS_MULTIPATH_INDICATOR_UNKNOWN     = 0,
+    //! The measurement is indicated to be affected by multipath
+    CHRE_GNSS_MULTIPATH_INDICATOR_PRESENT     = 1,
+    //! The measurement is indicated to be not affected by multipath
+    CHRE_GNSS_MULTIPATH_INDICATOR_NOT_PRESENT = 2,
+};
+
+/**
+ * Represents an estimate of the GNSS clock time (see the Android GPS HAL for
+ * more detailed information)
+ */
+struct chreGnssClock {
+    //! The GNSS receiver hardware clock value in nanoseconds, including
+    //! uncertainty
+    int64_t time_ns;
+
+    //! The difference between hardware clock inside GNSS receiver and the
+    //! estimated GNSS time in nanoseconds; contains bias uncertainty
+    int64_t full_bias_ns;
+
+    //! Sub-nanosecond bias, adds to full_bias_ns
+    float bias_ns;
+
+    //! The clock's drift in nanoseconds per second
+    float drift_nsps;
+
+    //! 1-sigma uncertainty associated with the clock's bias in nanoseconds
+    float bias_uncertainty_ns;
+
+    //! 1-sigma uncertainty associated with the clock's drift in nanoseconds
+    //! per second
+    float drift_uncertainty_nsps;
+
+    //! While this number stays the same, timeNs should flow continuously
+    uint32_t hw_clock_discontinuity_count;
+
+    //! A set of flags indicating the validity of the fields in this data
+    //! structure (see GNSS_CLOCK_HAS_*)
+    uint16_t flags;
+
+    //! Reserved for future use; set to 0
+    uint8_t reserved[2];
+};
+
+/**
+ * Represents a GNSS measurement; contains raw and computed information (see the
+ * Android GPS HAL for more detailed information)
+ */
+struct chreGnssMeasurement {
+    //! Hardware time offset from time_ns for this measurement, in nanoseconds
+    int64_t time_offset_ns;
+
+    //! Accumulated delta range since the last channel reset in micro-meters
+    int64_t accumulated_delta_range_um;
+
+    //! Received GNSS satellite time at the time of measurement, in nanoseconds
+    int64_t received_sv_time_in_ns;
+
+    //! 1-sigma uncertainty of received GNSS satellite time, in nanoseconds
+    int64_t received_sv_time_uncertainty_in_ns;
+
+    //! Pseudorange rate at the timestamp in meters per second (uncorrected)
+    float pseudorange_rate_mps;
+
+    //! 1-sigma uncertainty of pseudorange rate in meters per second
+    float pseudorange_rate_uncertainty_mps;
+
+    //! 1-sigma uncertainty of the accumulated delta range in meters
+    float accumulated_delta_range_uncertainty_m;
+
+    //! Carrier-to-noise density in dB-Hz, in the range of [0, 63]
+    float c_n0_dbhz;
+
+    //! Signal to noise ratio (dB), power above observed noise at correlators
+    float snr_db;
+
+    //! Satellite sync state flags (GNSS_MEASUREMENT_STATE_*) - sets modulus for
+    //! received_sv_time_in_ns
+    uint16_t state;
+
+    //! Set of ADR state flags (GNSS_ADR_STATE_*)
+    uint16_t accumulated_delta_range_state;
+
+    //! Satellite vehicle ID number
+    int16_t svid;
+
+    //! Constellation of the given satellite vehicle
+    //! @see #chreGnssConstellationType
+    uint8_t constellation;
+
+    //! @see #chreGnssMultipathIndicator
+    uint8_t multipath_indicator;
+
+    //! Carrier frequency of the signal tracked in Hz.
+    //! For example, it can be the GPS central frequency for L1 = 1575.45 MHz,
+    //! or L2 = 1227.60 MHz, L5 = 1176.45 MHz, various GLO channels, etc.
+    //!
+    //! Set to CHRE_GNSS_MEASUREMENT_CARRIER_FREQUENCY_UNKNOWN if not reported.
+    //!
+    //! For an L1, L5 receiver tracking a satellite on L1 and L5 at the same
+    //! time, two chreGnssMeasurement structs must be reported for this same
+    //! satellite, in one of the measurement structs, all the values related to
+    //! L1 must be filled, and in the other all of the values related to L5
+    //! must be filled.
+    //! @since v1.4
+    float carrier_frequency_hz;
+};
+
+/**
+ * Data structure sent with events associated with CHRE_EVENT_GNSS_DATA, enabled
+ * via chreGnssMeasurementSessionStartAsync()
+ */
+struct chreGnssDataEvent {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that it only sends the client the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! Number of chreGnssMeasurement entries included in this event. Must be in
+    //! the range [0, CHRE_GNSS_MAX_MEASUREMENT]
+    uint8_t measurement_count;
+
+    //! Reserved for future use; set to 0
+    uint8_t reserved[6];
+
+    struct chreGnssClock clock;
+
+    //! Pointer to an array containing measurement_count measurements
+    const struct chreGnssMeasurement *measurements;
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_GNSS_LOCATION, enabled via
+ * chreGnssLocationSessionStartAsync(). This is modeled after GpsLocation in the
+ * GPS HAL, but does not use the double data type.
+ */
+struct chreGnssLocationEvent {
+    //! UTC timestamp for location fix in milliseconds since January 1, 1970
+    uint64_t timestamp;
+
+    //! Fixed point latitude, degrees times 10^7 (roughly centimeter resolution)
+    int32_t latitude_deg_e7;
+
+    //! Fixed point longitude, degrees times 10^7 (roughly centimeter
+    //! resolution)
+    int32_t longitude_deg_e7;
+
+    //! Altitude in meters above the WGS 84 reference ellipsoid
+    float altitude;
+
+    //! Horizontal speed in meters per second
+    float speed;
+
+    //! Clockwise angle between north and current heading, in degrees; range
+    //! [0, 360)
+    float bearing;
+
+    //! Expected horizontal accuracy in meters such that a circle with a radius
+    //! of length 'accuracy' from the latitude and longitude has a 68%
+    //! probability of including the true location.
+    float accuracy;
+
+    //! A set of flags indicating which fields in this structure are valid.
+    //! If any fields are not available, the flag must not be set and the field
+    //! must be initialized to 0.
+    //! @see #GpsLocationFlags
+    uint16_t flags;
+
+    //! Reserved for future use; set to 0
+    //! @since v1.3
+    uint8_t reserved[2];
+
+    //! Expected vertical accuracy in meters such that a range of
+    //! 2 * altitude_accuracy centered around altitude has a 68% probability of
+    //! including the true altitude.
+    //! @since v1.3
+    float altitude_accuracy;
+
+    //! Expected speed accuracy in meters per second such that a range of
+    //! 2 * speed_accuracy centered around speed has a 68% probability of
+    //! including the true speed.
+    //! @since v1.3
+    float speed_accuracy;
+
+    //! Expected bearing accuracy in degrees such that a range of
+    //! 2 * bearing_accuracy centered around bearing has a 68% probability of
+    //! including the true bearing.
+    //! @since v1.3
+    float bearing_accuracy;
+};
+
+
+/**
+ * Retrieves a set of flags indicating the GNSS features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the Nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_GNSS_CAPABILITIES_* flags set
+ *
+ * @since v1.1
+ */
+uint32_t chreGnssGetCapabilities(void);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_GNSS somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following GNSS APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to GNSS data by adding metadata
+ * to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_GNSS) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Initiates a GNSS positioning session, or changes the requested interval of an
+ * existing session. If starting or modifying the session was successful, then
+ * the GNSS engine will work on determining the device's position.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details. If the "Location" setting is disabled at the Android level,
+ * the CHRE implementation is expected to return a result with
+ * CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_LOCATION flag set, then this method will return false.
+ *
+ * @param minIntervalMs The desired minimum interval between location fixes
+ *        delivered to the client via CHRE_EVENT_GNSS_LOCATION, in milliseconds.
+ *        The requesting client must allow for fixes to be delivered at shorter
+ *        or longer interval than requested. For example, adverse RF conditions
+ *        may result in fixes arriving at a longer interval, etc.
+ * @param minTimeToNextFixMs The desired minimum time to the next location fix.
+ *        If this is 0, the GNSS engine should start working on the next fix
+ *        immediately. If greater than 0, the GNSS engine should not spend
+ *        measurable power to produce a location fix until this amount of time
+ *        has elapsed.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssLocationSessionStartAsync(uint32_t minIntervalMs,
+                                       uint32_t minTimeToNextFixMs,
+                                       const void *cookie);
+
+/**
+ * Terminates an existing GNSS positioning session. If no positioning session
+ * is active at the time of this request, it is treated as if an active session
+ * was successfully ended.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details.
+ *
+ * After CHRE_EVENT_GNSS_ASYNC_RESULT is delivered to the client, no more
+ * CHRE_EVENT_GNSS_LOCATION events will be delievered until a new location
+ * session is started.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_LOCATION flag set, then this method will return false.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssLocationSessionStopAsync(const void *cookie);
+
+/**
+ * Initiates a request to receive raw GNSS measurements. A GNSS measurement
+ * session can exist independently of location sessions. In other words, a
+ * Nanoapp is able to receive measurements at its requested interval both with
+ * and without an active location session.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details. If the "Location" setting is disabled at the Android level,
+ * the CHRE implementation is expected to return a result with
+ * CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_MEASUREMENTS flag set, then this method will return
+ * false.
+ *
+ * @param minIntervalMs The desired minimum interval between measurement reports
+ *        delivered via CHRE_EVENT_GNSS_DATA. When requested at 1000ms or
+ *        faster, and GNSS measurements are tracked, device should report
+ *        measurements as fast as requested, and shall report no slower than
+ *        once every 1000ms, on average.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssMeasurementSessionStartAsync(uint32_t minIntervalMs,
+                                          const void *cookie);
+
+/**
+ * Terminates an existing raw GNSS measurement session. If no measurement
+ * session is active at the time of this request, it is treated as if an active
+ * session was successfully ended.
+ *
+ * This result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_GNSS_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_MEASUREMENTS flag set, then this method will return
+ * false.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires GNSS permission
+ */
+bool chreGnssMeasurementSessionStopAsync(const void *cookie);
+
+/**
+ * Controls whether this nanoapp will passively receive GNSS-based location
+ * fixes produced as a result of location sessions initiated by other entities.
+ * This function allows a nanoapp to opportunistically receive location fixes
+ * via CHRE_EVENT_GNSS_LOCATION events without imposing additional power cost,
+ * though with no guarantees as to when or how often those events will arrive.
+ * There will be no duplication of events if a passive location listener and
+ * location session are enabled in parallel.
+ *
+ * Enabling passive location listening is not required to receive events for an
+ * active location session started via chreGnssLocationSessionStartAsync(). This
+ * setting is independent of the active location session, so modifying one does
+ * not have an effect on the other.
+ *
+ * If chreGnssGetCapabilities() returns a value that does not have the
+ * CHRE_GNSS_CAPABILITIES_LOCATION flag set or the value returned by
+ * chreGetApiVersion() is less than CHRE_API_VERSION_1_2, then this method will
+ * return false.
+ *
+ * If chreGnssGetCapabilities() includes
+ * CHRE_GNSS_CAPABILITIES_GNSS_ENGINE_BASED_PASSIVE_LISTENER, the passive
+ * registration is recorded at the GNSS engine level, so events include fixes
+ * requested by the applications processor and potentially other non-CHRE
+ * clients. If this flag is not set, then only fixes requested by other nanoapps
+ * within CHRE are provided.
+ *
+ * @param enable true to receive opportunistic location fixes, false to disable
+ *
+ * @return true if the configuration was processed successfully, false on error
+ *     or if this feature is not supported
+ *
+ * @since v1.2
+ * @note Requires GNSS permission
+ */
+bool chreGnssConfigurePassiveLocationListener(bool enable);
+
+#else  /* defined(CHRE_NANOAPP_USES_GNSS) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_GNSS_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_GNSS must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreGnssLocationSessionStartAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssLocationSessionStartAsync")
+#define chreGnssLocationSessionStopAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssLocationSessionStopAsync")
+#define chreGnssMeasurementSessionStartAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssMeasurementSessionStartAsync")
+#define chreGnssMeasurementSessionStopAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssMeasurementSessionStopAsync")
+#define chreGnssConfigurePassiveLocationListener(...) \
+    CHRE_BUILD_ERROR(CHRE_GNSS_PERM_ERROR_STRING \
+                     "chreGnssConfigurePassiveLocationListener")
+#endif  /* defined(CHRE_NANOAPP_USES_GNSS) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_GNSS_H_ */
diff --git a/chre_api/legacy/v1_7/chre/nanoapp.h b/chre_api/legacy/v1_7/chre/nanoapp.h
new file mode 100644
index 0000000..da199ee
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/nanoapp.h
@@ -0,0 +1,93 @@
+/*
+ * 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_NANOAPP_H_
+#define _CHRE_NANOAPP_H_
+
+/**
+ * @file
+ * Methods in the Context Hub Runtime Environment which must be implemented
+ * by the nanoapp.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Method invoked by the CHRE when loading the nanoapp.
+ *
+ * Every CHRE method is legal to call from this method.
+ *
+ * @return  'true' if the nanoapp successfully started.  'false' if the nanoapp
+ *     failed to properly initialize itself (for example, could not obtain
+ *     sufficient memory from the heap).  If this method returns 'false', the
+ *     nanoapp will be unloaded by the CHRE (and nanoappEnd will
+ *     _not_ be invoked in that case).
+ * @see nanoappEnd
+ */
+bool nanoappStart(void);
+
+/**
+ * Method invoked by the CHRE when there is an event for this nanoapp.
+ *
+ * Every CHRE method is legal to call from this method.
+ *
+ * @param senderInstanceId  The Instance ID for the source of this event.
+ *     Note that this may be CHRE_INSTANCE_ID, indicating that the event
+ *     was generated by the CHRE.
+ * @param eventType  The event type.  This might be one of the CHRE_EVENT_*
+ *     types defined in this API.  But it might also be a user-defined event.
+ * @param eventData  The associated data, if any, for this specific type of
+ *     event.  From the nanoapp's perspective, this eventData's lifetime ends
+ *     when this method returns, and thus any data the nanoapp wishes to
+ *     retain must be copied.  Note that interpretation of event data is
+ *     given by the event type, and for some events may not be a valid
+ *     pointer.  See documentation of the specific CHRE_EVENT_* types for how to
+ *     interpret this data for those.  Note that for user events, you will
+ *     need to establish what this data means.
+ */
+void nanoappHandleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                        const void *eventData);
+
+/**
+ * Method invoked by the CHRE when unloading the nanoapp.
+ *
+ * It is not valid to attempt to send events or messages, or to invoke functions
+ * which will generate events to this app, within the nanoapp implementation of
+ * this function.  That means it is illegal for the nanoapp invoke any of the
+ * following:
+ *
+ * - chreSendEvent()
+ * - chreSendMessageToHost()
+ * - chreSensorConfigure()
+ * - chreSensorConfigureModeOnly()
+ * - chreTimerSet()
+ * - etc.
+ *
+ * @see nanoappStart
+ */
+void nanoappEnd(void);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_NANOAPP_H_ */
diff --git a/chre_api/legacy/v1_7/chre/re.h b/chre_api/legacy/v1_7/chre/re.h
new file mode 100644
index 0000000..20a69b6
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/re.h
@@ -0,0 +1,437 @@
+/*
+ * 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_RE_H_
+#define _CHRE_RE_H_
+
+/**
+ * @file
+ * Some of the core Runtime Environment utilities of the Context Hub
+ * Runtime Environment.
+ *
+ * This includes functions for memory allocation, logging, and timers.
+ */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <chre/toolchain.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The instance ID for the CHRE.
+ *
+ * This ID is used to identify events generated by the CHRE (as
+ * opposed to events generated by another nanoapp).
+ */
+#define CHRE_INSTANCE_ID  UINT32_C(0)
+
+/**
+ * A timer ID representing an invalid timer.
+ *
+ * This valid is returned by chreTimerSet() if a timer cannot be
+ * started.
+ */
+#define CHRE_TIMER_INVALID  UINT32_C(-1)
+
+
+/**
+ * The maximum size, in characters including null terminator, guaranteed for
+ * logging debug data with one call of chreDebugDumpLog() without getting
+ * truncated.
+ *
+ * @see chreDebugDumpLog
+ * @since v1.4
+ */
+#define CHRE_DEBUG_DUMP_MINIMUM_MAX_SIZE 1000
+
+/**
+ * Logging levels used to indicate severity level of logging messages.
+ *
+ * CHRE_LOG_ERROR: Something fatal has happened, i.e. something that will have
+ *     user-visible consequences and won't be recoverable without explicitly
+ *     deleting some data, uninstalling applications, wiping the data
+ *     partitions or reflashing the entire phone (or worse).
+ * CHRE_LOG_WARN: Something that will have user-visible consequences but is
+ *     likely to be recoverable without data loss by performing some explicit
+ *     action, ranging from waiting or restarting an app all the way to
+ *     re-downloading a new version of an application or rebooting the device.
+ * CHRE_LOG_INFO: Something interesting to most people happened, i.e. when a
+ *     situation is detected that is likely to have widespread impact, though
+ *     isn't necessarily an error.
+ * CHRE_LOG_DEBUG: Used to further note what is happening on the device that
+ *     could be relevant to investigate and debug unexpected behaviors. You
+ *     should log only what is needed to gather enough information about what
+ *     is going on about your component.
+ *
+ * There is currently no API to turn on/off logging by level, but we anticipate
+ * adding such in future releases.
+ *
+ * @see chreLog
+ */
+enum chreLogLevel {
+    CHRE_LOG_ERROR,
+    CHRE_LOG_WARN,
+    CHRE_LOG_INFO,
+    CHRE_LOG_DEBUG
+};
+
+
+/**
+ * Get the application ID.
+ *
+ * The application ID is set by the loader of the nanoapp.  This is not
+ * assured to be unique among all nanoapps running in the system.
+ *
+ * @return The application ID.
+ */
+uint64_t chreGetAppId(void);
+
+/**
+ * Get the instance ID.
+ *
+ * The instance ID is the CHRE handle to this nanoapp.  This is assured
+ * to be unique among all nanoapps running in the system, and to be
+ * different from the CHRE_INSTANCE_ID.  This is the ID used to communicate
+ * between nanoapps.
+ *
+ * @return The instance ID
+ */
+uint32_t chreGetInstanceId(void);
+
+/**
+ * A method for logging information about the system.
+ *
+ * The chreLog logging activity alone must not cause host wake-ups. For
+ * example, logs could be buffered in internal memory when the host is asleep,
+ * and delivered when appropriate (e.g. the host wakes up). If done this way,
+ * the internal buffer is recommended to be large enough (at least a few KB), so
+ * that multiple messages can be buffered. When these logs are sent to the host,
+ * they are strongly recommended to be made visible under the tag 'CHRE' in
+ * logcat - a future version of the CHRE API may make this a hard requirement.
+ *
+ * A log entry can have a variety of levels (@see LogLevel).  This function
+ * allows a variable number of arguments, in a printf-style format.
+ *
+ * A nanoapp needs to be able to rely upon consistent printf format
+ * recognition across any platform, and thus we establish formats which
+ * are required to be handled by every CHRE implementation.  Some of the
+ * integral formats may seem obscure, but this API heavily uses types like
+ * uint32_t and uint16_t.  The platform independent macros for those printf
+ * formats, like PRId32 or PRIx16, end up using some of these "obscure"
+ * formats on some platforms, and thus are required.
+ *
+ * For the initial N release, our emphasis is on correctly getting information
+ * into the log, and minimizing the requirements for CHRE implementations
+ * beyond that.  We're not as concerned about how the information is visually
+ * displayed.  As a result, there are a number of format sub-specifiers which
+ * are "OPTIONAL" for the N implementation.  "OPTIONAL" in this context means
+ * that a CHRE implementation is allowed to essentially ignore the specifier,
+ * but it must understand the specifier enough in order to properly skip it.
+ *
+ * For a nanoapp author, an OPTIONAL format means you might not get exactly
+ * what you want on every CHRE implementation, but you will always get
+ * something valid.
+ *
+ * To be clearer, here's an example with the OPTIONAL 0-padding for integers
+ * for different hypothetical CHRE implementations.
+ * Compliant, chose to implement OPTIONAL format:
+ *   chreLog(level, "%04x", 20) ==> "0014"
+ * Compliant, chose not to implement OPTIONAL format:
+ *   chreLog(level, "%04x", 20) ==> "14"
+ * Non-compliant, discarded format because the '0' was assumed to be incorrect:
+ *   chreLog(level, "%04x", 20) ==> ""
+ *
+ * Note that some of the OPTIONAL specifiers will probably become
+ * required in future APIs.
+ *
+ * We also have NOT_SUPPORTED specifiers.  Nanoapp authors should not use any
+ * NOT_SUPPORTED specifiers, as unexpected things could happen on any given
+ * CHRE implementation.  A CHRE implementation is allowed to support this
+ * (for example, when using shared code which already supports this), but
+ * nanoapp authors need to avoid these.
+ *
+ * Unless specifically noted as OPTIONAL or NOT_SUPPORTED, format
+ * (sub-)specifiers listed below are required.
+ *
+ * OPTIONAL format sub-specifiers:
+ * - '-' (left-justify within the given field width)
+ * - '+' (precede the result with a '+' sign if it is positive)
+ * - ' ' (precede the result with a blank space if no sign is going to be
+ *        output)
+ * - '#' (For 'o', 'x' or 'X', precede output with "0", "0x" or "0X",
+ *        respectively.  For floating point, unconditionally output a decimal
+ *        point.)
+ * - '0' (left pad the number with zeroes instead of spaces when <width>
+ *        needs padding)
+ * - <width> (A number representing the minimum number of characters to be
+ *            output, left-padding with blank spaces if needed to meet the
+ *            minimum)
+ * - '.'<precision> (A number which has different meaning depending on context.)
+ *    - Integer context: Minimum number of digits to output, padding with
+ *          leading zeros if needed to meet the minimum.
+ *    - 'f' context: Number of digits to output after the decimal
+ *          point (to the right of it).
+ *    - 's' context: Maximum number of characters to output.
+ *
+ * Integral format specifiers:
+ * - 'd' (signed)
+ * - 'u' (unsigned)
+ * - 'o' (octal)
+ * - 'x' (hexadecimal, lower case)
+ * - 'X' (hexadecimal, upper case)
+ *
+ * Integral format sub-specifiers (as prefixes to an above integral format):
+ * - 'hh' (char)
+ * - 'h' (short)
+ * - 'l' (long)
+ * - 'll' (long long)
+ * - 'z' (size_t)
+ * - 't' (ptrdiff_t)
+ *
+ * Other format specifiers:
+ * - 'f' (floating point)
+ * - 'c' (character)
+ * - 's' (character string, terminated by '\0')
+ * - 'p' (pointer)
+ * - '%' (escaping the percent sign (i.e. "%%" becomes "%"))
+ *
+ * NOT_SUPPORTED specifiers:
+ * - 'n' (output nothing, but fill in a given pointer with the number
+ *        of characters written so far)
+ * - '*' (indicates that the width/precision value comes from one of the
+ *        arguments to the function)
+ * - 'e', 'E' (scientific notation output)
+ * - 'g', 'G' (Shortest floating point representation)
+ *
+ * @param level  The severity level for this message.
+ * @param formatStr  Either the entirety of the message, or a printf-style
+ *     format string of the format documented above.
+ * @param ...  A variable number of arguments necessary for the given
+ *     'formatStr' (there may be no additional arguments for some 'formatStr's).
+ */
+CHRE_PRINTF_ATTR(2, 3)
+void chreLog(enum chreLogLevel level, const char *formatStr, ...);
+
+/**
+ * Get the system time.
+ *
+ * This returns a time in nanoseconds in reference to some arbitrary
+ * time in the past.  This method is only useful for determining timing
+ * between events on the system, and is not useful for determining
+ * any sort of absolute time.
+ *
+ * This value must always increase (and must never roll over).  This
+ * value has no meaning across CHRE reboots.
+ *
+ * @return The system time, in nanoseconds.
+ */
+uint64_t chreGetTime(void);
+
+/**
+ * Retrieves CHRE's current estimated offset between the local CHRE clock
+ * exposed in chreGetTime(), and the host-side clock exposed in the Android API
+ * SystemClock.elapsedRealtimeNanos().  This offset is formed as host time minus
+ * CHRE time, so that it can be added to the value returned by chreGetTime() to
+ * determine the current estimate of the host time.
+ *
+ * A call to this function must not require waking up the host and should return
+ * quickly.
+ *
+ * This function must always return a valid value from the earliest point that
+ * it can be called by a nanoapp.  In other words, it is not valid to return
+ * some fixed/invalid value while waiting for the initial offset estimate to be
+ * determined - this initial offset must be ready before nanoapps are started.
+ *
+ * @return An estimate of the offset between CHRE's time returned in
+ *     chreGetTime() and the time on the host given in the Android API
+ *     SystemClock.elapsedRealtimeNanos(), accurate to within +/- 10
+ *     milliseconds, such that adding this offset to chreGetTime() produces the
+ *     estimated current time on the host.  This value may change over time to
+ *     account for drift, etc., so multiple calls to this API may produce
+ *     different results.
+ *
+ * @since v1.1
+ */
+int64_t chreGetEstimatedHostTimeOffset(void);
+
+/**
+ * Convenience function to retrieve CHRE's estimate of the current time on the
+ * host, corresponding to the Android API SystemClock.elapsedRealtimeNanos().
+ *
+ * @return An estimate of the current time on the host, accurate to within
+ *     +/- 10 milliseconds.  This estimate is *not* guaranteed to be
+ *     monotonically increasing, and may move backwards as a result of receiving
+ *     new information from the host.
+ *
+ * @since v1.1
+ */
+static inline uint64_t chreGetEstimatedHostTime(void) {
+    int64_t offset = chreGetEstimatedHostTimeOffset();
+    uint64_t time = chreGetTime();
+
+    // Just casting time to int64_t and adding the (potentially negative) offset
+    // should be OK under most conditions, but this way avoids issues if
+    // time >= 2^63, which is technically allowed since we don't specify a start
+    // value for chreGetTime(), though one would assume 0 is roughly boot time.
+    if (offset >= 0) {
+        time += (uint64_t) offset;
+    } else {
+        // Assuming chreGetEstimatedHostTimeOffset() is implemented properly,
+        // this will never underflow, because offset = hostTime - chreTime,
+        // and both times are monotonically increasing (e.g. when determining
+        // the offset, if hostTime is 0 and chreTime is 100 we'll have
+        // offset = -100, but chreGetTime() will always return >= 100 after that
+        // point).
+        time -= (uint64_t) (offset * -1);
+    }
+
+    return time;
+}
+
+/**
+ * Set a timer.
+ *
+ * When the timer fires, nanoappHandleEvent will be invoked with
+ * CHRE_EVENT_TIMER and with the given 'cookie'.
+ *
+ * A CHRE implementation is required to provide at least 32
+ * timers.  However, there's no assurance there will be any available
+ * for any given nanoapp (if it's loaded late, etc).
+ *
+ * @param duration  Time, in nanoseconds, before the timer fires.
+ * @param cookie  Argument that will be sent to nanoappHandleEvent upon the
+ *     timer firing.  This is allowed to be NULL and does not need to be
+ *     a valid pointer (assuming the nanoappHandleEvent code is expecting such).
+ * @param oneShot  If true, the timer will just fire once.  If false, the
+ *     timer will continue to refire every 'duration', until this timer is
+ *     canceled (@see chreTimerCancel).
+ *
+ * @return  The timer ID.  If the system is unable to set a timer
+ *     (no more available timers, etc.) then CHRE_TIMER_INVALID will
+ *     be returned.
+ *
+ * @see nanoappHandleEvent
+ */
+uint32_t chreTimerSet(uint64_t duration, const void *cookie, bool oneShot);
+
+/**
+ * Cancel a timer.
+ *
+ * After this method returns, the CHRE assures there will be no more
+ * events sent from this timer, and any enqueued events from this timer
+ * will need to be evicted from the queue by the CHRE.
+ *
+ * @param timerId  A timer ID obtained by this nanoapp via chreTimerSet().
+ * @return true if the timer was cancelled, false otherwise.  We may
+ *     fail to cancel the timer if it's a one shot which (just) fired,
+ *     or if the given timer ID is not owned by the calling app.
+ */
+bool chreTimerCancel(uint32_t timerId);
+
+/**
+ * Terminate this nanoapp.
+ *
+ * This takes effect immediately.
+ *
+ * The CHRE will no longer execute this nanoapp.  The CHRE will not invoke
+ * nanoappEnd(), nor will it call any memory free callbacks in the nanoapp.
+ *
+ * The CHRE will unload/evict this nanoapp's code.
+ *
+ * @param abortCode  A value indicating the reason for aborting.  (Note that
+ *    in this version of the API, there is no way for anyone to access this
+ *    code, but future APIs may expose it.)
+ * @return Never.  This method does not return, as the CHRE stops nanoapp
+ *    execution immediately.
+ */
+void chreAbort(uint32_t abortCode);
+
+/**
+ * Allocate a given number of bytes from the system heap.
+ *
+ * The nanoapp is required to free this memory via chreHeapFree() prior to
+ * the nanoapp ending.
+ *
+ * While the CHRE implementation is required to free up heap resources of
+ * a nanoapp when unloading it, future requirements and tests focused on
+ * nanoapps themselves may check for memory leaks, and will require nanoapps
+ * to properly manage their heap resources.
+ *
+ * @param bytes  The number of bytes requested.
+ * @return  A pointer to 'bytes' contiguous bytes of heap memory, or NULL
+ *     if the allocation could not be performed.  This pointer must be suitably
+ *     aligned for any kind of variable.
+ *
+ * @see chreHeapFree.
+ */
+void *chreHeapAlloc(uint32_t bytes);
+
+/**
+ * Free a heap allocation.
+ *
+ * This allocation must be from a value returned from a chreHeapAlloc() call
+ * made by this nanoapp.  In other words, it is illegal to free memory
+ * allocated by another nanoapp (or the CHRE).
+ *
+ * @param ptr  'ptr' is required to be a value returned from chreHeapAlloc().
+ *     Note that since chreHeapAlloc can return NULL, CHRE
+ *     implementations must safely handle 'ptr' being NULL.
+ *
+ * @see chreHeapAlloc.
+ */
+void chreHeapFree(void *ptr);
+
+/**
+ * Logs the nanoapp's debug data into debug dumps.
+ *
+ * A debug dump is a string representation of information that can be used to
+ * diagnose and debug issues. While chreLog() is useful for logging events as
+ * they happen, the debug dump is a complementary function typically used to
+ * output a snapshot of a nanoapp's state, history, vital statistics, etc. The
+ * CHRE framework is required to pass this information to the debug method in
+ * the Context Hub HAL, where it can be captured in Android bugreports, etc.
+ *
+ * This function must only be called while handling CHRE_DEBUG_DUMP_EVENT,
+ * otherwise it will have no effect. A nanoapp can call this function multiple
+ * times while handling the event. If the resulting formatted string from a
+ * single call to this function is longer than CHRE_DEBUG_DUMP_MINIMUM_MAX_SIZE
+ * characters, it may get truncated.
+ *
+ * @param formatStr A printf-style format string of the format documented in
+ *     chreLog().
+ * @param ... A variable number of arguments necessary for the given 'formatStr'
+ *     (there may be no additional arguments for some 'formatStr's).
+ *
+ * @see chreConfigureDebugDumpEvent
+ * @see chreLog
+ *
+ * @since v1.4
+ */
+CHRE_PRINTF_ATTR(1, 2)
+void chreDebugDumpLog(const char *formatStr, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_RE_H_ */
+
diff --git a/chre_api/legacy/v1_7/chre/sensor.h b/chre_api/legacy/v1_7/chre/sensor.h
new file mode 100644
index 0000000..7ead7ee
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/sensor.h
@@ -0,0 +1,1111 @@
+/*
+ * 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_SENSOR_H_
+#define _CHRE_SENSOR_H_
+
+/**
+ * @file
+ * API dealing with sensor interaction in the Context Hub Runtime
+ * Environment.
+ *
+ * This includes the definition of our sensor types and the ability to
+ * configure them for receiving events.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <chre/common.h>
+#include <chre/event.h>
+#include <chre/sensor_types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * Base value for all of the data events for sensors.
+ *
+ * The value for a data event FOO is
+ * CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_FOO
+ *
+ * This allows for easy mapping, and also explains why there are gaps
+ * in our values since we don't have all possible sensor types assigned.
+ */
+#define CHRE_EVENT_SENSOR_DATA_EVENT_BASE  CHRE_EVENT_SENSOR_FIRST_EVENT
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in SI units (m/s^2) and measure the acceleration applied to
+ * the device.
+ */
+#define CHRE_EVENT_SENSOR_ACCELEROMETER_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_ACCELEROMETER)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorOccurrenceData
+ *
+ * Since this is a one-shot sensor, after this event is delivered to the
+ * nanoapp, the sensor automatically goes into DONE mode.  Sensors of this
+ * type must be configured with a ONE_SHOT mode.
+ */
+#define CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorOccurrenceData
+ *
+ * Since this is a one-shot sensor, after this event is delivered to the
+ * nanoapp, the sensor automatically goes into DONE mode.  Sensors of this
+ * type must be configured with a ONE_SHOT mode.
+ */
+#define CHRE_EVENT_SENSOR_STATIONARY_DETECT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_STATIONARY_DETECT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_GYROSCOPE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GYROSCOPE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in micro-Tesla (uT) and measure the geomagnetic
+ * field in the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'pressure' field within 'readings'.
+ * This value is in hectopascals (hPa).
+ */
+#define CHRE_EVENT_SENSOR_PRESSURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_PRESSURE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'light' field within 'readings'.
+ * This value is in SI lux units.
+ */
+#define CHRE_EVENT_SENSOR_LIGHT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_LIGHT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorByteData
+ *
+ * The data is interpreted from the following fields in 'readings':
+ * o 'isNear': If set to 1, we are nearby (on the order of centimeters);
+ *       if set to 0, we are far. The meaning of near/far in this field must be
+ *       consistent with the Android definition.
+ * o 'invalid': If set to 1, this is not a valid reading of this data.
+ *       As of CHRE API v1.2, this field is deprecated and must always be set to
+ *       0.  If an invalid reading is generated by the sensor hardware, it must
+ *       be dropped and not delivered to any nanoapp.
+ *
+ * In prior versions of the CHRE API, there can be an invalid event generated
+ * upon configuring this sensor.  Thus, the 'invalid' field must be checked on
+ * the first event before interpreting 'isNear'.
+ */
+#define CHRE_EVENT_SENSOR_PROXIMITY_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_PROXIMITY)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorOccurrenceData
+ *
+ * This data is generated every time a step is taken by the user.
+ *
+ * This is backed by the same algorithm that feeds Android's
+ * SENSOR_TYPE_STEP_DETECTOR, and therefore sacrifices some accuracy to target
+ * an update latency of under 2 seconds.
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_STEP_DETECT_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_STEP_DETECT)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorUint64Data
+ *
+ * The value of the data is the cumulative number of steps taken by the user
+ * since the last reboot while the sensor is active. This data is generated
+ * every time a step is taken by the user.
+ *
+ * This is backed by the same algorithm that feeds Android's
+ * SENSOR_TYPE_STEP_COUNTER, and therefore targets high accuracy with under
+ * 10 seconds of update latency.
+ *
+ * @since v1.5
+ */
+#define CHRE_EVENT_SENSOR_STEP_COUNTER_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_STEP_COUNTER)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The value of the data is the measured hinge angle between 0 and 360 degrees
+ * inclusive.
+ *
+ * This is backed by the same algorithm that feeds Android's
+ * SENSOR_TYPE_HINGE_ANGLE.
+ *
+ * @since v1.5
+ */
+#define CHRE_EVENT_SENSOR_HINGE_ANGLE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_HINGE_ANGLE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in SI units (m/s^2) and measure the acceleration applied to
+ * the device.
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x', 'y', and 'z' fields within
+ * 'readings', or by the 3D array 'v' (v[0] == x; v[1] == y; v[2] == z).
+ *
+ * All values are in micro-Tesla (uT) and measure the geomagnetic
+ * field in the X, Y and Z axis.
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'temperature' field within 'readings'.
+ * This value is in degrees Celsius.
+ */
+#define CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'temperature' field within 'readings'.
+ * This value is in degrees Celsius.
+ */
+#define CHRE_EVENT_SENSOR_GYROSCOPE_TEMPERATURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFloatData
+ *
+ * The data can be interpreted using the 'temperature' field within 'readings'.
+ * This value is in degrees Celsius.
+ */
+#define CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA \
+    (CHRE_EVENT_SENSOR_DATA_EVENT_BASE + CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE)
+
+/**
+ * First value for sensor events which are not data from the sensor.
+ *
+ * Unlike the data event values, these other event values don't have any
+ * mapping to sensor types.
+ */
+#define CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE \
+    (CHRE_EVENT_SENSOR_FIRST_EVENT + 0x0100)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorSamplingStatusEvent
+ *
+ * Indicates that the interval and/or the latency which this sensor is
+ * sampling at has changed.
+ */
+#define CHRE_EVENT_SENSOR_SAMPLING_CHANGE \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 0)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x_bias', 'y_bias', and 'z_bias'
+ * field within 'readings', or by the 3D array 'bias' (bias[0] == x_bias;
+ * bias[1] == y_bias; bias[2] == z_bias). Bias is subtracted from uncalibrated
+ * data to generate calibrated data.
+ *
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis.
+ *
+ * If bias delivery is supported, this event is generated by default when
+ * chreSensorConfigure is called to enable for the sensor of type
+ * CHRE_SENSOR_TYPE_GYROSCOPE, or if bias delivery is explicitly enabled
+ * through chreSensorConfigureBiasEvents() for the sensor.
+ */
+#define CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 1)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x_bias', 'y_bias', and 'z_bias'
+ * field within 'readings', or by the 3D array 'bias' (bias[0] == x_bias;
+ * bias[1] == y_bias; bias[2] == z_bias). Bias is subtracted from uncalibrated
+ * data to generate calibrated data.
+ *
+ * All values are in micro-Tesla (uT) and measure the geomagnetic
+ * field in the X, Y and Z axis.
+ *
+ * If bias delivery is supported, this event is generated by default when
+ * chreSensorConfigure is called to enable for the sensor of type
+ * CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD, or if bias delivery is explicitly enabled
+ * through chreSensorConfigureBiasEvents() for the sensor.
+ */
+#define CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 2)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data can be interpreted using the 'x_bias', 'y_bias', and 'z_bias'
+ * field within 'readings', or by the 3D array 'bias' (bias[0] == x_bias;
+ * bias[1] == y_bias; bias[2] == z_bias). Bias is subtracted from uncalibrated
+ * data to generate calibrated data.
+ *
+ * All values are in SI units (m/s^2) and measure the acceleration applied to
+ * the device.
+ *
+ * If bias delivery is supported, this event is generated by default when
+ * chreSensorConfigure is called to enable for the sensor of type
+ * CHRE_SENSOR_TYPE_ACCELEROMETER, or if bias delivery is explicitly enabled
+ * through chreSensorConfigureBiasEvents() for the sensor.
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 3)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorFlushCompleteEvent
+ *
+ * An event indicating that a flush request made by chreSensorFlushAsync has
+ * completed.
+ *
+ * @see chreSensorFlushAsync
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_FLUSH_COMPLETE \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 4)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data of this event is the same as that of
+ * CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO, except the sensorHandle field of
+ * chreSensorDataHeader contains the handle of the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE.
+ *
+ * This event is only generated if the bias reporting is explicitly enabled
+ * for a nanoapp through chreSensorConfigureBiasEvents() for the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE.
+ *
+ * @see CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 5)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data of this event is the same as that of
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO, except the sensorHandle field
+ * of chreSensorDataHeader contains the handle of the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD.
+ *
+ * This event is only generated if the bias reporting is explicitly enabled
+ * for a nanoapp through chreSensorConfigureBiasEvents() for the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD.
+ *
+ * @see CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 6)
+
+/**
+ * nanoappHandleEvent argument: struct chreSensorThreeAxisData
+ *
+ * The data of this event is the same as that of
+ * CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO, except the sensorHandle field
+ * of chreSensorDataHeader contains the handle of the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER.
+ *
+ * This event is only generated if the bias reporting is explicitly enabled
+ * for a nanoapp through chreSensorConfigureBiasEvents for the sensor of type
+ * CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER.
+ *
+ * @see CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO
+ *
+ * @since v1.3
+ */
+#define CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_BIAS_INFO \
+    (CHRE_EVENT_SENSOR_OTHER_EVENTS_BASE + 7)
+
+#if CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_BIAS_INFO > \
+    CHRE_EVENT_SENSOR_LAST_EVENT
+#error Too many sensor events.
+#endif
+
+/**
+ * Value indicating we want the smallest possible latency for a sensor.
+ *
+ * This literally translates to 0 nanoseconds for the chreSensorConfigure()
+ * argument.  While we won't get exactly 0 nanoseconds, the CHRE will
+ * queue up this event As Soon As Possible.
+ */
+#define CHRE_SENSOR_LATENCY_ASAP  UINT64_C(0)
+
+/**
+ * Special value indicating non-importance, or non-applicability of the sampling
+ * interval.
+ *
+ * @see chreSensorConfigure
+ * @see chreSensorSamplingStatus
+ */
+#define CHRE_SENSOR_INTERVAL_DEFAULT  UINT64_C(-1)
+
+/**
+ * Special value indicating non-importance of the latency.
+ *
+ * @see chreSensorConfigure
+ * @see chreSensorSamplingStatus
+ */
+#define CHRE_SENSOR_LATENCY_DEFAULT  UINT64_C(-1)
+
+/**
+ * A sensor index value indicating that it is the default sensor.
+ *
+ * @see chreSensorFind
+ */
+#define CHRE_SENSOR_INDEX_DEFAULT  UINT8_C(0)
+
+/**
+ * Special value indicating non-importance of the batch interval.
+ *
+ * @see chreSensorConfigureWithBatchInterval
+ */
+#define CHRE_SENSOR_BATCH_INTERVAL_DEFAULT  UINT64_C(-1)
+
+// This is used to define elements of enum chreSensorConfigureMode.
+#define CHRE_SENSOR_CONFIGURE_RAW_POWER_ON           (1 << 0)
+
+// This is used to define elements of enum chreSensorConfigureMode.
+#define CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS  (1 << 1)
+
+// This is used to define elements of enum chreSensorConfigureMode.
+#define CHRE_SENSOR_CONFIGURE_RAW_REPORT_ONE_SHOT    (2 << 1)
+
+/**
+ * The maximum amount of time allowed to elapse between the call to
+ * chreSensorFlushAsync() and when CHRE_EVENT_SENSOR_FLUSH_COMPLETE is delivered
+ * to the nanoapp on a successful flush.
+ */
+#define CHRE_SENSOR_FLUSH_COMPLETE_TIMEOUT_NS  (5 * CHRE_NSEC_PER_SEC)
+
+/**
+ * Modes we can configure a sensor to use.
+ *
+ * Our mode will affect not only how/if we receive events, but
+ * also whether or not the sensor will be powered on our behalf.
+ *
+ * @see chreSensorConfigure
+ */
+enum chreSensorConfigureMode {
+    /**
+     * Get events from the sensor.
+     *
+     * Power: Turn on if not already on.
+     * Reporting: Continuous.  Send each new event as it comes (subject to
+     *     batching and latency).
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS =
+        (CHRE_SENSOR_CONFIGURE_RAW_POWER_ON |
+         CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS),
+
+    /**
+     * Get a single event from the sensor and then become DONE.
+     *
+     * Once the event is sent, the sensor automatically
+     * changes to CHRE_SENSOR_CONFIGURE_MODE_DONE mode.
+     *
+     * Power: Turn on if not already on.
+     * Reporting: One shot.  Send the next event and then be DONE.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_ONE_SHOT =
+        (CHRE_SENSOR_CONFIGURE_RAW_POWER_ON |
+         CHRE_SENSOR_CONFIGURE_RAW_REPORT_ONE_SHOT),
+
+    /**
+     * Get events from a sensor that are generated for any client in the system.
+     *
+     * This is considered passive because the sensor will not be powered on for
+     * the sake of our nanoapp.  If and only if another client in the system has
+     * requested this sensor power on will we get events.
+     *
+     * This can be useful for something which is interested in seeing data, but
+     * not interested enough to be responsible for powering on the sensor.
+     *
+     * Power: Do not power the sensor on our behalf.
+     * Reporting: Continuous.  Send each event as it comes.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_CONTINUOUS =
+        CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS,
+
+    /**
+     * Get a single event from a sensor that is generated for any client in the
+     * system.
+     *
+     * See CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_CONTINUOUS for more details on
+     * what the "passive" means.
+     *
+     * Power: Do not power the sensor on our behalf.
+     * Reporting: One shot.  Send only the next event and then be DONE.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_ONE_SHOT =
+        CHRE_SENSOR_CONFIGURE_RAW_REPORT_ONE_SHOT,
+
+    /**
+     * Indicate we are done using this sensor and no longer interested in it.
+     *
+     * See chreSensorConfigure for more details on expressing interest or
+     * lack of interest in a sensor.
+     *
+     * Power: Do not power the sensor on our behalf.
+     * Reporting: None.
+     */
+    CHRE_SENSOR_CONFIGURE_MODE_DONE = 0,
+};
+
+/**
+ * A structure containing information about a Sensor.
+ *
+ * See documentation of individual fields below.
+ */
+struct chreSensorInfo {
+    /**
+     * The name of the sensor.
+     *
+     * A text name, useful for logging/debugging, describing the Sensor.  This
+     * is not assured to be unique (i.e. there could be multiple sensors with
+     * the name "Temperature").
+     *
+     * CHRE implementations may not set this as NULL.  An empty
+     * string, while discouraged, is legal.
+     */
+    const char *sensorName;
+
+    /**
+     * One of the CHRE_SENSOR_TYPE_* defines above.
+     */
+    uint8_t sensorType;
+
+    /**
+     * Flag indicating if this sensor is on-change.
+     *
+     * An on-change sensor only generates events when underlying state
+     * changes.  This has the same meaning as on-change does in the Android
+     * Sensors HAL.  See sensors.h for much more details.
+     *
+     * A value of 1 indicates this is on-change.  0 indicates this is not
+     * on-change.
+     */
+    uint8_t isOnChange          : 1;
+
+    /**
+     * Flag indicating if this sensor is one-shot.
+     *
+     * A one-shot sensor only triggers a single event, and then automatically
+     * disables itself.
+     *
+     * A value of 1 indicates this is one-shot.  0 indicates this is not
+     * on-change.
+     */
+    uint8_t isOneShot           : 1;
+
+    /**
+     * Flag indicating if this sensor supports reporting bias info events.
+     *
+     * This field will be set to 0 when running on CHRE API versions prior to
+     * v1.3, but must be ignored (i.e. does not mean bias info event is not
+     * supported).
+     *
+     * @see chreSensorConfigureBiasEvents
+     *
+     * @since v1.3
+     */
+    uint8_t reportsBiasEvents   : 1;
+
+    /**
+     * Flag indicating if this sensor supports passive mode requests.
+     *
+     * This field will be set to 0 when running on CHRE API versions prior to
+     * v1.4, and must be ignored (i.e. does not mean passive mode requests are
+     * not supported).
+     *
+     * @see chreSensorConfigure
+     *
+     * @since v1.4
+     */
+    uint8_t supportsPassiveMode : 1;
+
+    uint8_t unusedFlags         : 4;
+
+    /**
+     * The minimum sampling interval supported by this sensor, in nanoseconds.
+     *
+     * Requests to chreSensorConfigure with a lower interval than this will
+     * fail.  If the sampling interval is not applicable to this sensor, this
+     * will be set to CHRE_SENSOR_INTERVAL_DEFAULT.
+     *
+     * This field will be set to 0 when running on CHRE API versions prior to
+     * v1.1, indicating that the minimum interval is not known.
+     *
+     * @since v1.1
+     */
+    uint64_t minInterval;
+
+    /**
+     * Uniquely identifies the sensor for a given type. A value of 0 indicates
+     * that this is the "default" sensor, which is returned by
+     * chreSensorFindDefault().
+     *
+     * The sensor index of a given type must be stable across boots (i.e. must
+     * not change), and a different sensor of the same type must have different
+     * sensor index values, and the set of sensorIndex values for a given sensor
+     * type must be continuguous.
+     *
+     * @since v1.5
+     */
+    uint8_t sensorIndex;
+};
+
+/**
+ * The status of a sensor's sampling configuration.
+ */
+struct chreSensorSamplingStatus {
+    /**
+     * The interval, in nanoseconds, at which the sensor is now sampling.
+     *
+     * If this is CHRE_SENSOR_INTERVAL_DEFAULT, then a sampling interval
+     * isn't meaningful for this sensor.
+     *
+     * Note that if 'enabled' is false, this value is not meaningful.
+     */
+    uint64_t interval;
+
+    /**
+     * The latency, in nanoseconds, at which the senor is now reporting.
+     *
+     * If this is CHRE_SENSOR_LATENCY_DEFAULT, then a latency
+     * isn't meaningful for this sensor.
+     *
+     * The effective batch interval can be derived from this value by
+     * adding the current sampling interval.
+     *
+     * Note that if 'enabled' is false, this value is not meaningful.
+     */
+    uint64_t latency;
+
+    /**
+     * True if the sensor is actively powered and sampling; false otherwise.
+     */
+    bool enabled;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_SENSOR_SAMPLING_CHANGE.
+ *
+ * Note that only at least one of 'interval' or 'latency' must be
+ * different than it was prior to this event.  Thus, one of these
+ * fields may be (but doesn't need to be) the same as before.
+ */
+struct chreSensorSamplingStatusEvent {
+    /**
+     * The handle of the sensor which has experienced a change in sampling.
+     */
+    uint32_t sensorHandle;
+
+    /**
+     * The new sampling status.
+     *
+     * At least one of the field in this struct will be different from
+     * the previous sampling status event.
+     */
+    struct chreSensorSamplingStatus status;
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE_EVENT_SENSOR_FLUSH_COMPLETE.
+ *
+ * @see chreSensorFlushAsync
+ *
+ * @since v1.3
+ */
+struct chreSensorFlushCompleteEvent {
+    /**
+     * The handle of the sensor which a flush was completed.
+     */
+    uint32_t sensorHandle;
+
+    /**
+     * Populated with a value from enum {@link #chreError}, indicating whether
+     * the flush failed, and if so, provides the cause of the failure.
+     */
+    uint8_t errorCode;
+
+    /**
+     * Reserved for future use. Set to 0.
+     */
+    uint8_t reserved[3];
+
+    /**
+     * Set to the cookie parameter given to chreSensorFlushAsync.
+     */
+    const void *cookie;
+};
+
+/**
+ * Find the default sensor for a given sensor type.
+ *
+ * @param sensorType One of the CHRE_SENSOR_TYPE_* constants.
+ * @param handle  If a sensor is found, then the memory will be filled with
+ *     the value for the sensor's handle.  This argument must be non-NULL.
+ * @return true if a sensor was found, false otherwise.
+ */
+bool chreSensorFindDefault(uint8_t sensorType, uint32_t *handle);
+
+/**
+ * Finds a sensor of a given index and sensor type.
+ *
+ * For CHRE implementations that support multiple sensors of the same sensor
+ * type, this method can be used to get the non-default sensor(s). The default
+ * sensor, as defined in the chreSensorFindDefault(), will be returned if
+ * a sensor index of zero is specified.
+ *
+ * A simple example of iterating all available sensors of a given type is
+ * provided here:
+ *
+ * uint32_t handle;
+ * for (uint8_t i = 0; chreSensorFind(sensorType, i, &handle); i++) {
+ *   chreLog(CHRE_LOG_INFO,
+ *           "Found sensor index %" PRIu8 ", which has handle %" PRIu32,
+ *           i, handle);
+ * }
+ *
+ * If this method is invoked for CHRE versions prior to v1.5, invocations with
+ * sensorIndex value of 0 will be equivalent to using chreSensorFindDefault, and
+ * if sensorIndex is non-zero will return false.
+ *
+ * In cases where multiple sensors are supported in both the Android sensors
+ * framework and CHRE, the sensorName of the chreSensorInfo struct for a given
+ * sensor instance must match exactly with that of the
+ * android.hardware.Sensor#getName() return value. This can be used to match a
+ * sensor instance between the Android and CHRE sensors APIs.
+ *
+ * @param sensorType One of the CHRE_SENSOR_TYPE_* constants.
+ * @param sensorIndex The index of the desired sensor.
+ * @param handle  If a sensor is found, then the memory will be filled with
+ *     the value for the sensor's handle.  This argument must be non-NULL.
+ * @return true if a sensor was found, false otherwise.
+ *
+ * @since v1.5
+ */
+bool chreSensorFind(uint8_t sensorType, uint8_t sensorIndex, uint32_t *handle);
+
+/**
+ * Get the chreSensorInfo struct for a given sensor.
+ *
+ * @param sensorHandle  The sensor handle, as obtained from
+ *     chreSensorFindDefault() or passed to nanoappHandleEvent().
+ * @param info  If the sensor is valid, then this memory will be filled with
+ *     the SensorInfo contents for this sensor.  This argument must be
+ *     non-NULL.
+ * @return true if the senor handle is valid and 'info' was filled in;
+ *     false otherwise.
+ */
+bool chreGetSensorInfo(uint32_t sensorHandle, struct chreSensorInfo *info);
+
+/**
+ * Get the chreSensorSamplingStatus struct for a given sensor.
+ *
+ * Note that this may be different from what was requested in
+ * chreSensorConfigure(), for multiple reasons.  It's possible that the sensor
+ * does not exactly support the interval requested in chreSensorConfigure(), so
+ * a faster one was chosen.
+ *
+ * It's also possible that there is another user of this sensor who has
+ * requested a faster interval and/or lower latency.  This latter scenario
+ * should be noted, because it means the sensor rate can change due to no
+ * interaction from this nanoapp.  Note that the
+ * CHRE_EVENT_SENSOR_SAMPLING_CHANGE event will trigger in this case, so it's
+ * not necessary to poll for such a change.
+ *
+ * @param sensorHandle  The sensor handle, as obtained from
+ *     chreSensorFindDefault() or passed to nanoappHandleEvent().
+ * @param status  If the sensor is valid, then this memory will be filled with
+ *     the sampling status contents for this sensor.  This argument must be
+ *     non-NULL.
+ * @return true if the senor handle is valid and 'status' was filled in;
+ *     false otherwise.
+ */
+bool chreGetSensorSamplingStatus(uint32_t sensorHandle,
+                                 struct chreSensorSamplingStatus *status);
+
+/**
+ * Configures a given sensor at a specific interval and latency and mode.
+ *
+ * If this sensor's chreSensorInfo has isOneShot set to 1,
+ * then the mode must be one of the ONE_SHOT modes, or this method will fail.
+ *
+ * The CHRE wants to power as few sensors as possible, in keeping with its
+ * low power design.  As such, it only turns on sensors when there are clients
+ * actively interested in that sensor data, and turns off sensors as soon as
+ * there are no clients interested in them.  Calling this method generally
+ * indicates an interest, and using CHRE_SENSOR_CONFIGURE_MODE_DONE shows
+ * when we are no longer interested.
+ *
+ * Thus, each initial Configure of a sensor (per nanoapp) needs to eventually
+ * have a DONE call made, either directly or on its behalf.  Subsequent calls
+ * to a Configure method within the same nanoapp, when there has been no DONE
+ * in between, still only require a single DONE call.
+ *
+ * For example, the following is valid usage:
+ * <code>
+ *   chreSensorConfigure(myHandle, mode, interval0, latency0);
+ *   [...]
+ *   chreSensorConfigure(myHandle, mode, interval1, latency0);
+ *   [...]
+ *   chreSensorConfigure(myHandle, mode, interval1, latency1);
+ *   [...]
+ *   chreSensorConfigureModeOnly(myHandle, CHRE_SENSOR_CONFIGURE_MODE_DONE);
+ * </code>
+ *
+ * The first call to Configure is the one which creates the requirement
+ * to eventually call with DONE.  The subsequent calls are just changing the
+ * interval/latency.  They have not changed the fact that this nanoapp is
+ * still interested in output from the sensor 'myHandle'.  Thus, only one
+ * single call for DONE is needed.
+ *
+ * There is a special case.  One-shot sensors, sensors which
+ * just trigger a single event and never trigger again, implicitly go into
+ * DONE mode after that single event triggers.  Thus, the
+ * following are legitimate usages:
+ * <code>
+ *   chreSensorConfigure(myHandle, MODE_ONE_SHOT, interval, latency);
+ *   [...]
+ *   [myHandle triggers an event]
+ *   [no need to configure to DONE].
+ * </code>
+ *
+ * And:
+ * <code>
+ *   chreSensorConfigure(myHandle, MODE_ONE_SHOT, interval, latency);
+ *   [...]
+ *   chreSensorConfigureModeOnly(myHandle, MODE_DONE);
+ *   [we cancelled myHandle before it ever triggered an event]
+ * </code>
+ *
+ * Note that while PASSIVE modes, by definition, don't express an interest in
+ * powering the sensor, DONE is still necessary to silence the event reporting.
+ * Starting with CHRE API v1.4, for sensors that do not support passive mode, a
+ * request with mode set to CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_CONTINUOUS or
+ * CHRE_SENSOR_CONFIGURE_MODE_PASSIVE_ONE_SHOT will be rejected. CHRE API
+ * versions 1.3 and older implicitly assume that passive mode is supported
+ * across all sensors, however this is not necessarily the case. Clients can
+ * call chreSensorInfo to identify whether a sensor supports passive mode.
+ *
+ * When a calibrated sensor (e.g. CHRE_SENSOR_TYPE_ACCELEROMETER) is
+ * successfully enabled through this method and if bias delivery is supported,
+ * by default CHRE will start delivering bias events for the sensor
+ * (e.g. CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO) to the nanoapp. If the
+ * nanoapp does not wish to receive these events, they can be disabled through
+ * chreSensorConfigureBiasEvents after enabling the sensor.
+ *
+ * @param sensorHandle  The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param mode  The mode to use.  See descriptions within the
+ *     chreSensorConfigureMode enum.
+ * @param interval  The interval, in nanoseconds, at which we want events from
+ *     the sensor.  On success, the sensor will be set to 'interval', or a value
+ *     less than 'interval'.  There is a special value
+ *     CHRE_SENSOR_INTERVAL_DEFAULT, in which we don't express a preference for
+ *     the interval, and allow the sensor to choose what it wants.  Note that
+ *     due to batching, we may receive events less frequently than
+ *     'interval'.
+ * @param latency  The maximum latency, in nanoseconds, allowed before the
+ *     CHRE begins delivery of an event.  This will control how many events
+ *     can be queued by the sensor before requiring a delivery event.
+ *     Latency is defined as the "timestamp when event is queued by the CHRE"
+ *     minus "timestamp of oldest unsent data reading".
+ *     There is a special value CHRE_SENSOR_LATENCY_DEFAULT, in which we don't
+ *     express a preference for the latency, and allow the sensor to choose what
+ *     it wants.
+ *     Note that there is no assurance of how long it will take an event to
+ *     get through a CHRE's queueing system, and thus there is no ability to
+ *     request a minimum time from the occurrence of a phenomenon to when the
+ *     nanoapp receives the information.  The current CHRE API has no
+ *     real-time elements, although future versions may introduce some to
+ *     help with this issue.
+ * @return true if the configuration succeeded, false otherwise.
+ *
+ * @see chreSensorConfigureMode
+ * @see chreSensorFindDefault
+ * @see chreSensorInfo
+ * @see chreSensorConfigureBiasEvents
+ */
+bool chreSensorConfigure(uint32_t sensorHandle,
+                         enum chreSensorConfigureMode mode,
+                         uint64_t interval, uint64_t latency);
+
+/**
+ * Short cut for chreSensorConfigure where we only want to configure the mode
+ * and do not care about interval/latency.
+ *
+ * @see chreSensorConfigure
+ */
+static inline bool chreSensorConfigureModeOnly(
+        uint32_t sensorHandle, enum chreSensorConfigureMode mode) {
+    return chreSensorConfigure(sensorHandle,
+                               mode,
+                               CHRE_SENSOR_INTERVAL_DEFAULT,
+                               CHRE_SENSOR_LATENCY_DEFAULT);
+}
+
+/**
+ * Convenience function that wraps chreSensorConfigure but enables batching to
+ * be controlled by specifying the desired maximum batch interval rather
+ * than maximum sample latency.  Users may find the batch interval to be a more
+ * intuitive method of expressing the desired batching behavior.
+ *
+ * Batch interval is different from latency as the batch interval time is
+ * counted starting when the prior event containing a batch of sensor samples is
+ * delivered, while latency starts counting when the first sample is deferred to
+ * start collecting a batch.  In other words, latency ignores the time between
+ * the last sample in a batch to the first sample of the next batch, while it's
+ * included in the batch interval, as illustrated below.
+ *
+ *  Time      0   1   2   3   4   5   6   7   8
+ *  Batch             A           B           C
+ *  Sample   a1  a2  a3  b1  b2  b3  c1  c2  c3
+ *  Latency  [        ]  [        ]  [        ]
+ *  BatchInt          |           |           |
+ *
+ * In the diagram, the effective sample interval is 1 time unit, latency is 2
+ * time units, and batch interval is 3 time units.
+ *
+ * @param sensorHandle See chreSensorConfigure#sensorHandle
+ * @param mode See chreSensorConfigure#mode
+ * @param sampleInterval See chreSensorConfigure#interval, but note that
+ *     CHRE_SENSOR_INTERVAL_DEFAULT is not a supported input to this method.
+ * @param batchInterval The desired maximum interval, in nanoseconds, between
+ *     CHRE enqueuing each batch of sensor samples.
+ * @return Same as chreSensorConfigure
+ *
+ * @see chreSensorConfigure
+ *
+ * @since v1.1
+ */
+static inline bool chreSensorConfigureWithBatchInterval(
+        uint32_t sensorHandle, enum chreSensorConfigureMode mode,
+        uint64_t sampleInterval, uint64_t batchInterval) {
+    bool result = false;
+
+    if (sampleInterval != CHRE_SENSOR_INTERVAL_DEFAULT) {
+        uint64_t latency;
+        if (batchInterval == CHRE_SENSOR_BATCH_INTERVAL_DEFAULT) {
+            latency = CHRE_SENSOR_LATENCY_DEFAULT;
+        } else if (batchInterval > sampleInterval) {
+            latency = batchInterval - sampleInterval;
+        } else {
+            latency = CHRE_SENSOR_LATENCY_ASAP;
+        }
+        result = chreSensorConfigure(sensorHandle, mode, sampleInterval,
+                                     latency);
+    }
+
+    return result;
+}
+
+/**
+ * Configures the reception of bias events for a specific sensor.
+ *
+ * If bias event delivery is supported for a sensor, the sensor's chreSensorInfo
+ * has reportsBiasEvents set to 1. If supported, it must be supported for both
+ * calibrated and uncalibrated versions of the sensor. If supported, CHRE must
+ * provide bias events to the nanoapp by default when chreSensorConfigure is
+ * called to enable the calibrated version of the sensor (for backwards
+ * compatibility reasons, as this is the defined behavior for CHRE API v1.0).
+ * When configuring uncalibrated sensors, nanoapps must explicitly configure an
+ * enable request through this method to receive bias events. If bias event
+ * delivery is not supported for the sensor, this method will return false and
+ * no bias events will be generated.
+ *
+ * To enable bias event delivery (enable=true), the nanoapp must be registered
+ * to the sensor through chreSensorConfigure, and bias events will only be
+ * generated when the sensor is powered on. To disable the bias event delivery,
+ * this method can be invoked with enable=false.
+ *
+ * If an enable configuration is successful, the calling nanoapp will receive
+ * bias info events, e.g. CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO, when the
+ * bias status changes (or first becomes available). Calibrated data
+ * (e.g. CHRE_SENSOR_TYPE_ACCELEROMETER) is generated by subracting bias from
+ * uncalibrated data (e.g. CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER).
+ * Calibrated sensor events are generated by applying the most recent bias
+ * available (i.e. timestamp of calibrated data are greater than or equal to the
+ * timestamp of the bias data that has been applied to it). The configuration of
+ * bias event delivery persists until the sensor is unregistered by the nanoapp
+ * through chreSensorConfigure or modified through this method.
+ *
+ * To get an initial bias before new bias events, the nanoapp should get the
+ * bias synchronously after this method is invoked, e.g.:
+ *
+ * if (chreSensorConfigure(handle, ...)) {
+ *   chreSensorConfigureBiasEvents(handle, true);
+ *   chreSensorGetThreeAxisBias(handle, &bias);
+ * }
+ *
+ * Note that chreSensorGetThreeAxisBias() should be called after
+ * chreSensorConfigureBiasEvents() to ensure that no bias events are lost.
+ *
+ * If called while running on a CHRE API version below v1.3, this function
+ * returns false and has no effect. The default behavior regarding bias events
+ * is unchanged, meaning that the implementation may still send bias events
+ * when a calibrated sensor is registered (if supported), and will not send bias
+ * events when an uncalibrated sensor is registered.
+ *
+ * @param sensorHandle The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param enable true to receive bias events, false otherwise
+ *
+ * @return true if the configuration succeeded, false otherwise
+ *
+ * @since v1.3
+ */
+bool chreSensorConfigureBiasEvents(uint32_t sensorHandle, bool enable);
+
+/**
+ * Synchronously provides the most recent bias info available for a sensor. The
+ * bias will only be provided for a sensor that supports bias event delivery
+ * using the chreSensorThreeAxisData type. If the bias is not yet available
+ * (but is supported), this method will store data with a bias of 0 and the
+ * accuracy field in chreSensorDataHeader set to CHRE_SENSOR_ACCURACY_UNKNOWN.
+ *
+ * If called while running on a CHRE API version below v1.3, this function
+ * returns false.
+ *
+ * @param sensorHandle The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param bias A pointer to where the bias will be stored.
+ *
+ * @return true if the bias was successfully stored, false if sensorHandle was
+ *     invalid or the sensor does not support three axis bias delivery
+ *
+ * @since v1.3
+ *
+ * @see chreSensorConfigureBiasEvents
+ */
+bool chreSensorGetThreeAxisBias(uint32_t sensorHandle,
+                                struct chreSensorThreeAxisData *bias);
+
+/**
+ * Makes a request to flush all samples stored for batching. The nanoapp must be
+ * registered to the sensor through chreSensorConfigure, and the sensor must be
+ * powered on. If the request is accepted, all batched samples of the sensor
+ * are sent to nanoapps registered to the sensor. During a flush, it is treated
+ * as though the latency as given in chreSensorConfigure has expired. When all
+ * batched samples have been flushed (or the flush fails), the nanoapp will
+ * receive a unicast CHRE_EVENT_SENSOR_FLUSH_COMPLETE event. The time to deliver
+ * this event must not exceed CHRE_SENSOR_FLUSH_COMPLETE_TIMEOUT_NS after this
+ * method is invoked. If there are no samples in the batch buffer (either in
+ * hardware FIFO or software), then this method will return true and a
+ * CHRE_EVENT_SENSOR_FLUSH_COMPLETE event is delivered immediately.
+ *
+ * If a flush request is invalid (e.g. the sensor refers to a one-shot sensor,
+ * or the sensor was not enabled), and this API will return false and no
+ * CHRE_EVENT_SENSOR_FLUSH_COMPLETE event will be delivered.
+ *
+ * If multiple flush requests are made for a sensor prior to flush completion,
+ * then the requesting nanoapp will receive all batched samples existing at the
+ * time of the latest flush request. In this case, the number of
+ * CHRE_EVENT_SENSOR_FLUSH_COMPLETE events received must equal the number of
+ * flush requests made.
+ *
+ * If a sensor request is disabled after a flush request is made through this
+ * method but before the flush operation is completed, the nanoapp will receive
+ * a CHRE_EVENT_SENSOR_FLUSH_COMPLETE with the error code
+ * CHRE_ERROR_FUNCTION_DISABLED for any pending flush requests.
+ *
+ * Starting with CHRE API v1.3, implementations must support this capability
+ * across all exposed sensor types.
+ *
+ * @param sensorHandle  The handle to the sensor, as obtained from
+ *     chreSensorFindDefault().
+ * @param cookie  An opaque value that will be included in the
+ *     chreSensorFlushCompleteEvent sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.3
+ */
+bool chreSensorFlushAsync(uint32_t sensorHandle, const void *cookie);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_SENSOR_H_ */
diff --git a/chre_api/legacy/v1_7/chre/sensor_types.h b/chre_api/legacy/v1_7/chre/sensor_types.h
new file mode 100644
index 0000000..63b495b
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/sensor_types.h
@@ -0,0 +1,468 @@
+/*
+ * 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_SENSOR_TYPES_H_
+#define _CHRE_SENSOR_TYPES_H_
+
+/**
+ * @file
+ * Standalone definition of sensor types, and the data structures of the sample
+ * events they emit.
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file
+ * The CHRE_SENSOR_TYPE_* defines are the sensor types supported.
+ *
+ * Unless otherwise noted, each of these sensor types is based off of a
+ * corresponding sensor type in the Android API's sensors.h interface.
+ * For a given CHRE_SENSOR_TYPE_FOO, it corresponds to the SENSOR_TYPE_FOO in
+ * hardware/libhardware/include/hardware/sensors.h of the Android code base.
+ *
+ * Unless otherwise noted below, a CHRE_SENSOR_TYPE_FOO should be assumed
+ * to work the same as the Android SENSOR_TYPE_FOO, as documented in the
+ * sensors.h documentation and as detailed within the Android Compatibility
+ * Definition Document.
+ *
+ * Note that every sensor will generate CHRE_EVENT_SENSOR_SAMPLING_CHANGE
+ * events, so it is not listed with each individual sensor.
+ */
+
+/**
+ * Start value for all of the vendor-defined private sensors.
+ *
+ * @since v1.2
+ */
+#define CHRE_SENSOR_TYPE_VENDOR_START  UINT8_C(192)
+
+/**
+ * Accelerometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_ACCELEROMETER_DATA and
+ *     optionally CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO
+ *
+ * Note that the ACCELEROMETER_DATA is always the fully calibrated data,
+ * including factory calibration and runtime calibration if available.
+ *
+ * @see chreConfigureSensorBiasEvents
+ */
+#define CHRE_SENSOR_TYPE_ACCELEROMETER  UINT8_C(1)
+
+/**
+ * Instantaneous motion detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA
+ *
+ * This is a one-shot sensor.
+ *
+ * This does not have a direct analogy within sensors.h.  This is similar
+ * to SENSOR_TYPE_MOTION_DETECT, but this triggers instantly upon any
+ * motion, instead of waiting for a period of continuous motion.
+ */
+#define CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT  UINT8_C(2)
+
+/**
+ * Stationary detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_STATIONARY_DETECT_DATA
+ *
+ * This is a one-shot sensor.
+ */
+#define CHRE_SENSOR_TYPE_STATIONARY_DETECT  UINT8_C(3)
+
+/**
+ * Gyroscope.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GYROSCOPE_DATA and
+ *     optionally CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO
+ *
+ * Note that the GYROSCOPE_DATA is always the fully calibrated data, including
+ * factory calibration and runtime calibration if available.
+ *
+ * @see chreConfigureSensorBiasEvents
+ */
+#define CHRE_SENSOR_TYPE_GYROSCOPE  UINT8_C(6)
+
+/**
+ * Uncalibrated gyroscope.
+ *
+ * Generates: CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA
+ *
+ * Note that the UNCALIBRATED_GYROSCOPE_DATA must be factory calibrated data,
+ * but not runtime calibrated.
+ */
+#define CHRE_SENSOR_TYPE_UNCALIBRATED_GYROSCOPE  UINT8_C(7)
+
+/**
+ * Magnetometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA and
+ *     optionally CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO
+ *
+ * Note that the GEOMAGNETIC_FIELD_DATA is always the fully calibrated data,
+ * including factory calibration and runtime calibration if available.
+ *
+ * @see chreConfigureSensorBiasEvents
+ */
+#define CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD  UINT8_C(8)
+
+/**
+ * Uncalibrated magnetometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA
+ *
+ * Note that the UNCALIBRATED_GEOMAGNETIC_FIELD_DATA must be factory calibrated
+ * data, but not runtime calibrated.
+ */
+#define CHRE_SENSOR_TYPE_UNCALIBRATED_GEOMAGNETIC_FIELD  UINT8_C(9)
+
+/**
+ * Barometric pressure sensor.
+ *
+ * Generates: CHRE_EVENT_SENSOR_PRESSURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_PRESSURE  UINT8_C(10)
+
+/**
+ * Ambient light sensor.
+ *
+ * Generates: CHRE_EVENT_SENSOR_LIGHT_DATA
+ *
+ * This is an on-change sensor.
+ */
+#define CHRE_SENSOR_TYPE_LIGHT  UINT8_C(12)
+
+/**
+ * Proximity detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_PROXIMITY_DATA
+ *
+ * This is an on-change sensor.
+ */
+#define CHRE_SENSOR_TYPE_PROXIMITY  UINT8_C(13)
+
+/**
+ * Step detection.
+ *
+ * Generates: CHRE_EVENT_SENSOR_STEP_DETECT_DATA
+ *
+ * @since v1.3
+ */
+#define CHRE_SENSOR_TYPE_STEP_DETECT  UINT8_C(23)
+
+/**
+ * Step counter.
+ *
+ * Generates: CHRE_EVENT_SENSOR_STEP_COUNTER_DATA
+ *
+ * This is an on-change sensor. Note that the data returned by this sensor must
+ * match the value that can be obtained via the Android sensors framework at the
+ * same point in time. This means, if CHRE reboots from the rest of the system,
+ * the counter must not reset to 0.
+ *
+ * @since v1.5
+ */
+#define CHRE_SENSOR_TYPE_STEP_COUNTER UINT8_C(24)
+
+/**
+ * Hinge angle sensor.
+ *
+ * Generates: CHRE_EVENT_SENSOR_HINGE_ANGLE_DATA
+ *
+ * This is an on-change sensor.
+ *
+ * A sensor of this type measures the angle, in degrees, between two
+ * integral parts of the device. Movement of a hinge measured by this sensor
+ * type is expected to alter the ways in which the user may interact with
+ * the device, for example by unfolding or revealing a display.
+ *
+ * @since v1.5
+ */
+#define CHRE_SENSOR_TYPE_HINGE_ANGLE UINT8_C(36)
+
+/**
+ * Uncalibrated accelerometer.
+ *
+ * Generates: CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA
+ *
+ * Note that the UNCALIBRATED_ACCELEROMETER_DATA must be factory calibrated
+ * data, but not runtime calibrated.
+ */
+#define CHRE_SENSOR_TYPE_UNCALIBRATED_ACCELEROMETER  UINT8_C(55)
+
+/**
+ * Accelerometer temperature.
+ *
+ * Generates: CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_ACCELEROMETER_TEMPERATURE  UINT8_C(56)
+
+/**
+ * Gyroscope temperature.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GYROSCOPE_TEMPERATURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_GYROSCOPE_TEMPERATURE  UINT8_C(57)
+
+/**
+ * Magnetometer temperature.
+ *
+ * Generates: CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA
+ */
+#define CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE  UINT8_C(58)
+
+#if CHRE_SENSOR_TYPE_GEOMAGNETIC_FIELD_TEMPERATURE >= CHRE_SENSOR_TYPE_VENDOR_START
+#error Too many sensor types
+#endif
+
+/**
+ * Values that can be stored in the accuracy field of chreSensorDataHeader.
+ * If CHRE_SENSOR_ACCURACY_UNKNOWN is returned, then the driver did not provide
+ * accuracy information with the data. Values in the range
+ * [CHRE_SENSOR_ACCURACY_VENDOR_START, CHRE_SENSOR_ACCURACY_VENDOR_END] are
+ * reserved for vendor-specific values for vendor sensor types, and are not used
+ * by CHRE for standard sensor types.
+ *
+ * Otherwise, the values have the same meaning as defined in the Android
+ * Sensors definition:
+ * https://developer.android.com/reference/android/hardware/SensorManager
+ *
+ * @since v1.3
+ *
+ * @defgroup CHRE_SENSOR_ACCURACY
+ * @{
+ */
+
+#define CHRE_SENSOR_ACCURACY_UNKNOWN       UINT8_C(0)
+#define CHRE_SENSOR_ACCURACY_UNRELIABLE    UINT8_C(1)
+#define CHRE_SENSOR_ACCURACY_LOW           UINT8_C(2)
+#define CHRE_SENSOR_ACCURACY_MEDIUM        UINT8_C(3)
+#define CHRE_SENSOR_ACCURACY_HIGH          UINT8_C(4)
+#define CHRE_SENSOR_ACCURACY_VENDOR_START  UINT8_C(192)
+#define CHRE_SENSOR_ACCURACY_VENDOR_END    UINT8_MAX
+
+/** @} */
+
+/**
+ * Header used in every structure containing batchable data from a sensor.
+ *
+ * The typical structure for sensor data looks like:
+ *
+ *   struct chreSensorTypeData {
+ *       struct chreSensorDataHeader header;
+ *       struct chreSensorTypeSampleData {
+ *           uint32_t timestampDelta;
+ *           union {
+ *               <type> value;
+ *               <type> interpretation0;
+ *               <type> interpretation1;
+ *           };
+ *       } readings[1];
+ *   };
+ *
+ * Despite 'readings' being declared as an array of 1 element,
+ * an instance of the struct will actually have 'readings' as
+ * an array of header.readingCount elements (which may be 1).
+ * The 'timestampDelta' is in relation to the previous 'readings' (or
+ * the baseTimestamp for readings[0].  So,
+ * Timestamp for readings[0] == header.baseTimestamp +
+ *     readings[0].timestampDelta.
+ * Timestamp for readings[1] == timestamp for readings[0] +
+ *     readings[1].timestampDelta.
+ * And thus, in order to determine the timestamp for readings[N], it's
+ * necessary to process through all of the N-1 readings.  The advantage,
+ * though, is that our entire readings can span an arbitrary length of time,
+ * just as long as any two consecutive readings differ by no more than
+ * 4.295 seconds (timestampDelta, like all time in the CHRE, is in
+ * nanoseconds).
+ *
+ * If a sensor has batched readings where two consecutive readings differ by
+ * more than 4.295 seconds, the CHRE will split them across multiple
+ * instances of the struct, and send multiple events.
+ *
+ * The value from the sensor is typically expressed in a union,
+ * allowing a generic access to the data ('value'), along with
+ * differently named access giving a more natural interpretation
+ * of the data for the specific sensor types which use this
+ * structure.  This allows, for example, barometer code to
+ * reference readings[N].pressure, and an ambient light sensor
+ * to reference readings[N].light, while both use the same
+ * structure.
+ */
+struct chreSensorDataHeader {
+    /**
+     * The base timestamp, in nanoseconds; must be in the same time base as
+     * chreGetTime().
+     */
+    uint64_t baseTimestamp;
+
+    /**
+     * The handle of the sensor producing this event.
+     */
+    uint32_t sensorHandle;
+
+    /**
+     * The number elements in the 'readings' array.
+     *
+     * This must be at least 1.
+     */
+    uint16_t readingCount;
+
+    /**
+     * The accuracy of the sensor data.
+     *
+     * @ref CHRE_SENSOR_ACCURACY
+     *
+     * @since v1.3
+     */
+    uint8_t accuracy;
+
+    /**
+     * Reserved bytes.
+     *
+     * This must be 0.
+     */
+    uint8_t reserved;
+};
+
+/**
+ * Data for a sensor which reports on three axes.
+ *
+ * This is used by CHRE_EVENT_SENSOR_ACCELEROMETER_DATA,
+ * CHRE_EVENT_SENSOR_ACCELEROMETER_BIAS_INFO,
+ * CHRE_EVENT_SENSOR_UNCALIBRATED_ACCELEROMETER_DATA,
+ * CHRE_EVENT_SENSOR_GYROSCOPE_DATA,
+ * CHRE_EVENT_SENSOR_GYROSCOPE_BIAS_INFO,
+ * CHRE_EVENT_SENSOR_UNCALIBRATED_GYROSCOPE_DATA,
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_DATA,
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_BIAS_INFO, and
+ * CHRE_EVENT_SENSOR_UNCALIBRATED_GEOMAGNETIC_FIELD_DATA.
+ */
+struct chreSensorThreeAxisData {
+    /**
+     * @see chreSensorDataHeader
+     */
+    struct chreSensorDataHeader header;
+    struct chreSensorThreeAxisSampleData {
+        /**
+         * @see chreSensorDataHeader
+         */
+        uint32_t timestampDelta;
+        union {
+            float values[3];
+            float v[3];
+            struct {
+                float x;
+                float y;
+                float z;
+            };
+            float bias[3];
+            struct {
+                float x_bias;
+                float y_bias;
+                float z_bias;
+            };
+        };
+    } readings[1];
+};
+
+/**
+ * Data from a sensor where we only care about a event occurring.
+ *
+ * This is a bit unusual in that our readings have no data in addition
+ * to the timestamp.  But since we only care about the occurrence, we
+ * don't need to know anything else.
+ *
+ * Used by: CHRE_EVENT_SENSOR_INSTANT_MOTION_DETECT_DATA,
+ *     CHRE_EVENT_SENSOR_STATIONARY_DETECT_DATA, and
+ *     CHRE_EVENT_SENSOR_STEP_DETECT_DATA.
+ */
+struct chreSensorOccurrenceData {
+    struct chreSensorDataHeader header;
+    struct chreSensorOccurrenceSampleData {
+        uint32_t timestampDelta;
+        // This space intentionally left blank.
+        // Only the timestamp is meaningful here, there
+        // is no additional data.
+    } readings[1];
+};
+
+/**
+ * This is used by CHRE_EVENT_SENSOR_LIGHT_DATA,
+ * CHRE_EVENT_SENSOR_PRESSURE_DATA,
+ * CHRE_EVENT_SENSOR_ACCELEROMETER_TEMPERATURE_DATA,
+ * CHRE_EVENT_SENSOR_GYROSCOPE_TEMPERATURE_DATA,
+ * CHRE_EVENT_SENSOR_GEOMAGNETIC_FIELD_TEMPERATURE_DATA, and
+ * CHRE_EVENT_SENSOR_HINGE_ANGLE_DATA.
+ */
+struct chreSensorFloatData {
+    struct chreSensorDataHeader header;
+    struct chreSensorFloatSampleData {
+        uint32_t timestampDelta;
+        union {
+            float value;
+            float light;        //!< Unit: lux
+            float pressure;     //!< Unit: hectopascals (hPa)
+            float temperature;  //!< Unit: degrees Celsius
+            float angle;        //!< Unit: angular degrees
+        };
+    } readings[1];
+};
+
+/**
+ * CHRE_EVENT_SENSOR_PROXIMITY_DATA.
+ */
+struct chreSensorByteData {
+    struct chreSensorDataHeader header;
+    struct chreSensorByteSampleData {
+        uint32_t timestampDelta;
+        union {
+            uint8_t value;
+            struct {
+                uint8_t isNear : 1;
+                //! @deprecated As of v1.2, this field is deprecated and must
+                //! always be set to 0
+                uint8_t invalid : 1;
+                uint8_t padding0 : 6;
+            };
+        };
+    } readings[1];
+};
+
+/**
+ * Data for a sensor which reports a single uint64 value.
+ *
+ * This is used by CHRE_EVENT_SENSOR_STEP_COUNTER_DATA.
+ */
+struct chreSensorUint64Data {
+    struct chreSensorDataHeader header;
+    struct chreSensorUint64SampleData {
+        uint32_t timestampDelta;
+        uint64_t value;
+    } readings[1];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_SENSOR_TYPES_H_ */
diff --git a/chre_api/legacy/v1_7/chre/toolchain.h b/chre_api/legacy/v1_7/chre/toolchain.h
new file mode 100644
index 0000000..c2b8722
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/toolchain.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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_TOOLCHAIN_H_
+#define CHRE_TOOLCHAIN_H_
+
+/**
+ * @file
+ * Compiler/build toolchain-specific macros used by the CHRE API
+ */
+
+#if defined(__GNUC__) || defined(__clang__)
+// For GCC and clang
+
+#define CHRE_DEPRECATED(message) \
+  __attribute__((deprecated(message)))
+
+// Enable printf-style compiler warnings for mismatched format string and args
+#define CHRE_PRINTF_ATTR(formatPos, argStart) \
+  __attribute__((format(printf, formatPos, argStart)))
+
+#define CHRE_BUILD_ERROR(message) CHRE_DO_PRAGMA(GCC error message)
+#define CHRE_DO_PRAGMA(message) _Pragma(#message)
+
+#elif defined(__ICCARM__) || defined(__CC_ARM)
+// For IAR ARM and Keil MDK-ARM compilers
+
+#define CHRE_PRINTF_ATTR(formatPos, argStart)
+
+#elif defined(_MSC_VER)
+// For Microsoft Visual Studio
+
+#define CHRE_PRINTF_ATTR(formatPos, argStart)
+
+#else  // if !defined(__GNUC__) && !defined(__clang__)
+
+#error Need to add support for new compiler
+
+#endif
+
+// For platforms that don't support error pragmas, utilize the best method of
+// showing an error depending on the platform support.
+#ifndef CHRE_BUILD_ERROR
+#ifdef __cplusplus  // C++17 or greater assumed
+#define CHRE_BUILD_ERROR(message) static_assert(0, message)
+#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+#define CHRE_BUILD_ERROR(message) _Static_assert(0, message)
+#else
+#define CHRE_BUILD_ERROR(message) char buildError[-1] = message
+#endif
+#endif
+
+#endif  // CHRE_TOOLCHAIN_H_
diff --git a/chre_api/legacy/v1_7/chre/user_settings.h b/chre_api/legacy/v1_7/chre/user_settings.h
new file mode 100644
index 0000000..3780a3d
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/user_settings.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 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_USER_SETTINGS_H_
+#define _CHRE_USER_SETTINGS_H_
+
+/**
+ * @file
+ * The API for requesting notifications on changes in the settings of the
+ * active user. If the device is set up with one or more secondary users
+ * (see https://source.android.com/devices/tech/admin/multi-user), the user
+ * settings in CHRE reflect that of the currently active user.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <chre/event.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The user settings that nanoapps can request notifications for on a status
+ * change.
+ *
+ * NOTE: The WIFI available setting indicates the overall availability
+ * of WIFI related functionality. For example, if wifi is disabled for
+ * connectivity but enabled for location, the WIFI available setting is
+ * enabled.
+ *
+ * NOTE: The BLE available setting indicates the overall availability of
+ * BLE related functionality. For example, if BLE is disabled for connectivity,
+ * but enabled for location, the BLE available setting is enabled.
+ *
+ * @defgroup CHRE_USER_SETTINGS
+ * @{
+ */
+#define CHRE_USER_SETTING_LOCATION             UINT8_C(0)
+#define CHRE_USER_SETTING_WIFI_AVAILABLE       UINT8_C(1)
+#define CHRE_USER_SETTING_AIRPLANE_MODE        UINT8_C(2)
+#define CHRE_USER_SETTING_MICROPHONE           UINT8_C(3)
+#define CHRE_USER_SETTING_BLE_AVAILABLE        UINT8_C(4)
+
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for settings notifications.
+ *
+ * @param offset Index into the event ID block, valid in the range [0,15]
+ */
+#define CHRE_SETTING_EVENT_ID(offset) (CHRE_EVENT_SETTING_CHANGED_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreUserSettingChangedEvent
+ *
+ * Notify nanoapps of a change in the associated setting. Nanoapps must first
+ * register (via chreUserSettingConfigureEvents) for events before they are
+ * sent out.
+ */
+#define CHRE_EVENT_SETTING_CHANGED_LOCATION         CHRE_SETTING_EVENT_ID(0)
+#define CHRE_EVENT_SETTING_CHANGED_WIFI_AVAILABLE   CHRE_SETTING_EVENT_ID(1)
+#define CHRE_EVENT_SETTING_CHANGED_AIRPLANE_MODE    CHRE_SETTING_EVENT_ID(2)
+#define CHRE_EVENT_SETTING_CHANGED_MICROPHONE       CHRE_SETTING_EVENT_ID(3)
+#define CHRE_EVENT_SETTING_CHANGED_BLE_AVAILABLE    CHRE_SETTING_EVENT_ID(4)
+
+#if CHRE_EVENT_SETTING_CHANGED_BLE_AVAILABLE > CHRE_EVENT_SETTING_CHANGED_LAST_EVENT
+#error Too many setting changed events.
+#endif
+
+/**
+ * Indicates the current state of a setting.
+ * The setting state is 'unknown' only in the following scenarios:
+ *  - CHRE hasn't received the initial state yet on a restart.
+ *  - The nanoapp is running on CHRE v1.4 or older
+ *  - Nanoapp provided in invalid setting ID to chreUserSettingGetStatus.
+ */
+enum chreUserSettingState {
+  CHRE_USER_SETTING_STATE_UNKNOWN = -1,
+  CHRE_USER_SETTING_STATE_DISABLED = 0,
+  CHRE_USER_SETTING_STATE_ENABLED = 1
+};
+
+/**
+ * The nanoappHandleEvent argument for CHRE settings changed notifications.
+ */
+struct chreUserSettingChangedEvent {
+  //! Indicates the setting whose state has changed.
+  uint8_t setting;
+
+  //! A value that corresponds to a member in enum chreUserSettingState,
+  // indicating the latest value of the setting.
+  int8_t settingState;
+};
+
+/**
+ * Get the current state of a given setting.
+ *
+ * @param setting The setting to get the current status of.
+ *
+ * @return The current state of the requested setting. The state is returned
+ * as an int8_t to be consistent with the associated event data, but is
+ * guaranteed to be a valid enum chreUserSettingState member.
+ *
+ * @since v1.5
+ */
+int8_t chreUserSettingGetState(uint8_t setting);
+
+/**
+ * Register or deregister for a notification on a status change for a given
+ * setting. Note that registration does not produce an event with the initial
+ * (or current) state, though nanoapps can use chreUserSettingGetState() for
+ * this purpose.
+ *
+ * @param setting The setting on whose change a notification is desired.
+ * @param enable The nanoapp is registered to receive notifications on a
+ * change in the user settings if this parameter is true, otherwise the
+ * nanoapp receives no further notifications for this setting.
+ *
+ * @since v1.5
+ */
+void chreUserSettingConfigureEvents(uint8_t setting, bool enable);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_USER_SETTINGS_H_ */
diff --git a/chre_api/legacy/v1_7/chre/version.h b/chre_api/legacy/v1_7/chre/version.h
new file mode 100644
index 0000000..79b968a
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/version.h
@@ -0,0 +1,247 @@
+/*
+ * 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_VERSION_H_
+#define _CHRE_VERSION_H_
+
+/**
+ * @file
+ * Definitions and methods for the versioning of the Context Hub Runtime
+ * Environment.
+ *
+ * The CHRE API versioning pertains to all header files in the CHRE API.
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Value for version 0.1 of the Context Hub Runtime Environment API interface.
+ *
+ * This is a legacy version of the CHRE API. Version 1.0 is considered the first
+ * official CHRE API version.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_0_1 UINT32_C(0x00010000)
+
+/**
+ * Value for version 1.0 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android Nougat release.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_0 UINT32_C(0x01000000)
+
+/**
+ * Value for version 1.1 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android O release. It adds
+ * initial support for new GNSS, WiFi, and WWAN modules.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_1 UINT32_C(0x01010000)
+
+/**
+ * Value for version 1.2 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android P release. It adds
+ * initial support for the new audio module.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_2 UINT32_C(0x01020000)
+
+/**
+ * Value for version 1.3 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android Q release. It adds
+ * support for GNSS location altitude/speed/bearing accuracy. It also adds step
+ * detect as a standard CHRE sensor and supports bias event delivery and sensor
+ * data flushing.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_3 UINT32_C(0x01030000)
+
+/**
+ * Value for version 1.4 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android R release. It adds
+ * support for collecting debug dump information from nanoapps, receiving L5
+ * GNSS measurements, determining if a sensor supports passive requests,
+ * receiving 5G cell info, and deprecates chreSendMessageToHost.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_4 UINT32_C(0x01040000)
+
+/**
+ * Value for version 1.5 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API shipped with the Android S release. It adds
+ * support for multiple sensors of the same type, permissions for sensitive CHRE
+ * APIs / data usage, ability to receive user settings updates, step counter and
+ * hinge angle sensors, improved WiFi scan preferences to support power
+ * optimization, new WiFi security types, increased the lower bound for the
+ * maximum CHRE to host message size, and increased GNSS measurements in
+ * chreGnssDataEvent.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_5 UINT32_C(0x01050000)
+
+/**
+ * Value for version 1.6 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API is shipped with the Android T release. It adds
+ * support for BLE scanning, subscribing to the WiFi NAN discovery engine,
+ * subscribing to host endpoint notifications, requesting metadata for a host
+ * endpoint ID, nanoapps publishing RPC services they support, and limits the
+ * nanoapp instance ID size to INT16_MAX.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_6 UINT32_C(0x01060000)
+
+/**
+ * Value for version 1.7 of the Context Hub Runtime Environment API interface.
+ *
+ * This version of the CHRE API is shipped with the Android U release.
+ *
+ * @note This version of the CHRE API has not been finalized yet, and is
+ * currently considered a preview that is subject to change.
+ *
+ * @see CHRE_API_VERSION
+ */
+#define CHRE_API_VERSION_1_7 UINT32_C(0x01070000)
+
+/**
+ * Major and Minor Version of this Context Hub Runtime Environment API.
+ *
+ * The major version changes when there is an incompatible API change.
+ *
+ * The minor version changes when there is an addition in functionality
+ * in a backwards-compatible manner.
+ *
+ * We define the version number as an unsigned 32-bit value.  The most
+ * significant byte is the Major Version.  The second-most significant byte
+ * is the Minor Version.  The two least significant bytes are the Patch
+ * Version.  The Patch Version is not defined by this header API, but
+ * is provided by a specific CHRE implementation (see chreGetVersion()).
+ *
+ * Note that version numbers can always be numerically compared with
+ * expected results, so 1.0.0 < 1.0.4 < 1.1.0 < 2.0.300 < 3.5.0.
+ */
+#define CHRE_API_VERSION CHRE_API_VERSION_1_7
+
+/**
+ * Utility macro to extract only the API major version of a composite CHRE
+ * version.
+ *
+ * @param version A uint32_t version, e.g. the value returned by
+ *     chreGetApiVersion()
+ *
+ * @return The API major version in the least significant byte, e.g. 0x01
+ */
+#define CHRE_EXTRACT_MAJOR_VERSION(version) \
+  (uint32_t)(((version) & UINT32_C(0xFF000000)) >> 24)
+
+/**
+ * Utility macro to extract only the API minor version of a composite CHRE
+ * version.
+ *
+ * @param version A uint32_t version, e.g. the CHRE_API_VERSION constant
+ *
+ * @return The API minor version in the least significant byte, e.g. 0x01
+ */
+#define CHRE_EXTRACT_MINOR_VERSION(version) \
+  (uint32_t)(((version) & UINT32_C(0x00FF0000)) >> 16)
+
+/**
+ * Utility macro to extract only the API minor version of a composite CHRE
+ * version.
+ *
+ * @param version A complete uint32_t version, e.g. the value returned by
+ *     chreGetVersion()
+ *
+ * @return The implementation patch version in the least significant two bytes,
+ *     e.g. 0x0123, with all other bytes set to 0
+ */
+#define CHRE_EXTRACT_PATCH_VERSION(version) (uint32_t)((version) & UINT32_C(0xFFFF))
+
+/**
+ * Get the API version the CHRE implementation was compiled against.
+ *
+ * This is not necessarily the CHRE_API_VERSION in the header the nanoapp was
+ * built against, and indeed may not have even appeared in the context_hub_os.h
+ * header which this nanoapp was built against.
+ *
+ * By definition, this will have the two least significant bytes set to 0,
+ * and only contain the major and minor version number.
+ *
+ * @return The API version.
+ */
+uint32_t chreGetApiVersion(void);
+
+/**
+ * Get the version of this CHRE implementation.
+ *
+ * By definition, ((chreGetApiVersion() & UINT32_C(0xFFFF0000)) ==
+ *                 (chreGetVersion()    & UINT32_C(0xFFFF0000))).
+ *
+ * The Patch Version, in the lower two bytes, only have meaning in context
+ * of this specific platform ID.  It is increased by the platform every time
+ * a backwards-compatible bug fix is released.
+ *
+ * @return The version.
+ *
+ * @see chreGetPlatformId()
+ */
+uint32_t chreGetVersion(void);
+
+/**
+ * Get the Platform ID of this CHRE.
+ *
+ * The most significant five bytes are the vendor ID as set out by the
+ * NANOAPP_VENDOR convention in the original context hub HAL header file
+ * (context_hub.h), also used by nanoapp IDs.
+ *
+ * The least significant three bytes are set by the vendor, but must be
+ * unique for each different CHRE implementation/hardware that the vendor
+ * supplies.
+ *
+ * The idea is that in the case of known bugs in the field, a new nanoapp could
+ * be shipped with a workaround that would use this value, and chreGetVersion(),
+ * to have code that can conditionally work around the bug on a buggy version.
+ * Thus, we require this uniqueness to allow such a setup to work.
+ *
+ * @return The platform ID.
+ *
+ * @see CHRE_EXTRACT_VENDOR_ID
+ */
+uint64_t chreGetPlatformId(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CHRE_VERSION_H_ */
diff --git a/chre_api/legacy/v1_7/chre/wifi.h b/chre_api/legacy/v1_7/chre/wifi.h
new file mode 100644
index 0000000..b4bda9c
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/wifi.h
@@ -0,0 +1,1313 @@
+/*
+ * 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_WIFI_H_
+#define _CHRE_WIFI_H_
+
+/**
+ * @file
+ * WiFi (IEEE 802.11) API, currently covering scanning features useful for
+ * determining location and offloading certain connectivity scans.
+ *
+ * In this file, specification references use the following shorthand:
+ *
+ *    Shorthand | Full specification name
+ *   ---------- | ------------------------
+ *     "802.11" | IEEE Std 802.11-2007
+ *     "HT"     | IEEE Std 802.11n-2009
+ *     "VHT"    | IEEE Std 802.11ac-2013
+ *     "WiFi 6" | IEEE Std 802.11ax draft
+ *     "NAN"    | Wi-Fi Neighbor Awareness Networking (NAN) Technical
+ *                Specification (v3.2)
+ *
+ * In the current version of CHRE API, the 6GHz band introduced in WiFi 6 is
+ * not supported. A scan request from CHRE should not result in scanning 6GHz
+ * channels. In particular, if a 6GHz channel is specified in scanning or
+ * ranging request parameter, CHRE should return an error code of
+ * CHRE_ERROR_NOT_SUPPORTED. Additionally, CHRE implementations must not include
+ * observations of access points on 6GHz channels in scan results, especially
+ * those produced due to scan monitoring.
+ */
+
+#include "common.h"
+#include <chre/common.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags returned by chreWifiGetCapabilities().
+ * @defgroup CHRE_WIFI_CAPABILITIES
+ * @{
+ */
+
+//! No WiFi APIs are supported
+#define CHRE_WIFI_CAPABILITIES_NONE              UINT32_C(0)
+
+//! Listening to scan results is supported, as enabled via
+//! chreWifiConfigureScanMonitorAsync()
+#define CHRE_WIFI_CAPABILITIES_SCAN_MONITORING   UINT32_C(1 << 0)
+
+//! Requesting WiFi scans on-demand is supported via chreWifiRequestScanAsync()
+#define CHRE_WIFI_CAPABILITIES_ON_DEMAND_SCAN    UINT32_C(1 << 1)
+
+//! Specifying the radio chain preference in on-demand scan requests, and
+//! reporting it in scan events is supported
+//! @since v1.2
+#define CHRE_WIFI_CAPABILITIES_RADIO_CHAIN_PREF  UINT32_C(1 << 2)
+
+//! Requesting RTT ranging is supported via chreWifiRequestRangingAsync()
+//! @since v1.2
+#define CHRE_WIFI_CAPABILITIES_RTT_RANGING       UINT32_C(1 << 3)
+
+//! Specifies if WiFi NAN service subscription is supported. If a platform
+//! supports subscriptions, then it must also support RTT ranging for NAN
+//! services via chreWifiNanRequestRangingAsync()
+//! @since v1.6
+#define CHRE_WIFI_CAPABILITIES_NAN_SUB           UINT32_C(1 << 4)
+
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for WiFi
+ * @param offset  Index into WiFi event ID block; valid range [0,15]
+ */
+#define CHRE_WIFI_EVENT_ID(offset)  (CHRE_EVENT_WIFI_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreAsyncResult
+ *
+ * Communicates the asynchronous result of a request to the WiFi API. The
+ * requestType field in {@link #chreAsyncResult} is set to a value from enum
+ * chreWifiRequestType.
+ */
+#define CHRE_EVENT_WIFI_ASYNC_RESULT  CHRE_WIFI_EVENT_ID(0)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiScanEvent
+ *
+ * Provides results of a WiFi scan.
+ */
+#define CHRE_EVENT_WIFI_SCAN_RESULT  CHRE_WIFI_EVENT_ID(1)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiRangingEvent
+ *
+ * Provides results of an RTT ranging request.
+ */
+#define CHRE_EVENT_WIFI_RANGING_RESULT  CHRE_WIFI_EVENT_ID(2)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanIdentifierEvent
+ *
+ * Lets the client know if the NAN engine was able to successfully assign
+ * an identifier to the subscribe call. The 'cookie' field in the event
+ * argument struct can be used to track which subscribe request this identifier
+ * maps to.
+ */
+#define CHRE_EVENT_WIFI_NAN_IDENTIFIER_RESULT   CHRE_WIFI_EVENT_ID(3)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanDiscoveryEvent
+ *
+ * Event that is sent whenever a NAN service matches the criteria specified
+ * in a subscription request.
+ */
+#define CHRE_EVENT_WIFI_NAN_DISCOVERY_RESULT  CHRE_WIFI_EVENT_ID(4)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanSessionLostEvent
+ *
+ * Informs the client that a discovered service is no longer available or
+ * visible.
+ * The ID of the service on the client that was communicating with the extinct
+ * service is indicated by the event argument.
+ */
+#define CHRE_EVENT_WIFI_NAN_SESSION_LOST  CHRE_WIFI_EVENT_ID(5)
+
+/**
+ * nanoappHandleEvent argument: struct chreWifiNanSessionTerminatedEvent
+ *
+ * Signals the end of a NAN subscription session. The termination can be due to
+ * the user turning the WiFi off, or other platform reasons like not being able
+ * to support NAN concurrency with the host. The terminated event will have a
+ * reason code appropriately populated to denote why the event was sent.
+ */
+#define CHRE_EVENT_WIFI_NAN_SESSION_TERMINATED  CHRE_WIFI_EVENT_ID(6)
+
+// NOTE: Do not add new events with ID > 15; only values 0-15 are reserved
+// (see chre/event.h)
+
+/**
+ * The maximum amount of time that is allowed to elapse between a call to
+ * chreWifiRequestScanAsync() that returns true, and the associated
+ * CHRE_EVENT_WIFI_ASYNC_RESULT used to indicate whether the scan completed
+ * successfully or not.
+ */
+#define CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS  (30 * CHRE_NSEC_PER_SEC)
+
+/**
+ * The maximum amount of time that is allowed to elapse between a call to
+ * chreWifiRequestRangingAsync() that returns true, and the associated
+ * CHRE_EVENT_WIFI_RANGING_RESULT used to indicate whether the ranging operation
+ * completed successfully or not.
+ */
+#define CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS  (30 * CHRE_NSEC_PER_SEC)
+
+/**
+ * The current compatibility version of the chreWifiScanEvent structure,
+ * including nested structures.
+ */
+#define CHRE_WIFI_SCAN_EVENT_VERSION  UINT8_C(1)
+
+/**
+ * The current compatibility version of the chreWifiRangingEvent structure,
+ * including nested structures.
+ */
+#define CHRE_WIFI_RANGING_EVENT_VERSION  UINT8_C(0)
+
+/**
+ * Maximum number of frequencies that can be explicitly specified when
+ * requesting a scan
+ * @see #chreWifiScanParams
+ */
+#define CHRE_WIFI_FREQUENCY_LIST_MAX_LEN  (20)
+
+/**
+ * Maximum number of SSIDs that can be explicitly specified when requesting a
+ * scan
+ * @see #chreWifiScanParams
+ */
+#define CHRE_WIFI_SSID_LIST_MAX_LEN  (20)
+
+/**
+ * The maximum number of devices that can be specified in a single RTT ranging
+ * request.
+ * @see #chreWifiRangingParams
+ */
+#define CHRE_WIFI_RANGING_LIST_MAX_LEN  (10)
+
+/**
+ * The maximum number of octets in an SSID (see 802.11 7.3.2.1)
+ */
+#define CHRE_WIFI_SSID_MAX_LEN  (32)
+
+/**
+ * The number of octets in a BSSID (see 802.11 7.1.3.3.3)
+ */
+#define CHRE_WIFI_BSSID_LEN  (6)
+
+/**
+ * Set of flags which can either indicate a frequency band. Specified as a bit
+ * mask to allow for combinations in future API versions.
+ * @defgroup CHRE_WIFI_BAND_MASK
+ * @{
+ */
+
+#define CHRE_WIFI_BAND_MASK_2_4_GHZ  UINT8_C(1 << 0)  //!< 2.4 GHz
+#define CHRE_WIFI_BAND_MASK_5_GHZ    UINT8_C(1 << 1)  //!< 5 GHz
+
+/** @} */
+
+/**
+ * Characteristics of a scanned device given in struct chreWifiScanResult.flags
+ * @defgroup CHRE_WIFI_SCAN_RESULT_FLAGS
+ * @{
+ */
+
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_NONE                         UINT8_C(0)
+
+//! Element ID 61 (HT Operation) is present (see HT 7.3.2)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_HT_OPS_PRESENT               UINT8_C(1 << 0)
+
+//! Element ID 192 (VHT Operation) is present (see VHT 8.4.2)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_VHT_OPS_PRESENT              UINT8_C(1 << 1)
+
+//! Element ID 127 (Extended Capabilities) is present, and bit 70 (Fine Timing
+//! Measurement Responder) is set to 1 (see IEEE Std 802.11-2016 9.4.2.27)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER             UINT8_C(1 << 2)
+
+//! Retained for backwards compatibility
+//! @see CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_IS_80211MC_RTT_RESPONDER \
+    CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER
+
+//! HT Operation element indicates that a secondary channel is present
+//! (see HT 7.3.2.57)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_HAS_SECONDARY_CHANNEL_OFFSET UINT8_C(1 << 3)
+
+//! HT Operation element indicates that the secondary channel is below the
+//! primary channel (see HT 7.3.2.57)
+#define CHRE_WIFI_SCAN_RESULT_FLAGS_SECONDARY_CHANNEL_OFFSET_IS_BELOW  \
+                                                                 UINT8_C(1 << 4)
+
+/** @} */
+
+/**
+ * Identifies the authentication methods supported by an AP. Note that not every
+ * combination of flags may be possible. Based on WIFI_PNO_AUTH_CODE_* from
+ * hardware/libhardware_legacy/include/hardware_legacy/gscan.h in Android.
+ * @defgroup CHRE_WIFI_SECURITY_MODE_FLAGS
+ * @{
+ */
+
+#define CHRE_WIFI_SECURITY_MODE_UNKONWN  UINT8_C(0)
+
+#define CHRE_WIFI_SECURITY_MODE_OPEN  UINT8_C(1 << 0)  //!< No auth/security
+#define CHRE_WIFI_SECURITY_MODE_WEP   UINT8_C(1 << 1)
+#define CHRE_WIFI_SECURITY_MODE_PSK   UINT8_C(1 << 2)  //!< WPA-PSK or WPA2-PSK
+#define CHRE_WIFI_SECURITY_MODE_EAP   UINT8_C(1 << 3)  //!< WPA-EAP or WPA2-EAP
+
+//! @since v1.5
+#define CHRE_WIFI_SECURITY_MODE_SAE   UINT8_C(1 << 4)
+
+//! @since v1.5
+#define CHRE_WIFI_SECURITY_MODE_EAP_SUITE_B  UINT8_C(1 << 5)
+
+//! @since v1.5
+#define CHRE_WIFI_SECURITY_MODE_OWE   UINT8_C(1 << 6)
+
+/** @} */
+
+/**
+ * Identifies which radio chain was used to discover an AP. The underlying
+ * hardware does not necessarily support more than one radio chain.
+ * @defgroup CHRE_WIFI_RADIO_CHAIN_FLAGS
+ * @{
+ */
+
+#define CHRE_WIFI_RADIO_CHAIN_UNKNOWN  UINT8_C(0)
+#define CHRE_WIFI_RADIO_CHAIN_0        UINT8_C(1 << 0)
+#define CHRE_WIFI_RADIO_CHAIN_1        UINT8_C(1 << 1)
+
+/** @} */
+
+//! Special value indicating that an LCI uncertainty fields is not provided
+//! Ref: RFC 6225
+#define CHRE_WIFI_LCI_UNCERTAINTY_UNKNOWN  UINT8_C(0)
+
+/**
+ * Defines the flags that may be returned in
+ * {@link #chreWifiRangingResult.flags}. Undefined bits are reserved for future
+ * use and must be ignored by nanoapps.
+ * @defgroup CHRE_WIFI_RTT_RESULT_FLAGS
+ * @{
+ */
+
+//! If set, the nested chreWifiLci structure is populated; otherwise it is
+//! invalid and must be ignored
+#define CHRE_WIFI_RTT_RESULT_HAS_LCI  UINT8_C(1 << 0)
+
+/** @} */
+
+/**
+ * Identifies a WiFi frequency band
+ */
+enum chreWifiBand {
+    CHRE_WIFI_BAND_2_4_GHZ = CHRE_WIFI_BAND_MASK_2_4_GHZ,
+    CHRE_WIFI_BAND_5_GHZ   = CHRE_WIFI_BAND_MASK_5_GHZ,
+};
+
+/**
+ * Indicates the BSS operating channel width determined from the VHT and/or HT
+ * Operation elements. Refer to VHT 8.4.2.161 and HT 7.3.2.57.
+ */
+enum chreWifiChannelWidth {
+    CHRE_WIFI_CHANNEL_WIDTH_20_MHZ         = 0,
+    CHRE_WIFI_CHANNEL_WIDTH_40_MHZ         = 1,
+    CHRE_WIFI_CHANNEL_WIDTH_80_MHZ         = 2,
+    CHRE_WIFI_CHANNEL_WIDTH_160_MHZ        = 3,
+    CHRE_WIFI_CHANNEL_WIDTH_80_PLUS_80_MHZ = 4,
+};
+
+/**
+ * Indicates the type of scan requested or performed
+ */
+enum chreWifiScanType {
+    //! Perform a purely active scan using probe requests. Do not scan channels
+    //! restricted to use via Dynamic Frequency Selection (DFS) only.
+    CHRE_WIFI_SCAN_TYPE_ACTIVE = 0,
+
+    //! Perform an active scan on unrestricted channels, and also perform a
+    //! passive scan on channels that are restricted to use via Dynamic
+    //! Frequency Selection (DFS), e.g. the U-NII bands 5250-5350MHz and
+    //! 5470-5725MHz in the USA as mandated by FCC regulation.
+    CHRE_WIFI_SCAN_TYPE_ACTIVE_PLUS_PASSIVE_DFS = 1,
+
+    //! Perform a passive scan, only listening for beacons.
+    CHRE_WIFI_SCAN_TYPE_PASSIVE = 2,
+
+    //! Client has no preference for a particular scan type.
+    //! Only valid in a {@link #chreWifiScanParams}.
+    //!
+    //! On a v1.4 or earlier platform, this will fall back to
+    //! CHRE_WIFI_SCAN_TYPE_ACTIVE if {@link #chreWifiScanParams.channelSet} is
+    //! set to CHRE_WIFI_CHANNEL_SET_NON_DFS, and to
+    //! CHRE_WIFI_SCAN_TYPE_ACTIVE_PLUS_PASSIVE_DFS otherwise.
+    //!
+    //! If CHRE_WIFI_CAPABILITIES_RADIO_CHAIN_PREF is supported, a v1.5 or
+    //! later platform shall perform a type of scan optimized for {@link
+    //! #chreWifiScanParams.radioChainPref}.
+    //!
+    //! Clients are strongly encouraged to set this value in {@link
+    //! #chreWifiScanParams.scanType} and instead express their preferences
+    //! through {@link #chreWifiRadioChainPref} and {@link #chreWifiChannelSet}
+    //! so the platform can best optimize power and performance.
+    //!
+    //! @since v1.5
+    CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE = 3,
+};
+
+/**
+ * Indicates whether RTT ranging with a specific device succeeded
+ */
+enum chreWifiRangingStatus {
+    //! Ranging completed successfully
+    CHRE_WIFI_RANGING_STATUS_SUCCESS = 0,
+
+    //! Ranging failed due to an unspecified error
+    CHRE_WIFI_RANGING_STATUS_ERROR   = 1,
+};
+
+/**
+ * Possible values for {@link #chreWifiLci.altitudeType}. Ref: RFC 6225 2.4
+ */
+enum chreWifiLciAltitudeType {
+    CHRE_WIFI_LCI_ALTITUDE_TYPE_UNKNOWN = 0,
+    CHRE_WIFI_LCI_ALTITUDE_TYPE_METERS  = 1,
+    CHRE_WIFI_LCI_ALTITUDE_TYPE_FLOORS  = 2,
+};
+
+/**
+ * Indicates a type of request made in this API. Used to populate the resultType
+ * field of struct chreAsyncResult sent with CHRE_EVENT_WIFI_ASYNC_RESULT.
+ */
+enum chreWifiRequestType {
+    CHRE_WIFI_REQUEST_TYPE_CONFIGURE_SCAN_MONITOR = 1,
+    CHRE_WIFI_REQUEST_TYPE_REQUEST_SCAN           = 2,
+    CHRE_WIFI_REQUEST_TYPE_RANGING                = 3,
+    CHRE_WIFI_REQUEST_TYPE_NAN_SUBSCRIBE          = 4,
+};
+
+/**
+ * Allows a nanoapp to express its preference for how multiple available
+ * radio chains should be used when performing an on-demand scan. This is only a
+ * preference from the nanoapp and is not guaranteed to be honored by the WiFi
+ * firmware.
+ */
+enum chreWifiRadioChainPref {
+    //! No preference for radio chain usage
+    CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT = 0,
+
+    //! In a scan result, indicates that the radio chain preference used for the
+    //! scan is not known
+    CHRE_WIFI_RADIO_CHAIN_PREF_UNKNOWN = CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT,
+
+    //! Prefer to use available radio chains in a way that minimizes time to
+    //! complete the scan
+    CHRE_WIFI_RADIO_CHAIN_PREF_LOW_LATENCY = 1,
+
+    //! Prefer to use available radio chains in a way that minimizes total power
+    //! consumed for the scan
+    CHRE_WIFI_RADIO_CHAIN_PREF_LOW_POWER = 2,
+
+    //! Prefer to use available radio chains in a way that maximizes accuracy of
+    //! the scan result, e.g. RSSI measurements
+    CHRE_WIFI_RADIO_CHAIN_PREF_HIGH_ACCURACY = 3,
+};
+
+/**
+ * WiFi NAN subscription type.
+ */
+enum chreWifiNanSubscribeType {
+    //! In the active mode, explicit transmission of a subscribe message is
+    //! requested, and publish messages are processed.
+    CHRE_WIFI_NAN_SUBSCRIBE_TYPE_ACTIVE = 0,
+
+    //! In the passive mode, no transmission of a subscribe message is
+    //! requested, but received publish messages are checked for matches.
+    CHRE_WIFI_NAN_SUBSCRIBE_TYPE_PASSIVE = 1,
+};
+
+/**
+ * Indicates the reason for a subscribe session termination.
+ */
+enum chreWifiNanTerminatedReason {
+    CHRE_WIFI_NAN_TERMINATED_BY_USER_REQUEST = 0,
+    CHRE_WIFI_NAN_TERMINATED_BY_TIMEOUT = 1,
+    CHRE_WIFI_NAN_TERMINATED_BY_FAILURE = 2,
+};
+
+/**
+ * SSID with an explicit length field, used when an array of SSIDs is supplied.
+ */
+struct chreWifiSsidListItem {
+    //! Number of valid bytes in ssid. Valid range [0, CHRE_WIFI_SSID_MAX_LEN]
+    uint8_t ssidLen;
+
+    //! Service Set Identifier (SSID)
+    uint8_t ssid[CHRE_WIFI_SSID_MAX_LEN];
+};
+
+/**
+ * Indicates the set of channels to be scanned.
+ *
+ * @since v1.5
+ */
+enum chreWifiChannelSet {
+    //! The set of channels that allows active scan using probe request.
+    CHRE_WIFI_CHANNEL_SET_NON_DFS = 0,
+
+    //! The set of all channels supported.
+    CHRE_WIFI_CHANNEL_SET_ALL = 1,
+};
+
+/**
+ * Data structure passed to chreWifiRequestScanAsync
+ */
+struct chreWifiScanParams {
+    //! Set to a value from @ref enum chreWifiScanType
+    uint8_t scanType;
+
+    //! Indicates whether the client is willing to tolerate receiving cached
+    //! results of a previous scan, and if so, the maximum age of the scan that
+    //! the client will accept. "Age" in this case is defined as the elapsed
+    //! time between when the most recent scan was completed and the request is
+    //! received, in milliseconds. If set to 0, no cached results may be
+    //! provided, and all scan results must come from a "fresh" WiFi scan, i.e.
+    //! one that completes strictly after this request is received. If more than
+    //! one scan is cached and meets this age threshold, only the newest scan is
+    //! provided.
+    uint32_t maxScanAgeMs;
+
+    //! If set to 0, scan all frequencies. Otherwise, this indicates the number
+    //! of frequencies to scan, as specified in the frequencyList array. Valid
+    //! range [0, CHRE_WIFI_FREQUENCY_LIST_MAX_LEN].
+    uint16_t frequencyListLen;
+
+    //! Pointer to an array of frequencies to scan, given as channel center
+    //! frequencies in MHz. This field may be NULL if frequencyListLen is 0.
+    const uint32_t *frequencyList;
+
+    //! If set to 0, do not restrict scan to any SSIDs. Otherwise, this
+    //! indicates the number of SSIDs in the ssidList array to be used for
+    //! directed probe requests. Not applicable and ignore when scanType is
+    //! CHRE_WIFI_SCAN_TYPE_PASSIVE.
+    uint8_t ssidListLen;
+
+    //! Pointer to an array of SSIDs to use for directed probe requests. May be
+    //! NULL if ssidListLen is 0.
+    const struct chreWifiSsidListItem *ssidList;
+
+    //! Set to a value from enum chreWifiRadioChainPref to specify the desired
+    //! trade-off between power consumption, accuracy, etc. If
+    //! chreWifiGetCapabilities() does not have the applicable bit set, this
+    //! parameter is ignored.
+    //! @since v1.2
+    uint8_t radioChainPref;
+
+    //! Set to a value from enum chreWifiChannelSet to specify the set of
+    //! channels to be scanned. This field is considered by the platform only
+    //! if scanType is CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE and frequencyListLen
+    //! is equal to zero.
+    //!
+    //! @since v1.5
+    uint8_t channelSet;
+};
+
+/**
+ * Provides information about a single access point (AP) detected in a scan.
+ */
+struct chreWifiScanResult {
+    //! Number of milliseconds prior to referenceTime in the enclosing
+    //! chreWifiScanEvent struct when the probe response or beacon frame that
+    //! was used to populate this structure was received.
+    uint32_t ageMs;
+
+    //! Capability Information field sent by the AP (see 802.11 7.3.1.4). This
+    //! field must reflect native byte order and bit ordering, such that
+    //! (capabilityInfo & 1) gives the bit for the ESS subfield.
+    uint16_t capabilityInfo;
+
+    //! Number of valid bytes in ssid. Valid range [0, CHRE_WIFI_SSID_MAX_LEN]
+    uint8_t ssidLen;
+
+    //! Service Set Identifier (SSID), a series of 0 to 32 octets identifying
+    //! the access point. Note that this is commonly a human-readable ASCII
+    //! string, but this is not the required encoding per the standard.
+    uint8_t ssid[CHRE_WIFI_SSID_MAX_LEN];
+
+    //! Basic Service Set Identifier (BSSID), represented in big-endian byte
+    //! order, such that the first octet of the OUI is accessed in byte index 0.
+    uint8_t bssid[CHRE_WIFI_BSSID_LEN];
+
+    //! A set of flags from CHRE_WIFI_SCAN_RESULT_FLAGS_*
+    uint8_t flags;
+
+    //! RSSI (Received Signal Strength Indicator), in dBm. Typically negative.
+    //! If multiple radio chains were used to scan this AP, this is a "best
+    //! available" measure that may be a composite of measurements taken across
+    //! the radio chains.
+    int8_t  rssi;
+
+    //! Operating band, set to a value from enum chreWifiBand
+    uint8_t band;
+
+    /**
+     * Indicates the center frequency of the primary 20MHz channel, given in
+     * MHz. This value is derived from the channel number via the formula:
+     *
+     *     primaryChannel (MHz) = CSF + 5 * primaryChannelNumber
+     *
+     * Where CSF is the channel starting frequency (in MHz) given by the
+     * operating class/band (i.e. 2407 or 5000), and primaryChannelNumber is the
+     * channel number in the range [1, 200].
+     *
+     * Refer to VHT 22.3.14.
+     */
+    uint32_t primaryChannel;
+
+    /**
+     * If the channel width is 20 MHz, this field is not relevant and set to 0.
+     * If the channel width is 40, 80, or 160 MHz, then this denotes the channel
+     * center frequency (in MHz). If the channel is 80+80 MHz, then this denotes
+     * the center frequency of segment 0, which contains the primary channel.
+     * This value is derived from the frequency index using the same formula as
+     * for primaryChannel.
+     *
+     * Refer to VHT 8.4.2.161, and VHT 22.3.14.
+     *
+     * @see #primaryChannel
+     */
+    uint32_t centerFreqPrimary;
+
+    /**
+     * If the channel width is 80+80MHz, then this denotes the center frequency
+     * of segment 1, which does not contain the primary channel. Otherwise, this
+     * field is not relevant and set to 0.
+     *
+     * @see #centerFreqPrimary
+     */
+    uint32_t centerFreqSecondary;
+
+    //! @see #chreWifiChannelWidth
+    uint8_t channelWidth;
+
+    //! Flags from CHRE_WIFI_SECURITY_MODE_* indicating supported authentication
+    //! and associated security modes
+    //! @see CHRE_WIFI_SECURITY_MODE_FLAGS
+    uint8_t securityMode;
+
+    //! Identifies the radio chain(s) used to discover this AP
+    //! @see CHRE_WIFI_RADIO_CHAIN_FLAGS
+    //! @since v1.2
+    uint8_t radioChain;
+
+    //! If the CHRE_WIFI_RADIO_CHAIN_0 bit is set in radioChain, gives the RSSI
+    //! measured on radio chain 0 in dBm; otherwise invalid and set to 0. This
+    //! field, along with its relative rssiChain1, can be used to determine RSSI
+    //! measurements from each radio chain when multiple chains were used to
+    //! discover this AP.
+    //! @see #radioChain
+    //! @since v1.2
+    int8_t rssiChain0;
+    int8_t rssiChain1;  //!< @see #rssiChain0
+
+    //! Reserved; set to 0
+    uint8_t reserved[7];
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_WIFI_SCAN_RESULT.
+ */
+struct chreWifiScanEvent {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that the client only receives the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! The number of entries in the results array in this event. The CHRE
+    //! implementation may split scan results across multiple events for memory
+    //! concerns, etc.
+    uint8_t resultCount;
+
+    //! The total number of results returned by the scan. Allows an event
+    //! consumer to identify when it has received all events associated with a
+    //! scan.
+    uint8_t resultTotal;
+
+    //! Sequence number for this event within the series of events comprising a
+    //! complete scan result. Scan events are delivered strictly in order, i.e.
+    //! this is monotonically increasing for the results of a single scan. Valid
+    //! range [0, <number of events for scan> - 1]. The number of events for a
+    //! scan is typically given by
+    //! ceil(resultTotal / <max results per event supported by platform>).
+    uint8_t eventIndex;
+
+    //! A value from enum chreWifiScanType indicating the type of scan performed
+    uint8_t scanType;
+
+    //! If a directed scan was performed to a limited set of SSIDs, then this
+    //! identifies the number of unique SSIDs included in the probe requests.
+    //! Otherwise, this is set to 0, indicating that the scan was not limited by
+    //! SSID. Note that if this is non-zero, the list of SSIDs used is not
+    //! included in the scan event.
+    uint8_t ssidSetSize;
+
+    //! If 0, indicates that all frequencies applicable for the scanType were
+    //! scanned. Otherwise, indicates the number of frequencies scanned, as
+    //! specified in scannedFreqList.
+    uint16_t scannedFreqListLen;
+
+    //! Timestamp when the scan was completed, from the same time base as
+    //! chreGetTime() (in nanoseconds)
+    uint64_t referenceTime;
+
+    //! Pointer to an array containing scannedFreqListLen values comprising the
+    //! set of frequencies that were scanned. Frequencies are specified as
+    //! channel center frequencies in MHz. May be NULL if scannedFreqListLen is
+    //! 0.
+    const uint32_t *scannedFreqList;
+
+    //! Pointer to an array containing resultCount entries. May be NULL if
+    //! resultCount is 0.
+    const struct chreWifiScanResult *results;
+
+    //! Set to a value from enum chreWifiRadioChainPref indicating the radio
+    //! chain preference used for the scan. If the applicable bit is not set in
+    //! chreWifiGetCapabilities(), this will always be set to
+    //! CHRE_WIFI_RADIO_CHAIN_PREF_UNKNOWN.
+    //! @since v1.2
+    uint8_t radioChainPref;
+};
+
+/**
+ * Identifies a device to perform RTT ranging against. These values are normally
+ * populated based on the contents of a scan result.
+ * @see #chreWifiScanResult
+ * @see chreWifiRangingTargetFromScanResult()
+ */
+struct chreWifiRangingTarget {
+    //! Device MAC address, specified in the same byte order as
+    //! {@link #chreWifiScanResult.bssid}
+    uint8_t macAddress[CHRE_WIFI_BSSID_LEN];
+
+    //! Center frequency of the primary 20MHz channel, in MHz
+    //! @see #chreWifiScanResult.primaryChannel
+    uint32_t primaryChannel;
+
+    //! Channel center frequency, in MHz, or 0 if not relevant
+    //! @see #chreWifiScanResult.centerFreqPrimary
+    uint32_t centerFreqPrimary;
+
+    //! Channel center frequency of segment 1 if channel width is 80+80MHz,
+    //! otherwise 0
+    //! @see #chreWifiScanResult.centerFreqSecondary
+    uint32_t centerFreqSecondary;
+
+    //! @see #chreWifiChannelWidth
+    uint8_t channelWidth;
+
+    //! Reserved for future use and ignored by CHRE
+    uint8_t reserved[3];
+};
+
+/**
+ * Parameters for an RTT ("Fine Timing Measurement" in terms of 802.11-2016)
+ * ranging request, supplied to chreWifiRequestRangingAsync().
+ */
+struct chreWifiRangingParams {
+    //! Number of devices to perform ranging against and the length of
+    //! targetList, in range [1, CHRE_WIFI_RANGING_LIST_MAX_LEN].
+    uint8_t targetListLen;
+
+    //! Array of macAddressListLen MAC addresses (e.g. BSSIDs) with which to
+    //! attempt RTT ranging.
+    const struct chreWifiRangingTarget *targetList;
+};
+
+/**
+ * Provides the result of RTT ranging with a single device.
+ */
+struct chreWifiRangingResult {
+    //! Time when the ranging operation on this device was performed, in the
+    //! same time base as chreGetTime() (in nanoseconds)
+    uint64_t timestamp;
+
+    //! MAC address of the device for which ranging was requested
+    uint8_t macAddress[CHRE_WIFI_BSSID_LEN];
+
+    //! Gives the result of ranging to this device. If not set to
+    //! CHRE_WIFI_RANGING_STATUS_SUCCESS, the ranging attempt to this device
+    //! failed, and other fields in this structure may be invalid.
+    //! @see #chreWifiRangingStatus
+    uint8_t status;
+
+    //! The mean RSSI measured during the RTT burst, in dBm. Typically negative.
+    //! If status is not CHRE_WIFI_RANGING_STATUS_SUCCESS, will be set to 0.
+    int8_t rssi;
+
+    //! Estimated distance to the device with the given BSSID, in millimeters.
+    //! Generally the mean of multiple measurements performed in a single burst.
+    //! If status is not CHRE_WIFI_RANGING_STATUS_SUCCESS, will be set to 0.
+    uint32_t distance;
+
+    //! Standard deviation of estimated distance across multiple measurements
+    //! performed in a single RTT burst, in millimeters. If status is not
+    //! CHRE_WIFI_RANGING_STATUS_SUCCESS, will be set to 0.
+    uint32_t distanceStdDev;
+
+    //! Location Configuration Information (LCI) information optionally returned
+    //! during the ranging procedure. Only valid if {@link #flags} has the
+    //! CHRE_WIFI_RTT_RESULT_HAS_LCI bit set. Refer to IEEE 802.11-2016
+    //! 9.4.2.22.10, 11.24.6.7, and RFC 6225 (July 2011) for more information.
+    //! Coordinates are to be interpreted according to the WGS84 datum.
+    struct chreWifiLci {
+        //! Latitude in degrees as 2's complement fixed-point with 25 fractional
+        //! bits, i.e. degrees * 2^25. Ref: RFC 6225 2.3
+        int64_t latitude;
+
+        //! Longitude, same format as {@link #latitude}
+        int64_t longitude;
+
+        //! Altitude represented as a 2's complement fixed-point value with 8
+        //! fractional bits. Interpretation depends on {@link #altitudeType}. If
+        //! UNKNOWN, this field must be ignored. If *METERS, distance relative
+        //! to the zero point in the vertical datum. If *FLOORS, a floor value
+        //! relative to the ground floor, potentially fractional, e.g. to
+        //! indicate mezzanine levels. Ref: RFC 6225 2.4
+        int32_t altitude;
+
+        //! Maximum extent of latitude uncertainty in degrees, decoded via this
+        //! formula: 2 ^ (8 - x) where "x" is the encoded value passed in this
+        //! field. Unknown if set to CHRE_WIFI_LCI_UNCERTAINTY_UNKNOWN.
+        //! Ref: RFC 6225 2.3.2
+        uint8_t latitudeUncertainty;
+
+        //! @see #latitudeUncertainty
+        uint8_t longitudeUncertainty;
+
+        //! Defines how to interpret altitude, set to a value from enum
+        //! chreWifiLciAltitudeType
+        uint8_t altitudeType;
+
+        //! Uncertainty in altitude, decoded via this formula: 2 ^ (21 - x)
+        //! where "x" is the encoded value passed in this field. Unknown if set
+        //! to CHRE_WIFI_LCI_UNCERTAINTY_UNKNOWN. Only applies when altitudeType
+        //! is CHRE_WIFI_LCI_ALTITUDE_TYPE_METERS. Ref: RFC 6225 2.4.5
+        uint8_t altitudeUncertainty;
+    } lci;
+
+    //! Refer to CHRE_WIFI_RTT_RESULT_FLAGS
+    uint8_t flags;
+
+    //! Reserved; set to 0
+    uint8_t reserved[7];
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_WIFI_RANGING_RESULT.
+ */
+struct chreWifiRangingEvent {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that the client only receives the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! The number of ranging results included in the results array; matches the
+    //! number of MAC addresses specified in the request
+    uint8_t resultCount;
+
+    //! Reserved; set to 0
+    uint8_t reserved[2];
+
+    //! Pointer to an array containing resultCount entries
+    const struct chreWifiRangingResult *results;
+};
+
+/**
+ * Indicates the WiFi NAN capabilities of the device. Must contain non-zero
+ * values if WiFi NAN is supported.
+ */
+struct chreWifiNanCapabilities {
+    //! Maximum length of the match filter arrays (applies to both tx and rx
+    //! match filters).
+    uint32_t maxMatchFilterLength;
+
+    //! Maximum length of the service specific information byte array.
+    uint32_t maxServiceSpecificInfoLength;
+
+    //! Maximum length of the service name. Includes the NULL terminator.
+    uint8_t maxServiceNameLength;
+
+    //! Reserved for future use.
+    uint8_t reserved[3];
+};
+
+/**
+ * Data structure sent with events of type
+ * CHRE_EVENT_WIFI_NAN_IDENTIFIER_RESULT
+ */
+struct chreWifiNanIdentifierEvent {
+    //! A unique ID assigned by the NAN engine for the subscribe request
+    //! associated with the cookie encapsulated in the async result below. The
+    //! ID is set to 0 if there was a request failure in which case the async
+    //! result below contains the appropriate error code indicating the failure
+    //! reason.
+    uint32_t id;
+
+    //! Structure which contains the cookie associated with the publish/
+    //! subscribe request, along with an error code that indicates request
+    //! success or failure.
+    struct chreAsyncResult result;
+};
+
+/**
+ * Indicates the desired configuration for a WiFi NAN ranging request.
+ */
+struct chreWifiNanRangingParams {
+    //! MAC address of the NAN device for which range is to be determined.
+    uint8_t macAddress[CHRE_WIFI_BSSID_LEN];
+};
+
+/**
+ * Configuration parameters specific to the Subscribe Function (Spec 4.1.1.1)
+ */
+struct chreWifiNanSubscribeConfig {
+    //! Indicates the subscribe type, set to a value from @ref
+    //! chreWifiNanSubscribeType.
+    uint8_t subscribeType;
+
+    //! UTF-8 name string that identifies the service/application. Must be NULL
+    //! terminated. Note that the string length cannot be greater than the
+    //! maximum length specified by @ref chreWifiNanCapabilities. No
+    //! restriction is placed on the string case, since the service name
+    //! matching is expected to be case insensitive.
+    const char *service;
+
+    //! An array of bytes (and the associated array length) of service-specific
+    //! information. Note that the array length must be less than the
+    //! maxServiceSpecificInfoLength parameter obtained from the NAN
+    //! capabilities (@see struct chreWifiNanCapabilities).
+    const uint8_t *serviceSpecificInfo;
+    uint32_t serviceSpecificInfoSize;
+
+    //! Ordered sequence of {length | value} pairs that specify match criteria
+    //! beyond the service name. 'length' uses 1 byte, and its value indicates
+    //! the number of bytes of the match criteria that follow. The length of
+    //! the match filter array should not exceed the maximum match filter
+    //! length obtained from @ref chreWifiNanGetCapabilities. When a service
+    //! publish message discovery frame containing the Service ID being
+    //! subscribed to is received, the matching is done as follows:
+    //! Each {length | value} pair in the kth position (1 <= k <= #length-value
+    //! pairs) is compared against the kth {length | value} pair in the
+    //! matching filter field of the publish message.
+    //! - For a kth position {length | value} pair in the rx match filter with
+    //!   a length of 0, a match is declared regardless of the tx match filter
+    //!   contents.
+    //! - For a kth position {length | value} pair in the rx match with a non-
+    //!   zero length, there must be an exact match with the kth position pair
+    //!    in the match filter field of the received service descriptor for a
+    //!    match to be found.
+    //! Please refer to Appendix H of the NAN spec for examples on matching.
+    //! The match filter length should not exceed the maxMatchFilterLength
+    //! obtained from @ref chreWifiNanCapabilities.
+    const uint8_t *matchFilter;
+    uint32_t matchFilterLength;
+};
+
+/**
+ * Data structure sent with events of type
+ * CHRE_EVENT_WIFI_NAN_DISCOVERY_RESULT.
+ */
+struct chreWifiNanDiscoveryEvent {
+    //! Identifier of the subscribe function instance that requested a
+    //! discovery.
+    uint32_t subscribeId;
+
+    //! Identifier of the publisher on the remote NAN device.
+    uint32_t publishId;
+
+    //! NAN interface address of the publisher
+    uint8_t publisherAddress[CHRE_WIFI_BSSID_LEN];
+
+    //! An array of bytes (and the associated array length) of service-specific
+    //! information. Note that the array length must be less than the
+    //! maxServiceSpecificInfoLength parameter obtained from the NAN
+    //! capabilities (@see struct chreWifiNanCapabilities).
+    const uint8_t *serviceSpecificInfo;
+    uint32_t serviceSpecificInfoSize;
+};
+
+/**
+ * Data structure sent with events of type CHRE_EVENT_WIFI_NAN_SESSION_LOST.
+ */
+struct chreWifiNanSessionLostEvent {
+    //! The original ID (returned by the NAN discovery engine) of the subscriber
+    //! instance.
+    uint32_t id;
+
+    //! The ID of the previously discovered publisher on a peer NAN device that
+    //! is no longer connected.
+    uint32_t peerId;
+};
+
+/**
+ * Data structure sent with events of type
+ * CHRE_EVENT_WIFI_NAN_SESSION_TERMINATED.
+ */
+struct chreWifiNanSessionTerminatedEvent {
+    //! The original ID (returned by the NAN discovery engine) of the subscriber
+    //! instance that was terminated.
+    uint32_t id;
+
+    //! A value that maps to one of the termination reasons in @ref enum
+    //! chreWifiNanTerminatedReason.
+    uint8_t reason;
+
+    //! Reserved for future use.
+    uint8_t reserved[3];
+};
+
+/**
+ * Retrieves a set of flags indicating the WiFi features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the Nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_WIFI_CAPABILITIES_* flags set
+ *
+ * @since v1.1
+ */
+uint32_t chreWifiGetCapabilities(void);
+
+/**
+ * Retrieves device-specific WiFi NAN capabilities, and populates them in
+ * the @ref chreWifiNanCapabilities structure.
+ *
+ * @param capabilities Structure into which the WiFi NAN capabilities of
+ *        the device are populated into. Must not be NULL.
+ * @return true if WiFi NAN is supported, false otherwise.
+ *
+ * @since v1.6
+ */
+bool chreWifiNanGetCapabilities(struct chreWifiNanCapabilities *capabilities);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_WIFI somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following WiFi APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to WiFi data by adding metadata
+ * to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_WIFI) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Manages a client's request to receive the results of WiFi scans performed for
+ * other purposes, for example scans done to maintain connectivity and scans
+ * requested by other clients. The presence of this request has no effect on the
+ * frequency or configuration of the WiFi scans performed - it is purely a
+ * registration by the client to receive the results of scans that would
+ * otherwise occur normally. This should include all available scan results,
+ * including those that are not normally sent to the applications processor,
+ * such as Preferred Network Offload (PNO) scans. Scan results provided because
+ * of this registration must not contain cached results - they are always
+ * expected to contain the fresh results from a recent scan.
+ *
+ * An active scan monitor subscription must persist across temporary conditions
+ * under which no WiFi scans will be performed, for example if WiFi is
+ * completely disabled via user-controlled settings, or if the WiFi system
+ * restarts independently of CHRE. Likewise, a request to enable a scan monitor
+ * subscription must succeed under normal conditions, even in circumstances
+ * where no WiFi scans will be performed. In these cases, the scan monitor
+ * implementation must produce scan results once the temporary condition is
+ * cleared, for example after WiFi is enabled by the user.
+ *
+ * These scan results are delivered to the Nanoapp's handle event callback using
+ * CHRE_EVENT_WIFI_SCAN_RESULT.
+ *
+ * An active scan monitor subscription is not necessary to receive the results
+ * of an on-demand scan request sent via chreWifiRequestScanAsync(), and it does
+ * not result in duplicate delivery of scan results generated from
+ * chreWifiRequestScanAsync().
+ *
+ * If no monitor subscription is active at the time of a request with
+ * enable=false, it is treated as if an active subscription was successfully
+ * ended.
+ *
+ * The result of this request is delivered asynchronously via an event of type
+ * CHRE_EVENT_WIFI_ASYNC_RESULT. Refer to the note in {@link #chreAsyncResult}
+ * for more details.
+ *
+ * @param enable Set to true to enable monitoring scan results, false to
+ *        disable
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WiFi permission
+ */
+bool chreWifiConfigureScanMonitorAsync(bool enable, const void *cookie);
+
+/**
+ * Sends an on-demand request for WiFi scan results. This may trigger a new
+ * scan, or be entirely serviced from cache, depending on the maxScanAgeMs
+ * parameter.
+ *
+ * This resulting status of this request is delivered asynchronously via an
+ * event of type CHRE_EVENT_WIFI_ASYNC_RESULT. The result must be delivered
+ * within CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS of the this request. Refer to the
+ * note in {@link #chreAsyncResult} for more details.
+ *
+ * A successful result provided in CHRE_EVENT_WIFI_ASYNC_RESULT indicates that
+ * the scan results are ready to be delivered in a subsequent event (or events,
+ * which arrive consecutively without any other scan results in between)
+ * of type CHRE_EVENT_WIFI_SCAN_RESULT.
+ *
+ * WiFi scanning must be disabled if both "WiFi scanning" and "WiFi" settings
+ * are disabled at the Android level. In this case, the CHRE implementation is
+ * expected to return a result with CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * It is not valid for a client to request a new scan while a result is pending
+ * based on a previous scan request from the same client. In this situation, the
+ * CHRE implementation is expected to return a result with CHRE_ERROR_BUSY.
+ * However, if a scan is currently pending or in progress due to a request from
+ * another client, whether within the CHRE or otherwise, the implementation must
+ * not fail the request for this reason. If the pending scan satisfies the
+ * client's request parameters, then the implementation should use its results
+ * to satisfy the request rather than scheduling a new scan.
+ *
+ * @param params A set of parameters for the scan request. Must not be NULL.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WiFi permission
+ */
+bool chreWifiRequestScanAsync(const struct chreWifiScanParams *params,
+                              const void *cookie);
+
+/**
+ * Convenience function which calls chreWifiRequestScanAsync() with a default
+ * set of scan parameters.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WiFi permission
+ */
+static inline bool chreWifiRequestScanAsyncDefault(const void *cookie) {
+    static const struct chreWifiScanParams params = {
+        /*.scanType=*/         CHRE_WIFI_SCAN_TYPE_NO_PREFERENCE,
+        /*.maxScanAgeMs=*/     5000,  // 5 seconds
+        /*.frequencyListLen=*/ 0,
+        /*.frequencyList=*/    NULL,
+        /*.ssidListLen=*/      0,
+        /*.ssidList=*/         NULL,
+        /*.radioChainPref=*/   CHRE_WIFI_RADIO_CHAIN_PREF_DEFAULT,
+        /*.channelSet=*/       CHRE_WIFI_CHANNEL_SET_NON_DFS
+    };
+    return chreWifiRequestScanAsync(&params, cookie);
+}
+
+/**
+ * Issues a request to initiate distance measurements using round-trip time
+ * (RTT), aka Fine Timing Measurement (FTM), to one or more devices identified
+ * by MAC address. Within CHRE, MACs are typically the BSSIDs of scanned APs
+ * that have the CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER flag set.
+ *
+ * This resulting status of this request is delivered asynchronously via an
+ * event of type CHRE_EVENT_WIFI_ASYNC_RESULT. The result must be delivered
+ * within CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS of the this request. Refer to the
+ * note in {@link #chreAsyncResult} for more details.
+ *
+ * WiFi RTT ranging must be disabled if any of the following is true:
+ * - Both "WiFi" and "WiFi Scanning" settings are disabled at the Android level.
+ * - The "Location" setting is disabled at the Android level.
+ * In this case, the CHRE implementation is expected to return a result with
+ * CHRE_ERROR_FUNCTION_DISABLED.
+ *
+ * A successful result provided in CHRE_EVENT_WIFI_ASYNC_RESULT indicates that
+ * the results of ranging will be delivered in a subsequent event of type
+ * CHRE_EVENT_WIFI_RANGING_RESULT. Note that the CHRE_EVENT_WIFI_ASYNC_RESULT
+ * gives an overall status - for example, it is used to indicate failure if the
+ * entire ranging request was rejected because WiFi is disabled. However, it is
+ * valid for this event to indicate success, but RTT ranging to fail for all
+ * requested devices - for example, they may be out of range. Therefore, it is
+ * also necessary to check the status field in {@link #chreWifiRangingResult}.
+ *
+ * @param params Structure containing the parameters of the scan request,
+ *        including the list of devices to attempt ranging.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.2
+ * @note Requires WiFi permission
+ */
+bool chreWifiRequestRangingAsync(const struct chreWifiRangingParams *params,
+                                 const void *cookie);
+
+/**
+ * Helper function to populate an instance of struct chreWifiRangingTarget with
+ * the contents of a scan result provided in struct chreWifiScanResult.
+ * Populates other parameters that are not directly derived from the scan result
+ * with default values.
+ *
+ * @param scanResult The scan result to parse as input
+ * @param rangingTarget The RTT ranging target to populate as output
+ *
+ * @note Requires WiFi permission
+ */
+static inline void chreWifiRangingTargetFromScanResult(
+        const struct chreWifiScanResult *scanResult,
+        struct chreWifiRangingTarget *rangingTarget) {
+    memcpy(rangingTarget->macAddress, scanResult->bssid,
+           sizeof(rangingTarget->macAddress));
+    rangingTarget->primaryChannel      = scanResult->primaryChannel;
+    rangingTarget->centerFreqPrimary   = scanResult->centerFreqPrimary;
+    rangingTarget->centerFreqSecondary = scanResult->centerFreqSecondary;
+    rangingTarget->channelWidth        = scanResult->channelWidth;
+
+    // Note that this is not strictly necessary (CHRE can see which API version
+    // the nanoapp was built against, so it knows to ignore these fields), but
+    // we do it here to keep things nice and tidy
+    memset(rangingTarget->reserved, 0, sizeof(rangingTarget->reserved));
+}
+
+/**
+ * Subscribe to a NAN service.
+ *
+ * Sends a subscription request to the NAN discovery engine with the
+ * specified configration parameters. If successful, a unique non-zero
+ * subscription ID associated with this instance of the subscription
+ * request is assigned by the NAN discovery engine. The subscription request
+ * is active until explicitly canceled, or if the connection was interrupted.
+ *
+ * Note that CHRE forwards any discovery events that it receives to the
+ * subscribe function instance, and does no duplicate filtering. If
+ * multiple events of the same discovery are undesirable, it is up to the
+ * platform NAN discovery engine implementation to implement redundancy
+ * detection mechanisms.
+ *
+ * If WiFi is turned off by the user at the Android level, an existing
+ * subscribe session is canceled, and a CHRE_EVENT_WIFI_ASYNC_RESULT event is
+ * event is sent to the subscriber. Nanoapps are expected to register for user
+ * settings notifications (@see chreUserSettingConfigureEvents), and
+ * re-establish a subscribe session on a WiFi re-enabled settings changed
+ * notification.
+ *
+ * @param config Service subscription configuration
+ * @param cookie A value that the nanoapp uses to track this particular
+ *        subscription request.
+ * @return true if NAN is enabled and a subscription request was successfully
+ *         made to the NAN engine. The actual result of the service discovery
+ *         is sent via a CHRE_EVENT_WIFI_NAN_DISCOVERY_RESULT event.
+ *
+ * @since v1.6
+ * @note Requires WiFi permission
+ */
+bool chreWifiNanSubscribe(struct chreWifiNanSubscribeConfig *config,
+                          const void *cookie);
+
+/**
+ * Cancel a subscribe function instance.
+ *
+ * @param subscriptionId The ID that was originally assigned to this instance
+ *        of the subscribe function.
+ * @return true if NAN is enabled, the subscribe ID  was found and the instance
+ *         successfully canceled.
+ *
+ * @since v1.6
+ * @note Requires WiFi permission
+ */
+bool chreWifiNanSubscribeCancel(uint32_t subscriptionID);
+
+/**
+ * Request RTT ranging from a peer NAN device.
+ *
+ * Nanoapps can use this API to explicitly request measurement reports from
+ * the peer device. Note that both end points have to support ranging for a
+ * successful request. The MAC address of the peer NAN device for which ranging
+ * is desired may be obtained either from a NAN service discovery or from an
+ * out-of-band source (HAL service, BLE, etc.).
+ *
+ * If WiFi is turned off by the user at the Android level, an existing
+ * ranging session is canceled, and a CHRE_EVENT_WIFI_ASYNC_RESULT event is
+ * sent to the subscriber. Nanoapps are expected to register for user settings
+ * notifications (@see chreUserSettingConfigureEvents), and perform another
+ * ranging request on a WiFi re-enabled settings changed notification.
+ *
+ * A successful result provided in CHRE_EVENT_WIFI_ASYNC_RESULT indicates that
+ * the results of ranging will be delivered in a subsequent event of type
+ * CHRE_EVENT_WIFI_RANGING_RESULT.
+ *
+ * @param params Structure containing the parameters of the ranging request,
+ *        including the MAC address of the peer NAN device to attempt ranging.
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ * @return true if the request was accepted for processing, false otherwise.
+ */
+bool chreWifiNanRequestRangingAsync(const struct chreWifiNanRangingParams *params,
+                                    const void *cookie);
+
+#else  /* defined(CHRE_NANOAPP_USES_WIFI) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_WIFI_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_WIFI must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreWifiConfigureScanMonitorAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiConfigureScanMonitorAsync")
+#define chreWifiRequestScanAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiRequestScanAsync")
+#define chreWifiRequestScanAsyncDefault(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiRequestScanAsyncDefault")
+#define chreWifiRequestRangingAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiRequestRangingAsync")
+#define chreWifiRangingTargetFromScanResult(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING \
+                     "chreWifiRangingTargetFromScanResult")
+#define chreWifiNanSubscribe(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiNanSubscribe")
+#define chreWifiNanSubscribeCancel(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiNanSubscribeCancel")
+#define chreWifiNanRequestRangingAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WIFI_PERM_ERROR_STRING "chreWifiNanRequestRangingAsync")
+#endif  /* defined(CHRE_NANOAPP_USES_WIFI) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_WIFI_H_ */
diff --git a/chre_api/legacy/v1_7/chre/wwan.h b/chre_api/legacy/v1_7/chre/wwan.h
new file mode 100644
index 0000000..51cf5f9
--- /dev/null
+++ b/chre_api/legacy/v1_7/chre/wwan.h
@@ -0,0 +1,591 @@
+/*
+ * 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_WWAN_H_
+#define _CHRE_WWAN_H_
+
+/**
+ * @file
+ * Wireless Wide Area Network (WWAN, i.e. mobile/cellular network) API relevant
+ * for querying cell tower identity and associated information that can be
+ * useful in determining location.
+ *
+ * Based on Android N RIL definitions (located at this path as of the time of
+ * this comment: hardware/ril/include/telephony/ril.h), version 12. Updated
+ * based on Android radio HAL definition (hardware/interfaces/radio) for more
+ * recent Android builds. Refer to those files and associated documentation for
+ * further details.
+ *
+ * In general, the parts of this API that are taken from the RIL follow the
+ * field naming conventions established in that interface rather than the CHRE
+ * API conventions, in order to avoid confusion and enable code re-use where
+ * applicable. Note that structure names include the chreWwan* prefix rather
+ * than RIL_*, but field names are the same. If necessary to enable code
+ * sharing, it is recommended to create typedefs that map from the CHRE
+ * structures to the associated RIL type names, for example "typedef struct
+ * chreWwanCellIdentityGsm RIL_CellIdentityGsm_v12", etc.
+ */
+
+#include <chre/common.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The set of flags returned by chreWwanGetCapabilities().
+ * @defgroup CHRE_WWAN_CAPABILITIES
+ * @{
+ */
+
+//! No WWAN APIs are supported
+#define CHRE_WWAN_CAPABILITIES_NONE  UINT32_C(0)
+
+//! Current cell information can be queried via chreWwanGetCellInfoAsync()
+#define CHRE_WWAN_GET_CELL_INFO      UINT32_C(1 << 0)
+
+/** @} */
+
+/**
+ * Produce an event ID in the block of IDs reserved for WWAN
+ * @param offset  Index into WWAN event ID block; valid range [0,15]
+ */
+#define CHRE_WWAN_EVENT_ID(offset)  (CHRE_EVENT_WWAN_FIRST_EVENT + (offset))
+
+/**
+ * nanoappHandleEvent argument: struct chreWwanCellInfoResult
+ *
+ * Provides the result of an asynchronous request for cell info sent via
+ * chreWwanGetCellInfoAsync().
+ */
+#define CHRE_EVENT_WWAN_CELL_INFO_RESULT  CHRE_WWAN_EVENT_ID(0)
+
+// NOTE: Do not add new events with ID > 15; only values 0-15 are reserved
+// (see chre/event.h)
+
+/**
+ * The current version of struct chreWwanCellInfoResult associated with this
+ * API definition.
+ */
+#define CHRE_WWAN_CELL_INFO_RESULT_VERSION  UINT8_C(1)
+
+//! Reference: RIL_CellIdentityGsm_v12
+struct chreWwanCellIdentityGsm {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 16-bit Location Area Code, 0..65535, INT32_MAX if unknown
+    int32_t lac;
+
+    //! 16-bit GSM Cell Identity described in TS 27.007, 0..65535,
+    //! INT32_MAX if unknown
+    int32_t cid;
+
+    //! 16-bit GSM Absolute RF channel number, INT32_MAX if unknown
+    int32_t arfcn;
+
+    //! 6-bit Base Station Identity Code, UINT8_MAX if unknown
+    uint8_t bsic;
+
+    //! Reserved for future use; must be set to 0
+    uint8_t reserved[3];
+};
+
+//! Reference: RIL_CellIdentityWcdma_v12
+struct chreWwanCellIdentityWcdma {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 16-bit Location Area Code, 0..65535, INT32_MAX if unknown
+    int32_t lac;
+
+    //! 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455,
+    //! INT32_MAX if unknown
+    int32_t cid;
+
+    //! 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511,
+    //! INT32_MAX if unknown
+    int32_t psc;
+
+    //! 16-bit UMTS Absolute RF Channel Number, INT32_MAX if unknown
+    int32_t uarfcn;
+};
+
+//! Reference: RIL_CellIdentityCdma
+struct chreWwanCellIdentityCdma {
+    //! Network Id 0..65535, INT32_MAX if unknown
+    int32_t networkId;
+
+    //! CDMA System Id 0..32767, INT32_MAX if unknown
+    int32_t systemId;
+
+    //! Base Station Id 0..65535, INT32_MAX if unknown
+    int32_t basestationId;
+
+    //! Longitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0.
+    //! It is represented in units of 0.25 seconds and ranges from -2592000
+    //! to 2592000, both values inclusive (corresponding to a range of -180
+    //! to +180 degrees). INT32_MAX if unknown
+    int32_t longitude;
+
+    //! Latitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0.
+    //! It is represented in units of 0.25 seconds and ranges from -1296000
+    //! to 1296000, both values inclusive (corresponding to a range of -90
+    //! to +90 degrees). INT32_MAX if unknown
+    int32_t latitude;
+};
+
+//! Reference: RIL_CellIdentityLte_v12
+struct chreWwanCellIdentityLte {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 28-bit Cell Identity described in TS ???, INT32_MAX if unknown
+    int32_t ci;
+
+    //! physical cell id 0..503, INT32_MAX if unknown
+    int32_t pci;
+
+    //! 16-bit tracking area code, INT32_MAX if unknown
+    int32_t tac;
+
+    //! 18-bit LTE Absolute RF Channel Number, INT32_MAX if unknown
+    int32_t earfcn;
+};
+
+//! Reference: RIL_CellIdentityTdscdma
+struct chreWwanCellIdentityTdscdma {
+    //! 3-digit Mobile Country Code, 0..999, INT32_MAX if unknown
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, 0..999, INT32_MAX if unknown
+    int32_t mnc;
+
+    //! 16-bit Location Area Code, 0..65535, INT32_MAX if unknown
+    int32_t lac;
+
+    //! 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455,
+    //! INT32_MAX if unknown
+    int32_t cid;
+
+    //! 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT32_MAX if
+    //! unknown
+    int32_t cpid;
+};
+
+//! Reference: [email protected] CellIdentityNr
+//! @since v1.4
+struct chreWwanCellIdentityNr {
+    //! 3-digit Mobile Country Code, in range [0, 999]. This value must be valid
+    //! for registered or camped cells. INT32_MAX means invalid/unreported.
+    int32_t mcc;
+
+    //! 2 or 3-digit Mobile Network Code, in range [0, 999]. This value must be
+    //! valid for registered or camped cells. INT32_MAX means
+    //! invalid/unreported.
+    int32_t mnc;
+
+    //! NR Cell Identity in range [0, 68719476735] (36 bits), which
+    //! unambiguously identifies a cell within a public land mobile network
+    //! (PLMN). This value must be valid for registered or camped cells.
+    //! Reference: TS 38.413 section 9.3.1.7.
+    //!
+    //! Note: for backward compatibility reasons, the nominally int64_t nci is
+    //! split into two uint32_t values, with nci0 being the least significant 4
+    //! bytes. If chreWwanUnpackNrNci returns INT64_MAX, it means nci is
+    //! invalid/unreported.
+    //!
+    //! Users are recommended to use the helper accessor chreWwanUnpackNrNci to
+    //! access the nci field.
+    //!
+    //! @see chreWwanUnpackNrNci
+    uint32_t nci0;
+    uint32_t nci1;
+
+    //! Physical cell id in range [0, 1007]. This value must be valid.
+    //! Reference: TS 38.331 section 6.3.2.
+    int32_t pci;
+
+    //! 24-bit tracking area code in range [0, 16777215]. INT32_MAX means
+    //! invalid/unreported.
+    //! Reference: TS 38.413 section 9.3.3.10 and TS 29.571 section 5.4.2.
+    int32_t tac;
+
+    //! NR Absolute Radio Frequency Channel Number, in range [0, 3279165]. This
+    //! value must be valid.
+    //! Reference: TS 38.101-1 section 5.4.2.1 and TS 38.101-2 section 5.4.2.1.
+    int32_t nrarfcn;
+};
+
+//! Reference: RIL_GSM_SignalStrength_v12
+struct chreWwanSignalStrengthGsm {
+    //! Valid values are (0-31, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t signalStrength;
+
+    //! bit error rate (0-7, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t bitErrorRate;
+
+    //! Timing Advance in bit periods. 1 bit period = 48.13 us.
+    //! INT32_MAX means invalid/unreported.
+    int32_t timingAdvance;
+};
+
+//! Reference: RIL_SignalStrengthWcdma
+struct chreWwanSignalStrengthWcdma {
+    //! Valid values are (0-31, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t signalStrength;
+
+    //! bit error rate (0-7, 99) as defined in TS 27.007 8.5
+    //! INT32_MAX means invalid/unreported.
+    int32_t bitErrorRate;
+};
+
+//! Reference: RIL_CDMA_SignalStrength
+struct chreWwanSignalStrengthCdma {
+    //! Valid values are positive integers.  This value is the actual RSSI value
+    //! multiplied by -1.  Example: If the actual RSSI is -75, then this
+    //! response value will be 75.
+    //! INT32_MAX means invalid/unreported.
+    int32_t dbm;
+
+    //! Valid values are positive integers.  This value is the actual Ec/Io
+    //! multiplied by -10.  Example: If the actual Ec/Io is -12.5 dB, then this
+    //! response value will be 125.
+    //! INT32_MAX means invalid/unreported.
+    int32_t ecio;
+};
+
+//! Reference: RIL_EVDO_SignalStrength
+struct chreWwanSignalStrengthEvdo {
+    //! Valid values are positive integers.  This value is the actual RSSI value
+    //! multiplied by -1.  Example: If the actual RSSI is -75, then this
+    //! response value will be 75.
+    //! INT32_MAX means invalid/unreported.
+    int32_t dbm;
+
+    //! Valid values are positive integers.  This value is the actual Ec/Io
+    //! multiplied by -10.  Example: If the actual Ec/Io is -12.5 dB, then this
+    //! response value will be 125.
+    //! INT32_MAX means invalid/unreported.
+    int32_t ecio;
+
+    //! Valid values are 0-8.  8 is the highest signal to noise ratio.
+    //! INT32_MAX means invalid/unreported.
+    int32_t signalNoiseRatio;
+};
+
+//! Reference: RIL_LTE_SignalStrength_v8
+struct chreWwanSignalStrengthLte {
+    //! Valid values are (0-31, 99) as defined in TS 27.007 8.5
+    int32_t signalStrength;
+
+    //! The current Reference Signal Receive Power in dBm multiplied by -1.
+    //! Range: 44 to 140 dBm
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.133 9.1.4
+    int32_t rsrp;
+
+    //! The current Reference Signal Receive Quality in dB multiplied by -1.
+    //! Range: 3 to 20 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.133 9.1.7
+    int32_t rsrq;
+
+    //! The current reference signal signal-to-noise ratio in 0.1 dB units.
+    //! Range: -200 to +300 (-200 = -20.0 dB, +300 = 30dB).
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.101 8.1.1
+    int32_t rssnr;
+
+    //! The current Channel Quality Indicator.
+    //! Range: 0 to 15.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 36.101 9.2, 9.3, A.4
+    int32_t cqi;
+
+    //! timing advance in micro seconds for a one way trip from cell to device.
+    //! Approximate distance can be calculated using 300m/us * timingAdvance.
+    //! Range: 0 to 0x7FFFFFFE
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP 36.321 section 6.1.3.5
+    //! also: http://www.cellular-planningoptimization.com/2010/02/timing-advance-with-calculation.html
+    int32_t timingAdvance;
+};
+
+//! Reference: RIL_TD_SCDMA_SignalStrength
+struct chreWwanSignalStrengthTdscdma {
+    //! The Received Signal Code Power in dBm multiplied by -1.
+    //! Range : 25 to 120
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: 3GPP TS 25.123, section 9.1.1.1
+    int32_t rscp;
+};
+
+//! Reference: [email protected] NrSignalStrength
+//! @since v1.4
+struct chreWwanSignalStrengthNr {
+    //! SS (second synchronization) reference signal received power in dBm
+    //! multiplied by -1.
+    //! Range [44, 140], INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.1 and TS 38.133 section 10.1.6.
+    int32_t ssRsrp;
+
+    //! SS reference signal received quality in 0.5 dB units.
+    //! Range [-86, 41] with -86 = -43.0 dB and 41 = 20.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.3 and TS 38.133 section 10.1.11.1.
+    int32_t ssRsrq;
+
+    //! SS signal-to-noise and interference ratio in 0.5 dB units.
+    //! Range [-46, 81] with -46 = -23.0 dB and 81 = 40.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.5 and TS 38.133 section 10.1.16.1.
+    int32_t ssSinr;
+
+    //! CSI reference signal received power in dBm multiplied by -1.
+    //! Range [44, 140], INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.2 and TS 38.133 section 10.1.6.
+    int32_t csiRsrp;
+
+    //! CSI reference signal received quality in 0.5 dB units.
+    //! Range [-86, 41] with -86 = -43.0 dB and 41 = 20.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.4 and TS 38.133 section 10.1.11.1.
+    int32_t csiRsrq;
+
+    //! CSI signal-to-noise and interference ratio in 0.5 dB units.
+    //! Range [-46, 81] with -46 = -23.0 dB and 81 = 40.5 dB.
+    //! INT32_MAX means invalid/unreported.
+    //! Reference: TS 38.215 section 5.1.6 and TS 38.133 section 10.1.16.1.
+    int32_t csiSinr;
+};
+
+//! Reference: RIL_CellInfoGsm_v12
+struct chreWwanCellInfoGsm {
+    struct chreWwanCellIdentityGsm    cellIdentityGsm;
+    struct chreWwanSignalStrengthGsm  signalStrengthGsm;
+};
+
+//! Reference: RIL_CellInfoWcdma_v12
+struct chreWwanCellInfoWcdma {
+    struct chreWwanCellIdentityWcdma    cellIdentityWcdma;
+    struct chreWwanSignalStrengthWcdma  signalStrengthWcdma;
+};
+
+//! Reference: RIL_CellInfoCdma
+struct chreWwanCellInfoCdma {
+    struct chreWwanCellIdentityCdma    cellIdentityCdma;
+    struct chreWwanSignalStrengthCdma  signalStrengthCdma;
+    struct chreWwanSignalStrengthEvdo  signalStrengthEvdo;
+};
+
+//! Reference: RIL_CellInfoLte_v12
+struct chreWwanCellInfoLte {
+    struct chreWwanCellIdentityLte    cellIdentityLte;
+    struct chreWwanSignalStrengthLte  signalStrengthLte;
+};
+
+//! Reference: RIL_CellInfoTdscdma
+struct chreWwanCellInfoTdscdma {
+    struct chreWwanCellIdentityTdscdma    cellIdentityTdscdma;
+    struct chreWwanSignalStrengthTdscdma  signalStrengthTdscdma;
+};
+
+//! Reference: [email protected] CellInfoNr
+//! @since v1.4
+struct chreWwanCellInfoNr {
+    struct chreWwanCellIdentityNr    cellIdentityNr;
+    struct chreWwanSignalStrengthNr  signalStrengthNr;
+};
+
+//! Reference: RIL_CellInfoType
+//! All other values are reserved and should be ignored by nanoapps.
+enum chreWwanCellInfoType {
+    CHRE_WWAN_CELL_INFO_TYPE_GSM      = 1,
+    CHRE_WWAN_CELL_INFO_TYPE_CDMA     = 2,
+    CHRE_WWAN_CELL_INFO_TYPE_LTE      = 3,
+    CHRE_WWAN_CELL_INFO_TYPE_WCDMA    = 4,
+    CHRE_WWAN_CELL_INFO_TYPE_TD_SCDMA = 5,
+    CHRE_WWAN_CELL_INFO_TYPE_NR       = 6,  //! @since v1.4
+};
+
+//! Reference: RIL_TimeStampType
+enum chreWwanCellTimeStampType {
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_UNKNOWN  = 0,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_ANTENNA  = 1,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_MODEM    = 2,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_OEM_RIL  = 3,
+    CHRE_WWAN_CELL_TIMESTAMP_TYPE_JAVA_RIL = 4,
+};
+
+//! Reference: RIL_CellInfo_v12
+struct chreWwanCellInfo {
+    //! Timestamp in nanoseconds; must be in the same time base as chreGetTime()
+    uint64_t timeStamp;
+
+    //! A value from enum {@link #CellInfoType} indicating the radio access
+    //! technology of the cell, and which field in union CellInfo can be used
+    //! to retrieve additional information
+    uint8_t cellInfoType;
+
+    //! A value from enum {@link #CellTimeStampType} that identifies the source
+    //! of the value in timeStamp. This is typically set to
+    //! CHRE_WWAN_CELL_TIMESTAMP_TYPE_OEM_RIL, and indicates the time given by
+    //! chreGetTime() that an intermediate module received the data from the
+    //! modem and forwarded it to the requesting CHRE client.
+    uint8_t timeStampType;
+
+    //! !0 if this cell is registered, 0 if not registered
+    uint8_t registered;
+
+    //! Reserved for future use; must be set to 0
+    uint8_t reserved;
+
+    //! The value in cellInfoType indicates which field in this union is valid
+    union chreWwanCellInfoPerRat {
+        struct chreWwanCellInfoGsm     gsm;
+        struct chreWwanCellInfoCdma    cdma;
+        struct chreWwanCellInfoLte     lte;
+        struct chreWwanCellInfoWcdma   wcdma;
+        struct chreWwanCellInfoTdscdma tdscdma;
+        struct chreWwanCellInfoNr      nr;  //! @since v1.4
+    } CellInfo;
+};
+
+/**
+ * Data structure provided with events of type CHRE_EVENT_WWAN_CELL_INFO_RESULT.
+ */
+struct chreWwanCellInfoResult {
+    //! Indicates the version of the structure, for compatibility purposes.
+    //! Clients do not normally need to worry about this field; the CHRE
+    //! implementation guarantees that the client only receives the structure
+    //! version it expects.
+    uint8_t version;
+
+    //! Populated with a value from enum {@link #chreError}, indicating whether
+    //! the request failed, and if so, provides the cause of the failure
+    uint8_t errorCode;
+
+    //! The number of valid entries in cells[]
+    uint8_t cellInfoCount;
+
+    //! Reserved for future use; must be set to 0
+    uint8_t reserved;
+
+    //! Set to the cookie parameter given to chreWwanGetCellInfoAsync()
+    const void *cookie;
+
+    //! Pointer to an array of cellInfoCount elements containing information
+    //! about serving and neighbor cells
+    const struct chreWwanCellInfo *cells;
+};
+
+
+/**
+ * Retrieves a set of flags indicating the WWAN features supported by the
+ * current CHRE implementation. The value returned by this function must be
+ * consistent for the entire duration of the Nanoapp's execution.
+ *
+ * The client must allow for more flags to be set in this response than it knows
+ * about, for example if the implementation supports a newer version of the API
+ * than the client was compiled against.
+ *
+ * @return A bitmask with zero or more CHRE_WWAN_CAPABILITIES_* flags set
+ *
+ * @since v1.1
+ */
+uint32_t chreWwanGetCapabilities(void);
+
+/**
+ * Nanoapps must define CHRE_NANOAPP_USES_WWAN somewhere in their build
+ * system (e.g. Makefile) if the nanoapp needs to use the following WWAN APIs.
+ * In addition to allowing access to these APIs, defining this macro will also
+ * ensure CHRE enforces that all host clients this nanoapp talks to have the
+ * required Android permissions needed to listen to WWAN data by adding metadata
+ * to the nanoapp.
+ */
+#if defined(CHRE_NANOAPP_USES_WWAN) || !defined(CHRE_IS_NANOAPP_BUILD)
+
+/**
+ * Query information about the current serving cell and its neighbors. This does
+ * not perform a network scan, but should return state from the current network
+ * registration data stored in the cellular modem. This is effectively the same
+ * as a request for RIL_REQUEST_GET_CELL_INFO_LIST in the RIL.
+ *
+ * The requested cellular information is returned asynchronously via
+ * CHRE_EVENT_WWAN_CELL_INFO_RESULT. The implementation must send this event,
+ * either with successful data or an error status, within
+ * CHRE_ASYNC_RESULT_TIMEOUT_NS.
+ *
+ * If the airplane mode setting is enabled at the Android level, the CHRE
+ * implementation is expected to return a successful asynchronous result with an
+ * empty cell info list.
+ *
+ * @param cookie An opaque value that will be included in the chreAsyncResult
+ *        sent in relation to this request.
+ *
+ * @return true if the request was accepted for processing, false otherwise
+ *
+ * @since v1.1
+ * @note Requires WWAN permission
+ */
+bool chreWwanGetCellInfoAsync(const void *cookie);
+
+/**
+ * Helper accessor for nci in the chreWwanCellIdentityNr struct.
+ *
+ * @return nci or INT64_MAX if invalid/unreported.
+ *
+ * @see chreWwanCellIdentityNr
+ *
+ * @since v1.4
+ * @note Requires WWAN permission
+ */
+static inline int64_t chreWwanUnpackNrNci(
+    const struct chreWwanCellIdentityNr *nrCellId) {
+  return (int64_t) (((uint64_t) nrCellId->nci1 << 32) | nrCellId->nci0);
+}
+
+#else  /* defined(CHRE_NANOAPP_USES_WWAN) || !defined(CHRE_IS_NANOAPP_BUILD) */
+#define CHRE_WWAN_PERM_ERROR_STRING \
+    "CHRE_NANOAPP_USES_WWAN must be defined when building this nanoapp in " \
+    "order to refer to "
+#define chreWwanGetCellInfoAsync(...) \
+    CHRE_BUILD_ERROR(CHRE_WWAN_PERM_ERROR_STRING "chreWwanGetCellInfoAsync")
+#define chreWwanUnpackNrNci(...) \
+    CHRE_BUILD_ERROR(CHRE_WWAN_PERM_ERROR_STRING "chreWwanUnpackNrNci")
+#endif  /* defined(CHRE_NANOAPP_USES_WWAN) || !defined(CHRE_IS_NANOAPP_BUILD) */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _CHRE_WWAN_H_ */
diff --git a/core/audio_request_manager.cc b/core/audio_request_manager.cc
index 3c0790e..47c919d 100644
--- a/core/audio_request_manager.cc
+++ b/core/audio_request_manager.cc
@@ -22,6 +22,7 @@
 #include "chre/platform/system_time.h"
 #include "chre/util/nested_data_ptr.h"
 #include "chre/util/system/debug_dump.h"
+#include "chre/util/system/event_callbacks.h"
 #include "chre/util/time.h"
 
 /*
@@ -59,7 +60,7 @@
                                           uint32_t handle, bool enable,
                                           uint64_t bufferDuration,
                                           uint64_t deliveryInterval) {
-  uint32_t numSamples;
+  uint32_t numSamples = 0;
   return validateConfigureSourceArguments(handle, enable, bufferDuration,
                                           deliveryInterval, &numSamples) &&
          doConfigureSource(nanoapp->getInstanceId(), handle, enable, numSamples,
@@ -86,9 +87,14 @@
   // Cast off the event const so that it can be provided to the callback as
   // non-const. The event is provided to nanoapps as const and the runtime
   // itself will not modify this memory so this is safe.
-  EventLoopManagerSingleton::get()->deferCallback(
-      SystemCallbackType::AudioHandleDataEvent,
-      const_cast<struct chreAudioDataEvent *>(audioDataEvent), callback);
+  struct chreAudioDataEvent *event =
+      const_cast<struct chreAudioDataEvent *>(audioDataEvent);
+  if (!EventLoopManagerSingleton::get()->deferCallback(
+          SystemCallbackType::AudioHandleDataEvent, event, callback)) {
+    EventLoopManagerSingleton::get()
+        ->getAudioRequestManager()
+        .handleFreeAudioDataEvent(event);
+  }
 }
 
 void AudioRequestManager::handleAudioAvailability(uint32_t handle,
diff --git a/core/ble_request.cc b/core/ble_request.cc
index 7eb4262..229eab5 100644
--- a/core/ble_request.cc
+++ b/core/ble_request.cc
@@ -174,14 +174,14 @@
 void BleRequest::logStateToBuffer(DebugDumpWrapper &debugDump,
                                   bool isPlatformRequest) const {
   if (!isPlatformRequest) {
-    debugDump.print("  instanceId=%" PRIu32 " status=%" PRIu8, mInstanceId,
+    debugDump.print("  instanceId=%" PRIu16 " status=%" PRIu8, mInstanceId,
                     static_cast<uint8_t>(mStatus));
   }
   debugDump.print(" %s", mEnabled ? " enable" : " disable\n");
   if (mEnabled) {
-    debugDump.print(" mode=%" PRIu8 " reportDelayMs=%" PRIu32
-                    " rssiThreshold=%" PRId8,
-                    mMode, mReportDelayMs, mRssiThreshold);
+    debugDump.print(
+        " mode=%" PRIu8 " reportDelayMs=%" PRIu32 " rssiThreshold=%" PRId8,
+        static_cast<uint8_t>(mMode), mReportDelayMs, mRssiThreshold);
     if (isPlatformRequest) {
       debugDump.print(" filters=[");
       for (const chreBleGenericFilter &filter : mFilters) {
diff --git a/core/ble_request_manager.cc b/core/ble_request_manager.cc
index f5aef54..811af1d 100644
--- a/core/ble_request_manager.cc
+++ b/core/ble_request_manager.cc
@@ -20,6 +20,7 @@
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/log.h"
 #include "chre/util/nested_data_ptr.h"
+#include "chre/util/system/event_callbacks.h"
 
 namespace chre {
 
@@ -116,6 +117,30 @@
   return 1;
 }
 
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+bool BleRequestManager::readRssiAsync(Nanoapp *nanoapp,
+                                      uint16_t connectionHandle,
+                                      const void *cookie) {
+  CHRE_ASSERT(nanoapp);
+  if (mPendingRssiRequests.full()) {
+    LOG_OOM();
+    return false;
+  }
+  if (mPendingRssiRequests.empty()) {
+    // no previous request existed, so issue this one immediately to get
+    // an early exit if we get a failure
+    auto status = readRssi(connectionHandle);
+    if (status != CHRE_ERROR_NONE) {
+      return false;
+    }
+  }
+  // it's pending, so report the result asynchronously
+  mPendingRssiRequests.push(
+      BleReadRssiRequest{nanoapp->getInstanceId(), connectionHandle, cookie});
+  return true;
+}
+#endif
+
 void BleRequestManager::addBleRequestLog(uint32_t instanceId, bool enabled,
                                          size_t requestIndex,
                                          bool compliesWithBleSetting) {
@@ -235,15 +260,16 @@
 void BleRequestManager::handlePlatformChangeSync(bool enable,
                                                  uint8_t errorCode) {
   bool success = (errorCode == CHRE_ERROR_NONE);
-  if (mPendingPlatformRequest.isEnabled() != enable) {
+  // Requests to disable BLE scans should always succeed
+  if (!mPendingPlatformRequest.isEnabled() && enable) {
     errorCode = CHRE_ERROR;
     success = false;
-    CHRE_ASSERT_LOG(false, "BLE PAL did not transition to expected state");
+    CHRE_ASSERT_LOG(false, "Unable to stop BLE scan");
   }
   if (mInternalRequestPending) {
     mInternalRequestPending = false;
     if (!success) {
-      FATAL_ERROR("Failed to resync BLE platform");
+      LOGE("Failed to resync BLE platform");
     }
   } else {
     for (BleRequest &req : mRequests.getMutableRequests()) {
@@ -355,6 +381,99 @@
   }
 }
 
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+void BleRequestManager::handleReadRssi(uint8_t errorCode,
+                                       uint16_t connectionHandle, int8_t rssi) {
+  struct readRssiResponse {
+    uint8_t errorCode;
+    int8_t rssi;
+    uint16_t connectionHandle;
+  };
+
+  auto callback = [](uint16_t /* eventType */, void *eventData,
+                     void * /* extraData */) {
+    readRssiResponse response = NestedDataPtr<readRssiResponse>(eventData);
+    EventLoopManagerSingleton::get()->getBleRequestManager().handleReadRssiSync(
+        response.errorCode, response.connectionHandle, response.rssi);
+  };
+
+  EventLoopManagerSingleton::get()->deferCallback(
+      SystemCallbackType::BleReadRssiEvent,
+      NestedDataPtr<readRssiResponse>(
+          readRssiResponse{errorCode, rssi, connectionHandle}),
+      callback);
+}
+
+void BleRequestManager::handleReadRssiSync(uint8_t errorCode,
+                                           uint16_t connectionHandle,
+                                           int8_t rssi) {
+  if (mPendingRssiRequests.empty()) {
+    FATAL_ERROR(
+        "Got unexpected handleReadRssi event without outstanding request");
+  }
+
+  if (mPendingRssiRequests.front().connectionHandle != connectionHandle) {
+    FATAL_ERROR(
+        "Got readRssi event for mismatched connection handle (%d != %d)",
+        mPendingRssiRequests.front().connectionHandle, connectionHandle);
+  }
+
+  resolvePendingRssiRequest(errorCode, rssi);
+  dispatchNextRssiRequestIfAny();
+}
+
+void BleRequestManager::resolvePendingRssiRequest(uint8_t errorCode,
+                                                  int8_t rssi) {
+  auto event = memoryAlloc<chreBleReadRssiEvent>();
+  if (event == nullptr) {
+    FATAL_ERROR("Failed to alloc BLE async result");
+  }
+
+  event->result.cookie = mPendingRssiRequests.front().cookie;
+  event->result.success = (errorCode == CHRE_ERROR_NONE);
+  event->result.requestType = CHRE_BLE_REQUEST_TYPE_READ_RSSI;
+  event->result.errorCode = errorCode;
+  event->result.reserved = 0;
+  event->connectionHandle = mPendingRssiRequests.front().connectionHandle;
+  event->rssi = rssi;
+
+  EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie(
+      CHRE_EVENT_BLE_RSSI_READ, event, freeEventDataCallback,
+      mPendingRssiRequests.front().instanceId);
+
+  mPendingRssiRequests.pop();
+}
+
+void BleRequestManager::dispatchNextRssiRequestIfAny() {
+  while (!mPendingRssiRequests.empty()) {
+    auto req = mPendingRssiRequests.front();
+    auto status = readRssi(req.connectionHandle);
+    if (status == CHRE_ERROR_NONE) {
+      // control flow resumes in the handleReadRssi() callback, on completion
+      return;
+    }
+    resolvePendingRssiRequest(status, 0x7F /* failure RSSI from BT spec */);
+  }
+}
+
+uint8_t BleRequestManager::readRssi(uint16_t connectionHandle) {
+  if (!bleSettingEnabled()) {
+    return CHRE_ERROR_FUNCTION_DISABLED;
+  }
+  auto success = mPlatformBle.readRssiAsync(connectionHandle);
+  if (success) {
+    return CHRE_ERROR_NONE;
+  } else {
+    return CHRE_ERROR;
+  }
+}
+#endif
+
+bool BleRequestManager::getScanStatus(struct chreBleScanStatus * /* status */) {
+  // TODO(b/266820139): Implement this
+  return false;
+}
+
 void BleRequestManager::onSettingChanged(Setting setting, bool /* state */) {
   if (setting == Setting::BLE_AVAILABLE) {
     if (asyncResponsePending()) {
@@ -424,7 +543,7 @@
 }
 
 bool BleRequestManager::isValidAdType(uint8_t adType) {
-  return adType == CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16;
+  return adType == CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE;
 }
 
 bool BleRequestManager::bleSettingEnabled() {
@@ -459,8 +578,8 @@
     if (log.enable && log.compliesWithBleSetting) {
       debugDump.print(" mode=%" PRIu8 " reportDelayMs=%" PRIu32
                       " rssiThreshold=%" PRId8 " scanCount=%" PRIu8 "\n",
-                      log.mode, log.reportDelayMs, log.rssiThreshold,
-                      log.scanFilterCount);
+                      static_cast<uint8_t>(log.mode), log.reportDelayMs,
+                      log.rssiThreshold, log.scanFilterCount);
     } else if (log.enable) {
       debugDump.print(" request did not comply with BLE setting\n");
     }
diff --git a/core/chre_metrics.proto b/core/chre_metrics.proto
new file mode 100644
index 0000000..d06df84
--- /dev/null
+++ b/core/chre_metrics.proto
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! DISCLAIMER !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// The messages definition here must be in sync with atoms definitions in
+// hardware/google/pixel/pixelstats/pixelatoms.proto
+
+// C++ namespace: android.chre.metrics
+package android.chre.metrics;
+
+option optimize_for = LITE_RUNTIME;
+
+option java_package = "android.chre";
+option java_outer_classname = "Metrics";
+
+/**
+ * Logs an event indicating that a nanoapp loading has failed at the HAL.
+ */
+message ChreHalNanoappLoadFailed {
+  // Vendor reverse domain name (expecting "com.google.pixel").
+  optional string reverse_domain_name = 1;
+
+  enum Type {
+    TYPE_UNKNOWN = 0;
+    // Corresponds to preloaded nanoapps on the device.
+    TYPE_PRELOADED = 1;
+    // Dynamic loading of a nanoapp (e.g. code download).
+    TYPE_DYNAMIC = 2;
+  }
+
+  enum Reason {
+    REASON_UNKNOWN = 0;
+    // A generic error code that does not match any of the others.
+    REASON_ERROR_GENERIC = 1;
+    // Failure at the connection between HAL<->CHRE.
+    REASON_CONNECTION_ERROR = 2;
+    // System ran out of memory.
+    REASON_OOM = 3;
+    // Nanoapp did not have the right signing for loading.
+    REASON_SIGNATURE_MISMATCH = 4;
+  }
+
+  // The 64-bit unique nanoapp identifier of the nanoapp that failed.
+  optional int64 nanoapp_id = 2;
+
+  // The type of the load event.
+  optional Type type = 3;
+
+  // The reason for the failure.
+  optional Reason reason = 4;
+}
+
+/**
+ * An enum describing a module within CHRE.
+ */
+enum ChreModuleType {
+  CHRE_MODULE_TYPE_UNKNOWN = 0;
+  CHRE_MODULE_TYPE_CHRE = 1;  // Core CHRE framework
+  CHRE_MODULE_TYPE_PAL = 2;   // PAL module (could be CHPP)
+  CHRE_MODULE_TYPE_NANOAPP = 3;
+}
+
+/**
+ * An enum describing the CHRE PAL type.
+ */
+enum ChrePalType {
+  CHRE_PAL_TYPE_UNKNOWN = 0;
+  CHRE_PAL_TYPE_SENSOR = 1;
+  CHRE_PAL_TYPE_WIFI = 2;
+  CHRE_PAL_TYPE_GNSS = 3;
+  CHRE_PAL_TYPE_WWAN = 4;
+  CHRE_PAL_TYPE_AUDIO = 5;
+  CHRE_PAL_TYPE_BLE = 6;
+}
+
+/**
+ * Logs an event indicating that a CHRE PAL open has failed.
+ */
+message ChrePalOpenFailed {
+  // Vendor reverse domain name (expecting "com.google.pixel").
+  optional string reverse_domain_name = 1;
+
+  enum Type {
+    TYPE_UNKNOWN = 0;
+    // Initial open when CHRE starts.
+    INITIAL_OPEN = 1;
+    // Any form of "reopen" event internally in the PAL.
+    REOPEN = 2;
+  }
+
+  // The PAL this failure event is for.
+  optional ChrePalType pal = 2;
+
+  // The type of failure observed.
+  optional Type type = 3;
+}
+
+/**
+ * The type of CHRE API request.
+ */
+enum ChreApiType {
+  CHRE_API_TYPE_UNKNOWN = 0;
+  CHRE_API_TYPE_WIFI_SCAN_MONITOR = 1;
+  CHRE_API_TYPE_WIFI_ACTIVE_SCAN = 2;
+  CHRE_API_TYPE_WIFI_RTT_RANGING = 3;
+}
+
+/**
+ * The type of CHRE API error.
+ */
+enum ChreError {
+  CHRE_ERROR_UNKNOWN = 0;
+
+  // No error occurred.
+  CHRE_ERROR_NONE = 1;
+
+  // An unspecified failure occurred.
+  CHRE_ERROR = 2;
+
+  // One or more supplied arguments are invalid.
+  CHRE_ERROR_INVALID_ARGUMENT = 3;
+
+  // Unable to satisfy request because the system is busy.
+  CHRE_ERROR_BUSY = 4;
+
+  // Unable to allocate memory.
+  CHRE_ERROR_NO_MEMORY = 5;
+
+  // The requested feature is not supported.
+  CHRE_ERROR_NOT_SUPPORTED = 6;
+
+  // A timeout occurred while processing the request.
+  CHRE_ERROR_TIMEOUT = 7;
+
+  // The relevant capability is disabled, for example due to a user
+  // configuration that takes precedence over this request.
+  CHRE_ERROR_FUNCTION_DISABLED = 8;
+}
+
+/**
+ * Distribution of CHRE API error codes.
+ */
+message ChreApiErrorCodeDistributionTaken {
+  // Vendor reverse domain name (expecting "com.google.pixel").
+  optional string reverse_domain_name = 1;
+
+  // The chreGetTime() value when this snapshot was taken, in milliseconds.
+  optional int32 snapshot_chre_get_time_ms = 2;
+
+  // The CHRE API type.
+  optional ChreApiType api_type = 3;
+
+  // Corresponds to the CHRE error code that occurred, as defined in the
+  // "enum chreError" field in chre_api/chre/common.h.
+  optional ChreError error_code = 4;
+  optional int32 num_errors = 5;
+}
+
+/**
+ * Snapshot of the dynamic memory allocated in CHRE.
+ */
+message ChreDynamicMemorySnapshotReported {
+  // Vendor reverse domain name (expecting "com.google.pixel").
+  optional string reverse_domain_name = 1;
+
+  // The chreGetTime() value when this snapshot was taken, in milliseconds.
+  optional int32 snapshot_chre_get_time_ms = 2;
+
+  // The type of the module.
+  optional ChreModuleType module_type = 3;
+
+  // The unique 64-bit ID for a nanoapp, only used if the module_type is
+  // NANOAPP. If module_type is PAL, then it represents the ChrePalType enum. If
+  // the module_type is CHRE, then a zero value should be used.
+  optional int64 pal_type_or_nanoapp_id = 4;
+
+  // The max allocation amount of this module in bytes.
+  optional int32 max_allocation_bytes = 5;
+
+  // The current allocation amount of this module in bytes.
+  optional int32 current_allocation_bytes = 6;
+}
+
+/**
+ * Snapshot of the event queue stats in CHRE.
+ */
+message ChreEventQueueSnapshotReported {
+  // Vendor reverse domain name (expecting "com.google.pixel").
+  optional string reverse_domain_name = 1;
+
+  // The chreGetTime() value when this snapshot was taken, in milliseconds.
+  optional int32 snapshot_chre_get_time_ms = 2;
+
+  // The maximum size the event queue got to (i.e. num pending events).
+  optional int32 max_event_queue_size = 3;
+
+  // The average size the event queue got to (i.e. num pending events).
+  optional int32 mean_event_queue_size = 4;
+
+  // The number of events that were dropped due to capacity limits.
+  optional int32 num_dropped_events = 5;
+
+  // The maximum amount of time it took for an event, from when it was received,
+  // to when it was delivered to all interested modules. This value represents
+  // the total delay within the CHRE subsystem.
+  optional int64 max_queue_delay_us = 6;
+
+  // The mean value of the delay in microseconds.
+  optional int64 mean_queue_delay_us = 7;
+}
+
+/**
+ * Indicates that a nanoapp has woken up the AP.
+ */
+message ChreApWakeUpOccurred {
+  // Vendor reverse domain name (expecting "com.google.pixel").
+  optional string reverse_domain_name = 1;
+
+  // The 64-bit unique nanoapp identifier that describes the entity that has
+  // caused an AP wake-up from CHRE. Whenever this event occurs, this means that
+  // the nanoapp sent a message to the AP causing a transition between
+  // suspend/wake-up.
+  optional int64 nanoapp_id = 2;
+}
diff --git a/core/core.mk b/core/core.mk
index 3cfed3c..02aa3ec 100644
--- a/core/core.mk
+++ b/core/core.mk
@@ -15,12 +15,13 @@
 COMMON_SRCS += $(CHRE_PREFIX)/core/event_loop_manager.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/event_ref_queue.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/host_comms_manager.cc
-COMMON_SRCS += $(CHRE_PREFIX)/core/host_notifications.cc
+COMMON_SRCS += $(CHRE_PREFIX)/core/host_endpoint_manager.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/init.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/log.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/nanoapp.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/settings.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/static_nanoapps.cc
+COMMON_SRCS += $(CHRE_PREFIX)/core/system_health_monitor.cc
 COMMON_SRCS += $(CHRE_PREFIX)/core/timer_pool.cc
 
 # Optional audio support.
@@ -70,8 +71,7 @@
 
 NANOPB_EXTENSION = nanopb
 
-NANOPB_SRCS += $(CHRE_PREFIX)/../../hardware/google/pixel/pixelstats/pixelatoms.proto
-NANOPB_INCLUDES = $(CHRE_PREFIX)/../../hardware/google/pixel/pixelstats/
+NANOPB_SRCS += $(CHRE_PREFIX)/core/chre_metrics.proto
 
 include $(CHRE_PREFIX)/build/nanopb.mk
 endif
diff --git a/core/event_loop.cc b/core/event_loop.cc
index febcc95..56354c9 100644
--- a/core/event_loop.cc
+++ b/core/event_loop.cc
@@ -16,6 +16,7 @@
 
 #include "chre/core/event_loop.h"
 #include <cinttypes>
+#include <cstdint>
 
 #include "chre/core/event.h"
 #include "chre/core/event_loop_manager.h"
@@ -28,6 +29,7 @@
 #include "chre/util/conditional_lock_guard.h"
 #include "chre/util/lock_guard.h"
 #include "chre/util/system/debug_dump.h"
+#include "chre/util/system/event_callbacks.h"
 #include "chre/util/system/stats_container.h"
 #include "chre/util/time.h"
 #include "chre_api/chre/version.h"
@@ -39,6 +41,16 @@
 
 namespace {
 
+#ifndef CHRE_STATIC_EVENT_LOOP
+using DynamicMemoryPool =
+    SynchronizedExpandableMemoryPool<Event, CHRE_EVENT_PER_BLOCK,
+                                     CHRE_MAX_EVENT_BLOCKS>;
+#endif
+// TODO(b/264108686): Make this a compile time parameter.
+// How many low priority event to remove if the event queue is full
+// and a new event needs to be pushed.
+constexpr size_t targetLowPriorityEventRemove = 4;
+
 /**
  * Populates a chreNanoappInfo structure using info from the given Nanoapp
  * instance.
@@ -56,12 +68,33 @@
     info->appId = app->getAppId();
     info->version = app->getAppVersion();
     info->instanceId = app->getInstanceId();
+    if (app->getTargetApiVersion() >= CHRE_API_VERSION_1_8) {
+      CHRE_ASSERT(app->getRpcServices().size() <= Nanoapp::kMaxRpcServices);
+      info->rpcServiceCount =
+          static_cast<uint8_t>(app->getRpcServices().size());
+      info->rpcServices = app->getRpcServices().data();
+      memset(&info->reserved, 0, sizeof(info->reserved));
+    }
     success = true;
   }
 
   return success;
 }
 
+#ifndef CHRE_STATIC_EVENT_LOOP
+/**
+ * @return true if a event is a low priority event.
+ */
+bool isLowPriorityEvent(Event *event) {
+  CHRE_ASSERT_NOT_NULL(event);
+  return event->isLowPriority;
+}
+
+void deallocateFromMemoryPool(Event *event, void *memoryPool) {
+  static_cast<DynamicMemoryPool *>(memoryPool)->deallocate(event);
+}
+#endif
+
 }  // anonymous namespace
 
 bool EventLoop::findNanoappInstanceIdByAppId(uint64_t appId,
@@ -238,14 +271,40 @@
   return unloaded;
 }
 
+bool EventLoop::removeLowPriorityEventsFromBack(size_t removeNum) {
+#ifdef CHRE_STATIC_EVENT_LOOP
+  return false;
+#else
+  if (removeNum == 0) {
+    return true;
+  }
+
+  size_t numRemovedEvent = mEvents.removeMatchedFromBack(
+      isLowPriorityEvent, removeNum, deallocateFromMemoryPool, &mEventPool);
+  if (numRemovedEvent == 0 || numRemovedEvent == SIZE_MAX) {
+    LOGW("Cannot remove any low priority event");
+  } else {
+    mNumDroppedLowPriEvents += numRemovedEvent;
+  }
+  return numRemovedEvent > 0;
+#endif
+}
+
+bool EventLoop::hasNoSpaceForHighPriorityEvent() {
+  return mEventPool.full() &&
+         !removeLowPriorityEventsFromBack(targetLowPriorityEventRemove);
+}
+
+// TODO(b/264108686): Refactor this function and postSystemEvent
 void EventLoop::postEventOrDie(uint16_t eventType, void *eventData,
                                chreEventCompleteFunction *freeCallback,
                                uint16_t targetInstanceId,
                                uint16_t targetGroupMask) {
   if (mRunning) {
-    if (!allocateAndPostEvent(eventType, eventData, freeCallback,
-                              kSystemInstanceId, targetInstanceId,
-                              targetGroupMask)) {
+    if (hasNoSpaceForHighPriorityEvent() ||
+        !allocateAndPostEvent(eventType, eventData, freeCallback,
+                              false /*isLowPriority*/, kSystemInstanceId,
+                              targetInstanceId, targetGroupMask)) {
       FATAL_ERROR("Failed to post critical system event 0x%" PRIx16, eventType);
     }
   } else if (freeCallback != nullptr) {
@@ -256,16 +315,25 @@
 bool EventLoop::postSystemEvent(uint16_t eventType, void *eventData,
                                 SystemEventCallbackFunction *callback,
                                 void *extraData) {
-  if (mRunning) {
-    Event *event =
-        mEventPool.allocate(eventType, eventData, callback, extraData);
-
-    if (event == nullptr || !mEvents.push(event)) {
-      FATAL_ERROR("Failed to post critical system event 0x%" PRIx16, eventType);
-    }
-    return true;
+  if (!mRunning) {
+    return false;
   }
-  return false;
+
+  if (hasNoSpaceForHighPriorityEvent()) {
+    FATAL_ERROR("Failed to post critical system event 0x%" PRIx16
+                ": Full of high priority "
+                "events",
+                eventType);
+  }
+
+  Event *event = mEventPool.allocate(eventType, eventData, callback, extraData);
+  if (event == nullptr || !mEvents.push(event)) {
+    FATAL_ERROR("Failed to post critical system event 0x%" PRIx16
+                ": out of memory",
+                eventType);
+  }
+
+  return true;
 }
 
 bool EventLoop::postLowPriorityEventOrFree(
@@ -275,10 +343,15 @@
   bool eventPosted = false;
 
   if (mRunning) {
-    if (mEventPool.getFreeBlockCount() > kMinReservedHighPriorityEventCount) {
-      eventPosted = allocateAndPostEvent(eventType, eventData, freeCallback,
-                                         senderInstanceId, targetInstanceId,
-                                         targetGroupMask);
+#ifdef CHRE_STATIC_EVENT_LOOP
+    if (mEventPool.getFreeBlockCount() > kMinReservedHighPriorityEventCount)
+#else
+    if (mEventPool.getFreeSpaceCount() > kMinReservedHighPriorityEventCount)
+#endif
+    {
+      eventPosted = allocateAndPostEvent(
+          eventType, eventData, freeCallback, true /*isLowPriority*/,
+          senderInstanceId, targetInstanceId, targetGroupMask);
       if (!eventPosted) {
         LOGE("Failed to allocate event 0x%" PRIx16 " to instanceId %" PRIu16,
              eventType, targetInstanceId);
@@ -359,14 +432,15 @@
 
 bool EventLoop::allocateAndPostEvent(uint16_t eventType, void *eventData,
                                      chreEventCompleteFunction *freeCallback,
+                                     bool isLowPriority,
                                      uint16_t senderInstanceId,
                                      uint16_t targetInstanceId,
                                      uint16_t targetGroupMask) {
   bool success = false;
 
   Event *event =
-      mEventPool.allocate(eventType, eventData, freeCallback, senderInstanceId,
-                          targetInstanceId, targetGroupMask);
+      mEventPool.allocate(eventType, eventData, freeCallback, isLowPriority,
+                          senderInstanceId, targetInstanceId, targetGroupMask);
   if (event != nullptr) {
     success = mEvents.push(event);
   }
diff --git a/core/event_loop_manager.cc b/core/event_loop_manager.cc
index 4af698b..5116771 100644
--- a/core/event_loop_manager.cc
+++ b/core/event_loop_manager.cc
@@ -22,10 +22,6 @@
 
 namespace chre {
 
-void freeEventDataCallback(uint16_t /*eventType*/, void *eventData) {
-  memoryFree(eventData);
-}
-
 Nanoapp *EventLoopManager::validateChreApiCall(const char *functionName) {
   chre::Nanoapp *currentNanoapp =
       EventLoopManagerSingleton::get()->getEventLoop().getCurrentNanoapp();
diff --git a/core/gnss_manager.cc b/core/gnss_manager.cc
index fccf38b..ec41e81 100644
--- a/core/gnss_manager.cc
+++ b/core/gnss_manager.cc
@@ -24,6 +24,7 @@
 #include "chre/platform/fatal_error.h"
 #include "chre/util/nested_data_ptr.h"
 #include "chre/util/system/debug_dump.h"
+#include "chre/util/system/event_callbacks.h"
 
 namespace chre {
 
@@ -295,7 +296,7 @@
   }
 
   auto callback = [](uint16_t type, void *data, void * /*extraData*/) {
-    uint16_t reportEventType;
+    uint16_t reportEventType = 0;
     if (!getReportEventType(static_cast<SystemCallbackType>(type),
                             &reportEventType) ||
         !EventLoopManagerSingleton::get()
@@ -309,10 +310,9 @@
   };
 
   SystemCallbackType type;
-  if (!getCallbackType(kReportEventType, &type)) {
+  if (!getCallbackType(kReportEventType, &type) ||
+      !EventLoopManagerSingleton::get()->deferCallback(type, event, callback)) {
     freeReportEventCallback(kReportEventType, event);
-  } else {
-    EventLoopManagerSingleton::get()->deferCallback(type, event, callback);
   }
 }
 
@@ -721,7 +721,7 @@
   while (!mStateTransitions.empty()) {
     const auto &stateTransition = mStateTransitions.front();
 
-    size_t requestIndex;
+    size_t requestIndex = 0;
     bool hasRequest =
         nanoappHasRequest(stateTransition.nanoappInstanceId, &requestIndex);
 
diff --git a/core/host_comms_manager.cc b/core/host_comms_manager.cc
index feafb46..81afa58 100644
--- a/core/host_comms_manager.cc
+++ b/core/host_comms_manager.cc
@@ -157,9 +157,11 @@
             .sendDeferredMessageToNanoappFromHost(
                 static_cast<MessageFromHost *>(data));
       };
-      EventLoopManagerSingleton::get()->deferCallback(
-          SystemCallbackType::DeferredMessageToNanoappFromHost, craftedMessage,
-          callback);
+      if (!EventLoopManagerSingleton::get()->deferCallback(
+              SystemCallbackType::DeferredMessageToNanoappFromHost,
+              craftedMessage, callback)) {
+        mMessagePool.deallocate(craftedMessage);
+      }
     }
   }
 }
@@ -201,8 +203,12 @@
           static_cast<MessageToHost *>(data));
     };
 
-    EventLoopManagerSingleton::get()->deferCallback(
-        SystemCallbackType::MessageToHostComplete, msgToHost, freeMsgCallback);
+    if (!EventLoopManagerSingleton::get()->deferCallback(
+            SystemCallbackType::MessageToHostComplete, msgToHost,
+            freeMsgCallback)) {
+      EventLoopManagerSingleton::get()->getHostCommsManager().freeMessageToHost(
+          static_cast<MessageToHost *>(msgToHost));
+    }
   }
 }
 
diff --git a/core/host_notifications.cc b/core/host_endpoint_manager.cc
similarity index 65%
rename from core/host_notifications.cc
rename to core/host_endpoint_manager.cc
index 96a9df2..a88b9b0 100644
--- a/core/host_notifications.cc
+++ b/core/host_endpoint_manager.cc
@@ -14,25 +14,18 @@
  * limitations under the License.
  */
 
-#include "chre/core/host_notifications.h"
+#include "chre/core/host_endpoint_manager.h"
 
 #include "chre/core/event_loop_manager.h"
 #include "chre/util/dynamic_vector.h"
 #include "chre/util/nested_data_ptr.h"
+#include "chre/util/system/event_callbacks.h"
 
 namespace chre {
-
-namespace {
-
-//! Connected host endpoint metadata, which should only be accessed by the main
-//! CHRE event loop.
-// TODO(b/194287786): Re-organize this code into a class for better
-// organization.
-chre::DynamicVector<struct chreHostEndpointInfo> gHostEndpoints;
-
-bool isHostEndpointConnected(uint16_t hostEndpointId, size_t *index) {
-  for (size_t i = 0; i < gHostEndpoints.size(); i++) {
-    if (gHostEndpoints[i].hostEndpointId == hostEndpointId) {
+bool HostEndpointManager::isHostEndpointConnected(uint16_t hostEndpointId,
+                                                  size_t *index) {
+  for (size_t i = 0; i < mHostEndpoints.size(); i++) {
+    if (mHostEndpoints[i].hostEndpointId == hostEndpointId) {
       *index = i;
       return true;
     }
@@ -41,14 +34,15 @@
   return false;
 }
 
-void hostNotificationCallback(uint16_t type, void *data, void *extraData) {
+void HostEndpointManager::hostNotificationCallback(uint16_t type, void *data,
+                                                   void *extraData) {
   uint16_t hostEndpointId = NestedDataPtr<uint16_t>(data);
 
   SystemCallbackType callbackType = static_cast<SystemCallbackType>(type);
   if (callbackType == SystemCallbackType::HostEndpointDisconnected) {
     size_t index;
     if (isHostEndpointConnected(hostEndpointId, &index)) {
-      gHostEndpoints.erase(index);
+      mHostEndpoints.erase(index);
 
       uint16_t eventType = CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION;
       auto *eventData = memoryAlloc<struct chreHostEndpointNotification>();
@@ -73,7 +67,7 @@
 
     size_t index;
     if (!isHostEndpointConnected(hostEndpointId, &index)) {
-      gHostEndpoints.push_back(*info);
+      mHostEndpoints.push_back(*info);
     } else {
       LOGW("Got connected event for already existing host endpoint ID %" PRIu16,
            hostEndpointId);
@@ -83,38 +77,48 @@
   memoryFree(extraData);
 }
 
-}  // anonymous namespace
+auto HostEndpointManager::getHostNotificationCallback() {
+  return [](uint16_t type, void *data, void *extraData) {
+    EventLoopManagerSingleton::get()
+        ->getHostEndpointManager()
+        .hostNotificationCallback(type, data, extraData);
+  };
+}
 
-bool getHostEndpointInfo(uint16_t hostEndpointId,
-                         struct chreHostEndpointInfo *info) {
+bool HostEndpointManager::getHostEndpointInfo(
+    uint16_t hostEndpointId, struct chreHostEndpointInfo *info) {
   size_t index;
   if (isHostEndpointConnected(hostEndpointId, &index)) {
-    *info = gHostEndpoints[index];
+    *info = mHostEndpoints[index];
     return true;
   } else {
     return false;
   }
 }
 
-void postHostEndpointConnected(const struct chreHostEndpointInfo &info) {
+void HostEndpointManager::postHostEndpointConnected(
+    const struct chreHostEndpointInfo &info) {
   auto *infoData = memoryAlloc<struct chreHostEndpointInfo>();
   if (infoData == nullptr) {
     LOG_OOM();
   } else {
     memcpy(infoData, &info, sizeof(struct chreHostEndpointInfo));
 
+    auto callback = getHostNotificationCallback();
+
     EventLoopManagerSingleton::get()->deferCallback(
         SystemCallbackType::HostEndpointConnected,
-        NestedDataPtr<uint16_t>(info.hostEndpointId), hostNotificationCallback,
+        NestedDataPtr<uint16_t>(info.hostEndpointId), callback,
         infoData /* extraData */);
   }
 }
 
-void postHostEndpointDisconnected(uint16_t hostEndpointId) {
+void HostEndpointManager::postHostEndpointDisconnected(
+    uint16_t hostEndpointId) {
+  auto callback = getHostNotificationCallback();
   EventLoopManagerSingleton::get()->deferCallback(
       SystemCallbackType::HostEndpointDisconnected,
-      NestedDataPtr<uint16_t>(hostEndpointId), hostNotificationCallback,
-      nullptr);
+      NestedDataPtr<uint16_t>(hostEndpointId), callback, nullptr);
 }
 
 }  // namespace chre
diff --git a/core/include/chre/core/audio_request_manager.h b/core/include/chre/core/audio_request_manager.h
index 6df198f..8b97a34 100644
--- a/core/include/chre/core/audio_request_manager.h
+++ b/core/include/chre/core/audio_request_manager.h
@@ -132,11 +132,11 @@
    * One instance of an audio request from a nanoapp.
    */
   struct AudioRequest {
-    AudioRequest(uint32_t numSamples, Nanoseconds deliveryInterval,
-                 Nanoseconds nextEventTimestamp)
-        : numSamples(numSamples),
-          deliveryInterval(deliveryInterval),
-          nextEventTimestamp(nextEventTimestamp) {}
+    AudioRequest(uint32_t numSamplesIn, Nanoseconds deliveryIntervalIn,
+                 Nanoseconds nextEventTimestampIn)
+        : numSamples(numSamplesIn),
+          deliveryInterval(deliveryIntervalIn),
+          nextEventTimestamp(nextEventTimestampIn) {}
 
     //! The nanoapp instance IDs that own this request.
     DynamicVector<uint16_t> instanceIds;
@@ -178,18 +178,9 @@
    * kind of logic from appearing in the AudioRequestManager.
    */
   struct AudioDataEventRefCount {
-    /**
-     * Constructs an AudioDataEventRefCount object with an uninitialized
-     * refCount to allow searching in a list using the equality comparison
-     * below.
-     *
-     * @param event The event that this object tracks.
-     */
-    explicit AudioDataEventRefCount(struct chreAudioDataEvent *event)
-        : event(event) {}
-
-    AudioDataEventRefCount(struct chreAudioDataEvent *event, uint32_t refCount)
-        : event(event), refCount(refCount) {}
+    AudioDataEventRefCount(struct chreAudioDataEvent *eventIn,
+                           const uint32_t refCountIn = 0)
+        : event(eventIn), refCount(refCountIn) {}
 
     /**
      * @param audioDataEventRefCount The other object to perform an equality
diff --git a/core/include/chre/core/ble_request_manager.h b/core/include/chre/core/ble_request_manager.h
index 432a3c0..adc9cd5 100644
--- a/core/include/chre/core/ble_request_manager.h
+++ b/core/include/chre/core/ble_request_manager.h
@@ -22,6 +22,7 @@
 #include "chre/core/nanoapp.h"
 #include "chre/core/settings.h"
 #include "chre/platform/platform_ble.h"
+#include "chre/util/array_queue.h"
 #include "chre/util/non_copyable.h"
 #include "chre/util/system/debug_dump.h"
 #include "chre/util/time.h"
@@ -79,6 +80,36 @@
    */
   bool stopScanAsync(Nanoapp *nanoapp);
 
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+  /**
+   * Requests to read the RSSI of a peer device on the given LE connection
+   * handle.
+   *
+   * If the request is accepted, the response will be delivered in a
+   * CHRE_EVENT_BLE_RSSI_READ event with the same cookie.
+   *
+   * The request may be rejected if resources are not available to service the
+   * request (such as if too many outstanding requests already exist). If so,
+   * the client may retry later.
+   *
+   * Note that the connectionHandle is valid only while the connection remains
+   * active. If a peer device disconnects then reconnects, the handle may
+   * change. BluetoothGatt#getAclHandle() can be used from the Android framework
+   * to get the latest handle upon reconnection.
+   *
+   * @param connectionHandle
+   * @param cookie An opaque value that will be included in the chreAsyncResult
+   *               embedded in the response to this request.
+   * @return True if the request has been accepted and dispatched to the
+   *         controller. False otherwise.
+   *
+   * @since v1.8
+   *
+   */
+  bool readRssiAsync(Nanoapp *nanoapp, uint16_t connectionHandle,
+                     const void *cookie);
+#endif
+
   /**
    * Disables active scan for a nanoapp (no-op if no active scan).
    *
@@ -128,6 +159,29 @@
    */
   void handleRequestStateResyncCallback();
 
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+  /**
+   * Handles a readRssi response from the BLE PAL.
+   *
+   * @param errorCode error code from enum chreError, with CHRE_ERROR_NONE
+   *        indicating a successful response.
+   * @param connectionHandle the handle upon which the RSSI was read
+   * @param rssi the RSSI read, if successful
+   */
+  void handleReadRssi(uint8_t errorCode, uint16_t connectionHandle,
+                      int8_t rssi);
+#endif
+
+  /**
+   * Retrieves the current scan status.
+   *
+   * @param status A non-null pointer to where the scan status will be
+   *               populated.
+   *
+   * @return True if the status was obtained successfully.
+   */
+  bool getScanStatus(struct chreBleScanStatus *status);
+
   /**
    * Invoked when the host notifies CHRE that ble access has been
    * disabled via the user settings.
@@ -168,6 +222,20 @@
   // True if a setting change request is pending to be processed.
   bool mSettingChangePending;
 
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+  // A pending request from a nanoapp
+  struct BleReadRssiRequest {
+    uint16_t instanceId;
+    uint16_t connectionHandle;
+    const void *cookie;
+  };
+
+  // RSSI requests that have been accepted by the framework. The first entry (if
+  // present) has been dispatched to the PAL, and subsequent entries are queued.
+  static constexpr size_t kMaxPendingRssiRequests = 2;
+  ArrayQueue<BleReadRssiRequest, kMaxPendingRssiRequests> mPendingRssiRequests;
+#endif
+
   // Struct to hold ble request data for logging
   struct BleRequestLog {
     BleRequestLog(Nanoseconds timestamp, uint32_t instanceId, bool enable,
@@ -358,6 +426,55 @@
    */
   static bool isValidAdType(uint8_t adType);
 
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+  /**
+   * Handles a readRssi response from the BLE PAL.
+   * Runs in the context of the CHRE thread.
+   *
+   * @param errorCode error code from enum chreError, with CHRE_ERROR_NONE
+   *        indicating a successful response.
+   * @param connectionHandle the handle upon which the RSSI was read
+   * @param rssi the RSSI read, if successful
+   */
+  void handleReadRssiSync(uint8_t errorCode, uint16_t connectionHandle,
+                          int8_t rssi);
+
+  /**
+   * Posts a CHRE_EVENT_BLE_RSSI_READ event for the first request in
+   * mPendingRssiRequests with the specified errorCode and RSSI, and dequeues it
+   * from the queue.
+   *
+   * It is assumed that a pending request exists. Note that this does not
+   * dispatch the next request in the queue.
+   *
+   * @param errorCode the errorCode to include in the event
+   * @param rssi the RSSI to include in the event
+   */
+  void resolvePendingRssiRequest(uint8_t errorCode, int8_t rssi);
+
+  /**
+   * Dispatches the next RSSI request in the queue, if one exists. Must only
+   * be called if no request is presently outstanding (i.e. right after the
+   * previous request completes, or when no previous request existed).
+   *
+   * If the request fails synchronously, it will be dequeued and the failure
+   * event CHRE_EVENT_BLE_RSSI_READ will be sent. It will then try to
+   * dispatch the next request in the queue until either a request succeeds,
+   * or the queue is depleted.
+   */
+  void dispatchNextRssiRequestIfAny();
+
+  /**
+   * Checks BLE settings and, if enabled, issues a request to the PAL to read
+   * RSSI. Returns CHRE_ERROR_FUNCTION_DISABLED if BLE is disabled and
+   * CHRE_ERROR if the PAL returns an error.
+   *
+   * @param connectionHandle
+   * @return uint8_t the error code, with CHRE_ERROR_NONE indicating success
+   */
+  uint8_t readRssi(uint16_t connectionHandle);
+#endif
+
   /**
    * @return true if BLE setting is enabled.
    */
diff --git a/core/include/chre/core/event.h b/core/include/chre/core/event.h
index 82896f2..045dffe 100644
--- a/core/include/chre/core/event.h
+++ b/core/include/chre/core/event.h
@@ -47,7 +47,7 @@
 
   // Events targeted at nanoapps
   Event(uint16_t eventType_, void *eventData_,
-        chreEventCompleteFunction *freeCallback_,
+        chreEventCompleteFunction *freeCallback_, bool isLowPriority_,
         uint16_t senderInstanceId_ = kSystemInstanceId,
         uint16_t targetInstanceId_ = kBroadcastInstanceId,
         uint16_t targetAppGroupMask_ = kDefaultTargetGroupMask)
@@ -57,7 +57,8 @@
         freeCallback(freeCallback_),
         senderInstanceId(senderInstanceId_),
         targetInstanceId(targetInstanceId_),
-        targetAppGroupMask(targetAppGroupMask_) {
+        targetAppGroupMask(targetAppGroupMask_),
+        isLowPriority(isLowPriority_) {
     // Sending events to the system must only be done via the other constructor
     CHRE_ASSERT(targetInstanceId_ != kSystemInstanceId);
     CHRE_ASSERT(targetAppGroupMask_ > 0);
@@ -73,7 +74,8 @@
         systemEventCallback(systemEventCallback_),
         extraData(extraData_),
         targetInstanceId(kSystemInstanceId),
-        targetAppGroupMask(kDefaultTargetGroupMask) {
+        targetAppGroupMask(kDefaultTargetGroupMask),
+        isLowPriority(false) {
     // Posting events to the system must always have a corresponding callback
     CHRE_ASSERT(systemEventCallback_ != nullptr);
   }
@@ -143,8 +145,10 @@
   // all registered listeners.
   const uint16_t targetAppGroupMask;
 
+  const bool isLowPriority;
+
  private:
-  uint16_t mRefCount = 0;
+  uint8_t mRefCount = 0;
 
   //! @return Monotonic time reference for initializing receivedTimeMillis
   static uint16_t getTimeMillis();
diff --git a/core/include/chre/core/event_loop.h b/core/include/chre/core/event_loop.h
index 877d99b..3403410 100644
--- a/core/include/chre/core/event_loop.h
+++ b/core/include/chre/core/event_loop.h
@@ -26,14 +26,16 @@
 #include "chre/platform/power_control_manager.h"
 #include "chre/platform/system_time.h"
 #include "chre/util/dynamic_vector.h"
-#include "chre/util/fixed_size_blocking_queue.h"
 #include "chre/util/non_copyable.h"
-#include "chre/util/synchronized_memory_pool.h"
 #include "chre/util/system/debug_dump.h"
 #include "chre/util/system/stats_container.h"
 #include "chre/util/unique_ptr.h"
 #include "chre_api/chre/event.h"
 
+#ifdef CHRE_STATIC_EVENT_LOOP
+#include "chre/util/fixed_size_blocking_queue.h"
+#include "chre/util/synchronized_memory_pool.h"
+
 // These default values can be overridden in the variant-specific makefile.
 #ifndef CHRE_MAX_EVENT_COUNT
 #define CHRE_MAX_EVENT_COUNT 96
@@ -42,6 +44,27 @@
 #ifndef CHRE_MAX_UNSCHEDULED_EVENT_COUNT
 #define CHRE_MAX_UNSCHEDULED_EVENT_COUNT 96
 #endif
+#else
+#include "chre/util/blocking_segmented_queue.h"
+#include "chre/util/synchronized_expandable_memory_pool.h"
+
+// These default values can be overridden in the variant-specific makefile.
+#ifndef CHRE_EVENT_PER_BLOCK
+#define CHRE_EVENT_PER_BLOCK 24
+#endif
+
+#ifndef CHRE_MAX_EVENT_BLOCKS
+#define CHRE_MAX_EVENT_BLOCKS 4
+#endif
+
+#ifndef CHRE_UNSCHEDULED_EVENT_PER_BLOCK
+#define CHRE_UNSCHEDULED_EVENT_PER_BLOCK 24
+#endif
+
+#ifndef CHRE_MAX_UNSCHEDULED_EVENT_BLOCKS
+#define CHRE_MAX_UNSCHEDULED_EVENT_BLOCKS 4
+#endif
+#endif
 
 namespace chre {
 
@@ -53,8 +76,13 @@
 class EventLoop : public NonCopyable {
  public:
   EventLoop()
-      : mTimeLastWakeupBucketCycled(SystemTime::getMonotonicTime()),
-        mRunning(true) {}
+      :
+#ifndef CHRE_STATIC_EVENT_LOOP
+        mEvents(kMaxUnscheduleEventBlocks),
+#endif
+        mTimeLastWakeupBucketCycled(SystemTime::getMonotonicTime()),
+        mRunning(true) {
+  }
 
   /**
    * Synchronous callback used with forEachNanoapp
@@ -324,6 +352,7 @@
   }
 
  private:
+#ifdef CHRE_STATIC_EVENT_LOOP
   //! The maximum number of events that can be active in the system.
   static constexpr size_t kMaxEventCount = CHRE_MAX_EVENT_COUNT;
 
@@ -336,6 +365,44 @@
   static constexpr size_t kMaxUnscheduledEventCount =
       CHRE_MAX_UNSCHEDULED_EVENT_COUNT;
 
+  //! The memory pool to allocate incoming events from.
+  SynchronizedMemoryPool<Event, kMaxEventCount> mEventPool;
+
+  //! The blocking queue of incoming events from the system that have not been
+  //! distributed out to apps yet.
+  FixedSizeBlockingQueue<Event *, kMaxUnscheduledEventCount> mEvents;
+
+#else
+  //! The maximum number of event that can be stored in a block in mEventPool.
+  static constexpr size_t kEventPerBlock = CHRE_EVENT_PER_BLOCK;
+
+  //! The maximum number of event blocks that mEventPool can hold.
+  static constexpr size_t kMaxEventBlock = CHRE_MAX_EVENT_BLOCKS;
+
+  static constexpr size_t kMaxEventCount =
+      CHRE_EVENT_PER_BLOCK * CHRE_MAX_EVENT_BLOCKS;
+
+  //! The minimum number of events to reserve in the event pool for high
+  //! priority events.
+  static constexpr size_t kMinReservedHighPriorityEventCount = 16;
+
+  //! The maximum number of events per block that are awaiting to be scheduled.
+  //! These events are in a queue to be distributed to apps.
+  static constexpr size_t kMaxUnscheduledEventPerBlock =
+      CHRE_UNSCHEDULED_EVENT_PER_BLOCK;
+
+  //! The maximum number of event blocks that mEvents can hold.
+  static constexpr size_t kMaxUnscheduleEventBlocks =
+      CHRE_MAX_UNSCHEDULED_EVENT_BLOCKS;
+
+  //! The memory pool to allocate incoming events from.
+  SynchronizedExpandableMemoryPool<Event, kEventPerBlock, kMaxEventBlock>
+      mEventPool;
+
+  //! The blocking queue of incoming events from the system that have not been
+  //! distributed out to apps yet.
+  BlockingSegmentedQueue<Event *, kMaxUnscheduledEventPerBlock> mEvents;
+#endif
   //! The time interval of nanoapp wakeup buckets, adjust in conjuction with
   //! Nanoapp::kMaxSizeWakeupBuckets.
   static constexpr Nanoseconds kIntervalWakeupBucket =
@@ -344,9 +411,6 @@
   //! The last time wakeup buckets were pushed onto the nanoapps.
   Nanoseconds mTimeLastWakeupBucketCycled;
 
-  //! The memory pool to allocate incoming events from.
-  SynchronizedMemoryPool<Event, kMaxEventCount> mEventPool;
-
   //! The timer used schedule timed events for tasks running in this event loop.
   TimerPool mTimerPool;
 
@@ -361,10 +425,6 @@
   //! the thread context of this EventLoop.
   mutable Mutex mNanoappsLock;
 
-  //! The blocking queue of incoming events from the system that have not been
-  //! distributed out to apps yet.
-  FixedSizeBlockingQueue<Event *, kMaxUnscheduledEventCount> mEvents;
-
   //! Indicates whether the event loop is running.
   AtomicBool mRunning;
 
@@ -400,9 +460,25 @@
    */
   bool allocateAndPostEvent(uint16_t eventType, void *eventData,
                             chreEventCompleteFunction *freeCallback,
-                            uint16_t senderInstanceId,
+                            bool isLowPriority, uint16_t senderInstanceId,
                             uint16_t targetInstanceId,
                             uint16_t targetGroupMask);
+  /**
+   * Remove some low priority events from back of the queue.
+   *
+   * @param removeNum Number of low priority events to be removed.
+   * @return False if cannot remove any low priority event.
+   */
+  bool removeLowPriorityEventsFromBack(size_t removeNum);
+
+  /**
+   * Determine if there are space for high priority event.
+   * During the processing of determining the vacant space, it might
+   * remove low priority events to make space for high priority event.
+   *
+   * @return true if there are no space for a new high priority event.
+   */
+  bool hasNoSpaceForHighPriorityEvent();
 
   /**
    * Delivers the next event pending to the Nanoapp.
diff --git a/core/include/chre/core/event_loop_common.h b/core/include/chre/core/event_loop_common.h
index a319139..e487cbc 100644
--- a/core/include/chre/core/event_loop_common.h
+++ b/core/include/chre/core/event_loop_common.h
@@ -68,6 +68,8 @@
   BleAdvertisementEvent,
   BleScanResponse,
   BleRequestResyncEvent,
+  RequestTimeoutEvent,
+  BleReadRssiEvent,
 };
 
 //! Deferred/delayed callbacks use the event subsystem but are invariably sent
@@ -77,13 +79,6 @@
 using SystemEventCallbackFunction = void(uint16_t type, void *data,
                                          void *extraData);
 
-/**
- * Generic event free callback that can be used by any event where the event
- * data is allocated via memoryAlloc, and no special processing is needed in the
- * event complete callback other than freeing the event data.
- */
-void freeEventDataCallback(uint16_t eventType, void *eventData);
-
 }  // namespace chre
 
 #endif  // CHRE_CORE_EVENT_LOOP_COMMON_H_
diff --git a/core/include/chre/core/event_loop_manager.h b/core/include/chre/core/event_loop_manager.h
index 406bd79..f6e7d74 100644
--- a/core/include/chre/core/event_loop_manager.h
+++ b/core/include/chre/core/event_loop_manager.h
@@ -21,7 +21,9 @@
 #include "chre/core/event_loop.h"
 #include "chre/core/event_loop_common.h"
 #include "chre/core/host_comms_manager.h"
+#include "chre/core/host_endpoint_manager.h"
 #include "chre/core/settings.h"
+#include "chre/core/system_health_monitor.h"
 #include "chre/platform/memory_manager.h"
 #include "chre/platform/mutex.h"
 #include "chre/util/always_false.h"
@@ -104,12 +106,13 @@
    * @param data Arbitrary data to provide to the callback
    * @param callback Function to invoke from within the main CHRE thread
    * @param extraData Additional arbitrary data to provide to the callback
+   * @return If true, the callback was deferred successfully; false otherwise.
    */
-  void deferCallback(SystemCallbackType type, void *data,
+  bool deferCallback(SystemCallbackType type, void *data,
                      SystemEventCallbackFunction *callback,
                      void *extraData = nullptr) {
-    mEventLoop.postSystemEvent(static_cast<uint16_t>(type), data, callback,
-                               extraData);
+    return mEventLoop.postSystemEvent(static_cast<uint16_t>(type), data,
+                                      callback, extraData);
   }
 
   /**
@@ -124,35 +127,40 @@
    *        uint16_t, and can also be useful for debugging
    * @param data Pointer to arbitrary data to provide to the callback
    * @param callback Function to invoke from within the main CHRE thread
+   * @return If true, the callback was deferred successfully; false otherwise.
    */
   template <typename T>
-  void deferCallback(SystemCallbackType type, UniquePtr<T> &&data,
+  bool deferCallback(SystemCallbackType type, UniquePtr<T> &&data,
                      TypedSystemEventCallbackFunction<T> *callback) {
-    auto outerCallback = [](uint16_t type, void *data, void *extraData) {
+    auto outerCallback = [](uint16_t callbackType, void *eventData,
+                            void *extraData) {
       // Re-wrap eventData in UniquePtr so its destructor will get called and
       // the memory will be freed once we leave this scope
-      UniquePtr<T> dataWrapped = UniquePtr<T>(static_cast<T *>(data));
+      UniquePtr<T> dataWrapped = UniquePtr<T>(static_cast<T *>(eventData));
       auto *innerCallback =
           reinterpret_cast<TypedSystemEventCallbackFunction<T> *>(extraData);
-      innerCallback(static_cast<SystemCallbackType>(type),
+      innerCallback(static_cast<SystemCallbackType>(callbackType),
                     std::move(dataWrapped));
     };
     // Pass the "inner" callback (the caller's callback) through to the "outer"
     // callback using the extraData parameter. Note that we're leveraging the
     // C++11 ability to cast a function pointer to void*
-    if (mEventLoop.postSystemEvent(static_cast<uint16_t>(type), data.get(),
-                                   outerCallback,
-                                   reinterpret_cast<void *>(callback))) {
+    bool status = mEventLoop.postSystemEvent(
+        static_cast<uint16_t>(type), data.get(), outerCallback,
+        reinterpret_cast<void *>(callback));
+    if (status) {
       data.release();
     }
+    return status;
   }
 
   //! Override that allows passing a lambda for the callback
   template <typename T, typename LambdaT>
-  void deferCallback(SystemCallbackType type, UniquePtr<T> &&data,
+  bool deferCallback(SystemCallbackType type, UniquePtr<T> &&data,
                      LambdaT callback) {
-    deferCallback(type, std::move(data),
-                  static_cast<TypedSystemEventCallbackFunction<T> *>(callback));
+    return deferCallback(
+        type, std::move(data),
+        static_cast<TypedSystemEventCallbackFunction<T> *>(callback));
   }
 
   //! Disallows passing a null callback, as we don't include a null check in the
@@ -260,6 +268,10 @@
     return mHostCommsManager;
   }
 
+  HostEndpointManager &getHostEndpointManager() {
+    return mHostEndpointManager;
+  }
+
 #ifdef CHRE_SENSORS_SUPPORT_ENABLED
   /**
    * @return Returns a reference to the sensor request manager. This allows
@@ -325,6 +337,10 @@
     return mSettingManager;
   }
 
+  SystemHealthMonitor &getSystemHealthMonitor() {
+    return mSystemHealthMonitor;
+  }
+
   /**
    * Performs second-stage initialization of things that are not necessarily
    * required at construction time but need to be completed prior to executing
@@ -360,6 +376,10 @@
   //! Handles communications with the host processor.
   HostCommsManager mHostCommsManager;
 
+  HostEndpointManager mHostEndpointManager;
+
+  SystemHealthMonitor mSystemHealthMonitor;
+
 #ifdef CHRE_SENSORS_SUPPORT_ENABLED
   //! The SensorRequestManager that handles requests for all nanoapps. This
   //! manages the state of all sensors that runtime subscribes to.
diff --git a/core/include/chre/core/host_endpoint_manager.h b/core/include/chre/core/host_endpoint_manager.h
new file mode 100644
index 0000000..40781bb
--- /dev/null
+++ b/core/include/chre/core/host_endpoint_manager.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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_CORE_HOST_ENDPOINT_MANAGER_H_
+#define CHRE_CORE_HOST_ENDPOINT_MANAGER_H_
+
+#include <cinttypes>
+
+#include "chre/util/system/debug_dump.h"
+#include "chre_api/chre/event.h"
+
+namespace chre {
+
+/**
+ * Connected host endpoint metadata, which should only be accessed by the
+ * main CHRE event loop.
+ */
+class HostEndpointManager : public NonCopyable {
+ public:
+  /**
+   * Updates host endpoint connection to CHRE.
+   *
+   * @param info Metadata about the host endpoint that connected.
+   */
+  void postHostEndpointConnected(const struct chreHostEndpointInfo &info);
+
+  /**
+   * Updates host endpoint disconnection to CHRE.
+   *
+   * @param hostEndpointId The host endpoint ID.
+   */
+  void postHostEndpointDisconnected(uint16_t hostEndpointId);
+
+  /**
+   * Gets the Host endpoint information if it has been connected.
+   *
+   * @param hostEndpointId The host endpoint ID.
+   * @param info Where the retrieved info will be stored.
+   * @return true if the id is connected.
+   * @return false if the id is not connected.
+   */
+  bool getHostEndpointInfo(uint16_t hostEndpointId,
+                           struct chreHostEndpointInfo *info);
+
+ private:
+  /**
+   * Stores host endpoint information if it is connected.
+   */
+  chre::DynamicVector<struct chreHostEndpointInfo> mHostEndpoints =
+      chre::DynamicVector<struct chreHostEndpointInfo>();
+
+  /**
+   * Returns the index of where the endpoint id is stored
+   *
+   * @param hostEndpointId The host endpoint ID.
+   * @param index Where the retrieved index will be returned.
+   * @return true if the id is connected.
+   * @return false if the id is not connected.
+   */
+  bool isHostEndpointConnected(uint16_t hostEndpointId, size_t *index);
+
+  /**
+   * Callback function used in event loop to connect or disconnect the host
+   * endpoint.
+   *
+   * @param type Type if system callback type, needs to be
+   * HostEndpointDisconnected or HostEndpointConnected
+   * @param data Arbitrary data to provide to the callback
+   * @param extraData Additional arbitrary data to provide to the callback
+   */
+  void hostNotificationCallback(uint16_t type, void *data, void *extraData);
+
+  /**
+   * Get the hostNotificationCallback of the HostEndpointManager in
+   * EventLoopManager
+   */
+  auto getHostNotificationCallback();
+};
+
+};  // namespace chre
+
+#endif  // CHRE_CORE_HOST_ENDPOINT_MANAGER_H_
diff --git a/core/include/chre/core/host_notifications.h b/core/include/chre/core/host_notifications.h
deleted file mode 100644
index 1b88163..0000000
--- a/core/include/chre/core/host_notifications.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2021 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_CORE_HOST_NOTIFICATIONS_H_
-#define CHRE_CORE_HOST_NOTIFICATIONS_H_
-
-#include <cinttypes>
-
-#include "chre/util/system/debug_dump.h"
-#include "chre_api/chre/event.h"
-
-namespace chre {
-
-/**
- * Updates host endpoint connection to CHRE.
- *
- * @param info Metadata about the host endpoint that connected.
- */
-void postHostEndpointConnected(const struct chreHostEndpointInfo &info);
-
-/**
- * Updates host endpoint disconnection to CHRE.
- *
- * @param hostEndpointId The host endpoint ID.
- */
-void postHostEndpointDisconnected(uint16_t hostEndpointId);
-
-bool getHostEndpointInfo(uint16_t hostEndpointId,
-                         struct chreHostEndpointInfo *info);
-
-}  // namespace chre
-
-#endif  // CHRE_CORE_HOST_NOTIFICATIONS_H_
diff --git a/core/include/chre/core/nanoapp.h b/core/include/chre/core/nanoapp.h
index e1f6852..ae72327 100644
--- a/core/include/chre/core/nanoapp.h
+++ b/core/include/chre/core/nanoapp.h
@@ -18,6 +18,8 @@
 #define CHRE_CORE_NANOAPP_H_
 
 #include <cinttypes>
+#include <cstdint>
+#include <limits>
 
 #include "chre/core/event.h"
 #include "chre/core/event_ref_queue.h"
@@ -27,6 +29,7 @@
 #include "chre/util/fixed_size_vector.h"
 #include "chre/util/system/debug_dump.h"
 #include "chre/util/system/napp_permissions.h"
+#include "chre/util/system/stats_container.h"
 #include "chre_api/chre/event.h"
 
 namespace chre {
@@ -44,9 +47,28 @@
  */
 class Nanoapp : public PlatformNanoapp {
  public:
+  /** @see chrePublishRpcServices */
+  static constexpr size_t kMaxRpcServices = UINT8_MAX;
+  static_assert(
+      std::numeric_limits<decltype(chreNanoappInfo::rpcServiceCount)>::max() >=
+          kMaxRpcServices,
+      "Revisit the constant");
+
   Nanoapp();
 
   /**
+   * Calls the start function of the nanoapp. For dynamically loaded nanoapps,
+   * this must also result in calling through to any of the nanoapp's static
+   * global constructors/init functions, etc., prior to invoking the
+   * nanoappStart.
+   *
+   * @return true if the app was able to start successfully
+   *
+   * @see nanoappStart
+   */
+  bool start();
+
+  /**
    * @return The unique identifier for this Nanoapp instance
    */
   uint16_t getInstanceId() const {
@@ -212,7 +234,7 @@
                           size_t numServices);
 
   /**
-   * @return The list of RPC services pushblished by this nanoapp.
+   * @return The list of RPC services published by this nanoapp.
    */
   const DynamicVector<struct chreNanoappRpcService> &getRpcServices() const {
     return mRpcServices;
@@ -270,6 +292,9 @@
   //! wakeups over time intervals.
   FixedSizeVector<uint16_t, kMaxSizeWakeupBuckets> mWakeupBuckets;
 
+  //! Collects process time in nanoseconds of each event
+  StatsContainer<uint64_t> mEventProcessTime;
+
   //! Metadata needed for keeping track of the registered events for this
   //! nanoapp.
   struct EventRegistration {
@@ -292,6 +317,9 @@
   //! The list of RPC services for this nanoapp.
   DynamicVector<struct chreNanoappRpcService> mRpcServices;
 
+  //! Whether nanoappStart is being executed.
+  bool mIsInNanoappStart = false;
+
   //! @return index of event registration if found. mRegisteredEvents.size() if
   //!     not.
   size_t registrationIndex(uint16_t eventType) const;
diff --git a/core/include/chre/core/system_health_monitor.h b/core/include/chre/core/system_health_monitor.h
new file mode 100644
index 0000000..2b203a4
--- /dev/null
+++ b/core/include/chre/core/system_health_monitor.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 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_CORE_SYSTEM_HEALTH_MONITOR_H_
+#define CHRE_CORE_SYSTEM_HEALTH_MONITOR_H_
+
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/util/enum.h"
+#include "chre/util/non_copyable.h"
+
+namespace chre {
+
+/**
+ * Types of different health check id
+ * User should consider adding a new check id if current id does not describe
+ * the case accurately
+ *
+ * The goal of this enum class is to be granular enough to produce useful debug
+ * information and metric report
+ */
+enum class HealthCheckId : uint16_t {
+  WifiScanResponseTimeout = 0,
+  WifiConfigureScanMonitorTimeout = 1,
+  WifiRequestRangingTimeout = 2,
+  UnexpectedWifiPalCallback = 3,
+
+  //! Must be last
+  NumCheckIds
+};
+
+class SystemHealthMonitor : public NonCopyable {
+ public:
+  /**
+   * Configures if onCheckFailureImpl() should crash
+   *
+   * @param enable true if onCheckFailureImpl() should log the error and crash,
+   * false if onCheckFailureImpl() should only log the error
+   */
+  inline void setFatalErrorOnCheckFailure(bool enable) {
+    mShouldCheckCrash = enable;
+  }
+
+  /**
+   * Provides a runtime configurable way to call/skip FATAL_ERROR
+   * to prevent crashing on programming errors that are low visibility
+   * to users
+   *
+   * Also provides a counter to log the occurrence of each type of defined
+   * HealthCheckId
+   *
+   * @param condition Boolean expression which evaluates to false in the failure
+   *        case
+   * @param id predefined HealthCheckId used to record occurrence of each
+   * failure
+   */
+  static inline void check(bool condition, HealthCheckId id) {
+    if (!condition) {
+      SystemHealthMonitor::onFailure(id);
+    }
+  }
+
+  /**
+   * Similar to check() but should be called when HealthCheck has already failed
+   *
+   * @param id predefined HealthCheckId used to record occurrence of each
+   * failure
+   */
+  static void onFailure(HealthCheckId id);
+
+ private:
+  bool mShouldCheckCrash = false;
+
+  /**
+   * Records how many times a check failed on a HealthCheckId
+   */
+  uint16_t mCheckIdOccurrenceCounter[asBaseType(HealthCheckId::NumCheckIds)];
+
+  /**
+   * Implements the logic once check encountered a false condition
+   * This is needed to prevent the runtime overhead when calling a function
+   * when it is not necessary while also have the ability to modify object
+   * member
+   *
+   *  @param id which HealthCheckId that matches this failure
+   */
+  void onCheckFailureImpl(HealthCheckId id);
+};
+
+}  // namespace chre
+
+#endif  // CHRE_CORE_SYSTEM_HEALTH_MONITOR_H_
diff --git a/core/include/chre/core/wifi_request_manager.h b/core/include/chre/core/wifi_request_manager.h
index 76f0b9f..576b1d8 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/api_manager_common.h"
 #include "chre/core/nanoapp.h"
 #include "chre/core/settings.h"
+#include "chre/core/timer_pool.h"
 #include "chre/platform/platform_wifi.h"
 #include "chre/util/buffer.h"
 #include "chre/util/non_copyable.h"
@@ -294,6 +295,10 @@
   struct PendingRequestBase {
     uint16_t nanoappInstanceId;  //!< ID of the Nanoapp issuing this request
     const void *cookie;          //!< User data supplied by the nanoapp
+
+    PendingRequestBase() = default;
+    PendingRequestBase(uint16_t nanoappInstanceId_, const void *cookie_)
+        : nanoappInstanceId(nanoappInstanceId_), cookie(cookie_) {}
   };
 
   struct PendingRangingRequestBase : public PendingRequestBase {
@@ -328,6 +333,15 @@
     bool enable;  //!< Requested scan monitor state
   };
 
+  struct PendingScanRequest : public PendingRequestBase {
+    struct chreWifiScanParams scanParams;
+
+    PendingScanRequest(uint16_t nanoappInstanceId_, const void *cookie_,
+                       const struct chreWifiScanParams *scanParams_)
+        : PendingRequestBase(nanoappInstanceId_, cookie_),
+          scanParams(*scanParams_) {}
+  };
+
   //! An internal struct to hold scan request data for logging
   struct WifiScanRequestLog {
     WifiScanRequestLog(Nanoseconds timestampIn, uint16_t instanceIdIn,
@@ -353,6 +367,7 @@
 
   enum class PendingNanConfigType { UNKNOWN, ENABLE, DISABLE };
 
+  static constexpr size_t kMaxPendingScanRequest = 4;
   static constexpr size_t kMaxScanMonitorStateTransitions = 8;
   static constexpr size_t kMaxPendingRangingRequests = 4;
   static constexpr size_t kMaxPendingNanSubscriptionRequests = 4;
@@ -365,6 +380,11 @@
   ArrayQueue<PendingScanMonitorRequest, kMaxScanMonitorStateTransitions>
       mPendingScanMonitorRequests;
 
+  //! The queue of scan request. Only one asynchronous scan monitor state
+  //! transition can be in flight at one time. Any further requests are queued
+  //! here.
+  ArrayQueue<PendingScanRequest, kMaxPendingScanRequest> mPendingScanRequests;
+
   //! The list of nanoapps who have enabled scan monitoring. This list is
   //! maintained to ensure that nanoapps are always subscribed to wifi scan
   //! results as requested. Note that a request for wifi scan monitoring can
@@ -378,17 +398,6 @@
   //! format that is used is <subscriptionId, nanoappInstanceId>.
   DynamicVector<NanoappNanSubscriptions> mNanoappSubscriptions;
 
-  // TODO: Support multiple requests for active wifi scans.
-  //! The instance ID of the nanoapp that has a pending active scan request. At
-  //! this time, only one nanoapp can have a pending request for an active WiFi
-  //! scan.
-  Optional<uint16_t> mScanRequestingNanoappInstanceId;
-
-  //! The cookie passed in by a nanoapp making an active request for wifi scans.
-  //! Note that this will only be valid if the mScanRequestingNanoappInstanceId
-  //! is set.
-  const void *mScanRequestingNanoappCookie;
-
   //! This is set to true if the results of an active scan request are pending.
   bool mScanRequestResultsArePending = false;
 
@@ -401,9 +410,6 @@
   PendingNanConfigType mNanConfigRequestToHostPendingType =
       PendingNanConfigType::UNKNOWN;
 
-  //! System time when last scan request was made.
-  Nanoseconds mLastScanRequestTime;
-
   //! Tracks the in-flight ranging request and any others queued up behind it
   ArrayQueue<PendingRangingRequest, kMaxPendingRangingRequests>
       mPendingRangingRequests;
@@ -416,8 +422,16 @@
   static constexpr size_t kNumWifiRequestLogs = 10;
   ArrayQueue<WifiScanRequestLog, kNumWifiRequestLogs> mWifiScanRequestLogs;
 
-  //! Helps ensure we don't get stuck if platform isn't behaving as expected
-  Nanoseconds mRangingResponseTimeout;
+  //! Manages the timer when a ranging request is dispatched to the PAL.
+  TimerHandle mRequestRangingTimeoutHandle;
+
+  //! Manages the timer that starts when a configure scan monitor request is
+  //! dispatched to the PAL.
+  TimerHandle mConfigureScanMonitorTimeoutHandle;
+
+  //! Manages the timer that starts when a configure scan request is dispatched
+  //! to the PAL.
+  TimerHandle mScanRequestTimeoutHandle = CHRE_TIMER_INVALID;
 
   //! System time when the last WiFi scan event was received.
   Milliseconds mLastScanEventTime;
@@ -433,6 +447,14 @@
   bool scanMonitorIsEnabled() const;
 
   /**
+   * Check if a nanoapp already has a pending scan request.
+   *
+   * @param instanceId the instance ID of the nanoapp.
+   * @return true if the nanoapp already has a pending scan request in queue.
+   */
+  bool nanoappHasPendingScanRequest(uint16_t instanceId) const;
+
+  /**
    * @param instanceId the instance ID of the nanoapp.
    * @param index an optional pointer to a size_t to populate with the index of
    *        the nanoapp in the list of nanoapps.
@@ -676,6 +698,21 @@
   bool postRangingAsyncResult(uint8_t errorCode);
 
   /**
+   * Keep issuing pending configure scan monitor request to the platform in
+   * queued order util a successful dispatch or the queue is empty
+   */
+  void dispatchQueuedConfigureScanMonitorRequests();
+
+  /**
+   * Issues the pending scan requests to the platform in queued order until one
+   * dispatched successfully or the queue is empty.
+   *
+   * @param postAsyncResult if a dispatch failure should post a async result.
+   * @return true if successfully dispatched one request.
+   */
+  bool dispatchQueuedScanRequests(bool postAsyncResult);
+
+  /**
    * Issues the next pending ranging request to the platform.
    *
    * @return Result of PlatformWifi::requestRanging()
@@ -832,6 +869,49 @@
    * @param enable Indicates if a NAN enable or disable is being requested.
    */
   void sendNanConfiguration(bool enable);
+
+  /**
+   * Invoked on no response for a configure scan monitor request in the expected
+   * window.
+   */
+  void handleConfigureScanMonitorTimeout();
+
+  /**
+   * Sets up the system timer that invokes handleConfigureScanMonitorTimeout
+   * when the PAL does not respond to configure scan monitor request on time.
+   *
+   * @return TimerHandle that can be used later to cancel the timer if the PAL
+   * has responded in the expected time window.
+   */
+  TimerHandle setConfigureScanMonitorTimer();
+
+  /**
+   * Invoked on no response for a ranging request in the expected window.
+   */
+  void handleRangingRequestTimeout();
+
+  /**
+   * Sets up the system timer that invokes handleRangingRequestTimeout when the
+   * PAL does not respond on time.
+   *
+   * @return TimerHandle that can be used later to cancel the timer if the PAL
+   * has responded in the expected time window.
+   */
+  TimerHandle setRangingRequestTimer();
+
+  /**
+   * Invoked on no response for a scan request in the expected window.
+   */
+  void handleScanRequestTimeout();
+
+  /**
+   * Sets up the system timer that invokes handleScanRequestTimeout when the
+   * PAL does not respond on time.
+   *
+   * @return TimerHandle that can be used later to cancel the timer if the PAL
+   * has responded in the expected time window.
+   */
+  TimerHandle setScanRequestTimer();
 };
 
 }  // namespace chre
diff --git a/core/log.cc b/core/log.cc
index 8195621..c8ac766 100644
--- a/core/log.cc
+++ b/core/log.cc
@@ -14,24 +14,24 @@
  * limitations under the License.
  */
 
-#include <cstdio>
-
-#include "chre/core/event_loop_manager.h"
-#include "chre/platform/system_time.h"
-
-#ifdef CHRE_USE_TOKENIZED_LOGGING
-#include "pw_tokenizer/tokenize_to_global_handler_with_payload.h"
+#ifdef CHRE_TOKENIZED_LOGGING_ENABLED
+#include "chre/platform/log.h"
+#include "pw_tokenizer/encode_args.h"
+#include "pw_tokenizer/tokenize.h"
 
 // The callback function that must be defined to handle an encoded
 // tokenizer message.
-void pw_tokenizer_HandleEncodedMessageWithPayload(pw_tokenizer_Payload logLevel,
-                                                  const uint8_t encodedMsg[],
-                                                  size_t encodedMsgSize) {
-#if defined(CHRE_USE_BUFFERED_LOGGING)
-  chrePlatformEncodedLogToBuffer(static_cast<chreLogLevel>(logLevel),
-                                 encodedMsg, encodedMsgSize);
-#else
-#error "Tokenized logging is currently only supported with buffered logging."
-#endif  // CHRE_USE_BUFFERED_LOGGING
+
+void EncodeTokenizedMessage(uint32_t level, pw_tokenizer_Token token,
+                            pw_tokenizer_ArgTypes types, ...) {
+  va_list args;
+  va_start(args, types);
+  pw::tokenizer::EncodedMessage encodedMessage(token, types, args);
+  va_end(args);
+
+  chrePlatformEncodedLogToBuffer(static_cast<chreLogLevel>(level),
+                                 encodedMessage.data_as_uint8(),
+                                 encodedMessage.size());
 }
-#endif
+
+#endif  // CHRE_TOKENIZED_LOGGING_ENABLED
diff --git a/core/nanoapp.cc b/core/nanoapp.cc
index 2b0f20b..789f2da 100644
--- a/core/nanoapp.cc
+++ b/core/nanoapp.cc
@@ -20,11 +20,13 @@
 #include "chre/platform/assert.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/log.h"
+#include "chre/platform/tracing.h"
 #include "chre/util/system/debug_dump.h"
 #include "chre_api/chre/gnss.h"
 #include "chre_api/chre/version.h"
 
 #include <algorithm>
+#include <cstdint>
 
 #if CHRE_FIRST_SUPPORTED_API_VERSION < CHRE_API_VERSION_1_5
 #define CHRE_GNSS_MEASUREMENT_BACK_COMPAT_ENABLED
@@ -39,6 +41,14 @@
   cycleWakeupBuckets(1);
 }
 
+bool Nanoapp::start() {
+  traceRegisterNanoapp(getInstanceId(), getAppName());
+  mIsInNanoappStart = true;
+  bool success = PlatformNanoapp::start();
+  mIsInNanoappStart = false;
+  return success;
+}
+
 bool Nanoapp::isRegisteredForBroadcastEvent(const Event *event) const {
   bool registered = false;
   uint16_t eventType = event->eventType;
@@ -123,11 +133,23 @@
 }
 
 void Nanoapp::processEvent(Event *event) {
+  Nanoseconds eventStartTime = SystemTime::getMonotonicTime();
+  traceNanoappHandleEventStart(getInstanceId(), event->eventType);
   if (event->eventType == CHRE_EVENT_GNSS_DATA) {
     handleGnssMeasurementDataEvent(event);
   } else {
     handleEvent(event->senderInstanceId, event->eventType, event->eventData);
   }
+  traceNanoappHandleEventEnd(getInstanceId());
+  Nanoseconds eventProcessTime =
+      SystemTime::getMonotonicTime() - eventStartTime;
+  if (Milliseconds(eventProcessTime) >= Milliseconds(100)) {
+    LOGE("Nanoapp 0x%" PRIx64 " took %" PRIu64
+         " ms to process event type %" PRIu16,
+         getAppId(), Milliseconds(eventProcessTime).getMilliseconds(),
+         event->eventType);
+  }
+  mEventProcessTime.addValue(Milliseconds(eventProcessTime).getMilliseconds());
 }
 
 void Nanoapp::blameHostWakeup() {
@@ -166,7 +188,11 @@
   debugDump.print("%" PRIu16 " ]", mWakeupBuckets.front());
 
   // Print total wakeups since boot
-  debugDump.print(" totWakeups=%" PRIu32 " ]\n", mNumWakeupsSinceBoot);
+  debugDump.print(" totWakeups=%" PRIu32 " ", mNumWakeupsSinceBoot);
+
+  // Print mean and max event process time
+  debugDump.print("eventProcessTimeMs: mean=%" PRIu64 ", max=%" PRIu64 "\n",
+                  mEventProcessTime.getMean(), mEventProcessTime.getMax());
 }
 
 bool Nanoapp::permitPermissionUse(uint32_t permission) const {
@@ -221,15 +247,45 @@
 
 bool Nanoapp::publishRpcServices(struct chreNanoappRpcService *services,
                                  size_t numServices) {
-  // TODO(b/204426460): Validate this code is only called from nanoappStart().
+  if (!mIsInNanoappStart) {
+    LOGE("publishRpcServices must be called from nanoappStart");
+    return false;
+  }
+
+  const size_t startSize = mRpcServices.size();
+  const size_t endSize = startSize + numServices;
+  if (endSize > kMaxRpcServices) {
+    return false;
+  }
+
+  mRpcServices.reserve(endSize);
+
   bool success = true;
+
   for (size_t i = 0; i < numServices; i++) {
     if (!mRpcServices.push_back(services[i])) {
       LOG_OOM();
       success = false;
+      break;
     }
   }
 
+  if (success && mRpcServices.size() > 1) {
+    for (size_t i = 0; i < mRpcServices.size() - 1; i++) {
+      for (size_t j = i + 1; j < mRpcServices.size(); j++) {
+        if (mRpcServices[i].id == mRpcServices[j].id) {
+          LOGE("Service id = 0x%016" PRIx64 " can only be published once",
+               mRpcServices[i].id);
+          success = false;
+        }
+      }
+    }
+  }
+
+  if (!success) {
+    mRpcServices.resize(startSize);
+  }
+
   return success;
 }
 
diff --git a/core/sensor_request_manager.cc b/core/sensor_request_manager.cc
index e87442f..6230ccb 100644
--- a/core/sensor_request_manager.cc
+++ b/core/sensor_request_manager.cc
@@ -20,6 +20,7 @@
 #include "chre/util/macros.h"
 #include "chre/util/nested_data_ptr.h"
 #include "chre/util/system/debug_dump.h"
+#include "chre/util/system/event_callbacks.h"
 #include "chre/util/time.h"
 #include "chre_api/chre/version.h"
 
diff --git a/core/settings.cc b/core/settings.cc
index 21ee23b..13d09ae 100644
--- a/core/settings.cc
+++ b/core/settings.cc
@@ -22,6 +22,7 @@
 #include "chre/platform/log.h"
 #include "chre/util/macros.h"
 #include "chre/util/nested_data_ptr.h"
+#include "chre/util/system/event_callbacks.h"
 
 #include "chre_api/chre/user_settings.h"
 
diff --git a/core/system_health_monitor.cc b/core/system_health_monitor.cc
new file mode 100644
index 0000000..04825b9
--- /dev/null
+++ b/core/system_health_monitor.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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/core/system_health_monitor.h"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/log.h"
+#include "chre/util/macros.h"
+
+namespace chre {
+
+void SystemHealthMonitor::onFailure(HealthCheckId id) {
+  EventLoopManagerSingleton::get()->getSystemHealthMonitor().onCheckFailureImpl(
+      id);
+}
+
+void SystemHealthMonitor::onCheckFailureImpl(HealthCheckId id) {
+  auto index = asBaseType(id);
+  if (mShouldCheckCrash) {
+    FATAL_ERROR("HealthMonitor check failed for type %" PRIu16, index);
+  } else {
+    constexpr auto kMaxCount = std::numeric_limits<
+        std::remove_reference_t<decltype(mCheckIdOccurrenceCounter[0])>>::max();
+    CHRE_ASSERT(index < ARRAY_SIZE(mCheckIdOccurrenceCounter));
+    if (mCheckIdOccurrenceCounter[index] == kMaxCount) {
+      LOGD("Cannot record one more HealthCheckId %" PRIu16
+           "occurrence: overflow",
+           index);
+    } else {
+      mCheckIdOccurrenceCounter[index]++;
+    }
+    LOGE("HealthMonitor check failed for type %" PRIu16
+         ", occurrence: %" PRIu16,
+         index, mCheckIdOccurrenceCounter[index]);
+  }
+}
+
+}  // namespace chre
diff --git a/core/telemetry_manager.cc b/core/telemetry_manager.cc
index c58aea8..1cb47f0 100644
--- a/core/telemetry_manager.cc
+++ b/core/telemetry_manager.cc
@@ -24,7 +24,7 @@
 #include "chre/util/macros.h"
 #include "chre/util/nested_data_ptr.h"
 #include "chre/util/time.h"
-#include "pixelatoms.nanopb.h"
+#include "chre_metrics.nanopb.h"
 
 namespace chre {
 
@@ -37,15 +37,15 @@
 // details.
 
 //! Helper define macros for nanopb types.
-#define PIXELATOMS_GET(x) android_hardware_google_pixel_PixelAtoms_##x
-#define PIXELATOMS_GET_PAL_TYPE(x)                        \
-  _android_hardware_google_pixel_PixelAtoms_ChrePalType:: \
-      android_hardware_google_pixel_PixelAtoms_ChrePalType_CHRE_PAL_TYPE_##x
+#define CHREATOMS_GET(x) android_chre_metrics_##x
+#define CHREATOMS_GET_PAL_TYPE(x)     \
+  _android_chre_metrics_ChrePalType:: \
+      android_chre_metrics_ChrePalType_CHRE_PAL_TYPE_##x
 
 void sendMetricToHost(uint32_t atomId, const pb_field_t fields[],
                       const void *data) {
   size_t size;
-  if (!pb_get_encoded_size(&size, PIXELATOMS_GET(ChrePalOpenFailed_fields),
+  if (!pb_get_encoded_size(&size, CHREATOMS_GET(ChrePalOpenFailed_fields),
                            data)) {
     LOGE("Failed to get message size");
   } else {
@@ -54,13 +54,13 @@
       LOG_OOM();
     } else {
       pb_ostream_t stream = pb_ostream_from_buffer(bytes, size);
-      if (!pb_encode(&stream, PIXELATOMS_GET(ChrePalOpenFailed_fields), data)) {
+      if (!pb_encode(&stream, CHREATOMS_GET(ChrePalOpenFailed_fields), data)) {
         LOGE("Failed to metric error %s", PB_GET_ERROR(&stream));
       } else {
         HostCommsManager &manager =
             EventLoopManagerSingleton::get()->getHostCommsManager();
-        if (!manager.sendMetricLog(
-                PIXELATOMS_GET(Atom_chre_pal_open_failed_tag), bytes, size)) {
+        if (!manager.sendMetricLog(CHREATOMS_GET(Atom_chre_pal_open_failed_tag),
+                                   bytes, size)) {
           LOGE("Failed to send metric message");
         }
       }
@@ -69,25 +69,23 @@
   }
 }
 
-void sendPalOpenFailedMetric(
-    _android_hardware_google_pixel_PixelAtoms_ChrePalType pal) {
-  _android_hardware_google_pixel_PixelAtoms_ChrePalOpenFailed result =
-      PIXELATOMS_GET(ChrePalOpenFailed_init_default);
+void sendPalOpenFailedMetric(_android_chre_metrics_ChrePalType pal) {
+  _android_chre_metrics_ChrePalOpenFailed result =
+      CHREATOMS_GET(ChrePalOpenFailed_init_default);
   result.has_pal = true;
   result.pal = pal;
   result.has_type = true;
-  result
-      .type = _android_hardware_google_pixel_PixelAtoms_ChrePalOpenFailed_Type::
-      android_hardware_google_pixel_PixelAtoms_ChrePalOpenFailed_Type_INITIAL_OPEN;
+  result.type = _android_chre_metrics_ChrePalOpenFailed_Type::
+      android_chre_metrics_ChrePalOpenFailed_Type_INITIAL_OPEN;
 
-  sendMetricToHost(PIXELATOMS_GET(Atom_chre_pal_open_failed_tag),
-                   PIXELATOMS_GET(ChrePalOpenFailed_fields), &result);
+  sendMetricToHost(CHREATOMS_GET(Atom_chre_pal_open_failed_tag),
+                   CHREATOMS_GET(ChrePalOpenFailed_fields), &result);
 }
 
 void sendEventLoopStats(uint32_t maxQueueSize, uint32_t meanQueueSize,
                         uint32_t numDroppedEvents) {
-  _android_hardware_google_pixel_PixelAtoms_ChreEventQueueSnapshotReported
-      result = PIXELATOMS_GET(ChreEventQueueSnapshotReported_init_default);
+  _android_chre_metrics_ChreEventQueueSnapshotReported result =
+      CHREATOMS_GET(ChreEventQueueSnapshotReported_init_default);
   result.has_snapshot_chre_get_time_ms = true;
   result.snapshot_chre_get_time_ms =
       SystemTime::getMonotonicTime().toRawNanoseconds() /
@@ -99,30 +97,30 @@
   result.has_num_dropped_events = true;
   result.num_dropped_events = numDroppedEvents;
 
-  sendMetricToHost(PIXELATOMS_GET(Atom_chre_event_queue_snapshot_reported_tag),
-                   PIXELATOMS_GET(ChreEventQueueSnapshotReported_fields),
+  sendMetricToHost(CHREATOMS_GET(Atom_chre_event_queue_snapshot_reported_tag),
+                   CHREATOMS_GET(ChreEventQueueSnapshotReported_fields),
                    &result);
 }
 
-_android_hardware_google_pixel_PixelAtoms_ChrePalType toAtomPalType(
+_android_chre_metrics_ChrePalType toAtomPalType(
     TelemetryManager::PalType type) {
   switch (type) {
     case TelemetryManager::PalType::SENSOR:
-      return PIXELATOMS_GET_PAL_TYPE(SENSOR);
+      return CHREATOMS_GET_PAL_TYPE(SENSOR);
     case TelemetryManager::PalType::WIFI:
-      return PIXELATOMS_GET_PAL_TYPE(WIFI);
+      return CHREATOMS_GET_PAL_TYPE(WIFI);
     case TelemetryManager::PalType::GNSS:
-      return PIXELATOMS_GET_PAL_TYPE(GNSS);
+      return CHREATOMS_GET_PAL_TYPE(GNSS);
     case TelemetryManager::PalType::WWAN:
-      return PIXELATOMS_GET_PAL_TYPE(WWAN);
+      return CHREATOMS_GET_PAL_TYPE(WWAN);
     case TelemetryManager::PalType::AUDIO:
-      return PIXELATOMS_GET_PAL_TYPE(AUDIO);
+      return CHREATOMS_GET_PAL_TYPE(AUDIO);
     case TelemetryManager::PalType::BLE:
-      return PIXELATOMS_GET_PAL_TYPE(BLE);
+      return CHREATOMS_GET_PAL_TYPE(BLE);
     case TelemetryManager::PalType::UNKNOWN:
     default:
       LOGW("Unknown PAL type %" PRIu8, type);
-      return PIXELATOMS_GET_PAL_TYPE(UNKNOWN);
+      return CHREATOMS_GET_PAL_TYPE(UNKNOWN);
   }
 }
 
@@ -134,10 +132,10 @@
 
 void TelemetryManager::onPalOpenFailure(PalType type) {
   auto callback = [](uint16_t /*type*/, void *data, void * /*extraData*/) {
-    _android_hardware_google_pixel_PixelAtoms_ChrePalType palType =
+    _android_chre_metrics_ChrePalType palType =
         toAtomPalType(NestedDataPtr<PalType>(data));
 
-    if (palType != PIXELATOMS_GET_PAL_TYPE(UNKNOWN)) {
+    if (palType != CHREATOMS_GET_PAL_TYPE(UNKNOWN)) {
       sendPalOpenFailedMetric(palType);
     }
   };
diff --git a/core/tests/ble_request_test.cc b/core/tests/ble_request_test.cc
index f3ed88b..bc0dbc5 100644
--- a/core/tests/ble_request_test.cc
+++ b/core/tests/ble_request_test.cc
@@ -52,7 +52,7 @@
   filter.rssiThreshold = -5;
   filter.scanFilterCount = 1;
   auto scanFilters = std::make_unique<chreBleGenericFilter>();
-  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16;
+  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE;
   scanFilters->len = 2;
   filter.scanFilters = scanFilters.get();
   BleRequest enabled(0, true, CHRE_BLE_SCAN_MODE_AGGRESSIVE, 20, &filter);
@@ -65,7 +65,7 @@
   EXPECT_EQ(20, mergedRequest.getReportDelayMs());
   EXPECT_EQ(-5, mergedRequest.getRssiThreshold());
   EXPECT_EQ(1, mergedRequest.getGenericFilters().size());
-  EXPECT_EQ(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16,
+  EXPECT_EQ(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE,
             mergedRequest.getGenericFilters()[0].type);
   EXPECT_EQ(2, mergedRequest.getGenericFilters()[0].len);
 }
@@ -92,7 +92,7 @@
   filter.rssiThreshold = -5;
   filter.scanFilterCount = 1;
   auto scanFilters = std::make_unique<chreBleGenericFilter>();
-  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16;
+  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE;
   scanFilters->len = 4;
   filter.scanFilters = scanFilters.get();
 
@@ -107,7 +107,7 @@
   filter.rssiThreshold = -5;
   filter.scanFilterCount = 1;
   auto scanFilters = std::make_unique<chreBleGenericFilter>();
-  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16;
+  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE;
   scanFilters->len = 4;
   filter.scanFilters = scanFilters.get();
 
@@ -126,7 +126,7 @@
   filter.rssiThreshold = -5;
   filter.scanFilterCount = 1;
   auto scanFilters = std::make_unique<chreBleGenericFilter>();
-  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16;
+  scanFilters->type = CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE;
   scanFilters->len = 4;
   filter.scanFilters = scanFilters.get();
 
diff --git a/core/wifi_request_manager.cc b/core/wifi_request_manager.cc
index 5939618..637cc08 100644
--- a/core/wifi_request_manager.cc
+++ b/core/wifi_request_manager.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "chre/core/wifi_request_manager.h"
+
 #include <cinttypes>
 #include <cstddef>
 #include <cstdint>
@@ -21,16 +23,41 @@
 
 #include "chre/core/event_loop_manager.h"
 #include "chre/core/settings.h"
-#include "chre/core/wifi_request_manager.h"
+#include "chre/core/system_health_monitor.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/log.h"
 #include "chre/platform/system_time.h"
 #include "chre/util/nested_data_ptr.h"
 #include "chre/util/system/debug_dump.h"
+#include "chre/util/system/event_callbacks.h"
 #include "chre_api/chre/version.h"
 #include "include/chre/core/event_loop_common.h"
 #include "include/chre/core/wifi_request_manager.h"
 
+// The default timeout values can be overwritten to lower the runtime
+// for tests. Timeout values cannot be overwritten with a bigger value.
+#ifdef CHRE_TEST_ASYNC_RESULT_TIMEOUT_NS
+static_assert(CHRE_TEST_ASYNC_RESULT_TIMEOUT_NS <=
+              CHRE_ASYNC_RESULT_TIMEOUT_NS);
+#undef CHRE_ASYNC_RESULT_TIMEOUT_NS
+#define CHRE_ASYNC_RESULT_TIMEOUT_NS CHRE_TEST_ASYNC_RESULT_TIMEOUT_NS
+#endif
+
+#ifdef CHRE_TEST_WIFI_RANGING_RESULT_TIMEOUT_NS
+static_assert(CHRE_TEST_WIFI_RANGING_RESULT_TIMEOUT_NS <=
+              CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS);
+#undef CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS
+#define CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS \
+  CHRE_TEST_WIFI_RANGING_RESULT_TIMEOUT_NS
+#endif
+
+#ifdef CHRE_TEST_WIFI_SCAN_RESULT_TIMEOUT_NS
+static_assert(CHRE_TEST_WIFI_SCAN_RESULT_TIMEOUT_NS <=
+              CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS);
+#undef CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS
+#define CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS CHRE_TEST_WIFI_SCAN_RESULT_TIMEOUT_NS
+#endif
+
 namespace chre {
 
 WifiRequestManager::WifiRequestManager() {
@@ -50,6 +77,59 @@
   return mPlatformWifi.getCapabilities();
 }
 
+void WifiRequestManager::dispatchQueuedConfigureScanMonitorRequests() {
+  while (!mPendingScanMonitorRequests.empty()) {
+    const auto &stateTransition = mPendingScanMonitorRequests.front();
+    bool hasScanMonitorRequest =
+        nanoappHasScanMonitorRequest(stateTransition.nanoappInstanceId);
+    if (scanMonitorIsInRequestedState(stateTransition.enable,
+                                      hasScanMonitorRequest)) {
+      // We are already in the target state so just post an event indicating
+      // success
+      postScanMonitorAsyncResultEventFatal(
+          stateTransition.nanoappInstanceId, true /* success */,
+          stateTransition.enable, CHRE_ERROR_NONE, stateTransition.cookie);
+    } else if (scanMonitorStateTransitionIsRequired(stateTransition.enable,
+                                                    hasScanMonitorRequest)) {
+      if (!mPlatformWifi.configureScanMonitor(stateTransition.enable)) {
+        postScanMonitorAsyncResultEventFatal(
+            stateTransition.nanoappInstanceId, false /* success */,
+            stateTransition.enable, CHRE_ERROR, stateTransition.cookie);
+      } else {
+        mConfigureScanMonitorTimeoutHandle = setConfigureScanMonitorTimer();
+        break;
+      }
+    } else {
+      CHRE_ASSERT_LOG(false, "Invalid scan monitor state");
+    }
+    mPendingScanMonitorRequests.pop();
+  }
+}
+
+void WifiRequestManager::handleConfigureScanMonitorTimeout() {
+  if (mPendingScanMonitorRequests.empty()) {
+    LOGE("Configure Scan Monitor timer timedout with no pending request.");
+  } else {
+    EventLoopManagerSingleton::get()->getSystemHealthMonitor().onFailure(
+        HealthCheckId::WifiConfigureScanMonitorTimeout);
+    mPendingScanMonitorRequests.pop();
+
+    dispatchQueuedConfigureScanMonitorRequests();
+  }
+}
+
+TimerHandle WifiRequestManager::setConfigureScanMonitorTimer() {
+  auto callback = [](uint16_t /*type*/, void * /*data*/, void * /*extraData*/) {
+    EventLoopManagerSingleton::get()
+        ->getWifiRequestManager()
+        .handleConfigureScanMonitorTimeout();
+  };
+
+  return EventLoopManagerSingleton::get()->setDelayedCallback(
+      SystemCallbackType::RequestTimeoutEvent, nullptr, callback,
+      Nanoseconds(CHRE_ASYNC_RESULT_TIMEOUT_NS));
+}
+
 bool WifiRequestManager::configureScanMonitor(Nanoapp *nanoapp, bool enable,
                                               const void *cookie) {
   CHRE_ASSERT(nanoapp);
@@ -73,6 +153,8 @@
         mPendingScanMonitorRequests.pop_back();
         LOGE("Failed to enable the scan monitor for nanoapp instance %" PRIu16,
              instanceId);
+      } else {
+        mConfigureScanMonitorTimeoutHandle = setConfigureScanMonitorTimer();
       }
     }
   } else {
@@ -116,6 +198,9 @@
         static_cast<const struct chreWifiNanRangingParams *>(rangingParams);
     success = mPlatformWifi.requestNanRanging(params);
   }
+  if (success) {
+    mRequestRangingTimeoutHandle = setRangingRequestTimer();
+  }
   return success;
 }
 
@@ -140,6 +225,7 @@
 
 bool WifiRequestManager::sendRangingRequest(PendingRangingRequest &request) {
   bool success = false;
+
   if (request.type == RangingType::WIFI_AP) {
     struct chreWifiRangingParams params = {};
     params.targetListLen = static_cast<uint8_t>(request.targetList.size());
@@ -151,9 +237,36 @@
                 CHRE_WIFI_BSSID_LEN);
     success = mPlatformWifi.requestNanRanging(&params);
   }
+  if (success) {
+    mRequestRangingTimeoutHandle = setRangingRequestTimer();
+  }
   return success;
 }
 
+void WifiRequestManager::handleRangingRequestTimeout() {
+  if (mPendingRangingRequests.empty()) {
+    LOGE("Request ranging timer timedout with no pending request.");
+  } else {
+    EventLoopManagerSingleton::get()->getSystemHealthMonitor().onFailure(
+        HealthCheckId::WifiRequestRangingTimeout);
+    mPendingRangingRequests.pop();
+    while (!mPendingRangingRequests.empty() && !dispatchQueuedRangingRequest())
+      ;
+  }
+}
+
+TimerHandle WifiRequestManager::setRangingRequestTimer() {
+  auto callback = [](uint16_t /*type*/, void * /*data*/, void * /*extraData*/) {
+    EventLoopManagerSingleton::get()
+        ->getWifiRequestManager()
+        .handleRangingRequestTimeout();
+  };
+
+  return EventLoopManagerSingleton::get()->setDelayedCallback(
+      SystemCallbackType::RequestTimeoutEvent, nullptr, callback,
+      Nanoseconds(CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS));
+}
+
 bool WifiRequestManager::requestRanging(RangingType rangingType,
                                         Nanoapp *nanoapp,
                                         const void *rangingParams,
@@ -168,7 +281,6 @@
     PendingRangingRequest &req = mPendingRangingRequests.back();
     req.nanoappInstanceId = nanoapp->getInstanceId();
     req.cookie = cookie;
-
     if (mPendingRangingRequests.size() == 1) {
       // First in line; dispatch request immediately
       if (!areRequiredSettingsEnabled()) {
@@ -182,16 +294,8 @@
         mPendingRangingRequests.pop_back();
       } else {
         success = true;
-        mRangingResponseTimeout =
-            SystemTime::getMonotonicTime() +
-            Nanoseconds(CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS);
       }
     } else {
-      // Dispatch request later, after prior requests finish
-      // TODO(b/65331248): use a timer to ensure the platform is meeting its
-      // contract
-      CHRE_ASSERT_LOG(SystemTime::getMonotonicTime() <= mRangingResponseTimeout,
-                      "WiFi platform didn't give callback in time");
       success = updateRangingRequest(rangingType, req, rangingParams);
       if (!success) {
         LOG_OOM();
@@ -202,21 +306,47 @@
   return success;
 }
 
+void WifiRequestManager::handleScanRequestTimeout() {
+  if (mPendingScanRequests.empty()) {
+    LOGE("Scan Request timer timedout with no pending request.");
+  } else {
+    EventLoopManagerSingleton::get()->getSystemHealthMonitor().onFailure(
+        HealthCheckId::WifiScanResponseTimeout);
+    mPendingScanRequests.pop();
+    dispatchQueuedScanRequests(true /* postAsyncResult */);
+  }
+  mScanRequestTimeoutHandle = CHRE_TIMER_INVALID;
+}
+
+TimerHandle WifiRequestManager::setScanRequestTimer() {
+  CHRE_ASSERT(mScanRequestTimeoutHandle == CHRE_TIMER_INVALID);
+
+  auto callback = [](uint16_t /*type*/, void * /*data*/, void * /*extraData*/) {
+    EventLoopManagerSingleton::get()
+        ->getWifiRequestManager()
+        .handleScanRequestTimeout();
+  };
+
+  return EventLoopManagerSingleton::get()->setDelayedCallback(
+      SystemCallbackType::RequestTimeoutEvent, nullptr, callback,
+      Nanoseconds(CHRE_WIFI_SCAN_RESULT_TIMEOUT_NS));
+}
+
+bool WifiRequestManager::nanoappHasPendingScanRequest(
+    uint16_t instanceId) const {
+  for (const auto &scanRequest : mPendingScanRequests) {
+    if (scanRequest.nanoappInstanceId == instanceId) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool WifiRequestManager::requestScan(Nanoapp *nanoapp,
                                      const struct chreWifiScanParams *params,
                                      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();
-  }
-
   // Handle compatibility with nanoapps compiled against API v1.1, which doesn't
   // include the radioChainPref parameter in chreWifiScanParams
   struct chreWifiScanParams paramsCompat;
@@ -227,14 +357,13 @@
   }
 
   bool success = false;
-  if (mScanRequestingNanoappInstanceId.has_value()) {
-    LOGE("Active wifi scan request made by 0x%" PRIx64
-         " while a request by 0x%" PRIx64 " is in flight",
-         nanoapp->getAppId(),
-         EventLoopManagerSingleton::get()
-             ->getEventLoop()
-             .findNanoappByInstanceId(mScanRequestingNanoappInstanceId.value())
-             ->getAppId());
+  uint16_t nanoappInstanceId = nanoapp->getInstanceId();
+  if (nanoappHasPendingScanRequest(nanoappInstanceId)) {
+    LOGE("Can't issue new scan request: nanoapp: %" PRIx64
+         " already has a pending request",
+         nanoapp->getAppId());
+  } else if (!mPendingScanRequests.emplace(nanoappInstanceId, cookie, params)) {
+    LOG_OOM();
   } else if (!EventLoopManagerSingleton::get()
                   ->getSettingManager()
                   .getSettingEnabled(Setting::WIFI_AVAILABLE)) {
@@ -242,24 +371,20 @@
     success = true;
     handleScanResponse(false /* pending */, CHRE_ERROR_FUNCTION_DISABLED);
   } else {
-    success = mPlatformWifi.requestScan(params);
-    if (!success) {
-      LOGE("Wifi scan request failed");
+    if (mPendingScanRequests.size() == 1) {
+      success = dispatchQueuedScanRequests(false /* postAsyncResult */);
+    } else {
+      success = true;
     }
   }
 
-  if (success) {
-    mScanRequestingNanoappInstanceId = nanoapp->getInstanceId();
-    mScanRequestingNanoappCookie = cookie;
-    mLastScanRequestTime = SystemTime::getMonotonicTime();
-    addWifiScanRequestLog(nanoapp->getInstanceId(), params);
-  }
-
   return success;
 }
 
 void WifiRequestManager::handleScanMonitorStateChange(bool enabled,
                                                       uint8_t errorCode) {
+  EventLoopManagerSingleton::get()->cancelDelayedCallback(
+      mConfigureScanMonitorTimeoutHandle);
   struct CallbackState {
     bool enabled;
     uint8_t errorCode;
@@ -303,6 +428,8 @@
 
 void WifiRequestManager::handleRangingEvent(
     uint8_t errorCode, struct chreWifiRangingEvent *event) {
+  EventLoopManagerSingleton::get()->cancelDelayedCallback(
+      mRequestRangingTimeoutHandle);
   auto callback = [](uint16_t /*type*/, void *data, void *extraData) {
     uint8_t cbErrorCode = NestedDataPtr<uint8_t>(extraData);
     EventLoopManagerSingleton::get()
@@ -534,7 +661,7 @@
 }
 
 void WifiRequestManager::logStateToBuffer(DebugDumpWrapper &debugDump) const {
-  debugDump.print("\nWifi: scan monitor %s\n",
+  debugDump.print("\nWifi scan monitor %s\n",
                   scanMonitorIsEnabled() ? "enabled" : "disabled");
 
   if (scanMonitorIsEnabled()) {
@@ -544,9 +671,11 @@
     }
   }
 
-  if (mScanRequestingNanoappInstanceId.has_value()) {
-    debugDump.print(" Wifi request pending nanoappId=%" PRIu16 "\n",
-                    mScanRequestingNanoappInstanceId.value());
+  if (!mPendingScanRequests.empty()) {
+    debugDump.print(" Wifi scan request queue:\n");
+    for (const auto &request : mPendingScanRequests) {
+      debugDump.print(" nappId=%" PRIu16, request.nanoappInstanceId);
+    }
   }
 
   if (!mPendingScanMonitorRequests.empty()) {
@@ -562,6 +691,7 @@
                   mWifiScanRequestLogs.size());
   static_assert(kNumWifiRequestLogs <= INT8_MAX,
                 "kNumWifiRequestLogs must be <= INT8_MAX");
+
   for (int8_t i = static_cast<int8_t>(mWifiScanRequestLogs.size()) - 1; i >= 0;
        i--) {
     const auto &log = mWifiScanRequestLogs[static_cast<size_t>(i)];
@@ -809,33 +939,7 @@
     mPendingScanMonitorRequests.pop();
   }
 
-  while (!mPendingScanMonitorRequests.empty()) {
-    const auto &stateTransition = mPendingScanMonitorRequests.front();
-    bool hasScanMonitorRequest =
-        nanoappHasScanMonitorRequest(stateTransition.nanoappInstanceId);
-    if (scanMonitorIsInRequestedState(stateTransition.enable,
-                                      hasScanMonitorRequest)) {
-      // We are already in the target state so just post an event indicating
-      // success
-      postScanMonitorAsyncResultEventFatal(
-          stateTransition.nanoappInstanceId, true /* success */,
-          stateTransition.enable, CHRE_ERROR_NONE, stateTransition.cookie);
-    } else if (scanMonitorStateTransitionIsRequired(stateTransition.enable,
-                                                    hasScanMonitorRequest)) {
-      if (mPlatformWifi.configureScanMonitor(stateTransition.enable)) {
-        break;
-      } else {
-        postScanMonitorAsyncResultEventFatal(
-            stateTransition.nanoappInstanceId, false /* success */,
-            stateTransition.enable, CHRE_ERROR, stateTransition.cookie);
-      }
-    } else {
-      CHRE_ASSERT_LOG(false, "Invalid scan monitor state");
-      break;
-    }
-
-    mPendingScanMonitorRequests.pop();
-  }
+  dispatchQueuedConfigureScanMonitorRequests();
 }
 
 void WifiRequestManager::postNanAsyncResultEvent(uint16_t nanoappInstanceId,
@@ -857,31 +961,39 @@
         nanoappInstanceId);
   }
 }
+
 void WifiRequestManager::handleScanResponseSync(bool pending,
                                                 uint8_t errorCode) {
   // TODO(b/65206783): re-enable this assertion
-  // CHRE_ASSERT_LOG(mScanRequestingNanoappInstanceId.has_value(),
+  // CHRE_ASSERT_LOG(mPendingScanRequests.empty(),
   //                "handleScanResponseSync called with no outstanding
   //                request");
-  if (!mScanRequestingNanoappInstanceId.has_value()) {
+  if (mPendingScanRequests.empty()) {
     LOGE("handleScanResponseSync called with no outstanding request");
   }
 
+  if (mScanRequestTimeoutHandle != CHRE_TIMER_INVALID) {
+    EventLoopManagerSingleton::get()->cancelDelayedCallback(
+        mScanRequestTimeoutHandle);
+    mScanRequestTimeoutHandle = CHRE_TIMER_INVALID;
+  }
+
   // 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()) {
+  if (!mPendingScanRequests.empty()) {
     bool success = (pending && errorCode == CHRE_ERROR_NONE);
     if (!success) {
       LOGW("Wifi scan request failed: pending %d, errorCode %" PRIu8, pending,
            errorCode);
     }
-    postScanRequestAsyncResultEventFatal(*mScanRequestingNanoappInstanceId,
+    PendingScanRequest &currentScanRequest = mPendingScanRequests.front();
+    postScanRequestAsyncResultEventFatal(currentScanRequest.nanoappInstanceId,
                                          success, errorCode,
-                                         mScanRequestingNanoappCookie);
+                                         currentScanRequest.cookie);
 
     // Set a flag to indicate that results may be pending.
     mScanRequestResultsArePending = pending;
@@ -890,17 +1002,18 @@
       Nanoapp *nanoapp =
           EventLoopManagerSingleton::get()
               ->getEventLoop()
-              .findNanoappByInstanceId(*mScanRequestingNanoappInstanceId);
+              .findNanoappByInstanceId(currentScanRequest.nanoappInstanceId);
       if (nanoapp == nullptr) {
         LOGW("Received WiFi scan response for unknown nanoapp");
       } else {
         nanoapp->registerForBroadcastEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
       }
     } else {
-      // If the scan results are not pending, clear the nanoapp instance ID.
-      // Otherwise, wait for the results to be delivered and then clear the
-      // instance ID.
-      mScanRequestingNanoappInstanceId.reset();
+      // If the scan results are not pending, pop the first event since it's no
+      // longer waiting for anything. Otherwise, wait for the results to be
+      // delivered and then pop the first request.
+      mPendingScanRequests.pop();
+      dispatchQueuedScanRequests(true /* postAsyncResult */);
     }
   }
 }
@@ -944,8 +1057,6 @@
     asyncError = CHRE_ERROR;
   } else {
     success = true;
-    mRangingResponseTimeout = SystemTime::getMonotonicTime() +
-                              Nanoseconds(CHRE_WIFI_RANGING_RESULT_TIMEOUT_NS);
   }
 
   if (asyncError != CHRE_ERROR_NONE) {
@@ -989,6 +1100,34 @@
     ;
 }
 
+bool WifiRequestManager::dispatchQueuedScanRequests(bool postAsyncResult) {
+  while (!mPendingScanRequests.empty()) {
+    uint8_t asyncError = CHRE_ERROR_NONE;
+    const PendingScanRequest &currentScanRequest = mPendingScanRequests.front();
+
+    if (!EventLoopManagerSingleton::get()
+             ->getSettingManager()
+             .getSettingEnabled(Setting::WIFI_AVAILABLE)) {
+      asyncError = CHRE_ERROR_FUNCTION_DISABLED;
+    } else if (!mPlatformWifi.requestScan(&currentScanRequest.scanParams)) {
+      asyncError = CHRE_ERROR;
+    } else {
+      mScanRequestTimeoutHandle = setScanRequestTimer();
+      return true;
+    }
+
+    if (postAsyncResult) {
+      postScanRequestAsyncResultEvent(currentScanRequest.nanoappInstanceId,
+                                      false /*success*/, asyncError,
+                                      currentScanRequest.cookie);
+    } else {
+      LOGE("Wifi scan request failed");
+    }
+    mPendingScanRequests.pop();
+  }
+  return false;
+}
+
 void WifiRequestManager::handleRangingEventSync(
     uint8_t errorCode, struct chreWifiRangingEvent *event) {
   if (!areRequiredSettingsEnabled()) {
@@ -1025,20 +1164,19 @@
       mScanRequestResultsArePending = false;
     }
 
-    if (!mScanRequestResultsArePending &&
-        mScanRequestingNanoappInstanceId.has_value()) {
-      Nanoapp *nanoapp =
-          EventLoopManagerSingleton::get()
-              ->getEventLoop()
-              .findNanoappByInstanceId(*mScanRequestingNanoappInstanceId);
+    if (!mScanRequestResultsArePending && !mPendingScanRequests.empty()) {
+      uint16_t pendingNanoappInstanceId =
+          mPendingScanRequests.front().nanoappInstanceId;
+      Nanoapp *nanoapp = EventLoopManagerSingleton::get()
+                             ->getEventLoop()
+                             .findNanoappByInstanceId(pendingNanoappInstanceId);
       if (nanoapp == nullptr) {
         LOGW("Attempted to unsubscribe unknown nanoapp from WiFi scan events");
-      } else if (!nanoappHasScanMonitorRequest(
-                     *mScanRequestingNanoappInstanceId)) {
+      } else if (!nanoappHasScanMonitorRequest(pendingNanoappInstanceId)) {
         nanoapp->unregisterForBroadcastEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
       }
-
-      mScanRequestingNanoappInstanceId.reset();
+      mPendingScanRequests.pop();
+      dispatchQueuedScanRequests(true /* postAsyncResult */);
     }
   }
 
diff --git a/doc/framework_testing.md b/doc/framework_testing.md
index 01ebfca..5a336ba 100644
--- a/doc/framework_testing.md
+++ b/doc/framework_testing.md
@@ -9,7 +9,10 @@
 
 ## Unit tests
 
-Currently, unit tests exist for various core components and utilities. Since
+### Tests run on a host machine
+
+Currently, unit tests exist for various core components and utilities capable
+of running on a Linux host machine. Since
 platform-specific components likely aren’t compilable/available on a host
 machine, only components that are OS independent can be tested via this path.
 
@@ -20,6 +23,76 @@
 
 Unit tests can be built and executed using `run_tests.sh`.
 
+
+### On-device unit tests
+
+#### Background
+
+The framework aims to provide an environment to test CHRE (and its users) code
+on-device, using [Pigweed's][PW_URL] Unit Test [Framework][PW_UT_URL]. Test
+instantiations are syntactically identical to [Googletest][GT_URL], so
+modifications to on-host unit tests to run on-device are easier.
+
+CHRE recommends running the same host-side gtests on-device using this
+framework, to catch subtle bugs. For example, the target CPU may raise an
+exception on unaligned access, when the same code would run without any
+problems on a local x86-based machine.
+
+#### Use Cases
+
+Example use cases of the framework include:
+
+* In continuous integration frameworks with device farms
+* As a superior alternative to logging and/or flag based debugging to quickly test a feature
+* As a modular set of tests to test feature or peripheral functioning (eg: a system timer implementation) during device bringup.
+
+###### Note
+
+One key difference is to run the tests via a call to `chre::runAllTests` in
+_chre/test/common/unit_test.h_, which basically wraps the gtest `RUN_ALL_TESTS`
+macro, and implements CHRE specific event handlers for Pigweed's UT Framework.
+
+#### Running Tests
+
+Under the current incarnation of the CHRE Unit Test Framework, the following
+steps need to be taken to run the on-device tests:
+* Set to true and export an environment variable called `CHRE_ON_DEVICE_TESTS_ENABLED`
+from your Makefile invocation before CHRE is built.
+  * Ensure that this flag is not always set to avoid codesize bloat.
+* Append your test source file to `COMMON_SRCS` either in _test/test.mk_ or in
+your own Makefile.
+* Call `chre::runAllTests` from somewhere in your code.
+
+##### Sample code
+
+In _math_test.cc_
+```cpp
+#include <gtest/gtest.h>
+
+TEST(MyMath, Add) {
+  int x = 1, y = 2;
+  int result = myAdd(x, y);
+  EXPECT_EQ(result, 3);
+}
+```
+
+In _some_source.cc_
+```cpp
+#include "chre/test/common/unit_test.h"
+
+void utEntryFunc() {
+  chre::runAllTests();
+}
+```
+
+#### Caveats
+
+Some advanced features of gtest (SCOPED_TRACE, etc.) are unsupported by Pigweed.
+
+#### Compatibility
+
+The framework has been tested with Pigweed's git revision ee460238b8a7ec0a6b4f61fe7e67a12231db6d3e.
+
 ## PAL implementation tests
 
 PAL implementation tests verify implementations of PAL interfaces adhere to the
@@ -56,3 +129,7 @@
 under `java/test/chqts/` for the Java side code and `apps/test/chqts/` for the
 CHQTS-only nanoapp code and `apps/test/common/` for the nanoapp code shared by
 CHQTS and other test suites.
+
+[PW_URL]: https://pigweed.dev
+[PW_UT_URL]: https://pigweed.googlesource.com/pigweed/pigweed/+/refs/heads/master/pw_unit_test
+[GT_URL]: https://github.com/google/googletest
diff --git a/doc/nanoapp_developer_guide.md b/doc/nanoapp_developer_guide.md
index 99a2a86..7c199f0 100644
--- a/doc/nanoapp_developer_guide.md
+++ b/doc/nanoapp_developer_guide.md
@@ -99,8 +99,8 @@
 The CHRE API is the key interface between each nanoapp and the underlying
 system. Refer to the extensive API documentation in the header files at
 `chre_api/include`, as well as usage of the APIs by sample nanoapps. The CHRE
-API is normally included via `#include <chre.h>`.
-
+API is normally included via `
+#include "chre_api/chre.h"`.
 ## Utility Libraries
 
 Some source and header files under `util` are specifically designed to aid in
diff --git a/doc/porting_guide.md b/doc/porting_guide.md
index 7c4469f..1c960b7 100644
--- a/doc/porting_guide.md
+++ b/doc/porting_guide.md
@@ -216,6 +216,63 @@
 `TARGET_SO_LATE_LIBS` in the build variant’s makefile - see the build system
 documentation for more details.
 
+#### Recommended Implementation flow
+
+While there may be minor differences, most CHRE PAL [API][CHRE_PAL_DIR_URL]
+implementations follow the following pattern:
+
+1. **Implement the Module API for CHRE**
+
+CHRE provides a standardized structure containing various interfaces for
+calling into the vendor's closed source code in _pal/<feature>.h_. These
+functions must be implemented by the platform, and provided to CHRE when a call
+to _chrePal<feature>GetApi_ function is called. Functions to be implemented are
+of two broad categories:
+
+* _Access functions_
+
+      CHRE provides feature specific callbacks (see 2. below) to the PAL by
+      invoking the _open()_ function in the Module API provided above. The
+      structures returned by this function call must be stored somewhere by the
+      PAL, and used as necessary to call into the CHRE core. Typically, one or
+      more of these callbacks need to be invoked in response to a request from
+      CHRE using the Module API provided above.
+
+      The _close()_ function, when invoked by CHRE, indicates that CHRE is
+      shutting down. It is now the PAL's responsibility to perform cleanup,
+      cancel active requests, and not invoke any CHRE callbacks from this point
+      on.
+
+* _Request functions_
+
+      These are interfaces in the PAL API that are module specific, and used by
+      CHRE to call into the vendor PAL code.
+
+2. ** Use CHRE's callbacks **
+
+CHRE provides a set of module specific callbacks by invoking the _open()_ access
+function provided by the Module API. It then starts performing control or data
+requests as needed, by invoking the request functions in the Module API. The PAL
+is expected to process these requests, and invoke the appropriate CHRE provided
+callback in response. Note that some callbacks might require memory ownership
+to be held until explicitly released. For example, upon an audio request from
+CHRE via a call to the `requestAudioDataEvent` audio PAL API, the platform
+might invoke the `audioDataEventCallback` when the audio data is ready and
+packaged approapriately into a `chreAudioDataEvent` structure. The platform
+must ensure that the memory associated with this data structure remains in
+context until CHRE explicitly releases it via a call to `releaseAudioDataEvent`.
+
+Please refer to the appropriate PAL documentation to ensure that such
+dependencies are understood early in the development phase.
+
+#### Reference implementations
+
+Please refer to the various reference implementations that are
+[available][CHRE_LINUX_DIR_URL] for CHRE PALS for the Linux platform. Note that
+this implementation is meant to highlight the usage of the PAL APIs and
+callbacks, and might not necessarily port directly over into a resource
+constrained embedded context.
+
 ### PAL Verification
 
 There are several ways to test the PAL implementation beyond manual testing.
@@ -251,3 +308,56 @@
 working on a reference implementation for a future release. Please reach out via
 your TAM if you are interested in integrating this reference code prior to its
 public release.
+
+[CHRE_PAL_DIR_URL]:  https://cs.android.com/android/platform/superproject/+/master:system/chre/pal/include/chre/pal/
+[CHRE_LINUX_DIR_URL]: https://cs.android.com/android/platform/superproject/+/master:system/chre/platform/linux/
+
+## Adding Context Hub support
+
+Once you have implemented the necessary pieces described previously, you are
+now ready to add the Context Hub support on the device! Here are the necessary
+steps to do this:
+
+1. Add the HAL implementation on the device
+
+Add the build target of the Context Hub HAL implementation to your device .mk
+file. For example, if the default generic Context Hub HAL is being used, you
+can add the following:
+
+```
+PRODUCT_PACKAGES += \
+    android.hardware.contexthub-service.generic
+```
+
+
+Currently, the generic Context Hub HAL relies on the CHRE daemon to communicate
+with CHRE. If you are using one of our existing platforms, you can add one of
+the following CHRE daemon build targets to your PRODUCT_PACKAGES as you did the
+generic HAL above.
+
+Qualcomm target: `chre`\
+Exynos target: `chre_daemon_exynos`\
+MediaTek target: `TBD`
+
+Otherwise, you can look at those target definitions to define a new one for
+your specific platform.
+
+2. Add the relevant SElinux policies for the device
+
+Resolve any missing SElinux violations by using the relevant tools such as
+audit2allow, and updating the SElinux policies for your device. You may follow
+the directions in [the official Android page](https://source.android.com/docs/security/features/selinux/validate)
+for additional guidance.
+
+3. Add the Context Hub feature flag for the device
+
+Add the following in your device.mk file:
+
+```
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.hardware.context_hub.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.context_hub.xml
+```
+
+The above change will enable the Context Hub Service on the device, and expects
+that the Context Hub HAL comes up. If (1) and (2) are not performed, the device
+may fail to boot to the Android home screen.
diff --git a/external/flatbuffers/include/flatbuffers/flatbuffers.h b/external/flatbuffers/include/flatbuffers/flatbuffers.h
index 7b59fa0..0f45157 100644
--- a/external/flatbuffers/include/flatbuffers/flatbuffers.h
+++ b/external/flatbuffers/include/flatbuffers/flatbuffers.h
@@ -2841,31 +2841,31 @@
 #endif  // !defined(_WIN32) && !defined(__CYGWIN__)
 
 #define FLATBUFFERS_DEFINE_BITMASK_OPERATORS(E, T)\
-    inline E operator | (E lhs, E rhs){\
-        return E(T(lhs) | T(rhs));\
+    inline (E) operator | ((E) lhs, (E) rhs){\
+        return (E)(T(lhs) | T(rhs));\
     }\
-    inline E operator & (E lhs, E rhs){\
-        return E(T(lhs) & T(rhs));\
+    inline (E) operator & ((E) lhs, (E) rhs){\
+        return (E)(T(lhs) & T(rhs));\
     }\
-    inline E operator ^ (E lhs, E rhs){\
-        return E(T(lhs) ^ T(rhs));\
+    inline (E) operator ^ ((E) lhs, (E) rhs){\
+        return (E)(T(lhs) ^ T(rhs));\
     }\
-    inline E operator ~ (E lhs){\
-        return E(~T(lhs));\
+    inline (E) operator ~ ((E) lhs){\
+        return (E)(~T(lhs));\
     }\
-    inline E operator |= (E &lhs, E rhs){\
+    inline (E) operator |= ((E) &lhs, (E) rhs){\
         lhs = lhs | rhs;\
         return lhs;\
     }\
-    inline E operator &= (E &lhs, E rhs){\
+    inline (E) operator &= ((E) &lhs, (E) rhs){\
         lhs = lhs & rhs;\
         return lhs;\
     }\
-    inline E operator ^= (E &lhs, E rhs){\
+    inline (E) operator ^= ((E) &lhs, (E) rhs){\
         lhs = lhs ^ rhs;\
         return lhs;\
     }\
-    inline bool operator !(E rhs) \
+    inline bool operator !((E) rhs) \
     {\
         return !bool(T(rhs)); \
     }
diff --git a/external/kiss_fft/_kiss_fft_guts.h b/external/kiss_fft/_kiss_fft_guts.h
index 6aa102d..6968909 100644
--- a/external/kiss_fft/_kiss_fft_guts.h
+++ b/external/kiss_fft/_kiss_fft_guts.h
@@ -42,18 +42,19 @@
    C_ADDTO( res , a)    : res += a
  * */
 #ifdef FIXED_POINT
+#include <stdint.h>
 #if (FIXED_POINT==32)
 # define FRACBITS 31
 # define SAMPPROD int64_t
-#define SAMP_MAX 2147483647
+#define SAMP_MAX INT32_MAX
+#define SAMP_MIN INT32_MIN
 #else
 # define FRACBITS 15
-# define SAMPPROD int32_t 
-#define SAMP_MAX 32767
+# define SAMPPROD int32_t
+#define SAMP_MAX INT16_MAX
+#define SAMP_MIN INT16_MIN
 #endif
 
-#define SAMP_MIN -SAMP_MAX
-
 #if defined(CHECK_OVERFLOW)
 #ifdef CHRE_KISS_FFT_CAN_USE_STDIO
 #  define CHECK_OVERFLOW_OP(a,op,b)  \
diff --git a/external/kiss_fft/kiss_fft.h b/external/kiss_fft/kiss_fft.h
index 64c50f4..0305fdb 100644
--- a/external/kiss_fft/kiss_fft.h
+++ b/external/kiss_fft/kiss_fft.h
@@ -35,7 +35,7 @@
 
 
 #ifdef FIXED_POINT
-#include <sys/types.h>	
+#include <stdint.h>
 # if (FIXED_POINT == 32)
 #  define kiss_fft_scalar int32_t
 # else	
diff --git a/external/pigweed/pw_assert_nanoapp/public_overrides/pw_assert_backend/assert_backend.h b/external/pigweed/pw_assert_nanoapp/public_overrides/pw_assert_backend/assert_backend.h
new file mode 100644
index 0000000..98c1d77
--- /dev/null
+++ b/external/pigweed/pw_assert_nanoapp/public_overrides/pw_assert_backend/assert_backend.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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 _PW_ASSERT_NANOAPP_PW_ASSERT_BACKEND_H_
+#define _PW_ASSERT_NANOAPP_PW_ASSERT_BACKEND_H_
+
+#include "chre/re.h"
+#include "pw_log/log.h"
+
+#define PW_ASSERT_HANDLE_FAILURE(condition_string)  \
+  do {                                              \
+    PW_HANDLE_LOG(                                  \
+        PW_LOG_LEVEL_FATAL,                         \
+        PW_LOG_MODULE_NAME,                         \
+        PW_LOG_FLAGS,                               \
+        "Assert failed: " condition_string);        \
+    chreAbort(UINT32_MAX);                          \
+  } while (0)
+
+#endif // _PW_ASSERT_NANOAPP_PW_ASSERT_BACKEND_H_
diff --git a/external/pigweed/pw_assert_nanoapp/public_overrides/pw_assert_backend/check_backend.h b/external/pigweed/pw_assert_nanoapp/public_overrides/pw_assert_backend/check_backend.h
new file mode 100644
index 0000000..99029d9
--- /dev/null
+++ b/external/pigweed/pw_assert_nanoapp/public_overrides/pw_assert_backend/check_backend.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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 _PW_ASSERT_NANOAPP_PW_CHECK_BACKEND_H_
+#define _PW_ASSERT_NANOAPP_PW_CHECK_BACKEND_H_
+
+#include "chre/re.h"
+#include "pw_log/log.h"
+
+#include <stdint.h>
+
+#define PW_HANDLE_CRASH(message, ...) \
+  do {                                \
+    PW_HANDLE_LOG(PW_LOG_LEVEL_FATAL, \
+                  PW_LOG_MODULE_NAME, \
+                  PW_LOG_FLAGS,       \
+                  "Crash: " message,  \
+                  ##__VA_ARGS__);     \
+    chreAbort(UINT32_MAX);            \
+  } while (0)
+
+#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...) \
+  do {                                                           \
+    PW_LOG(PW_LOG_LEVEL_FATAL,                                   \
+           PW_LOG_MODULE_NAME,                                   \
+           PW_LOG_FLAGS,                                         \
+           "Check failed: " condition_string ". " message,       \
+           ##__VA_ARGS__);                                       \
+    chreAbort(UINT32_MAX);                                       \
+  } while (0)
+
+#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(arg_a_str,                \
+                                                arg_a_val,                \
+                                                comparison_op_str,        \
+                                                arg_b_str,                \
+                                                arg_b_val,                \
+                                                type_fmt,                 \
+                                                message, ...)             \
+  do {                                                                    \
+    PW_LOG(PW_LOG_LEVEL_FATAL,                                            \
+           PW_LOG_MODULE_NAME,                                            \
+           PW_LOG_FLAGS,                                                  \
+           "Check failed: "                                               \
+                 arg_a_str " (=" type_fmt ") "                            \
+                 comparison_op_str " "                                    \
+                 arg_b_str " (=" type_fmt ")"                             \
+                 ". " message,                                            \
+              arg_a_val, arg_b_val, ##__VA_ARGS__);                       \
+    chreAbort(UINT32_MAX);                                                \
+  } while(0)
+
+#endif // _PW_ASSERT_NANOAPP_PW_CHECK_BACKEND_H_
diff --git a/external/pigweed/pw_log_nanoapp/public_overrides/pw_log_backend/log_backend.h b/external/pigweed/pw_log_nanoapp/public_overrides/pw_log_backend/log_backend.h
new file mode 100644
index 0000000..d958429
--- /dev/null
+++ b/external/pigweed/pw_log_nanoapp/public_overrides/pw_log_backend/log_backend.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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 _PW_LOG_NANOAPP_PW_LOG_BACKEND_H_
+#define _PW_LOG_NANOAPP_PW_LOG_BACKEND_H_
+
+#include "chre/re.h"
+#include "pw_log/levels.h"
+
+#define PW_HANDLE_LOG(level, module, flags, fmt, ...)            \
+  do {                                                           \
+      enum chreLogLevel chreLevel = CHRE_LOG_ERROR;              \
+      switch (level) {                                           \
+        case PW_LOG_LEVEL_DEBUG:                                 \
+          chreLevel = CHRE_LOG_DEBUG;                            \
+          break;                                                 \
+        case PW_LOG_LEVEL_INFO:                                  \
+          chreLevel = CHRE_LOG_INFO;                             \
+          break;                                                 \
+        case PW_LOG_LEVEL_WARN:                                  \
+          chreLevel = CHRE_LOG_WARN;                             \
+          break;                                                 \
+        default:                                                 \
+          chreLevel = CHRE_LOG_ERROR;                            \
+      }                                                          \
+      chreLog(chreLevel, "PW " module ": "  fmt, ##__VA_ARGS__); \
+  } while (0)
+
+#endif // _PW_LOG_NANOAPP_PW_LOG_BACKEND_H_
diff --git a/external/pigweed/pw_rpc.mk b/external/pigweed/pw_rpc.mk
index be98522..35fb25f 100644
--- a/external/pigweed/pw_rpc.mk
+++ b/external/pigweed/pw_rpc.mk
@@ -12,7 +12,10 @@
 
 # Location of various Pigweed modules
 PIGWEED_DIR = $(ANDROID_BUILD_TOP)/external/pigweed
-CHRE_UTIL_DIR = $(ANDROID_BUILD_TOP)/system/chre/util
+CHRE_PREFIX = $(ANDROID_BUILD_TOP)/system/chre
+CHRE_UTIL_DIR = $(CHRE_PREFIX)/util
+CHRE_API_DIR = $(CHRE_PREFIX)/chre_api
+PIGWEED_CHRE_DIR=$(CHRE_PREFIX)/external/pigweed
 PIGWEED_CHRE_UTIL_DIR = $(CHRE_UTIL_DIR)/pigweed
 
 ifeq ($(NANOPB_PREFIX),)
@@ -30,25 +33,42 @@
 # Create proto used for header generation ######################################
 
 PW_RPC_PROTO_GENERATOR = $(PIGWEED_DIR)/pw_protobuf_compiler/py/pw_protobuf_compiler/generate_protos.py
-PW_RPC_GENERATOR_PROTO_SRCS = $(PIGWEED_DIR)/pw_rpc/internal/packet.proto
+PW_RPC_GENERATOR_PROTO = $(PIGWEED_DIR)/pw_rpc/internal/packet.proto
 PW_RPC_GENERATOR_COMPILED_PROTO = $(PW_RPC_GEN_PATH)/py/pw_rpc/internal/packet_pb2.py
+PW_PROTOBUF_PROTOS = $(PIGWEED_DIR)/pw_protobuf/pw_protobuf_protos/common.proto \
+	  $(PIGWEED_DIR)/pw_protobuf/pw_protobuf_protos/field_options.proto \
+	  $(PIGWEED_DIR)/pw_protobuf/pw_protobuf_protos/status.proto
 
 # Modifies PYTHONPATH so that python can see all of pigweed's modules used by
 # their protoc plugins
 PW_RPC_GENERATOR_CMD = PYTHONPATH=$$PYTHONPATH:$(PW_RPC_GEN_PATH)/py:$\
   $(PIGWEED_DIR)/pw_status/py:$(PIGWEED_DIR)/pw_protobuf/py:$\
-  $(PIGWEED_DIR)/pw_protobuf_compiler/py python3
+  $(PIGWEED_DIR)/pw_protobuf_compiler/py $(PYTHON)
 
-$(PW_RPC_GENERATOR_COMPILED_PROTO): $(PW_RPC_GENERATOR_PROTO_SRCS)
+$(PW_RPC_GENERATOR_COMPILED_PROTO): $(PW_RPC_GENERATOR_PROTO)
 	@echo " [PW_RPC] $<"
-	$(V)mkdir -p $(PW_RPC_GEN_PATH)/py/
+	$(V)mkdir -p $(PW_RPC_GEN_PATH)/py/pw_rpc/internal
+	$(V)mkdir -p $(PW_RPC_GEN_PATH)/py/pw_protobuf_codegen_protos
+	$(V)mkdir -p $(PW_RPC_GEN_PATH)/py/pw_protobuf_protos
 	$(V)cp -R $(PIGWEED_DIR)/pw_rpc/py/pw_rpc $(PW_RPC_GEN_PATH)/py/
+
+	$(PROTOC) -I$(PIGWEED_DIR)/pw_protobuf/pw_protobuf_protos \
+	  --experimental_allow_proto3_optional \
+	  --python_out=$(PW_RPC_GEN_PATH)/py/pw_protobuf_protos \
+	  $(PW_PROTOBUF_PROTOS)
+
+	$(PROTOC) -I$(PIGWEED_DIR)/pw_protobuf/pw_protobuf_codegen_protos \
+	  --experimental_allow_proto3_optional \
+	  --python_out=$(PW_RPC_GEN_PATH)/py/pw_protobuf_codegen_protos \
+	  $(PIGWEED_DIR)/pw_protobuf/pw_protobuf_codegen_protos/codegen_options.proto
+
 	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) --out-dir=$(PW_RPC_GEN_PATH)/py/pw_rpc/internal \
-	  --compile-dir=$(dir $<) --sources $(PW_RPC_GENERATOR_PROTO_SRCS) \
+	  --compile-dir=$(dir $<) --sources $(PW_RPC_GENERATOR_PROTO) \
 	  --language python
+
 	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) \
 	  --plugin-path=$(PIGWEED_DIR)/pw_protobuf/py/pw_protobuf/plugin.py \
-	  --compile-dir=$(dir $<) --sources $(PW_RPC_GENERATOR_PROTO_SRCS) \
+	  --compile-dir=$(dir $<) --sources $(PW_RPC_GENERATOR_PROTO) \
 	  --language pwpb
 
 # Generated PW RPC Files #######################################################
@@ -60,50 +80,50 @@
 # Include to-be-generated files
 COMMON_CFLAGS += -I$(PW_RPC_GEN_PATH)
 COMMON_CFLAGS += -I$(PW_RPC_GEN_PATH)/$(PIGWEED_DIR)
-COMMON_CFLAGS += $(addprefix -I$(PW_RPC_GEN_PATH)/, $(PW_RPC_INCLUDES))
+COMMON_CFLAGS += $(addprefix -I$(PW_RPC_GEN_PATH)/, $(abspath $(dir $(PW_RPC_SRCS))))
 
 COMMON_SRCS += $(PW_RPC_GEN_SRCS)
 
 # PW RPC library ###############################################################
 
 # Pigweed RPC include paths
-COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_assert/assert_lite_public_overrides
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_assert/public
-COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_assert_log/public
-COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_assert_log/public_overrides
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_bytes/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_containers/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_function/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_log/public
-COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_log_null/public
-COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_log_null/public_overrides
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_polyfill/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_polyfill/public_overrides
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_polyfill/standard_library_public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_preprocessor/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_protobuf/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_result/public
-COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_rpc/
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_rpc/nanopb/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_rpc/public
+COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_rpc/pwpb/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_rpc/raw/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_span/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_span/public_overrides
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_status/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_stream/public
+COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_string/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_sync/public
+COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_toolchain/public
 COMMON_CFLAGS += -I$(PIGWEED_DIR)/pw_varint/public
+COMMON_CFLAGS += -I$(PIGWEED_DIR)/third_party/fuchsia/repo/sdk/lib/fit/include
+COMMON_CFLAGS += -I$(PIGWEED_DIR)/third_party/fuchsia/repo/sdk/lib/stdcompat/include
 
 # Pigweed RPC sources
 COMMON_SRCS += $(PIGWEED_DIR)/pw_assert_log/assert_log.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_containers/intrusive_list.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_protobuf/decoder.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_protobuf/encoder.cc
+COMMON_SRCS += $(PIGWEED_DIR)/pw_protobuf/stream_decoder.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/call.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/channel.cc
+COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/channel_list.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/client.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/client_call.cc
-COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/client_server.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/endpoint.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/packet.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/server.cc
@@ -112,20 +132,44 @@
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/nanopb/common.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/nanopb/method.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/nanopb/server_reader_writer.cc
+COMMON_SRCS += $(PIGWEED_DIR)/pw_rpc/pwpb/server_reader_writer.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_stream/memory_stream.cc
+COMMON_SRCS += $(PIGWEED_DIR)/pw_varint/stream.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_varint/varint.cc
 
 # NanoPB header includes
 COMMON_CFLAGS += -I$(NANOPB_PREFIX)
 
+COMMON_CFLAGS += -DPW_RPC_USE_GLOBAL_MUTEX=0
+COMMON_CFLAGS += -DPW_RPC_YIELD_MODE=PW_RPC_YIELD_MODE_BUSY_LOOP
+
+# Enable closing a client stream.
+COMMON_CFLAGS += -DPW_RPC_CLIENT_STREAM_END_CALLBACK
+
+
+# Use dynamic channel allocation
+COMMON_CFLAGS += -DPW_RPC_DYNAMIC_ALLOCATION
+COMMON_CFLAGS += -DPW_RPC_DYNAMIC_CONTAINER\(type\)="chre::DynamicVector<type>"
+COMMON_CFLAGS += -DPW_RPC_DYNAMIC_CONTAINER_INCLUDE='"chre/util/dynamic_vector.h"'
+
 # NanoPB sources
 COMMON_SRCS += $(NANOPB_PREFIX)/pb_common.c
 COMMON_SRCS += $(NANOPB_PREFIX)/pb_decode.c
 COMMON_SRCS += $(NANOPB_PREFIX)/pb_encode.c
 
+COMMON_CFLAGS += -DPB_NO_PACKED_STRUCTS=1
+
 # Add CHRE Pigweed util sources since nanoapps should always use these
 COMMON_SRCS += $(PIGWEED_CHRE_UTIL_DIR)/chre_channel_output.cc
+COMMON_SRCS += $(PIGWEED_CHRE_UTIL_DIR)/rpc_client.cc
+COMMON_SRCS += $(PIGWEED_CHRE_UTIL_DIR)/rpc_helper.cc
+COMMON_SRCS += $(PIGWEED_CHRE_UTIL_DIR)/rpc_server.cc
 COMMON_SRCS += $(CHRE_UTIL_DIR)/nanoapp/callbacks.cc
+COMMON_SRCS += $(CHRE_UTIL_DIR)/dynamic_vector_base.cc
+
+# CHRE Pigweed overrides
+COMMON_CFLAGS += -I$(PIGWEED_CHRE_DIR)/pw_log_nanoapp/public_overrides
+COMMON_CFLAGS += -I$(PIGWEED_CHRE_DIR)/pw_assert_nanoapp/public_overrides
 
 # Generate PW RPC headers ######################################################
 
@@ -141,13 +185,57 @@
 	  --plugin-path=$(NANOPB_PROTOC) \
 	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language nanopb \
 	  --sources $<
+
+	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
+	  --plugin-path=$(PIGWEED_DIR)/pw_protobuf/py/pw_protobuf/plugin.py \
+	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language pwpb \
+		--sources $<
+
 	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
 	  --plugin-path=$(PIGWEED_DIR)/pw_rpc/py/pw_rpc/plugin_nanopb.py \
 	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language nanopb_rpc \
 	  --sources $<
+
 	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
 	  --plugin-path=$(PIGWEED_DIR)/pw_rpc/py/pw_rpc/plugin_raw.py \
 	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language raw_rpc \
 	  --sources $<
 
+	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
+	  --plugin-path=$(PIGWEED_DIR)/pw_rpc/py/pw_rpc/plugin_pwpb.py \
+	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language pwpb_rpc \
+	  --sources $<
+
+$(PW_RPC_GEN_PATH)/%.pb.c \
+        $(PW_RPC_GEN_PATH)/%.pb.h \
+        $(PW_RPC_GEN_PATH)/%.rpc.pb.h \
+        $(PW_RPC_GEN_PATH)/%.raw_rpc.pb.h: %.proto \
+                                           $(NANOPB_OPTIONS) \
+                                           $(NANOPB_GENERATOR_SRCS) \
+                                           $(PW_RPC_GENERATOR_COMPILED_PROTO)
+	@echo " [PW_RPC] $<"
+	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
+	  --plugin-path=$(NANOPB_PROTOC) \
+	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language nanopb \
+	  --sources $<
+
+	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
+	  --plugin-path=$(PIGWEED_DIR)/pw_protobuf/py/pw_protobuf/plugin.py \
+	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language pwpb \
+	  --sources $<
+
+	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
+	  --plugin-path=$(PIGWEED_DIR)/pw_rpc/py/pw_rpc/plugin_nanopb.py \
+	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language nanopb_rpc \
+	  --sources $<
+
+	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
+	  --plugin-path=$(PIGWEED_DIR)/pw_rpc/py/pw_rpc/plugin_raw.py \
+	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language raw_rpc \
+	  --sources $<
+
+	$(V)$(PW_RPC_GENERATOR_CMD) $(PW_RPC_PROTO_GENERATOR) \
+	  --plugin-path=$(PIGWEED_DIR)/pw_rpc/py/pw_rpc/plugin_pwpb.py \
+	  --out-dir=$(PW_RPC_GEN_PATH)/$(dir $<) --compile-dir=$(dir $<) --language pwpb_rpc \
+	  --sources $<
 endif
\ No newline at end of file
diff --git a/external/pigweed/pw_tokenizer.mk b/external/pigweed/pw_tokenizer.mk
index a0645f6..15b5c5a 100644
--- a/external/pigweed/pw_tokenizer.mk
+++ b/external/pigweed/pw_tokenizer.mk
@@ -11,10 +11,18 @@
 
 # Location of various Pigweed modules
 PIGWEED_DIR = $(ANDROID_BUILD_TOP)/external/pigweed
+PIGWEED_TOKENIZER_DIR=$(ANDROID_BUILD_TOP)/external/pigweed
+PIGWEED_SCRIPTS_DIR=$(PIGWEED_TOKENIZER_DIR)/pw_tokenizer/py/pw_tokenizer
+
+# Variables used by build_template.mk to generate the token mapping
+TOKEN_MAP_GEN_CMD     = $(PYTHON) $(PIGWEED_SCRIPTS_DIR)/database.py create \
+                          --force --type binary --database
+TOKEN_MAP_CSV_GEN_CMD = $(PYTHON) $(PIGWEED_SCRIPTS_DIR)/database.py create \
+                          --force --type csv --database
 
 # Pigweed source files
 COMMON_SRCS += $(PIGWEED_DIR)/pw_tokenizer/encode_args.cc
-COMMON_SRCS += $(PIGWEED_DIR)/pw_tokenizer/tokenize_to_global_handler_with_payload.cc
+COMMON_SRCS += $(PIGWEED_DIR)/pw_tokenizer/tokenize.cc
 COMMON_SRCS += $(PIGWEED_DIR)/pw_varint/varint.cc
 
 # Pigweed include paths
diff --git a/external/tflm/tflm.mk b/external/tflm/tflm.mk
index fad1e6d..63c7360 100644
--- a/external/tflm/tflm.mk
+++ b/external/tflm/tflm.mk
@@ -14,11 +14,6 @@
          export TFLM_PATH=$$(CHRE_PREFIX)/external/tflm/latest")
 endif
 
-ifeq ($(HEXAGON_SDK_PREFIX),)
-$(error "You must set HEXAGON_SDK_PREFIX, e.g. export \
-         HEXAGON_SDK_PREFIX=~/chre-sdk/vendor/qcom/tools/Qualcomm/Hexagon_SDK/latest")
-endif
-
 # TFLM Source Files ############################################################
 
 TFLM_SRCS = $(shell find $(TFLM_PATH) \( -name '*.cc' -o -name '*.c' \))
@@ -38,9 +33,11 @@
 COMMON_CFLAGS += -I$(TFLM_PATH)/third_party/gemmlowp
 
 # TFLM uses <complex> which requires including several SDK headers
-COMMON_CFLAGS += -I$(HEXAGON_SDK_PREFIX)/libs/common/qurt/latest/include/posix
-COMMON_CFLAGS += -I$(HEXAGON_SDK_PREFIX)/libs/common/qurt/latest/include/qurt
+ifneq ($(HEXAGON_SDK_PREFIX),)
+HEXAGON_CFLAGS += -I$(HEXAGON_SDK_PREFIX)/libs/qurt/latest/include/posix
+HEXAGON_CFLAGS += -I$(HEXAGON_SDK_PREFIX)/libs/qurt/latest/include/qurt
+endif
 
 COMMON_CFLAGS += -DTF_LITE_STATIC_MEMORY
 
-endif
\ No newline at end of file
+endif
diff --git a/external/tflm/tflm_sync_srcs.sh b/external/tflm/tflm_sync_srcs.sh
index 11b6b50..092fbe5 100755
--- a/external/tflm/tflm_sync_srcs.sh
+++ b/external/tflm/tflm_sync_srcs.sh
@@ -27,13 +27,13 @@
 # Generate chre related files
 cd tflm
 make -f tensorflow/lite/micro/tools/make/Makefile TARGET=chre generate_hello_world_make_project
-rm -rf tensorflow/lite/micro/tools/make/gen/chre_x86_64/prj/hello_world/make/tensorflow/lite/micro/examples
+rm -rf gen/chre_x86_64/prj/hello_world/make/tensorflow/lite/micro/examples
 
 # Remove the destination folder
 rm -rf $REAL_DEST_PATH
 
 # Copy files over
-cp -r tensorflow/lite/micro/tools/make/gen/chre_x86_64/prj/hello_world/make $REAL_DEST_PATH
+cp -r gen/chre_x86_64/prj/hello_world/make $REAL_DEST_PATH
 
 # Done
 echo "TFLM code sync'ed"
diff --git a/host/common/chre_aidl_hal_client.cc b/host/common/chre_aidl_hal_client.cc
new file mode 100644
index 0000000..b608657
--- /dev/null
+++ b/host/common/chre_aidl_hal_client.cc
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2022 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 <aidl/android/hardware/contexthub/BnContextHubCallback.h>
+#include <aidl/android/hardware/contexthub/IContextHub.h>
+#include <aidl/android/hardware/contexthub/NanoappBinary.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <dirent.h>
+#include <utils/String16.h>
+
+#include <cctype>
+#include <filesystem>
+#include <fstream>
+#include <future>
+#include <map>
+#include <regex>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include "chre_api/chre/version.h"
+#include "chre_host/file_stream.h"
+#include "chre_host/napp_header.h"
+
+using aidl::android::hardware::contexthub::AsyncEventType;
+using aidl::android::hardware::contexthub::BnContextHubCallback;
+using aidl::android::hardware::contexthub::ContextHubInfo;
+using aidl::android::hardware::contexthub::ContextHubMessage;
+using aidl::android::hardware::contexthub::HostEndpointInfo;
+using aidl::android::hardware::contexthub::IContextHub;
+using aidl::android::hardware::contexthub::NanoappBinary;
+using aidl::android::hardware::contexthub::NanoappInfo;
+using aidl::android::hardware::contexthub::NanSessionRequest;
+using aidl::android::hardware::contexthub::Setting;
+using android::chre::NanoAppBinaryHeader;
+using android::chre::readFileContents;
+using android::internal::ToString;
+using ndk::ScopedAStatus;
+
+namespace {
+// A default id 0 is used for every command requiring a context hub id. When
+// this is not the case the id number should be one of the arguments of the
+// commands.
+constexpr uint32_t kContextHubId = 0;
+constexpr int32_t kLoadTransactionId = 1;
+constexpr int32_t kUnloadTransactionId = 2;
+constexpr auto kTimeOutThresholdInSec = std::chrono::seconds(5);
+// Locations should be searched in the sequence defined below:
+const char *kPredefinedNanoappPaths[] = {
+    "/vendor/etc/chre/",
+    "/vendor/dsp/adsp/",
+    "/vendor/dsp/sdsp/",
+    "/vendor/lib/rfsa/adsp/",
+};
+// Please keep kUsage in alphabetical order
+constexpr char kUsage[] = R"(
+Usage: chre_aidl_hal_client COMMAND [ARGS]
+COMMAND ARGS...:
+  connect                     - connect to HAL, register the callback and keep
+                                the session alive while user can execute other
+                                commands. Use `exit` to quit the session.
+  connectEndpoint <HEX_HOST_ENDPOINT_ID>
+                              - associate an endpoint with the current client
+                                and notify HAL.
+  disableSetting <SETTING>    - disable a setting identified by a number defined
+                                in android/hardware/contexthub/Setting.aidl.
+  disableTestMode             - disable test mode.
+  disconnectEndpoint <HEX_HOST_ENDPOINT_ID>
+                              - remove an endpoint with the current client and
+                                notify HAL.
+  enableSetting <SETTING>     - enable a setting identified by a number defined
+                                in android/hardware/contexthub/Setting.aidl.
+  enableTestMode              - enable test mode.
+  getContextHubs              - get all the context hubs.
+  list <PATH_OF_NANOAPPS>     - list all the nanoapps' header info in the path.
+  load <APP_NAME>             - load the nanoapp specified by the name.
+                                If an absolute path like /path/to/awesome.so,
+                                which is optional, is not provided then default
+                                locations are searched.
+  query                       - show all loaded nanoapps (system apps excluded)
+  sendMessage <HEX_HOST_ENDPOINT_ID> <HEX_NANOAPP_ID | APP_NAME> <HEX_PAYLOAD>
+                              - send a payload to a nanoapp.
+  unload <HEX_NANOAPP_ID | APP_NAME>
+                              - unload the nanoapp specified by either the
+                                nanoapp id in hex format or the app name.
+                                If an absolute path like /path/to/awesome.so,
+                                which is optional, is not provided then default
+                                locations are searched.
+)";
+
+inline void throwError(const std::string &message) {
+  throw std::system_error{std::error_code(), message};
+}
+
+bool isValidHexNumber(const std::string &number) {
+  if (number.empty() ||
+      (number.substr(0, 2) != "0x" && number.substr(0, 2) != "0X")) {
+    return false;
+  }
+  for (int i = 2; i < number.size(); i++) {
+    if (!isxdigit(number[i])) {
+      throwError("Hex app id " + number + " contains invalid character.");
+    }
+  }
+  return number.size() > 2;
+}
+
+uint16_t verifyAndConvertEndpointHexId(const std::string &number) {
+  // host endpoint id must be a 16-bits long hex number.
+  if (isValidHexNumber(number)) {
+    int convertedNumber = std::stoi(number, /* idx= */ nullptr, /* base= */ 16);
+    if (convertedNumber < std::numeric_limits<uint16_t>::max()) {
+      return static_cast<uint16_t>(convertedNumber);
+    }
+  }
+  throwError("host endpoint id must be a 16-bits long hex number.");
+  return 0;  // code never reached.
+}
+
+bool isValidNanoappHexId(const std::string &number) {
+  if (!isValidHexNumber(number)) {
+    return false;
+  }
+  // Once the input has the hex prefix, an exception will be thrown if it is
+  // malformed because it shouldn't be treated as an app name anymore.
+  if (number.size() > 18) {
+    throwError("Hex app id must has a length of [3, 18] including the prefix.");
+  }
+  return true;
+}
+
+std::string parseAppVersion(uint32_t version) {
+  std::ostringstream stringStream;
+  stringStream << std::hex << "0x" << version << std::dec << " (v"
+               << CHRE_EXTRACT_MAJOR_VERSION(version) << "."
+               << CHRE_EXTRACT_MINOR_VERSION(version) << "."
+               << CHRE_EXTRACT_PATCH_VERSION(version) << ")";
+  return stringStream.str();
+}
+
+std::string parseTransactionId(int32_t transactionId) {
+  switch (transactionId) {
+    case kLoadTransactionId:
+      return "Loading";
+    case kUnloadTransactionId:
+      return "Unloading";
+    default:
+      return "Unknown";
+  }
+}
+
+class ContextHubCallback : public BnContextHubCallback {
+ public:
+  ScopedAStatus handleNanoappInfo(
+      const std::vector<NanoappInfo> &appInfo) override {
+    std::cout << appInfo.size() << " nanoapps loaded" << std::endl;
+    for (const NanoappInfo &app : appInfo) {
+      std::cout << "appId: 0x" << std::hex << app.nanoappId << std::dec << " {"
+                << "\n\tappVersion: " << parseAppVersion(app.nanoappVersion)
+                << "\n\tenabled: " << (app.enabled ? "true" : "false")
+                << "\n\tpermissions: " << ToString(app.permissions)
+                << "\n\trpcServices: " << ToString(app.rpcServices) << "\n}"
+                << std::endl;
+    }
+    setPromiseAndRefresh();
+    return ScopedAStatus::ok();
+  }
+
+  ScopedAStatus handleContextHubMessage(
+      const ContextHubMessage &message,
+      const std::vector<std::string> & /*msgContentPerms*/) override {
+    std::cout << "Received a message with type " << message.messageType
+              << " size " << message.messageBody.size() << " from nanoapp 0x"
+              << std::hex << message.nanoappId
+              << " sent to the host endpoint 0x" << message.hostEndPoint
+              << std::endl;
+    std::cout << "message: 0x";
+    for (const uint8_t &data : message.messageBody) {
+      std::cout << std::hex << static_cast<uint32_t>(data);
+    }
+    std::cout << std::endl;
+    setPromiseAndRefresh();
+    return ScopedAStatus::ok();
+  }
+
+  ScopedAStatus handleContextHubAsyncEvent(AsyncEventType /*event*/) override {
+    setPromiseAndRefresh();
+    return ScopedAStatus::ok();
+  }
+
+  // Called after loading/unloading a nanoapp.
+  ScopedAStatus handleTransactionResult(int32_t transactionId,
+                                        bool success) override {
+    std::cout << parseTransactionId(transactionId) << " transaction is "
+              << (success ? "successful" : "failed") << std::endl;
+    setPromiseAndRefresh();
+    return ScopedAStatus::ok();
+  }
+
+  ScopedAStatus handleNanSessionRequest(
+      const NanSessionRequest & /* request */) override {
+    return ScopedAStatus::ok();
+  }
+
+  std::promise<void> promise;
+
+ private:
+  void setPromiseAndRefresh() {
+    promise.set_value();
+    promise = std::promise<void>{};
+  }
+};
+
+std::shared_ptr<IContextHub> gContextHub = nullptr;
+std::shared_ptr<ContextHubCallback> gCallback = nullptr;
+
+/** Initializes gContextHub and register gCallback. */
+std::shared_ptr<IContextHub> getContextHub() {
+  if (gContextHub == nullptr) {
+    auto aidlServiceName = std::string() + IContextHub::descriptor + "/default";
+    ndk::SpAIBinder binder(
+        AServiceManager_waitForService(aidlServiceName.c_str()));
+    if (binder.get() == nullptr) {
+      throwError("Could not find Context Hub HAL");
+    }
+    gContextHub = IContextHub::fromBinder(binder);
+    gCallback = ContextHubCallback::make<ContextHubCallback>();
+
+    if (!gContextHub->registerCallback(kContextHubId, gCallback).isOk()) {
+      throwError("Failed to register the callback");
+    }
+  }
+  return gContextHub;
+}
+
+void printNanoappHeader(const NanoAppBinaryHeader &header) {
+  std::cout << " {"
+            << "\n\tappId: 0x" << std::hex << header.appId << std::dec
+            << "\n\tappVersion: " << parseAppVersion(header.appVersion)
+            << "\n\tflags: " << header.flags << "\n\ttarget CHRE API version: "
+            << static_cast<int>(header.targetChreApiMajorVersion) << "."
+            << static_cast<int>(header.targetChreApiMinorVersion) << "\n}"
+            << std::endl;
+}
+
+std::unique_ptr<NanoAppBinaryHeader> findHeaderByName(
+    const std::string &appName, const std::string &binaryPath) {
+  DIR *dir = opendir(binaryPath.c_str());
+  if (dir == nullptr) {
+    return nullptr;
+  }
+  std::regex regex(appName + ".napp_header");
+  std::cmatch match;
+
+  std::unique_ptr<NanoAppBinaryHeader> result = nullptr;
+  for (struct dirent *entry; (entry = readdir(dir)) != nullptr;) {
+    if (!std::regex_match(entry->d_name, match, regex)) {
+      continue;
+    }
+    std::ifstream input(std::string(binaryPath) + "/" + entry->d_name,
+                        std::ios::binary);
+    result = std::make_unique<NanoAppBinaryHeader>();
+    input.read(reinterpret_cast<char *>(result.get()),
+               sizeof(NanoAppBinaryHeader));
+    break;
+  }
+  closedir(dir);
+  return result;
+}
+
+void readNanoappHeaders(std::map<std::string, NanoAppBinaryHeader> &nanoapps,
+                        const std::string &binaryPath) {
+  DIR *dir = opendir(binaryPath.c_str());
+  if (dir == nullptr) {
+    return;
+  }
+  std::regex regex("(\\w+)\\.napp_header");
+  std::cmatch match;
+  for (struct dirent *entry; (entry = readdir(dir)) != nullptr;) {
+    if (!std::regex_match(entry->d_name, match, regex)) {
+      continue;
+    }
+    std::ifstream input(std::string(binaryPath) + "/" + entry->d_name,
+                        std::ios::binary);
+    input.read(reinterpret_cast<char *>(&nanoapps[match[1]]),
+               sizeof(NanoAppBinaryHeader));
+  }
+  closedir(dir);
+}
+
+void verifyStatus(const std::string &operation, const ScopedAStatus &status) {
+  if (!status.isOk()) {
+    throwError(operation + " fails with abnormal status " +
+               ToString(status.getMessage()) + " error code " +
+               ToString(status.getServiceSpecificError()));
+  }
+}
+
+void verifyStatusAndSignal(const std::string &operation,
+                           const ScopedAStatus &status,
+                           const std::future<void> &future_signal) {
+  verifyStatus(operation, status);
+  std::future_status future_status =
+      future_signal.wait_for(kTimeOutThresholdInSec);
+  if (future_status != std::future_status::ready) {
+    throwError(operation + " doesn't finish within " +
+               ToString(kTimeOutThresholdInSec.count()) + " seconds");
+  }
+}
+
+/** Finds the .napp_header file associated to the nanoapp.
+ *
+ * This function guarantees to return a non-null {@link NanoAppBinaryHeader}
+ * pointer. In case a .napp_header file cannot be found an exception will be
+ * raised.
+ *
+ * @param pathAndName name of the nanoapp that might be prefixed with it path.
+ * It will be normalized to the format of <absolute-path><name>.so at the end.
+ * For example, "abc" will be changed to "/path/to/abc.so".
+ * @return a unique pointer to the {@link NanoAppBinaryHeader} found
+ */
+std::unique_ptr<NanoAppBinaryHeader> findHeaderAndNormalizePath(
+    std::string &pathAndName) {
+  // To match the file pattern of [path]<name>[.so]
+  std::regex pathNameRegex("(.*?)(\\w+)(\\.so)?");
+  std::smatch smatch;
+  if (!std::regex_match(pathAndName, smatch, pathNameRegex)) {
+    throwError("Invalid nanoapp: " + pathAndName);
+  }
+  std::string fullPath = smatch[1];
+  std::string appName = smatch[2];
+  // absolute path is provided:
+  if (!fullPath.empty() && fullPath[0] == '/') {
+    auto result = findHeaderByName(appName, fullPath);
+    if (result == nullptr) {
+      throwError("Unable to find the nanoapp header for " + pathAndName);
+    }
+    pathAndName = fullPath + appName + ".so";
+    return result;
+  }
+  // relative path is searched form predefined locations:
+  for (const std::string &predefinedPath : kPredefinedNanoappPaths) {
+    auto result = findHeaderByName(appName, predefinedPath);
+    if (result == nullptr) {
+      continue;
+    }
+    pathAndName = predefinedPath + appName + ".so";
+    std::cout << "Found the nanoapp header for " << pathAndName << std::endl;
+    return result;
+  }
+  throwError("Unable to find the nanoapp header for " + pathAndName);
+  return nullptr;
+}
+
+int64_t getNanoappIdFrom(std::string &appIdOrName) {
+  int64_t appId;
+  if (isValidNanoappHexId(appIdOrName)) {
+    appId = std::stoll(appIdOrName, nullptr, 16);
+  } else {
+    // Treat the appIdOrName as the app name and try again
+    appId =
+        static_cast<int64_t>(findHeaderAndNormalizePath(appIdOrName)->appId);
+  }
+  return appId;
+}
+
+void getAllContextHubs() {
+  std::vector<ContextHubInfo> hubs{};
+  getContextHub()->getContextHubs(&hubs);
+  if (hubs.empty()) {
+    std::cerr << "Failed to get any context hub." << std::endl;
+    return;
+  }
+  for (const auto &hub : hubs) {
+    std::cout << "Context Hub " << hub.id << ": " << std::endl
+              << "  Name: " << hub.name << std::endl
+              << "  Vendor: " << hub.vendor << std::endl
+              << "  Max support message length (bytes): "
+              << hub.maxSupportedMessageLengthBytes << std::endl
+              << "  Version: " << static_cast<uint32_t>(hub.chreApiMajorVersion)
+              << "." << static_cast<uint32_t>(hub.chreApiMinorVersion)
+              << std::endl
+              << "  Chre platform id: 0x" << std::hex << hub.chrePlatformId
+              << std::endl;
+  }
+}
+
+void loadNanoapp(std::string &pathAndName) {
+  auto header = findHeaderAndNormalizePath(pathAndName);
+  std::vector<uint8_t> soBuffer{};
+  if (!readFileContents(pathAndName.c_str(), soBuffer)) {
+    throwError("Failed to open the content of " + pathAndName);
+  }
+  NanoappBinary binary;
+  binary.nanoappId = static_cast<int64_t>(header->appId);
+  binary.customBinary = soBuffer;
+  binary.flags = static_cast<int32_t>(header->flags);
+  binary.targetChreApiMajorVersion =
+      static_cast<int8_t>(header->targetChreApiMajorVersion);
+  binary.targetChreApiMinorVersion =
+      static_cast<int8_t>(header->targetChreApiMinorVersion);
+  binary.nanoappVersion = static_cast<int32_t>(header->appVersion);
+
+  auto status =
+      getContextHub()->loadNanoapp(kContextHubId, binary, kLoadTransactionId);
+  verifyStatusAndSignal(/* operation= */ "loading nanoapp " + pathAndName,
+                        status, gCallback->promise.get_future());
+}
+
+void unloadNanoapp(std::string &appIdOrName) {
+  auto appId = getNanoappIdFrom(appIdOrName);
+  auto status = getContextHub()->unloadNanoapp(kContextHubId, appId,
+                                               kUnloadTransactionId);
+  verifyStatusAndSignal(/* operation= */ "unloading nanoapp " + appIdOrName,
+                        status, gCallback->promise.get_future());
+}
+
+void queryNanoapps() {
+  auto status = getContextHub()->queryNanoapps(kContextHubId);
+  verifyStatusAndSignal(/* operation= */ "querying nanoapps", status,
+                        gCallback->promise.get_future());
+}
+
+void onEndpointConnected(const std::string &hexEndpointId) {
+  auto contextHub = getContextHub();
+  uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexEndpointId);
+  HostEndpointInfo info = {
+      .hostEndpointId = hostEndpointId,
+      .type = HostEndpointInfo::Type::NATIVE,
+      .packageName = "chre_aidl_hal_client",
+      .attributionTag{},
+  };
+  // connect the endpoint to HAL
+  verifyStatus(/* operation= */ "connect endpoint",
+               contextHub->onHostEndpointConnected(info));
+  std::cout << "onHostEndpointConnected() is called. " << std::endl;
+}
+
+void onEndpointDisconnected(const std::string &hexEndpointId) {
+  auto contextHub = getContextHub();
+  uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexEndpointId);
+  // disconnect the endpoint from HAL
+  verifyStatus(/* operation= */ "disconnect endpoint",
+               contextHub->onHostEndpointDisconnected(hostEndpointId));
+  std::cout << "onHostEndpointDisconnected() is called. " << std::endl;
+}
+
+/** Sends a hexPayload from hexHostEndpointId to appIdOrName. */
+void sendMessageToNanoapp(const std::string &hexHostEndpointId,
+                          std::string &appIdOrName,
+                          const std::string &hexPayload) {
+  if (!isValidHexNumber(hexPayload)) {
+    throwError("Invalid hex payload.");
+  }
+  auto appId = getNanoappIdFrom(appIdOrName);
+  uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexHostEndpointId);
+  ContextHubMessage contextHubMessage = {
+      .nanoappId = appId,
+      .hostEndPoint = hostEndpointId,
+      .messageBody = {},
+      .permissions = {},
+  };
+  // populate the payload
+  for (int i = 2; i < hexPayload.size(); i += 2) {
+    contextHubMessage.messageBody.push_back(
+        std::stoi(hexPayload.substr(i, 2), /* idx= */ nullptr, /* base= */ 16));
+  }
+  // send the message
+  auto contextHub = getContextHub();
+  onEndpointConnected(hexHostEndpointId);
+  auto status = contextHub->sendMessageToHub(kContextHubId, contextHubMessage);
+  verifyStatusAndSignal(/* operation= */ "sending a message to " + appIdOrName,
+                        status, gCallback->promise.get_future());
+  onEndpointDisconnected(hexHostEndpointId);
+}
+
+void changeSetting(const std::string &setting, bool enabled) {
+  auto contextHub = getContextHub();
+  int settingType = std::stoi(setting);
+  if (settingType < 1 || settingType > 7) {
+    throwError("setting type must be within [1, 7].");
+  }
+  ScopedAStatus status =
+      contextHub->onSettingChanged(static_cast<Setting>(settingType), enabled);
+  std::cout << "onSettingChanged is called to "
+            << (enabled ? "enable" : "disable") << " setting type "
+            << settingType << std::endl;
+  verifyStatus("change setting", status);
+}
+
+void enableTestModeOnContextHub() {
+  auto status = getContextHub()->setTestMode(true);
+  verifyStatusAndSignal(/* operation= */ "enabling test mode", status,
+                        gCallback->promise.get_future());
+}
+
+void disableTestModeOnContextHub() {
+  auto status = getContextHub()->setTestMode(false);
+  verifyStatusAndSignal(/* operation= */ "disabling test mode", status,
+                        gCallback->promise.get_future());
+}
+
+// Please keep Command in alphabetical order
+enum Command {
+  connect,
+  connectEndpoint,
+  disableSetting,
+  disableTestMode,
+  disconnectEndpoint,
+  enableSetting,
+  enableTestMode,
+  getContextHubs,
+  list,
+  load,
+  query,
+  sendMessage,
+  unload,
+  unsupported
+};
+
+struct CommandInfo {
+  Command cmd;
+  u_int8_t numofArgs;  // including cmd;
+};
+
+Command parseCommand(const std::vector<std::string> &cmdLine) {
+  std::map<std::string, CommandInfo> commandMap{
+      {"connect", {connect, 1}},
+      {"connectEndpoint", {connectEndpoint, 2}},
+      {"disableSetting", {disableSetting, 2}},
+      {"disableTestMode", {disableTestMode, 1}},
+      {"disconnectEndpoint", {disconnectEndpoint, 2}},
+      {"enableSetting", {enableSetting, 2}},
+      {"enableTestMode", {enableTestMode, 1}},
+      {"getContextHubs", {getContextHubs, 1}},
+      {"list", {list, 2}},
+      {"load", {load, 2}},
+      {"query", {query, 1}},
+      {"sendMessage", {sendMessage, 4}},
+      {"unload", {unload, 2}},
+  };
+  if (cmdLine.empty() || commandMap.find(cmdLine[0]) == commandMap.end()) {
+    return unsupported;
+  }
+  auto cmdInfo = commandMap.at(cmdLine[0]);
+  return cmdLine.size() == cmdInfo.numofArgs ? cmdInfo.cmd : unsupported;
+}
+
+void executeCommand(std::vector<std::string> cmdLine) {
+  switch (parseCommand(cmdLine)) {
+    case connectEndpoint: {
+      onEndpointConnected(cmdLine[1]);
+      break;
+    }
+    case disableSetting: {
+      changeSetting(cmdLine[1], false);
+      break;
+    }
+    case disableTestMode: {
+      disableTestModeOnContextHub();
+      break;
+    }
+    case disconnectEndpoint: {
+      onEndpointDisconnected(cmdLine[1]);
+      break;
+    }
+    case enableSetting: {
+      changeSetting(cmdLine[1], true);
+      break;
+    }
+    case enableTestMode: {
+      enableTestModeOnContextHub();
+      break;
+    }
+    case getContextHubs: {
+      getAllContextHubs();
+      break;
+    }
+    case list: {
+      std::map<std::string, NanoAppBinaryHeader> nanoapps{};
+      readNanoappHeaders(nanoapps, cmdLine[1]);
+      for (const auto &entity : nanoapps) {
+        std::cout << entity.first;
+        printNanoappHeader(entity.second);
+      }
+      break;
+    }
+    case load: {
+      loadNanoapp(cmdLine[1]);
+      break;
+    }
+    case query: {
+      queryNanoapps();
+      break;
+    }
+    case sendMessage: {
+      sendMessageToNanoapp(cmdLine[1], cmdLine[2], cmdLine[3]);
+      break;
+    }
+    case unload: {
+      unloadNanoapp(cmdLine[1]);
+      break;
+    }
+    default:
+      std::cout << kUsage;
+  }
+}
+
+std::vector<std::string> getCommandLine() {
+  std::string input;
+  std::cout << "> ";
+  std::getline(std::cin, input);
+  input.push_back('\n');
+  std::vector<std::string> result{};
+  for (int begin = 0, end = 0; end < input.size();) {
+    if (isspace(input[begin])) {
+      end = begin = begin + 1;
+      continue;
+    }
+    if (!isspace(input[end])) {
+      end += 1;
+      continue;
+    }
+    result.push_back(input.substr(begin, end - begin));
+    begin = end;
+  }
+  return result;
+}
+
+void connectToHal() {
+  auto hub = getContextHub();
+  std::cout << "Connected to context hub." << std::endl;
+  while (true) {
+    auto cmdLine = getCommandLine();
+    if (cmdLine.empty()) {
+      continue;
+    }
+    if (cmdLine.size() == 1 && cmdLine[0] == "connect") {
+      std::cout << "Already in a live session." << std::endl;
+      continue;
+    }
+    if (cmdLine.size() == 1 && cmdLine[0] == "exit") {
+      break;
+    }
+    try {
+      executeCommand(cmdLine);
+    } catch (std::system_error &e) {
+      std::cerr << e.what() << std::endl;
+    }
+  }
+}
+}  // anonymous namespace
+
+int main(int argc, char *argv[]) {
+  // Start binder thread pool to enable callbacks.
+  ABinderProcess_startThreadPool();
+
+  std::vector<std::string> cmdLine{};
+  for (int i = 1; i < argc; i++) {
+    cmdLine.emplace_back(argv[i]);
+  }
+  if (cmdLine.size() == 1 && cmdLine[0] == "connect") {
+    connectToHal();
+    return 0;
+  }
+  try {
+    executeCommand(cmdLine);
+  } catch (std::system_error &e) {
+    std::cerr << e.what() << std::endl;
+    return -1;
+  }
+  return 0;
+}
\ No newline at end of file
diff --git a/host/common/config_util.cc b/host/common/config_util.cc
new file mode 100644
index 0000000..e39083f
--- /dev/null
+++ b/host/common/config_util.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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_host/config_util.h"
+#include "chre_host/log.h"
+
+#include <json/json.h>
+#include <fstream>
+
+namespace android {
+namespace chre {
+
+bool getPreloadedNanoappsFromConfigFile(const std::string &configFilePath,
+                                        std::string &outDirectory,
+                                        std::vector<std::string> &outNanoapps) {
+  std::ifstream configFileStream(configFilePath);
+
+  Json::CharReaderBuilder builder;
+  Json::Value config;
+  if (!configFileStream) {
+    LOGE("Failed to open config file '%s'", configFilePath.c_str());
+    return false;
+  } else if (!Json::parseFromStream(builder, configFileStream, &config,
+                                    /* errs = */ nullptr)) {
+    LOGE("Failed to parse nanoapp config file");
+    return false;
+  } else if (!config.isMember("nanoapps") || !config.isMember("source_dir")) {
+    LOGE("Malformed preloaded nanoapps config");
+    return false;
+  }
+
+  outDirectory = config["source_dir"].asString();
+  for (Json::ArrayIndex i = 0; i < config["nanoapps"].size(); ++i) {
+    const std::string &nanoappName = config["nanoapps"][i].asString();
+    outNanoapps.push_back(nanoappName);
+  }
+  return true;
+}
+
+}  // namespace chre
+}  // namespace android
diff --git a/host/common/daemon_base.cc b/host/common/daemon_base.cc
index 7b4915e..b8241d3 100644
--- a/host/common/daemon_base.cc
+++ b/host/common/daemon_base.cc
@@ -14,24 +14,23 @@
  * limitations under the License.
  */
 
+#include <signal.h>
 #include <cstdlib>
 #include <fstream>
 
+#include "chre_host/config_util.h"
 #include "chre_host/daemon_base.h"
+#include "chre_host/file_stream.h"
 #include "chre_host/log.h"
 #include "chre_host/napp_header.h"
 
-#include <json/json.h>
-
 #ifdef CHRE_DAEMON_METRIC_ENABLED
-#include <aidl/android/frameworks/stats/IStats.h>
-#include <android/binder_manager.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+#include <chre_atoms_log.h>
+#include <system/chre/core/chre_metrics.pb.h>
 
 using ::aidl::android::frameworks::stats::IStats;
 using ::aidl::android::frameworks::stats::VendorAtom;
 using ::aidl::android::frameworks::stats::VendorAtomValue;
-namespace PixelAtoms = ::android::hardware::google::pixel::PixelAtoms;
 #endif  // CHRE_DAEMON_METRIC_ENABLED
 
 // Aliased for consistency with the way these symbols are referenced in
@@ -41,32 +40,53 @@
 namespace android {
 namespace chre {
 
+namespace {
+
+void signalHandler(void *ctx) {
+  auto *daemon = static_cast<ChreDaemonBase *>(ctx);
+  int rc = -1;
+  sigset_t signalMask;
+  sigfillset(&signalMask);
+  sigdelset(&signalMask, SIGINT);
+  sigdelset(&signalMask, SIGTERM);
+  if (sigprocmask(SIG_SETMASK, &signalMask, NULL) != 0) {
+    LOG_ERROR("Couldn't mask all signals except INT/TERM", errno);
+  }
+
+  while (true) {
+    int signum = 0;
+    if ((rc = sigwait(&signalMask, &signum)) != 0) {
+      LOGE("Sigwait failed: %d", rc);
+    }
+    LOGI("Received signal %d", signum);
+    if (signum == SIGINT || signum == SIGTERM) {
+      daemon->onShutdown();
+      break;
+    }
+  }
+}
+
+}  // anonymous namespace
+
 ChreDaemonBase::ChreDaemonBase() : mChreShutdownRequested(false) {
   mLogger.init();
+  mSignalHandlerThread = std::thread(signalHandler, this);
 }
 
 void ChreDaemonBase::loadPreloadedNanoapps() {
-  constexpr char kPreloadedNanoappsConfigPath[] =
+  const std::string kPreloadedNanoappsConfigPath =
       "/vendor/etc/chre/preloaded_nanoapps.json";
-  std::ifstream configFileStream(kPreloadedNanoappsConfigPath);
+  std::string directory;
+  std::vector<std::string> nanoapps;
+  bool success = getPreloadedNanoappsFromConfigFile(
+      kPreloadedNanoappsConfigPath, directory, nanoapps);
+  if (!success) {
+    LOGE("Failed to parse preloaded nanoapps config file");
+    return;
+  }
 
-  Json::CharReaderBuilder builder;
-  Json::Value config;
-  if (!configFileStream) {
-    LOGE("Failed to open config file '%s': %d (%s)",
-         kPreloadedNanoappsConfigPath, errno, strerror(errno));
-  } else if (!Json::parseFromStream(builder, configFileStream, &config,
-                                    /* errorMessage = */ nullptr)) {
-    LOGE("Failed to parse nanoapp config file");
-  } else if (!config.isMember("nanoapps") || !config.isMember("source_dir")) {
-    LOGE("Malformed preloaded nanoapps config");
-  } else {
-    const Json::Value &directory = config["source_dir"];
-    for (Json::ArrayIndex i = 0; i < config["nanoapps"].size(); i++) {
-      const Json::Value &nanoapp = config["nanoapps"][i];
-      loadPreloadedNanoapp(directory.asString(), nanoapp.asString(),
-                           static_cast<uint32_t>(i));
-    }
+  for (uint32_t i = 0; i < nanoapps.size(); ++i) {
+    loadPreloadedNanoapp(directory, nanoapps[i], i);
   }
 }
 
@@ -81,7 +101,7 @@
   // within the directory its own binary resides in.
   std::string nanoappFilename = name + ".so";
 
-  if (readFileContents(headerFile.c_str(), &headerBuffer) &&
+  if (!readFileContents(headerFile.c_str(), headerBuffer) ||
       !loadNanoapp(headerBuffer, nanoappFilename, transactionId)) {
     LOGE("Failed to load nanoapp: '%s'", name.c_str());
   }
@@ -109,49 +129,6 @@
   return success;
 }
 
-bool ChreDaemonBase::sendNanoappLoad(uint64_t appId, uint32_t appVersion,
-                                     uint32_t appTargetApiVersion,
-                                     const std::string &appBinaryName,
-                                     uint32_t transactionId) {
-  flatbuffers::FlatBufferBuilder builder;
-  HostProtocolHost::encodeLoadNanoappRequestForFile(
-      builder, transactionId, appId, appVersion, appTargetApiVersion,
-      appBinaryName.c_str());
-
-  bool success = sendMessageToChre(
-      kHostClientIdDaemon, builder.GetBufferPointer(), builder.GetSize());
-
-  if (!success) {
-    LOGE("Failed to send nanoapp filename.");
-  } else {
-    Transaction transaction = {
-        .transactionId = transactionId,
-        .nanoappId = appId,
-    };
-    mPreloadedNanoappPendingTransactions.push(transaction);
-  }
-
-  return success;
-}
-
-bool ChreDaemonBase::sendTimeSync(bool logOnError) {
-  bool success = false;
-  int64_t timeOffset = getTimeOffset(&success);
-
-  if (success) {
-    flatbuffers::FlatBufferBuilder builder(64);
-    HostProtocolHost::encodeTimeSyncMessage(builder, timeOffset);
-    success = sendMessageToChre(kHostClientIdDaemon, builder.GetBufferPointer(),
-                                builder.GetSize());
-
-    if (!success && logOnError) {
-      LOGE("Failed to deliver time sync message from host to CHRE");
-    }
-  }
-
-  return success;
-}
-
 bool ChreDaemonBase::sendTimeSyncWithRetry(size_t numRetries,
                                            useconds_t retryDelayUs,
                                            bool logOnError) {
@@ -165,145 +142,9 @@
   return success;
 }
 
-bool ChreDaemonBase::sendNanConfigurationUpdate(bool nanEnabled) {
-  flatbuffers::FlatBufferBuilder builder(32);
-  HostProtocolHost::encodeNanconfigurationUpdate(builder, nanEnabled);
-  return sendMessageToChre(kHostClientIdDaemon, builder.GetBufferPointer(),
-                           builder.GetSize());
-}
-
-bool ChreDaemonBase::sendMessageToChre(uint16_t clientId, void *data,
-                                       size_t length) {
-  bool success = false;
-  if (!HostProtocolHost::mutateHostClientId(data, length, clientId)) {
-    LOGE("Couldn't set host client ID in message container!");
-  } else {
-    LOGV("Delivering message from host (size %zu)", length);
-    getLogger().dump(static_cast<const uint8_t *>(data), length);
-    success = doSendMessage(data, length);
-  }
-
-  return success;
-}
-
-void ChreDaemonBase::onMessageReceived(const unsigned char *messageBuffer,
-                                       size_t messageLen) {
-  getLogger().dump(messageBuffer, messageLen);
-
-  uint16_t hostClientId;
-  fbs::ChreMessage messageType;
-  if (!HostProtocolHost::extractHostClientIdAndType(
-          messageBuffer, messageLen, &hostClientId, &messageType)) {
-    LOGW("Failed to extract host client ID from message - sending broadcast");
-    hostClientId = ::chre::kHostClientIdUnspecified;
-  }
-
-  if (messageType == fbs::ChreMessage::LogMessage) {
-    std::unique_ptr<fbs::MessageContainerT> container =
-        fbs::UnPackMessageContainer(messageBuffer);
-    const auto *logMessage = container->message.AsLogMessage();
-    const std::vector<int8_t> &logData = logMessage->buffer;
-
-    getLogger().log(reinterpret_cast<const uint8_t *>(logData.data()),
-                    logData.size());
-  } else if (messageType == fbs::ChreMessage::LogMessageV2) {
-    std::unique_ptr<fbs::MessageContainerT> container =
-        fbs::UnPackMessageContainer(messageBuffer);
-    const auto *logMessage = container->message.AsLogMessageV2();
-    const std::vector<int8_t> &logDataBuffer = logMessage->buffer;
-    const auto *logData =
-        reinterpret_cast<const uint8_t *>(logDataBuffer.data());
-    uint32_t numLogsDropped = logMessage->num_logs_dropped;
-
-    getLogger().logV2(logData, logDataBuffer.size(), numLogsDropped);
-  } else if (messageType == fbs::ChreMessage::TimeSyncRequest) {
-    sendTimeSync(true /* logOnError */);
-  } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRequest) {
-    configureLpma(true /* enabled */);
-  } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRelease) {
-    configureLpma(false /* enabled */);
-  } else if (messageType == fbs::ChreMessage::MetricLog) {
-#ifdef CHRE_DAEMON_METRIC_ENABLED
-    std::unique_ptr<fbs::MessageContainerT> container =
-        fbs::UnPackMessageContainer(messageBuffer);
-    const auto *metricMsg = container->message.AsMetricLog();
-    handleMetricLog(metricMsg);
-#endif  // CHRE_DAEMON_METRIC_ENABLED
-  } else if (messageType == fbs::ChreMessage::NanConfigurationRequest) {
-    std::unique_ptr<fbs::MessageContainerT> container =
-        fbs::UnPackMessageContainer(messageBuffer);
-    configureNan(container->message.AsNanConfigurationRequest()->enable);
-  } else if (hostClientId == kHostClientIdDaemon) {
-    handleDaemonMessage(messageBuffer);
-  } else if (hostClientId == ::chre::kHostClientIdUnspecified) {
-    mServer.sendToAllClients(messageBuffer, static_cast<size_t>(messageLen));
-  } else {
-    mServer.sendToClientById(messageBuffer, static_cast<size_t>(messageLen),
-                             hostClientId);
-  }
-}
-
-bool ChreDaemonBase::readFileContents(const char *filename,
-                                      std::vector<uint8_t> *buffer) {
-  bool success = false;
-  std::ifstream file(filename, std::ios::binary | std::ios::ate);
-  if (!file) {
-    LOGE("Couldn't open file '%s': %d (%s)", filename, errno, strerror(errno));
-  } else {
-    ssize_t size = file.tellg();
-    file.seekg(0, std::ios::beg);
-
-    buffer->resize(size);
-    if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) {
-      LOGE("Couldn't read from file '%s': %d (%s)", filename, errno,
-           strerror(errno));
-    } else {
-      success = true;
-    }
-  }
-
-  return success;
-}
-
-void ChreDaemonBase::handleDaemonMessage(const uint8_t *message) {
-  std::unique_ptr<fbs::MessageContainerT> container =
-      fbs::UnPackMessageContainer(message);
-  if (container->message.type != fbs::ChreMessage::LoadNanoappResponse) {
-    LOGE("Invalid message from CHRE directed to daemon");
-  } else {
-    const auto *response = container->message.AsLoadNanoappResponse();
-    if (mPreloadedNanoappPendingTransactions.empty()) {
-      LOGE("Received nanoapp load response with no pending load");
-    } else if (mPreloadedNanoappPendingTransactions.front().transactionId !=
-               response->transaction_id) {
-      LOGE("Received nanoapp load response with ID %" PRIu32
-           " expected transaction id %" PRIu32,
-           response->transaction_id,
-           mPreloadedNanoappPendingTransactions.front().transactionId);
-    } else {
-      if (!response->success) {
-        LOGE("Received unsuccessful nanoapp load response with ID %" PRIu32,
-             mPreloadedNanoappPendingTransactions.front().transactionId);
-
-#ifdef CHRE_DAEMON_METRIC_ENABLED
-        std::vector<VendorAtomValue> values(3);
-        values[0].set<VendorAtomValue::longValue>(
-            mPreloadedNanoappPendingTransactions.front().nanoappId);
-        values[1].set<VendorAtomValue::intValue>(
-            PixelAtoms::ChreHalNanoappLoadFailed::TYPE_PRELOADED);
-        values[2].set<VendorAtomValue::intValue>(
-            PixelAtoms::ChreHalNanoappLoadFailed::REASON_ERROR_GENERIC);
-        const VendorAtom atom{
-            .reverseDomainName = "",
-            .atomId = PixelAtoms::Atom::kChreHalNanoappLoadFailed,
-            .values{std::move(values)},
-        };
-        reportMetric(atom);
-#endif  // CHRE_DAEMON_METRIC_ENABLED
-      }
-      mPreloadedNanoappPendingTransactions.pop();
-    }
-  }
+void ChreDaemonBase::handleNanConfigurationRequest(
+    const ::chre::fbs::NanConfigurationRequestT * /*request*/) {
+  LOGE("NAN is unsupported on this platform");
 }
 
 #ifdef CHRE_DAEMON_METRIC_ENABLED
@@ -311,8 +152,8 @@
   const std::vector<int8_t> &encodedMetric = metricMsg->encoded_metric;
 
   switch (metricMsg->id) {
-    case PixelAtoms::Atom::kChrePalOpenFailed: {
-      PixelAtoms::ChrePalOpenFailed metric;
+    case Atoms::CHRE_PAL_OPEN_FAILED: {
+      metrics::ChrePalOpenFailed metric;
       if (!metric.ParseFromArray(encodedMetric.data(), encodedMetric.size())) {
         LOGE("Failed to parse metric data");
       } else {
@@ -320,16 +161,15 @@
         values[0].set<VendorAtomValue::intValue>(metric.pal());
         values[1].set<VendorAtomValue::intValue>(metric.type());
         const VendorAtom atom{
-            .reverseDomainName = "",
-            .atomId = PixelAtoms::Atom::kChrePalOpenFailed,
+            .atomId = Atoms::CHRE_PAL_OPEN_FAILED,
             .values{std::move(values)},
         };
         reportMetric(atom);
       }
       break;
     }
-    case PixelAtoms::Atom::kChreEventQueueSnapshotReported: {
-      PixelAtoms::ChreEventQueueSnapshotReported metric;
+    case Atoms::CHRE_EVENT_QUEUE_SNAPSHOT_REPORTED: {
+      metrics::ChreEventQueueSnapshotReported metric;
       if (!metric.ParseFromArray(encodedMetric.data(), encodedMetric.size())) {
         LOGE("Failed to parse metric data");
       } else {
@@ -348,8 +188,7 @@
         values[5].set<VendorAtomValue::intValue>(
             UINT32_MAX);  // mean_queue_delay_us
         const VendorAtom atom{
-            .reverseDomainName = "",
-            .atomId = PixelAtoms::Atom::kChreEventQueueSnapshotReported,
+            .atomId = Atoms::CHRE_EVENT_QUEUE_SNAPSHOT_REPORTED,
             .values{std::move(values)},
         };
         reportMetric(atom);
@@ -357,13 +196,15 @@
       break;
     }
     default: {
+#ifdef CHRE_LOG_ATOM_EXTENSION_ENABLED
+      handleVendorMetricLog(metricMsg);
+#else
       LOGW("Unknown metric ID %" PRIu32, metricMsg->id);
+#endif  // CHRE_LOG_ATOM_EXTENSION_ENABLED
     }
   }
 }
-#endif  // CHRE_DAEMON_METRIC_ENABLED
 
-#ifdef CHRE_DAEMON_METRIC_ENABLED
 void ChreDaemonBase::reportMetric(const VendorAtom &atom) {
   const std::string statsServiceName =
       std::string(IStats::descriptor).append("/default");
@@ -374,6 +215,10 @@
 
   std::shared_ptr<IStats> stats_client = IStats::fromBinder(ndk::SpAIBinder(
       AServiceManager_waitForService(statsServiceName.c_str())));
+  if (stats_client == nullptr) {
+    LOGE("Failed to get IStats service");
+    return;
+  }
 
   const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(atom);
   if (!ret.isOk()) {
@@ -382,9 +227,5 @@
 }
 #endif  // CHRE_DAEMON_METRIC_ENABLED
 
-void ChreDaemonBase::configureNan(bool /*enabled*/) {
-  LOGE("NAN not supported");
-}
-
 }  // namespace chre
 }  // namespace android
diff --git a/host/common/fbs_daemon_base.cc b/host/common/fbs_daemon_base.cc
new file mode 100644
index 0000000..94f0770
--- /dev/null
+++ b/host/common/fbs_daemon_base.cc
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2020 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 <cstdlib>
+#include <fstream>
+
+#include "chre_host/fbs_daemon_base.h"
+#include "chre_host/log.h"
+#include "chre_host/napp_header.h"
+
+#include <json/json.h>
+
+#ifdef CHRE_DAEMON_METRIC_ENABLED
+#include <aidl/android/frameworks/stats/IStats.h>
+#include <android/binder_manager.h>
+#include <chre_atoms_log.h>
+
+using ::aidl::android::frameworks::stats::IStats;
+using ::aidl::android::frameworks::stats::VendorAtom;
+using ::aidl::android::frameworks::stats::VendorAtomValue;
+#endif  // CHRE_DAEMON_METRIC_ENABLED
+
+// Aliased for consistency with the way these symbols are referenced in
+// CHRE-side code
+namespace fbs = ::chre::fbs;
+
+namespace android {
+namespace chre {
+
+bool FbsDaemonBase::sendNanoappLoad(uint64_t appId, uint32_t appVersion,
+                                    uint32_t appTargetApiVersion,
+                                    const std::string &appBinaryName,
+                                    uint32_t transactionId) {
+  flatbuffers::FlatBufferBuilder builder;
+  HostProtocolHost::encodeLoadNanoappRequestForFile(
+      builder, transactionId, appId, appVersion, appTargetApiVersion,
+      appBinaryName.c_str());
+
+  bool success = sendMessageToChre(
+      kHostClientIdDaemon, builder.GetBufferPointer(), builder.GetSize());
+
+  if (!success) {
+    LOGE("Failed to send nanoapp filename.");
+  } else {
+    Transaction transaction = {
+        .transactionId = transactionId,
+        .nanoappId = appId,
+    };
+    mPreloadedNanoappPendingTransactions.push(transaction);
+  }
+
+  return success;
+}
+
+bool FbsDaemonBase::sendTimeSync(bool logOnError) {
+  bool success = false;
+  int64_t timeOffset = getTimeOffset(&success);
+
+  if (success) {
+    flatbuffers::FlatBufferBuilder builder(64);
+    HostProtocolHost::encodeTimeSyncMessage(builder, timeOffset);
+    success = sendMessageToChre(kHostClientIdDaemon, builder.GetBufferPointer(),
+                                builder.GetSize());
+
+    if (!success && logOnError) {
+      LOGE("Failed to deliver time sync message from host to CHRE");
+    }
+  }
+
+  return success;
+}
+
+bool FbsDaemonBase::sendMessageToChre(uint16_t clientId, void *data,
+                                      size_t length) {
+  bool success = false;
+  if (!HostProtocolHost::mutateHostClientId(data, length, clientId)) {
+    LOGE("Couldn't set host client ID in message container!");
+  } else {
+    LOGV("Delivering message from host (size %zu)", length);
+    getLogger().dump(static_cast<const uint8_t *>(data), length);
+    success = doSendMessage(data, length);
+  }
+
+  return success;
+}
+
+void FbsDaemonBase::onMessageReceived(const unsigned char *messageBuffer,
+                                      size_t messageLen) {
+  getLogger().dump(messageBuffer, messageLen);
+
+  uint16_t hostClientId;
+  fbs::ChreMessage messageType;
+  if (!HostProtocolHost::extractHostClientIdAndType(
+          messageBuffer, messageLen, &hostClientId, &messageType)) {
+    LOGW("Failed to extract host client ID from message - sending broadcast");
+    hostClientId = ::chre::kHostClientIdUnspecified;
+  }
+
+  if (messageType == fbs::ChreMessage::LogMessage) {
+    std::unique_ptr<fbs::MessageContainerT> container =
+        fbs::UnPackMessageContainer(messageBuffer);
+    const auto *logMessage = container->message.AsLogMessage();
+    const std::vector<int8_t> &logData = logMessage->buffer;
+
+    getLogger().log(reinterpret_cast<const uint8_t *>(logData.data()),
+                    logData.size());
+  } else if (messageType == fbs::ChreMessage::LogMessageV2) {
+    std::unique_ptr<fbs::MessageContainerT> container =
+        fbs::UnPackMessageContainer(messageBuffer);
+    const auto *logMessage = container->message.AsLogMessageV2();
+    const std::vector<int8_t> &logDataBuffer = logMessage->buffer;
+    const auto *logData =
+        reinterpret_cast<const uint8_t *>(logDataBuffer.data());
+    uint32_t numLogsDropped = logMessage->num_logs_dropped;
+
+    getLogger().logV2(logData, logDataBuffer.size(), numLogsDropped);
+  } else if (messageType == fbs::ChreMessage::TimeSyncRequest) {
+    sendTimeSync(true /* logOnError */);
+  } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRequest) {
+    configureLpma(true /* enabled */);
+  } else if (messageType == fbs::ChreMessage::LowPowerMicAccessRelease) {
+    configureLpma(false /* enabled */);
+  } else if (messageType == fbs::ChreMessage::MetricLog) {
+#ifdef CHRE_DAEMON_METRIC_ENABLED
+    std::unique_ptr<fbs::MessageContainerT> container =
+        fbs::UnPackMessageContainer(messageBuffer);
+    const auto *metricMsg = container->message.AsMetricLog();
+    handleMetricLog(metricMsg);
+#endif  // CHRE_DAEMON_METRIC_ENABLED
+  } else if (messageType == fbs::ChreMessage::NanConfigurationRequest) {
+    std::unique_ptr<fbs::MessageContainerT> container =
+        fbs::UnPackMessageContainer(messageBuffer);
+    handleNanConfigurationRequest(
+        container->message.AsNanConfigurationRequest());
+  } else if (hostClientId == kHostClientIdDaemon) {
+    handleDaemonMessage(messageBuffer);
+  } else if (hostClientId == ::chre::kHostClientIdUnspecified) {
+    mServer.sendToAllClients(messageBuffer, static_cast<size_t>(messageLen));
+  } else {
+    mServer.sendToClientById(messageBuffer, static_cast<size_t>(messageLen),
+                             hostClientId);
+  }
+}
+
+void FbsDaemonBase::handleDaemonMessage(const uint8_t *message) {
+  std::unique_ptr<fbs::MessageContainerT> container =
+      fbs::UnPackMessageContainer(message);
+  if (container->message.type != fbs::ChreMessage::LoadNanoappResponse) {
+    LOGE("Invalid message from CHRE directed to daemon");
+  } else {
+    const auto *response = container->message.AsLoadNanoappResponse();
+    if (mPreloadedNanoappPendingTransactions.empty()) {
+      LOGE("Received nanoapp load response with no pending load");
+    } else if (mPreloadedNanoappPendingTransactions.front().transactionId !=
+               response->transaction_id) {
+      LOGE("Received nanoapp load response with ID %" PRIu32
+           " expected transaction id %" PRIu32,
+           response->transaction_id,
+           mPreloadedNanoappPendingTransactions.front().transactionId);
+    } else {
+      if (!response->success) {
+        LOGE("Received unsuccessful nanoapp load response with ID %" PRIu32,
+             mPreloadedNanoappPendingTransactions.front().transactionId);
+
+#ifdef CHRE_DAEMON_METRIC_ENABLED
+        std::vector<VendorAtomValue> values(3);
+        values[0].set<VendorAtomValue::longValue>(
+            mPreloadedNanoappPendingTransactions.front().nanoappId);
+        values[1].set<VendorAtomValue::intValue>(
+            Atoms::ChreHalNanoappLoadFailed::TYPE_PRELOADED);
+        values[2].set<VendorAtomValue::intValue>(
+            Atoms::ChreHalNanoappLoadFailed::REASON_ERROR_GENERIC);
+        const VendorAtom atom{
+            .atomId = Atoms::CHRE_HAL_NANOAPP_LOAD_FAILED,
+            .values{std::move(values)},
+        };
+        reportMetric(atom);
+#endif  // CHRE_DAEMON_METRIC_ENABLED
+      }
+      mPreloadedNanoappPendingTransactions.pop();
+    }
+  }
+}
+
+}  // namespace chre
+}  // namespace android
diff --git a/host/common/file_stream.cc b/host/common/file_stream.cc
new file mode 100644
index 0000000..5fd7c2b
--- /dev/null
+++ b/host/common/file_stream.cc
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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_host/file_stream.h"
+
+#include <fstream>
+#include "chre_host/log.h"
+
+namespace android {
+namespace chre {
+
+bool readFileContents(const char *filename, std::vector<uint8_t> &buffer) {
+  bool success = false;
+  std::ifstream file(filename, std::ios::binary | std::ios::ate);
+  if (!file) {
+    LOGE("Couldn't open file '%s': %d (%s)", filename, errno, strerror(errno));
+  } else {
+    ssize_t size = file.tellg();
+    file.seekg(0, std::ios::beg);
+
+    buffer.resize(size);
+    if (!file.read(reinterpret_cast<char *>(buffer.data()), size)) {
+      LOGE("Couldn't read from file '%s': %d (%s)", filename, errno,
+           strerror(errno));
+    } else {
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+}  // namespace chre
+}  // namespace android
diff --git a/host/common/host_protocol_host.cc b/host/common/host_protocol_host.cc
index 520c189..2493206 100644
--- a/host/common/host_protocol_host.cc
+++ b/host/common/host_protocol_host.cc
@@ -102,6 +102,15 @@
   finalize(builder, fbs::ChreMessage::HubInfoRequest, request.Union());
 }
 
+void HostProtocolHost::encodeDebugConfiguration(FlatBufferBuilder &builder) {
+#ifdef CHRE_HEALTH_MONITOR_CHECK_CRASH
+  auto request = fbs::CreateDebugConfiguration(builder, true);
+#else
+  auto request = fbs::CreateDebugConfiguration(builder, false);
+#endif  // CHRE_HEALTH_MONITOR_CHECK_CRASH
+  finalize(builder, fbs::ChreMessage::DebugConfiguration, request.Union());
+}
+
 void HostProtocolHost::encodeFragmentedLoadNanoappRequest(
     flatbuffers::FlatBufferBuilder &builder,
     const FragmentedLoadRequest &request, bool respondBeforeStart) {
diff --git a/host/common/include/chre_host/config_util.h b/host/common/include/chre_host/config_util.h
new file mode 100644
index 0000000..fd3a678
--- /dev/null
+++ b/host/common/include/chre_host/config_util.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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_HOST_CONFIG_UTIL_H_
+#define CHRE_HOST_CONFIG_UTIL_H_
+
+#include <functional>
+#include <vector>
+
+namespace android {
+namespace chre {
+
+/**
+ * Gets the preloaded nanoapps from the config file at path: configFilePath.
+ *
+ * @param configFilePath        the file path of the config file on the device
+ * @param outDirectory          (out) the directory that contains the nanoapps
+ *                              on the device
+ * @param outNanoapps           (out) the list of nanoapps in the directory
+ * @return bool                 success
+ */
+bool getPreloadedNanoappsFromConfigFile(const std::string &configFilePath,
+                                        std::string &outDirectory,
+                                        std::vector<std::string> &outNanoapps);
+
+}  // namespace chre
+}  // namespace android
+
+#endif  // CHRE_HOST_CONFIG_UTIL_H_
diff --git a/host/common/include/chre_host/daemon_base.h b/host/common/include/chre_host/daemon_base.h
index 66ca4e0..ad1a406 100644
--- a/host/common/include/chre_host/daemon_base.h
+++ b/host/common/include/chre_host/daemon_base.h
@@ -17,11 +17,20 @@
 #ifndef CHRE_DAEMON_H_
 #define CHRE_DAEMON_H_
 
+/**
+ * @file daemon_base.h
+ * This header defines the CHRE daemon base class, off of which all supported
+ * CHRE daemon variants are expected to derive from. The goal is to provide
+ * common (abstract or implemented) interfaces that all CHRE daemons must
+ * implement.
+ */
+
 #include <atomic>
 #include <cstdint>
 #include <map>
 #include <queue>
 #include <string>
+#include <thread>
 
 #include "chre_host/host_protocol_host.h"
 #include "chre_host/log_message_parser.h"
@@ -30,7 +39,7 @@
 #ifdef CHRE_DAEMON_METRIC_ENABLED
 #include <aidl/android/frameworks/stats/IStats.h>
 #include <android/binder_manager.h>
-#endif
+#endif  // CHRE_DAEMON_METRIC_ENABLED
 
 namespace android {
 namespace chre {
@@ -38,7 +47,9 @@
 class ChreDaemonBase {
  public:
   ChreDaemonBase();
-  virtual ~ChreDaemonBase() {}
+  virtual ~ChreDaemonBase() {
+    mSignalHandlerThread.join();
+  }
 
   /**
    * Initialize the CHRE daemon. We're expected to fail here and not start
@@ -64,7 +75,17 @@
    * @param length The size of the data to send.
    * @return true if successful, false otherwise.
    */
-  bool sendMessageToChre(uint16_t clientId, void *data, size_t dataLen);
+  virtual bool sendMessageToChre(uint16_t clientId, void *data,
+                                 size_t dataLen) = 0;
+
+  /**
+   * Function to be invoked on a shutdown request (eg: from a signal handler)
+   * to initiate a graceful shutdown of the daemon.
+   */
+  virtual void onShutdown() {
+    setShutdownRequested(true);
+    mServer.shutdownServer();
+  }
 
   /**
    * Function to query if a graceful shutdown of CHRE was requested
@@ -75,16 +96,6 @@
     return mChreShutdownRequested;
   }
 
-  /**
-   * Loads the supplied file into the provided buffer.
-   *
-   * @param filename The name of the file to load.
-   * @param buffer The buffer to load into.
-   * @return true if successful, false otherwise.
-   */
-  static bool readFileContents(const char *filename,
-                               std::vector<uint8_t> *buffer);
-
  protected:
   //! The host ID to use when preloading nanoapps. This is used before the
   //! server is started and is sufficiently high enough so as to not collide
@@ -111,9 +122,6 @@
    * ]}
    *
    * The napp_header and so files will both be loaded. All errors are logged.
-   *
-   * TODO: This is SLPI specific right now, and needs to be revisited to
-   * implement platform specific loading.
    */
   void loadPreloadedNanoapps();
 
@@ -155,10 +163,10 @@
    * @param transactionId The transaction ID to use when loading.
    * @return true if a request was successfully sent, false otherwise.
    */
-  bool sendNanoappLoad(uint64_t appId, uint32_t appVersion,
-                       uint32_t appTargetApiVersion,
-                       const std::string &appBinaryName,
-                       uint32_t transactionId);
+  virtual bool sendNanoappLoad(uint64_t appId, uint32_t appVersion,
+                               uint32_t appTargetApiVersion,
+                               const std::string &appBinaryName,
+                               uint32_t transactionId) = 0;
 
   /**
    * Send a time sync message to CHRE
@@ -167,7 +175,15 @@
    *
    * @return true if the time sync message was successfully sent to CHRE.
    */
-  bool sendTimeSync(bool logOnError);
+  virtual bool sendTimeSync(bool logOnError) = 0;
+
+  /**
+   * Computes and returns the clock drift between the system clock
+   * and the processor timer registers
+   *
+   * @return offset in nanoseconds
+   */
+  virtual int64_t getTimeOffset(bool *success) = 0;
 
   /**
    * Sends a time sync message to CHRE, retrying a specified time until success.
@@ -179,57 +195,60 @@
   bool sendTimeSyncWithRetry(size_t numRetries, useconds_t retryDelayUs,
                              bool logOnError);
 
-  bool sendNanConfigurationUpdate(bool nanEnabled);
-
   /**
    * Interface to a callback that is called when the Daemon receives a message.
    *
    * @param message A buffer containing the message
    * @param messageLen size of the message buffer in bytes
    */
-  void onMessageReceived(const unsigned char *message, size_t messageLen);
+  virtual void onMessageReceived(const unsigned char *message,
+                                 size_t messageLen) = 0;
 
   /**
    * Handles a message that is directed towards the daemon.
    *
    * @param message The message sent to the daemon.
    */
-  virtual void handleDaemonMessage(const uint8_t *message);
-
-  /**
-   * Platform-specific method to actually do the message sending requested by
-   * sendMessageToChre.
-   */
-  virtual bool doSendMessage(void *data, size_t dataLen) = 0;
+  virtual void handleDaemonMessage(const uint8_t *message) = 0;
 
   /**
    * Enables or disables LPMA (low power microphone access).
    */
   virtual void configureLpma(bool enabled) = 0;
 
-  /**
-   * Configures the daemon to send NAN enable/disable HAL requests.
-   */
-  virtual void configureNan(bool enabled);
-
 #ifdef CHRE_DAEMON_METRIC_ENABLED
   /**
    * Handles a metric log message sent from CHRE
+   *
    */
   virtual void handleMetricLog(const ::chre::fbs::MetricLogT *metric_msg);
-#endif  // CHRE_DAEMON_METRIC_ENABLED
 
-#ifdef CHRE_DAEMON_METRIC_ENABLED
+#ifdef CHRE_LOG_ATOM_EXTENSION_ENABLED
+  /**
+   * Handles additional metrics that aren't logged by the common CHRE code.
+   *
+   */
+  virtual void handleVendorMetricLog(
+      const ::chre::fbs::MetricLogT *metric_msg) = 0;
+#endif  // CHRE_LOG_ATOM_EXTENSION_ENABLED
+
   /**
    * Create and report CHRE vendor atom and send it to stats_client
    *
    * @param atom the vendor atom to be reported
    */
-  virtual void reportMetric(
-      const aidl::android::frameworks::stats::VendorAtom &atom);
+  void reportMetric(const aidl::android::frameworks::stats::VendorAtom &atom);
 #endif  // CHRE_DAEMON_METRIC_ENABLED
 
   /**
+   * Handles a NAN configuration request sent from CHRE.
+   *
+   * @param request NAN configuration request.
+   */
+  virtual void handleNanConfigurationRequest(
+      const ::chre::fbs::NanConfigurationRequestT *request);
+
+  /**
    * Returns the CHRE log message parser instance.
    * @return log message parser instance.
    */
@@ -243,20 +262,10 @@
  private:
   LogMessageParser mLogger;
 
+  std::thread mSignalHandlerThread;
+
   //! Set to true when we request a graceful shutdown of CHRE
   std::atomic<bool> mChreShutdownRequested;
-
-  //! Contains a set of transaction IDs and app IDs used to load the preloaded
-  //! nanoapps. The IDs are stored in the order they are sent.
-  std::queue<Transaction> mPreloadedNanoappPendingTransactions;
-
-  /**
-   * Computes and returns the clock drift between the system clock
-   * and the processor timer registers
-   *
-   * @return offset in nanoseconds
-   */
-  virtual int64_t getTimeOffset(bool *success) = 0;
 };
 
 }  // namespace chre
diff --git a/host/common/include/chre_host/fbs_daemon_base.h b/host/common/include/chre_host/fbs_daemon_base.h
new file mode 100644
index 0000000..da9de81
--- /dev/null
+++ b/host/common/include/chre_host/fbs_daemon_base.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 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_FBS_DAEMON_BASE_H_
+#define CHRE_FBS_DAEMON_BASE_H_
+
+/**
+ * @file fbs_daemon_base.h
+ * This header defines a base class for all CHRE daemon variants that use
+ * flatbuffers as the codec scheme for communicating with CHRE.
+ */
+
+#include "chre_host/daemon_base.h"
+#include "chre_host/host_protocol_host.h"
+
+namespace android {
+namespace chre {
+
+class FbsDaemonBase : public ChreDaemonBase {
+ public:
+  virtual ~FbsDaemonBase() {}
+
+  /**
+   * Send a message to CHRE
+   *
+   * @param clientId The client ID that this message originates from.
+   * @param data The data to pass down.
+   * @param length The size of the data to send.
+   * @return true if successful, false otherwise.
+   */
+  bool sendMessageToChre(uint16_t clientId, void *data,
+                         size_t dataLen) override;
+
+  /**
+   * Enables or disables LPMA (low power microphone access).
+   */
+  virtual void configureLpma(bool enabled) = 0;
+
+ protected:
+  /**
+   * Loads a nanoapp by sending the nanoapp filename to the CHRE framework. This
+   * method will return after sending the request so no guarantee is made that
+   * the nanoapp is loaded until after the response is received.
+   *
+   * @param appId The ID of the nanoapp to load.
+   * @param appVersion The version of the nanoapp to load.
+   * @param appTargetApiVersion The version of the CHRE API that the app
+   * targets.
+   * @param appBinaryName The name of the binary as stored in the filesystem.
+   * This will be used to load the nanoapp into CHRE.
+   * @param transactionId The transaction ID to use when loading.
+   * @return true if a request was successfully sent, false otherwise.
+   */
+  bool sendNanoappLoad(uint64_t appId, uint32_t appVersion,
+                       uint32_t appTargetApiVersion,
+                       const std::string &appBinaryName,
+                       uint32_t transactionId) override;
+
+  /**
+   * Send a time sync message to CHRE
+   *
+   * @param logOnError If true, logs an error message on failure.
+   *
+   * @return true if the time sync message was successfully sent to CHRE.
+   */
+  bool sendTimeSync(bool logOnError) override;
+
+  /**
+   * Interface to a callback that is called when the Daemon receives a message.
+   *
+   * @param message A buffer containing the message
+   * @param messageLen size of the message buffer in bytes
+   */
+  void onMessageReceived(const unsigned char *message,
+                         size_t messageLen) override;
+
+  /**
+   * Handles a message that is directed towards the daemon.
+   *
+   * @param message The message sent to the daemon.
+   */
+  void handleDaemonMessage(const uint8_t *message) override;
+
+  /**
+   * Platform-specific method to actually do the message sending requested by
+   * sendMessageToChre.
+   *
+   * @return true if message was sent successfully, false otherwise.
+   */
+  virtual bool doSendMessage(void *data, size_t dataLen) = 0;
+
+ private:
+  //! Contains a set of transaction IDs and app IDs used to load the preloaded
+  //! nanoapps. The IDs are stored in the order they are sent.
+  std::queue<Transaction> mPreloadedNanoappPendingTransactions;
+};
+
+}  // namespace chre
+}  // namespace android
+
+#endif  // CHRE_FBS_DAEMON_BASE_H_
diff --git a/host/common/include/chre_host/file_stream.h b/host/common/include/chre_host/file_stream.h
new file mode 100644
index 0000000..df89184
--- /dev/null
+++ b/host/common/include/chre_host/file_stream.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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_HOST_FILE_STREAM_H_
+#define CHRE_HOST_FILE_STREAM_H_
+
+#include <vector>
+
+namespace android {
+namespace chre {
+
+/**
+ * Reads a file and stores it into a buffer.
+ *
+ * @param filename The name of the file.
+ * @param buffer The buffer to store the contents of the file into.
+ * @return true if successfully read and stored.
+ */
+bool readFileContents(const char *filename, std::vector<uint8_t> &buffer);
+
+}  // namespace chre
+}  // namespace android
+
+#endif  // CHRE_HOST_FILE_STREAM_H_
diff --git a/host/common/include/chre_host/fragmented_load_transaction.h b/host/common/include/chre_host/fragmented_load_transaction.h
index 22f5487..f0b9ff4 100644
--- a/host/common/include/chre_host/fragmented_load_transaction.h
+++ b/host/common/include/chre_host/fragmented_load_transaction.h
@@ -29,6 +29,10 @@
 namespace android {
 namespace chre {
 
+// A special fragment id indicating the loading is not started yet. First
+// fragment starts from id 1.
+static constexpr uint32_t kNoFragmentId = 0;
+
 /**
  * A struct which represents a single fragmented request. The caller should use
  * this class along with FragmentedLoadTransaction to get global attributes for
diff --git a/host/common/include/chre_host/generated/host_messages_generated.h b/host/common/include/chre_host/generated/host_messages_generated.h
index 8f665b9..8b344e1 100644
--- a/host/common/include/chre_host/generated/host_messages_generated.h
+++ b/host/common/include/chre_host/generated/host_messages_generated.h
@@ -125,6 +125,10 @@
 struct NanConfigurationUpdateBuilder;
 struct NanConfigurationUpdateT;
 
+struct DebugConfiguration;
+struct DebugConfigurationBuilder;
+struct DebugConfigurationT;
+
 struct HostAddress;
 
 struct MessageContainer;
@@ -233,11 +237,12 @@
   BatchedMetricLog = 25,
   NanConfigurationRequest = 26,
   NanConfigurationUpdate = 27,
+  DebugConfiguration = 28,
   MIN = NONE,
-  MAX = NanConfigurationUpdate
+  MAX = DebugConfiguration
 };
 
-inline const ChreMessage (&EnumValuesChreMessage())[28] {
+inline const ChreMessage (&EnumValuesChreMessage())[29] {
   static const ChreMessage values[] = {
     ChreMessage::NONE,
     ChreMessage::NanoappMessage,
@@ -266,13 +271,14 @@
     ChreMessage::MetricLog,
     ChreMessage::BatchedMetricLog,
     ChreMessage::NanConfigurationRequest,
-    ChreMessage::NanConfigurationUpdate
+    ChreMessage::NanConfigurationUpdate,
+    ChreMessage::DebugConfiguration
   };
   return values;
 }
 
 inline const char * const *EnumNamesChreMessage() {
-  static const char * const names[29] = {
+  static const char * const names[30] = {
     "NONE",
     "NanoappMessage",
     "HubInfoRequest",
@@ -301,13 +307,14 @@
     "BatchedMetricLog",
     "NanConfigurationRequest",
     "NanConfigurationUpdate",
+    "DebugConfiguration",
     nullptr
   };
   return names;
 }
 
 inline const char *EnumNameChreMessage(ChreMessage e) {
-  if (flatbuffers::IsOutRange(e, ChreMessage::NONE, ChreMessage::NanConfigurationUpdate)) return "";
+  if (flatbuffers::IsOutRange(e, ChreMessage::NONE, ChreMessage::DebugConfiguration)) return "";
   const size_t index = static_cast<size_t>(e);
   return EnumNamesChreMessage()[index];
 }
@@ -424,6 +431,10 @@
   static const ChreMessage enum_value = ChreMessage::NanConfigurationUpdate;
 };
 
+template<> struct ChreMessageTraits<chre::fbs::DebugConfiguration> {
+  static const ChreMessage enum_value = ChreMessage::DebugConfiguration;
+};
+
 struct ChreMessageUnion {
   ChreMessage type;
   void *value;
@@ -672,6 +683,14 @@
     return type == ChreMessage::NanConfigurationUpdate ?
       reinterpret_cast<const chre::fbs::NanConfigurationUpdateT *>(value) : nullptr;
   }
+  chre::fbs::DebugConfigurationT *AsDebugConfiguration() {
+    return type == ChreMessage::DebugConfiguration ?
+      reinterpret_cast<chre::fbs::DebugConfigurationT *>(value) : nullptr;
+  }
+  const chre::fbs::DebugConfigurationT *AsDebugConfiguration() const {
+    return type == ChreMessage::DebugConfiguration ?
+      reinterpret_cast<const chre::fbs::DebugConfigurationT *>(value) : nullptr;
+  }
 };
 
 bool VerifyChreMessage(flatbuffers::Verifier &verifier, const void *obj, ChreMessage type);
@@ -3292,6 +3311,65 @@
 
 flatbuffers::Offset<NanConfigurationUpdate> CreateNanConfigurationUpdate(flatbuffers::FlatBufferBuilder &_fbb, const NanConfigurationUpdateT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
 
+struct DebugConfigurationT : public flatbuffers::NativeTable {
+  typedef DebugConfiguration TableType;
+  bool health_monitor_failure_crash;
+  DebugConfigurationT()
+      : health_monitor_failure_crash(false) {
+  }
+};
+
+struct DebugConfiguration FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  typedef DebugConfigurationT NativeTableType;
+  typedef DebugConfigurationBuilder Builder;
+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+    VT_HEALTH_MONITOR_FAILURE_CRASH = 4
+  };
+  bool health_monitor_failure_crash() const {
+    return GetField<uint8_t>(VT_HEALTH_MONITOR_FAILURE_CRASH, 0) != 0;
+  }
+  bool mutate_health_monitor_failure_crash(bool _health_monitor_failure_crash) {
+    return SetField<uint8_t>(VT_HEALTH_MONITOR_FAILURE_CRASH, static_cast<uint8_t>(_health_monitor_failure_crash), 0);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint8_t>(verifier, VT_HEALTH_MONITOR_FAILURE_CRASH) &&
+           verifier.EndTable();
+  }
+  DebugConfigurationT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+  void UnPackTo(DebugConfigurationT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+  static flatbuffers::Offset<DebugConfiguration> Pack(flatbuffers::FlatBufferBuilder &_fbb, const DebugConfigurationT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+};
+
+struct DebugConfigurationBuilder {
+  typedef DebugConfiguration Table;
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_health_monitor_failure_crash(bool health_monitor_failure_crash) {
+    fbb_.AddElement<uint8_t>(DebugConfiguration::VT_HEALTH_MONITOR_FAILURE_CRASH, static_cast<uint8_t>(health_monitor_failure_crash), 0);
+  }
+  explicit DebugConfigurationBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  DebugConfigurationBuilder &operator=(const DebugConfigurationBuilder &);
+  flatbuffers::Offset<DebugConfiguration> Finish() {
+    const auto end = fbb_.EndTable(start_);
+    auto o = flatbuffers::Offset<DebugConfiguration>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<DebugConfiguration> CreateDebugConfiguration(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    bool health_monitor_failure_crash = false) {
+  DebugConfigurationBuilder builder_(_fbb);
+  builder_.add_health_monitor_failure_crash(health_monitor_failure_crash);
+  return builder_.Finish();
+}
+
+flatbuffers::Offset<DebugConfiguration> CreateDebugConfiguration(flatbuffers::FlatBufferBuilder &_fbb, const DebugConfigurationT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+
 struct MessageContainerT : public flatbuffers::NativeTable {
   typedef MessageContainer TableType;
   chre::fbs::ChreMessageUnion message;
@@ -3399,6 +3477,9 @@
   const chre::fbs::NanConfigurationUpdate *message_as_NanConfigurationUpdate() const {
     return message_type() == chre::fbs::ChreMessage::NanConfigurationUpdate ? static_cast<const chre::fbs::NanConfigurationUpdate *>(message()) : nullptr;
   }
+  const chre::fbs::DebugConfiguration *message_as_DebugConfiguration() const {
+    return message_type() == chre::fbs::ChreMessage::DebugConfiguration ? static_cast<const chre::fbs::DebugConfiguration *>(message()) : nullptr;
+  }
   void *mutable_message() {
     return GetPointer<void *>(VT_MESSAGE);
   }
@@ -3535,6 +3616,10 @@
   return message_as_NanConfigurationUpdate();
 }
 
+template<> inline const chre::fbs::DebugConfiguration *MessageContainer::message_as<chre::fbs::DebugConfiguration>() const {
+  return message_as_DebugConfiguration();
+}
+
 struct MessageContainerBuilder {
   typedef MessageContainer Table;
   flatbuffers::FlatBufferBuilder &fbb_;
@@ -4441,6 +4526,32 @@
       _enabled);
 }
 
+inline DebugConfigurationT *DebugConfiguration::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
+  std::unique_ptr<chre::fbs::DebugConfigurationT> _o = std::unique_ptr<chre::fbs::DebugConfigurationT>(new DebugConfigurationT());
+  UnPackTo(_o.get(), _resolver);
+  return _o.release();
+}
+
+inline void DebugConfiguration::UnPackTo(DebugConfigurationT *_o, const flatbuffers::resolver_function_t *_resolver) const {
+  (void)_o;
+  (void)_resolver;
+  { auto _e = health_monitor_failure_crash(); _o->health_monitor_failure_crash = _e; }
+}
+
+inline flatbuffers::Offset<DebugConfiguration> DebugConfiguration::Pack(flatbuffers::FlatBufferBuilder &_fbb, const DebugConfigurationT* _o, const flatbuffers::rehasher_function_t *_rehasher) {
+  return CreateDebugConfiguration(_fbb, _o, _rehasher);
+}
+
+inline flatbuffers::Offset<DebugConfiguration> CreateDebugConfiguration(flatbuffers::FlatBufferBuilder &_fbb, const DebugConfigurationT *_o, const flatbuffers::rehasher_function_t *_rehasher) {
+  (void)_rehasher;
+  (void)_o;
+  struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const DebugConfigurationT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va;
+  auto _health_monitor_failure_crash = _o->health_monitor_failure_crash;
+  return chre::fbs::CreateDebugConfiguration(
+      _fbb,
+      _health_monitor_failure_crash);
+}
+
 inline MessageContainerT *MessageContainer::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
   std::unique_ptr<chre::fbs::MessageContainerT> _o = std::unique_ptr<chre::fbs::MessageContainerT>(new MessageContainerT());
   UnPackTo(_o.get(), _resolver);
@@ -4586,6 +4697,10 @@
       auto ptr = reinterpret_cast<const chre::fbs::NanConfigurationUpdate *>(obj);
       return verifier.VerifyTable(ptr);
     }
+    case ChreMessage::DebugConfiguration: {
+      auto ptr = reinterpret_cast<const chre::fbs::DebugConfiguration *>(obj);
+      return verifier.VerifyTable(ptr);
+    }
     default: return true;
   }
 }
@@ -4712,6 +4827,10 @@
       auto ptr = reinterpret_cast<const chre::fbs::NanConfigurationUpdate *>(obj);
       return ptr->UnPack(resolver);
     }
+    case ChreMessage::DebugConfiguration: {
+      auto ptr = reinterpret_cast<const chre::fbs::DebugConfiguration *>(obj);
+      return ptr->UnPack(resolver);
+    }
     default: return nullptr;
   }
 }
@@ -4826,6 +4945,10 @@
       auto ptr = reinterpret_cast<const chre::fbs::NanConfigurationUpdateT *>(value);
       return CreateNanConfigurationUpdate(_fbb, ptr, _rehasher).Union();
     }
+    case ChreMessage::DebugConfiguration: {
+      auto ptr = reinterpret_cast<const chre::fbs::DebugConfigurationT *>(value);
+      return CreateDebugConfiguration(_fbb, ptr, _rehasher).Union();
+    }
     default: return 0;
   }
 }
@@ -4940,6 +5063,10 @@
       value = new chre::fbs::NanConfigurationUpdateT(*reinterpret_cast<chre::fbs::NanConfigurationUpdateT *>(u.value));
       break;
     }
+    case ChreMessage::DebugConfiguration: {
+      value = new chre::fbs::DebugConfigurationT(*reinterpret_cast<chre::fbs::DebugConfigurationT *>(u.value));
+      break;
+    }
     default:
       break;
   }
@@ -5082,6 +5209,11 @@
       delete ptr;
       break;
     }
+    case ChreMessage::DebugConfiguration: {
+      auto ptr = reinterpret_cast<chre::fbs::DebugConfigurationT *>(value);
+      delete ptr;
+      break;
+    }
     default: break;
   }
   value = nullptr;
diff --git a/host/common/include/chre_host/host_protocol_host.h b/host/common/include/chre_host/host_protocol_host.h
index 8f6374e..29c1f8b 100644
--- a/host/common/include/chre_host/host_protocol_host.h
+++ b/host/common/include/chre_host/host_protocol_host.h
@@ -107,6 +107,14 @@
   static void encodeHubInfoRequest(flatbuffers::FlatBufferBuilder &builder);
 
   /**
+   * Encodes a message sending boot debug configuration to CHRE
+   *
+   * @param builder A newly constructed FlatBufferBuilder that will be used to
+   *        construct the message
+   */
+  static void encodeDebugConfiguration(flatbuffers::FlatBufferBuilder &builder);
+
+  /**
    * Encodes a message requesting to load a nanoapp specified by the included
    * (possibly fragmented) binary payload and metadata.
    *
diff --git a/host/common/include/chre_host/preloaded_nanoapp_loader.h b/host/common/include/chre_host/preloaded_nanoapp_loader.h
new file mode 100644
index 0000000..a605ae7
--- /dev/null
+++ b/host/common/include/chre_host/preloaded_nanoapp_loader.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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_HOST_PRELOADED_NANOAPP_LOADER_H_
+#define CHRE_HOST_PRELOADED_NANOAPP_LOADER_H_
+
+#include <android/binder_to_string.h>
+#include <cstdint>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include "chre_connection.h"
+#include "chre_host/generated/host_messages_generated.h"
+#include "chre_host/napp_header.h"
+#include "fragmented_load_transaction.h"
+#include "hal_client_id.h"
+
+namespace android::chre {
+
+using namespace ::android::hardware::contexthub::common::implementation;
+
+/**
+ * A class loads preloaded nanoapps.
+ *
+ * A context hub can include a set of nanoapps that are included in the device
+ * image and are loaded when CHRE starts. These are known as preloaded nanoapps.
+ * A HAL implementation should use this class to load preloaded nanoapps before
+ * exposing API to HAL clients.
+ */
+class PreloadedNanoappLoader {
+ public:
+  explicit PreloadedNanoappLoader(ChreConnection *connection,
+                                  std::string configPath)
+      : mConnection(connection), mConfigPath(std::move(configPath)){};
+
+  ~PreloadedNanoappLoader() = default;
+  /**
+   * Attempts to load all preloaded nanoapps from a config file.
+   *
+   * The config file is expected to be valid JSON with the following structure:
+   *
+   * { "nanoapps": [
+   *     "/path/to/nanoapp_1",
+   *     "/path/to/nanoapp_2"
+   * ]}
+   *
+   * The napp_header and so files will both be used.
+   */
+  void loadPreloadedNanoapps();
+
+  /** Callback function to handle the response from CHRE. */
+  bool onLoadNanoappResponse(const ::chre::fbs::LoadNanoappResponseT &response,
+                             HalClientId clientId);
+
+  void getPreloadedNanoappIds(std::vector<uint64_t> &out_preloadedNanoappIds);
+
+  /** Returns true if the loading is ongoing. */
+  [[nodiscard]] bool isPreloadOngoing() const {
+    return mIsPreloadingOngoing;
+  };
+
+ private:
+  /** Timeout value of waiting for the response of a fragmented load */
+  static constexpr auto kTimeoutInMs = std::chrono::milliseconds(2000);
+
+  /**
+   * Loads a preloaded nanoapp given a filename.
+   *
+   * This function allows the transaction to complete before the nanoapp starts
+   * so the server can start serving requests as soon as possible.
+   *
+   * @param directory The directory to load the nanoapp from.
+   * @param name The filename of the nanoapp to load.
+   * @param transactionId The transaction ID to use when loading the app.
+   */
+  void loadPreloadedNanoapp(const std::string &directory,
+                            const std::string &name, uint32_t transactionId);
+
+  /**
+   * Loads a preloaded nanoapp.
+   *
+   * @param header The nanoapp header binary blob.
+   * @param nanoapp The nanoapp binary.
+   * @param transactionId The transaction ID identifying this load transaction.
+   * @return true if successful, false otherwise.
+   */
+  bool loadNanoapp(const std::vector<uint8_t> &header,
+                   const std::vector<uint8_t> &nanoapp, uint32_t transactionId);
+
+  /**
+   * Chunks the nanoapp binary into fragments and load each fragment
+   * sequentially.
+   */
+  bool sendFragmentedLoadAndWaitForEachResponse(
+      uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+      uint32_t appTargetApiVersion, const uint8_t *appBinary, size_t appSize,
+      uint32_t transactionId);
+
+  /** Sends the FragmentedLoadRequest to CHRE. */
+  std::future<bool> sendFragmentedLoadRequest(
+      ::android::chre::FragmentedLoadRequest &request);
+
+  /** Verifies the future returned by sendFragmentedLoadRequest(). */
+  [[nodiscard]] static bool waitAndVerifyFuture(
+      std::future<bool> &future, const FragmentedLoadRequest &request);
+
+  /** Verifies the response of a loading request. */
+  [[nodiscard]] bool verifyFragmentLoadResponse(
+      const ::chre::fbs::LoadNanoappResponseT &response) const;
+
+  /** Tracks the transaction state of the ongoing nanoapp loading */
+  struct Transaction {
+    uint32_t transactionId;
+    size_t fragmentId;
+  };
+  Transaction mPreloadedNanoappPendingTransaction{0, 0};
+
+  /** The value of this promise carries the result in the load response. */
+  std::optional<std::promise<bool>> mFragmentedLoadPromise = std::nullopt;
+
+  /** The mutex used to guard states change for preloading. */
+  std::mutex mPreloadedNanoappsMutex;
+
+  bool mIsPreloadingOngoing = false;
+
+  ChreConnection *mConnection;
+  std::string mConfigPath;
+};
+
+}  // namespace android::chre
+
+#endif  // CHRE_HOST_PRELOADED_NANOAPP_LOADER_H_
diff --git a/host/common/include/chre_host/socket_server.h b/host/common/include/chre_host/socket_server.h
index 2aeecd2..6a19ecb 100644
--- a/host/common/include/chre_host/socket_server.h
+++ b/host/common/include/chre_host/socket_server.h
@@ -81,6 +81,10 @@
    */
   bool sendToClientById(const void *data, size_t length, uint16_t clientId);
 
+  void shutdownServer() {
+    sSignalReceived = true;
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(SocketServer);
 
@@ -119,7 +123,6 @@
   void serviceSocket();
 
   static std::atomic<bool> sSignalReceived;
-  static void signalHandler(int signal);
 };
 
 }  // namespace chre
diff --git a/host/common/include/chre_host/st_hal_lpma_handler.h b/host/common/include/chre_host/st_hal_lpma_handler.h
index 4210802..e68ffc0 100644
--- a/host/common/include/chre_host/st_hal_lpma_handler.h
+++ b/host/common/include/chre_host/st_hal_lpma_handler.h
@@ -17,6 +17,7 @@
 #ifndef ST_HAL_LPMA_HANDLER_H_
 #define ST_HAL_LPMA_HANDLER_H_
 
+#include <hardware_legacy/power.h>
 #include <condition_variable>
 #include <cstdio>
 #include <functional>
@@ -25,15 +26,24 @@
 
 #include "chre_host/log.h"
 
+#ifdef CHRE_ST_LPMA_HANDLER_AIDL
+#include <aidl/android/hardware/soundtrigger3/ISoundTriggerHw.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#else
 #include <android/hardware/soundtrigger/2.0/ISoundTriggerHw.h>
-#include <hardware_legacy/power.h>
+#endif  // CHRE_ST_LPMA_HANDLER_AIDL
 
+#ifdef CHRE_ST_LPMA_HANDLER_AIDL
+using aidl::android::hardware::soundtrigger3::ISoundTriggerHw;
+#else
 using android::hardware::hidl_death_recipient;
 using android::hardware::Return;
 using android::hardware::soundtrigger::V2_0::ISoundTriggerHw;
 using android::hardware::soundtrigger::V2_0::SoundModelHandle;
 using android::hardware::soundtrigger::V2_0::SoundModelType;
 using android::hidl::base::V1_0::IBase;
+#endif  // CHRE_ST_LPMA_HANDLER_AIDL
 
 namespace android {
 namespace chre {
@@ -47,37 +57,10 @@
  */
 class StHalLpmaHandler {
  public:
-  //! Class to handle when a connected ST HAL service dies
-  class StHalDeathRecipient : public hidl_death_recipient {
-   public:
-    StHalDeathRecipient() = delete;
-    explicit StHalDeathRecipient(std::function<void()> cb) : mCallback(cb) {}
-
-    /**
-     * Callback that is called when a connected service dies. It mainly
-     * resets the LPMA Enabled flag, and unblocks the LPMA processing thread.
-     * It is to be used in conjunction with linkToDeath(), which we do
-     * in checkConnectionToStHalServiceLocked().
-     */
-    virtual void serviceDied(uint64_t /* cookie */,
-                             const wp<IBase> & /* who */) override {
-      mCallback();
-    }
-
-   private:
-    std::function<void()> mCallback;
-  };
-
   StHalLpmaHandler() = delete;
-
   explicit StHalLpmaHandler(bool allowed);
 
-  ~StHalLpmaHandler() {
-    if (mThread.has_value()) {
-      // TODO: Change this to join after adding proper handler
-      mThread->detach();
-    }
-  }
+  ~StHalLpmaHandler();
 
   /**
    * If LPMA is enabled, starts a worker thread to load/unload models.
@@ -92,6 +75,49 @@
    */
   void enable(bool enabled);
 
+ private:
+  const bool mIsLpmaAllowed;
+  bool mCurrentLpmaEnabled;
+  bool mTargetLpmaEnabled;
+  bool mCondVarPredicate;
+  bool mStThreadShouldExit = false;
+
+  int mRetryCount;
+  useconds_t mRetryDelay;
+
+  std::optional<std::thread> mThread;
+  std::mutex mMutex;
+  std::condition_variable mCondVar;
+
+#ifdef CHRE_ST_LPMA_HANDLER_AIDL
+  int32_t mLpmaHandle = 0;
+  std::shared_ptr<ISoundTriggerHw> mStHalService;
+#else
+  //! Class to handle when a connected ST HAL service dies
+  class StHalDeathRecipient : public hidl_death_recipient {
+   public:
+    StHalDeathRecipient() = delete;
+    explicit StHalDeathRecipient(std::function<void()> cb) : mCallback(cb) {}
+
+    /**
+     * Callback that is called when a connected service dies. It mainly
+     * resets the LPMA Enabled flag, and unblocks the LPMA processing thread.
+     * It is to be used in conjunction with linkToDeath(), which we do
+     * in checkConnectionToStHalServiceLocked().
+     */
+    void serviceDied(uint64_t /* cookie */,
+                     const wp<IBase> & /* who */) override {
+      mCallback();
+    }
+
+   private:
+    std::function<void()> mCallback;
+  };
+  SoundModelHandle mLpmaHandle = 0;
+  sp<StHalDeathRecipient> mDeathRecipient;
+  sp<ISoundTriggerHw> mStHalService;
+#endif  // CHRE_ST_LPMA_HANDLER_AIDL
+
   /**
    * Loads the LPMA use case via the SoundTrigger HAL HIDL service.
    *
@@ -109,27 +135,31 @@
   void unload();
 
   /**
+   * If CHRE_LPMA_REQUEST_START_RECOGNITION is defined, calls startRecognition()
+   * on the currently loaded model. No-op otherwise.
+   *
+   * @return true on success
+   */
+  bool start();
+
+  //! Invokes stopRecognition() on the currently loaded model
+  /**
+   * If CHRE_LPMA_REQUEST_START_RECOGNITION is defined, calls stopRecognition()
+   * on the currently loaded model. No-op otherwise.
+   *
+   * @return true on success
+   */
+  void stop();
+
+  // Convenience methods
+  bool loadAndStart();
+  void stopAndUnload();
+
+  /**
    * Entry point for the thread that loads/unloads sound models from the
    * ST HAL
    */
-  void StHalLpmaHandlerThreadEntry();
-
- private:
-  const bool mIsLpmaAllowed;
-  bool mCurrentLpmaEnabled;
-  bool mTargetLpmaEnabled;
-  bool mCondVarPredicate;
-  SoundModelHandle mLpmaHandle = 0;
-
-  int mRetryCount;
-  useconds_t mRetryDelay;
-
-  std::optional<std::thread> mThread;
-  std::mutex mMutex;
-  std::condition_variable mCondVar;
-
-  sp<StHalDeathRecipient> mDeathRecipient;
-  sp<ISoundTriggerHw> mStHalService;
+  void stHalLpmaHandlerThreadEntry();
 
   /**
    * Checks for a valid connection to the ST HAL service, reconnects if not
@@ -145,22 +175,13 @@
   void onStHalServiceDeath();
 
   /**
-   * This function blocks on a condition variable and when notified, based
-   * on its current state and as notified by enable(), performs a load or
-   * unload. The function also resets the delay and retry counts if the current
-   * and next states match
+   * Invoke Hal to load or unload LPMA depending on the status of
+   * mTargetLpmaEnabled and mCurrentLpmaEnabled
    *
-   * @return true if the state update succeeded, and we don't need to retry with
-   * a delay
+   * @param locked lock that holds the mutex that guards mTargetLpmaEnabled
    */
-  bool waitOnStHalRequestAndProcess();
-
-  /**
-   * Delay retrying a load if a state update failed
-   */
-  void delay();
+  void stHalRequestAndProcessLocked(std::unique_lock<std::mutex> const &locked);
 };
-
 }  // namespace chre
 }  // namespace android
 
diff --git a/host/common/include/chre_host/time_syncer.h b/host/common/include/chre_host/time_syncer.h
new file mode 100644
index 0000000..9518ea2
--- /dev/null
+++ b/host/common/include/chre_host/time_syncer.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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_HOST_TIME_SYNCER_H_
+#define CHRE_HOST_TIME_SYNCER_H_
+
+#include <cstdint>
+#include <string>
+
+#include "chre_connection.h"
+
+namespace android::chre {
+
+using hardware::contexthub::common::implementation::ChreConnection;
+
+/** The class synchronizes time between the Context hub and Android. */
+class TimeSyncer {
+ public:
+  explicit TimeSyncer(ChreConnection *connection) : mConnection(connection) {}
+
+  /**
+   * Sends time sync message to Context hub and retries numRetries times until
+   * success.
+   *
+   * If the platform doesn't require the time sync the request will be ignored
+   * and true is returned.
+   *
+   * @return true if success, false otherwise.
+   */
+  bool sendTimeSyncWithRetry(size_t numOfRetries, useconds_t retryDelayUs);
+
+  /**
+   * Sends a time sync message to Context hub for once.
+   *
+   * If the platform doesn't require the time sync the request will be ignored
+   * and true is returned.
+   *
+   * @return true if success, false otherwise.
+   */
+  bool sendTimeSync();
+
+ private:
+  ChreConnection *mConnection;
+};
+
+}  // namespace android::chre
+
+#endif  // CHRE_HOST_TIME_SYNCER_H_
\ No newline at end of file
diff --git a/host/common/include/chre_host/wifi_ext_hal_handler.h b/host/common/include/chre_host/wifi_ext_hal_handler.h
index c793693..d83a625 100644
--- a/host/common/include/chre_host/wifi_ext_hal_handler.h
+++ b/host/common/include/chre_host/wifi_ext_hal_handler.h
@@ -25,10 +25,13 @@
 #include <condition_variable>
 #include <cstdint>
 #include <mutex>
+#include <string>
 #include <thread>
 
-#include <vendor/google/wifi_ext/1.3/IWifiExt.h>
-#include <vendor/google/wifi_ext/1.3/IWifiExtChreCallback.h>
+#include <aidl/android/hardware/wifi/WifiStatusCode.h>
+#include <aidl/vendor/google/wifi_ext/BnWifiExtChreCallback.h>
+#include <aidl/vendor/google/wifi_ext/IWifiExt.h>
+#include <android/binder_manager.h>
 
 #include "chre_host/log.h"
 
@@ -41,15 +44,12 @@
  */
 class WifiExtHalHandler {
  public:
-  using hidl_death_recipient = hardware::hidl_death_recipient;
-  using WifiStatus = hardware::wifi::V1_0::WifiStatus;
-  using WifiStatusCode = hardware::wifi::V1_0::WifiStatusCode;
-  using IBase = hidl::base::V1_0::IBase;
-  using IWifiExt = ::vendor::google::wifi_ext::V1_3::IWifiExt;
-  using IWifiExtChreNanCallback =
-      ::vendor::google::wifi_ext::V1_3::IWifiExtChreCallback;
+  using WifiStatusCode = aidl::android::hardware::wifi::WifiStatusCode;
+  using IWifiExt = aidl::vendor::google::wifi_ext::IWifiExt;
+  using BnWifiExtChreNanCallback =
+      aidl::vendor::google::wifi_ext::BnWifiExtChreCallback;
   using WifiChreNanRttState =
-      ::vendor::google::wifi_ext::V1_3::WifiChreNanRttState;
+      aidl::vendor::google::wifi_ext::WifiChreNanRttState;
 
   ~WifiExtHalHandler();
 
@@ -73,14 +73,14 @@
 
  private:
   //! CHRE NAN availability status change handler.
-  class WifiExtCallback : public IWifiExtChreNanCallback {
+  class WifiExtCallback : public BnWifiExtChreNanCallback {
    public:
     WifiExtCallback(std::function<void(bool)> cb) : mCallback(cb) {}
 
-    hardware::Return<void> onChreNanRttStateChanged(WifiChreNanRttState state) {
+    ndk::ScopedAStatus onChreNanRttStateChanged(WifiChreNanRttState state) {
       bool enabled = (state == WifiChreNanRttState::CHRE_AVAILABLE);
       onStatusChanged(enabled);
-      return hardware::Void();
+      return ndk::ScopedAStatus::ok();
     }
 
     void onStatusChanged(bool enabled) {
@@ -91,22 +91,6 @@
     std::function<void(bool)> mCallback;
   };
 
-  //! Handler for when a connected Wifi ext HAL service dies.
-  class WifiExtHalDeathRecipient : public hidl_death_recipient {
-   public:
-    WifiExtHalDeathRecipient() = delete;
-    explicit WifiExtHalDeathRecipient(std::function<void()> cb)
-        : mCallback(cb) {}
-
-    virtual void serviceDied(uint64_t /*cookie*/,
-                             const wp<IBase> & /*who*/) override {
-      mCallback();
-    }
-
-   private:
-    std::function<void()> mCallback;
-  };
-
   bool mThreadRunning = true;
   std::thread mThread;
   std::mutex mMutex;
@@ -116,9 +100,9 @@
   //! true, 'disable' otherwise) if it has a value.
   std::optional<bool> mEnableConfig;
 
-  sp<WifiExtHalDeathRecipient> mDeathRecipient;
-  sp<IWifiExt> mService;
-  sp<WifiExtCallback> mCallback;
+  AIBinder_DeathRecipient *mDeathRecipient;
+  std::shared_ptr<IWifiExt> mService;
+  std::shared_ptr<WifiExtCallback> mCallback;
 
   /**
    * Entry point for the thread that handles all interactions with the WiFi ext
@@ -144,7 +128,7 @@
   /**
    * Invoked by the HAL service death callback.
    */
-  void onWifiExtHalServiceDeath();
+  static void onWifiExtHalServiceDeath(void *cookie);
 
   /**
    * Dispatch a configuration request to the WiFi Ext HAL.
@@ -156,4 +140,4 @@
 };
 
 }  // namespace chre
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/host/common/log_message_parser.cc b/host/common/log_message_parser.cc
index 8afccab..9a9a95f 100644
--- a/host/common/log_message_parser.cc
+++ b/host/common/log_message_parser.cc
@@ -15,9 +15,13 @@
  */
 
 #include "chre_host/log_message_parser.h"
+
 #include <endian.h>
+#include <string.h>
+
 #include "chre/util/time.h"
 #include "chre_host/daemon_base.h"
+#include "chre_host/file_stream.h"
 #include "chre_host/log.h"
 #include "include/chre_host/log_message_parser.h"
 
@@ -39,10 +43,11 @@
     : mVerboseLoggingEnabled(kVerboseLoggingEnabled) {}
 
 std::unique_ptr<Detokenizer> LogMessageParser::logDetokenizerInit() {
+#ifdef CHRE_TOKENIZED_LOGGING_ENABLED
   constexpr const char kLogDatabaseFilePath[] =
       "/vendor/etc/chre/libchre_log_database.bin";
   std::vector<uint8_t> tokenData;
-  if (ChreDaemonBase::readFileContents(kLogDatabaseFilePath, &tokenData)) {
+  if (readFileContents(kLogDatabaseFilePath, tokenData)) {
     pw::tokenizer::TokenDatabase database =
         pw::tokenizer::TokenDatabase::Create(tokenData);
     if (database.ok()) {
@@ -54,6 +59,7 @@
   } else {
     LOGE("Failed to read CHRE Token database file");
   }
+#endif
   return std::unique_ptr<Detokenizer>(nullptr);
 }
 
@@ -122,13 +128,13 @@
 uint8_t LogMessageParser::getLogLevelFromMetadata(uint8_t metadata) {
   // The lower nibble of the metadata denotes the loglevel, as indicated
   // by the schema in host_messages.fbs.
-  return (metadata & 0xf);
+  return metadata & 0xf;
 }
 
 bool LogMessageParser::isLogMessageEncoded(uint8_t metadata) {
   // The upper nibble of the metadata denotes the encoding, as indicated
   // by the schema in host_messages.fbs.
-  return (((metadata >> 4) & 0xf) != 0);
+  return (metadata & 0xf0) != 0;
 }
 
 void LogMessageParser::log(const uint8_t *logBuffer, size_t logBufferSize) {
@@ -196,22 +202,32 @@
 
 void LogMessageParser::logV2(const uint8_t *logBuffer, size_t logBufferSize,
                              uint32_t numLogsDropped) {
+  // Size of the struct with an empty string.
+  constexpr size_t kMinLogMessageV2Size = sizeof(LogMessageV2) + 1;
+
   updateAndPrintDroppedLogs(numLogsDropped);
 
   size_t bufferIndex = 0;
-  while (bufferIndex < logBufferSize) {
-    const LogMessageV2 *message =
+  while (bufferIndex + kMinLogMessageV2Size <= logBufferSize) {
+    auto message =
         reinterpret_cast<const LogMessageV2 *>(&logBuffer[bufferIndex]);
-    size_t bufferIndexDelta = logBufferSize - bufferIndex;
+
     size_t logMessageSize;
     if (isLogMessageEncoded(message->metadata)) {
       logMessageSize = parseAndEmitTokenizedLogMessageAndGetSize(message);
     } else {
+      size_t maxLogMessageLen =
+          (logBufferSize - bufferIndex) - kMinLogMessageV2Size;
+      size_t logMessageLen = strnlen(message->logMessage, maxLogMessageLen);
+      if (message->logMessage[logMessageLen] != '\0') {
+        LOGE("Dropping log due to invalid buffer structure");
+        break;
+      }
       parseAndEmitLogMessage(message);
-      logMessageSize = strlen(message->logMessage) + 1;
+      // Account for the terminating '\0'
+      logMessageSize = logMessageLen + 1;
     }
-    bufferIndex +=
-        sizeof(LogMessageV2) + std::min(logMessageSize, bufferIndexDelta);
+    bufferIndex += sizeof(LogMessageV2) + logMessageSize;
   }
 }
 
diff --git a/host/common/preloaded_nanoapp_loader.cc b/host/common/preloaded_nanoapp_loader.cc
new file mode 100644
index 0000000..b6fb892
--- /dev/null
+++ b/host/common/preloaded_nanoapp_loader.cc
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 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_host/preloaded_nanoapp_loader.h"
+#include <chre_host/host_protocol_host.h>
+#include <fstream>
+#include "chre_host/config_util.h"
+#include "chre_host/file_stream.h"
+#include "chre_host/fragmented_load_transaction.h"
+#include "chre_host/log.h"
+#include "hal_client_id.h"
+
+namespace android::chre {
+
+using android::chre::readFileContents;
+using android::hardware::contexthub::common::implementation::kHalId;
+
+void PreloadedNanoappLoader::getPreloadedNanoappIds(
+    std::vector<uint64_t> &out_preloadedNanoappIds) {
+  std::vector<std::string> nanoappNames;
+  std::string directory;
+  out_preloadedNanoappIds.clear();
+  bool success =
+      getPreloadedNanoappsFromConfigFile(mConfigPath, directory, nanoappNames);
+  if (!success) {
+    LOGE("Failed to parse preloaded nanoapps config file");
+  }
+  for (const std::string &nanoappName : nanoappNames) {
+    std::string headerFile = directory + "/" + nanoappName + ".napp_header";
+    std::vector<uint8_t> headerBuffer;
+    if (!readFileContents(headerFile.c_str(), headerBuffer)) {
+      LOGE("Cannot read header file: %s", headerFile.c_str());
+      continue;
+    }
+    if (headerBuffer.size() != sizeof(NanoAppBinaryHeader)) {
+      LOGE("Header size mismatch");
+      continue;
+    }
+    const auto *appHeader =
+        reinterpret_cast<const NanoAppBinaryHeader *>(headerBuffer.data());
+    out_preloadedNanoappIds.emplace_back(appHeader->appId);
+  }
+}
+
+void PreloadedNanoappLoader::loadPreloadedNanoapps() {
+  std::string directory;
+  std::vector<std::string> nanoapps;
+
+  bool success =
+      getPreloadedNanoappsFromConfigFile(mConfigPath, directory, nanoapps);
+  if (!success) {
+    LOGE("Failed to load any preloaded nanoapp");
+  } else {
+    mIsPreloadingOngoing = true;
+    for (uint32_t i = 0; i < nanoapps.size(); ++i) {
+      loadPreloadedNanoapp(directory, nanoapps[i], i);
+    }
+    mIsPreloadingOngoing = false;
+  }
+}
+
+void PreloadedNanoappLoader::loadPreloadedNanoapp(const std::string &directory,
+                                                  const std::string &name,
+                                                  uint32_t transactionId) {
+  std::vector<uint8_t> headerBuffer;
+  std::vector<uint8_t> nanoappBuffer;
+
+  std::string headerFilename = directory + "/" + name + ".napp_header";
+  std::string nanoappFilename = directory + "/" + name + ".so";
+
+  if (!readFileContents(headerFilename.c_str(), headerBuffer) ||
+      !readFileContents(nanoappFilename.c_str(), nanoappBuffer) ||
+      !loadNanoapp(headerBuffer, nanoappBuffer, transactionId)) {
+    LOGE("Failed to load nanoapp: '%s'", name.c_str());
+  }
+}
+
+bool PreloadedNanoappLoader::loadNanoapp(const std::vector<uint8_t> &header,
+                                         const std::vector<uint8_t> &nanoapp,
+                                         uint32_t transactionId) {
+  if (header.size() != sizeof(NanoAppBinaryHeader)) {
+    LOGE("Nanoapp binary's header size is incorrect");
+    return false;
+  }
+  const auto *appHeader =
+      reinterpret_cast<const NanoAppBinaryHeader *>(header.data());
+
+  // Build the target API version from major and minor.
+  uint32_t targetApiVersion = (appHeader->targetChreApiMajorVersion << 24) |
+                              (appHeader->targetChreApiMinorVersion << 16);
+  return sendFragmentedLoadAndWaitForEachResponse(
+      appHeader->appId, appHeader->appVersion, appHeader->flags,
+      targetApiVersion, nanoapp.data(), nanoapp.size(), transactionId);
+}
+
+bool PreloadedNanoappLoader::sendFragmentedLoadAndWaitForEachResponse(
+    uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+    uint32_t appTargetApiVersion, const uint8_t *appBinary, size_t appSize,
+    uint32_t transactionId) {
+  std::vector<uint8_t> binary(appSize);
+  std::copy(appBinary, appBinary + appSize, binary.begin());
+
+  FragmentedLoadTransaction transaction(transactionId, appId, appVersion,
+                                        appFlags, appTargetApiVersion, binary);
+  while (!transaction.isComplete()) {
+    auto nextRequest = transaction.getNextRequest();
+    auto future = sendFragmentedLoadRequest(nextRequest);
+    if (!waitAndVerifyFuture(future, nextRequest)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool PreloadedNanoappLoader::waitAndVerifyFuture(
+    std::future<bool> &future, const FragmentedLoadRequest &request) {
+  if (!future.valid()) {
+    LOGE("Failed to send out the fragmented load fragment");
+    return false;
+  }
+  if (future.wait_for(kTimeoutInMs) != std::future_status::ready) {
+    LOGE(
+        "Waiting for response of fragment %zu transaction %d times out "
+        "after %lld ms",
+        request.fragmentId, request.transactionId, kTimeoutInMs.count());
+    return false;
+  }
+  if (!future.get()) {
+    LOGE(
+        "Received a failure result for loading fragment %zu of "
+        "transaction %d",
+        request.fragmentId, request.transactionId);
+    return false;
+  }
+  return true;
+}
+
+bool PreloadedNanoappLoader::verifyFragmentLoadResponse(
+    const ::chre::fbs::LoadNanoappResponseT &response) const {
+  if (!response.success) {
+    LOGE("Loading nanoapp binary fragment %d of transaction %u failed.",
+         response.fragment_id, response.transaction_id);
+    // TODO(b/247124878): Report metrics.
+    return false;
+  }
+  if (mPreloadedNanoappPendingTransaction.transactionId !=
+      response.transaction_id) {
+    LOGE(
+        "Fragmented load response with transactionId %u but transactionId "
+        "%u is expected",
+        response.transaction_id,
+        mPreloadedNanoappPendingTransaction.transactionId);
+    return false;
+  }
+  if (mPreloadedNanoappPendingTransaction.fragmentId != response.fragment_id) {
+    LOGE(
+        "Fragmented load response with unexpected fragment id %u while "
+        "%zu is expected",
+        response.fragment_id, mPreloadedNanoappPendingTransaction.fragmentId);
+    return false;
+  }
+  return true;
+}
+
+bool PreloadedNanoappLoader::onLoadNanoappResponse(
+    const ::chre::fbs::LoadNanoappResponseT &response, HalClientId clientId) {
+  std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
+  if (clientId != kHalId || !mFragmentedLoadPromise.has_value()) {
+    LOGE(
+        "Received an unexpected preload nanoapp %s response for client %d "
+        "transaction %u fragment %u",
+        response.success ? "success" : "failure", clientId,
+        response.transaction_id, response.fragment_id);
+    return false;
+  }
+  // set value for the future instance
+  mFragmentedLoadPromise->set_value(verifyFragmentLoadResponse(response));
+  // reset the promise as the value can only be retrieved once from it
+  mFragmentedLoadPromise = std::nullopt;
+  return true;
+}
+
+std::future<bool> PreloadedNanoappLoader::sendFragmentedLoadRequest(
+    ::android::chre::FragmentedLoadRequest &request) {
+  flatbuffers::FlatBufferBuilder builder(request.binary.size() + 128);
+  // TODO(b/247124878): Confirm if respondBeforeStart can be set to true on all
+  //  the devices.
+  HostProtocolHost::encodeFragmentedLoadNanoappRequest(
+      builder, request, /* respondBeforeStart= */ true);
+  HostProtocolHost::mutateHostClientId(builder.GetBufferPointer(),
+                                       builder.GetSize(), kHalId);
+  std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
+  if (!mConnection->sendMessage(builder.GetBufferPointer(),
+                                builder.GetSize())) {
+    // Returns an invalid future to indicate the failure
+    return std::future<bool>{};
+  }
+  mPreloadedNanoappPendingTransaction = {
+      .transactionId = request.transactionId,
+      .fragmentId = request.fragmentId,
+  };
+  mFragmentedLoadPromise = std::make_optional<std::promise<bool>>();
+  return mFragmentedLoadPromise->get_future();
+}
+}  // namespace android::chre
\ No newline at end of file
diff --git a/host/common/socket_client.cc b/host/common/socket_client.cc
index ba13adc..a8c18d9 100644
--- a/host/common/socket_client.cc
+++ b/host/common/socket_client.cc
@@ -24,6 +24,7 @@
 #include <chrono>
 
 #include <cutils/sockets.h>
+#include <sys/epoll.h>
 #include <sys/socket.h>
 #include <utils/RefBase.h>
 #include <utils/StrongPointer.h>
@@ -142,7 +143,37 @@
 
   LOGV("Receive thread started");
   while (!mGracefulShutdown && (mSockFd != INVALID_SOCKET || reconnect())) {
+    struct epoll_event requestedEvent;
+    requestedEvent.data.fd = mSockFd;
+    requestedEvent.events = EPOLLIN | EPOLLWAKEUP;
+
+    int epollFd = TEMP_FAILURE_RETRY(epoll_create1(0));
+    if (epollFd < 0) {
+      LOG_ERROR("Error creating epoll fd", errno);
+      break;
+    }
+
+    if (TEMP_FAILURE_RETRY(epoll_ctl(epollFd, EPOLL_CTL_ADD,
+                                     requestedEvent.data.fd, &requestedEvent)) <
+        0) {
+      LOG_ERROR("Error adding socket fd to epoll", errno);
+      close(epollFd);
+      break;
+    }
+
     while (!mGracefulShutdown) {
+      struct epoll_event returnedEvent;
+      // Blockingly wait for the next epoll event. The implicit wakelock will be
+      // held until the next call to epoll_wait on the same epoll file
+      // descriptor
+      int eventsReady = TEMP_FAILURE_RETRY(epoll_wait(epollFd, &returnedEvent,
+                                                      /* event_count= */ 1,
+                                                      /* timeout_ms= */ -1));
+      if (eventsReady < 0) {
+        LOG_ERROR("Poll error", errno);
+        break;
+      }
+
       ssize_t bytesReceived = recv(mSockFd, buffer, sizeof(buffer), 0);
       if (bytesReceived < 0) {
         LOG_ERROR("Exiting RX thread", errno);
@@ -162,6 +193,7 @@
       LOG_ERROR("Couldn't close socket", errno);
     }
     mSockFd = INVALID_SOCKET;
+    close(epollFd);
   }
 
   if (!mGracefulShutdown) {
diff --git a/host/common/socket_server.cc b/host/common/socket_server.cc
index 2079e66..e5403a2 100644
--- a/host/common/socket_server.cc
+++ b/host/common/socket_server.cc
@@ -34,28 +34,6 @@
 
 std::atomic<bool> SocketServer::sSignalReceived(false);
 
-namespace {
-
-void maskAllSignals() {
-  sigset_t signalMask;
-  sigfillset(&signalMask);
-  if (sigprocmask(SIG_SETMASK, &signalMask, NULL) != 0) {
-    LOG_ERROR("Couldn't mask all signals", errno);
-  }
-}
-
-void maskAllSignalsExceptIntAndTerm() {
-  sigset_t signalMask;
-  sigfillset(&signalMask);
-  sigdelset(&signalMask, SIGINT);
-  sigdelset(&signalMask, SIGTERM);
-  if (sigprocmask(SIG_SETMASK, &signalMask, NULL) != 0) {
-    LOG_ERROR("Couldn't mask all signals except INT/TERM", errno);
-  }
-}
-
-}  // anonymous namespace
-
 SocketServer::SocketServer() {
   // Initialize the socket fds field for all inactive client slots to -1, so
   // poll skips over it, and we don't attempt to send on it
@@ -259,18 +237,16 @@
   sigdelset(&signalMask, SIGINT);
   sigdelset(&signalMask, SIGTERM);
 
-  // Masking signals here ensure that after this point, we won't handle INT/TERM
-  // until after we call into ppoll()
-  maskAllSignals();
-  std::signal(SIGINT, signalHandler);
-  std::signal(SIGTERM, signalHandler);
-
   LOGI("Ready to accept connections");
   while (!sSignalReceived) {
-    int ret = TEMP_FAILURE_RETRY(
-        ppoll(mPollFds, 1 + kMaxActiveClients, nullptr, &signalMask));
-    maskAllSignalsExceptIntAndTerm();
+    int ret = ppoll(mPollFds, 1 + kMaxActiveClients, nullptr, &signalMask);
     if (ret == -1) {
+      // Don't use TEMP_FAILURE_RETRY since our logic needs to check
+      // sSignalReceived to see if it should exit where as TEMP_FAILURE_RETRY
+      // is a tight retry loop around ppoll.
+      if (errno == EINTR) {
+        continue;
+      }
       LOGI("Exiting poll loop: %s", strerror(errno));
       break;
     }
@@ -288,17 +264,8 @@
         handleClientData(mPollFds[i].fd);
       }
     }
-
-    // Mask all signals to ensure that sSignalReceived can't become true between
-    // checking it in the while condition and calling into ppoll()
-    maskAllSignals();
   }
 }
 
-void SocketServer::signalHandler(int signal) {
-  LOGD("Caught signal %d", signal);
-  sSignalReceived = true;
-}
-
 }  // namespace chre
 }  // namespace android
diff --git a/host/common/st_hal_lpma_handler.cc b/host/common/st_hal_lpma_handler.cc
index 29e0e51..b83ccbe 100644
--- a/host/common/st_hal_lpma_handler.cc
+++ b/host/common/st_hal_lpma_handler.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <cassert>
 #include <cinttypes>
 
 #include "chre_host/st_hal_lpma_handler.h"
@@ -23,7 +24,7 @@
 
 namespace {
 
-constexpr char kChreWakeLockName[] = "chre_daemon";
+constexpr char kChreWakeLockName[] = "chre_lpma_handler";
 
 void acquireWakeLock() {
   int rc;
@@ -51,13 +52,29 @@
 }  // anonymous namespace
 
 StHalLpmaHandler::StHalLpmaHandler(bool allowed) : mIsLpmaAllowed(allowed) {
+#ifdef CHRE_ST_LPMA_HANDLER_AIDL
+  // TODO(b/278167963): Add death recipient
+#else
   auto cb = [&]() { onStHalServiceDeath(); };
   mDeathRecipient = new StHalDeathRecipient(cb);
+#endif  // CHRE_ST_LPMA_HANDLER_AIDL
+}
+
+StHalLpmaHandler::~StHalLpmaHandler() {
+  if (mTargetLpmaEnabled) {
+    stopAndUnload();
+  }
+  if (mThread.has_value()) {
+    mStThreadShouldExit = true;
+    mCondVar.notify_all();
+    mThread->join();
+  }
+  releaseWakeLock();
 }
 
 void StHalLpmaHandler::init() {
   if (mIsLpmaAllowed) {
-    mThread = std::thread(&StHalLpmaHandler::StHalLpmaHandlerThreadEntry, this);
+    mThread = std::thread(&StHalLpmaHandler::stHalLpmaHandlerThreadEntry, this);
   }
 }
 
@@ -72,6 +89,157 @@
   }
 }
 
+bool StHalLpmaHandler::loadAndStart() {
+  if (load()) {
+    if (start()) {
+      return true;
+    } else {
+      unload();
+    }
+  }
+  return false;
+}
+
+void StHalLpmaHandler::stopAndUnload() {
+  stop();
+  unload();
+}
+
+void StHalLpmaHandler::stHalRequestAndProcessLocked(
+    std::unique_lock<std::mutex> const &locked) {
+  // Cannot use assert(locked.owns_lock()) since locked are not use in other
+  // places and non-debug version will not use assert. Compiler will not compile
+  // if there is unused parameter.
+  if (!locked.owns_lock()) {
+    assert(false);
+  }
+  if (mCurrentLpmaEnabled == mTargetLpmaEnabled) {
+    return;
+  } else if (mTargetLpmaEnabled && loadAndStart()) {
+    mCurrentLpmaEnabled = mTargetLpmaEnabled;
+  } else if (!mTargetLpmaEnabled) {
+    // Regardless of whether the use case fails to unload, set the
+    // currentLpmaEnabled to the targetLpmaEnabled. This will allow the next
+    // enable request to proceed. After a failure to unload occurs, the
+    // supplied handle is invalid and should not be unloaded again.
+    stopAndUnload();
+    mCurrentLpmaEnabled = mTargetLpmaEnabled;
+  }
+}
+
+void StHalLpmaHandler::stHalLpmaHandlerThreadEntry() {
+  LOGD("Starting LPMA thread");
+  constexpr useconds_t kInitialRetryDelayUs = 500000;
+  constexpr int kRetryGrowthFactor = 2;
+  constexpr int kRetryGrowthLimit = 5;     // Terminates at 8s retry interval.
+  constexpr int kRetryWakeLockLimit = 10;  // Retry with a wakelock 10 times.
+  std::unique_lock<std::mutex> lock(mMutex);
+  while (!mStThreadShouldExit) {
+    stHalRequestAndProcessLocked(lock);
+    bool retryNeeded = (mCurrentLpmaEnabled != mTargetLpmaEnabled);
+    releaseWakeLock();
+
+    if (retryNeeded) {
+      if (mRetryCount < kRetryGrowthLimit) {
+        mRetryCount += 1;
+        mRetryDelay = mRetryDelay * kRetryGrowthFactor;
+      }
+      mCondVar.wait_for(lock, std::chrono::microseconds(mRetryDelay), [this] {
+        return mCondVarPredicate || mStThreadShouldExit;
+      });
+    } else {
+      mRetryCount = 0;
+      mRetryDelay = kInitialRetryDelayUs;
+      mCondVar.wait(
+          lock, [this] { return mCondVarPredicate || mStThreadShouldExit; });
+    }
+    mCondVarPredicate = false;
+    if (mRetryCount <= kRetryWakeLockLimit) {
+      acquireWakeLock();
+    }
+  }
+}
+
+void StHalLpmaHandler::onStHalServiceDeath() {
+  LOGE("ST HAL Service Died");
+  std::lock_guard<std::mutex> lock(mMutex);
+  mStHalService = nullptr;
+  if (mTargetLpmaEnabled) {
+    // ST HAL has died, so assume that the sound model is no longer active,
+    // and trigger a reload of the sound model.
+    mCurrentLpmaEnabled = false;
+    mCondVarPredicate = true;
+    mCondVar.notify_one();
+  }
+}
+
+#ifdef CHRE_ST_LPMA_HANDLER_AIDL
+void StHalLpmaHandler::checkConnectionToStHalServiceLocked() {
+  if (mStHalService == nullptr) {
+    auto aidlServiceName =
+        std::string() + ISoundTriggerHw::descriptor + "/default";
+    ndk::SpAIBinder binder(
+        AServiceManager_waitForService(aidlServiceName.c_str()));
+    if (binder.get() != nullptr) {
+      LOGI("Connected to ST HAL service");
+      mStHalService = ISoundTriggerHw::fromBinder(binder);
+      // TODO(b/278167963): Add death recipient
+    }
+  }
+}
+
+bool StHalLpmaHandler::load() {
+  LOGV("Loading LPMA");
+
+  bool loaded = false;
+  checkConnectionToStHalServiceLocked();
+
+  aidl::android::media::soundtrigger::SoundModel soundModel;
+  soundModel.type = aidl::android::media::soundtrigger::SoundModelType::GENERIC;
+  soundModel.vendorUuid = "57caddb1-acdb-4dce-8cb0-2e95a2313aee";
+  soundModel.dataSize = 0;
+  auto status =
+      mStHalService->loadSoundModel(soundModel, nullptr, &mLpmaHandle);
+  if (status.isOk()) {
+    LOGI("Loaded LPMA");
+    loaded = true;
+  } else {
+    LOGE("Failed to load LPMA with error code %" PRId32,
+         status.getExceptionCode());
+  }
+  return loaded;
+}
+
+void StHalLpmaHandler::unload() {
+  checkConnectionToStHalServiceLocked();
+  auto status = mStHalService->unloadSoundModel(mLpmaHandle);
+  if (!status.isOk()) {
+    LOGE("Failed to unload LPMA with error code %" PRId32,
+         status.getExceptionCode());
+  }
+}
+
+bool StHalLpmaHandler::start() {
+  // TODO(b/278167963): Implement this
+  return true;
+}
+
+void StHalLpmaHandler::stop() {
+  // TODO(b/278167963): Implement this
+}
+
+#else
+
+void StHalLpmaHandler::checkConnectionToStHalServiceLocked() {
+  if (mStHalService == nullptr) {
+    mStHalService = ISoundTriggerHw::getService();
+    if (mStHalService != nullptr) {
+      LOGI("Connected to ST HAL service");
+      mStHalService->linkToDeath(mDeathRecipient, 0 /* flags */);
+    }
+  }
+}
+
 bool StHalLpmaHandler::load() {
   constexpr uint8_t kUuidNode[] = {0x2E, 0x95, 0xA2, 0x31, 0x3A, 0xEE};
 
@@ -127,84 +295,36 @@
   }
 }
 
-void StHalLpmaHandler::checkConnectionToStHalServiceLocked() {
-  if (mStHalService == nullptr) {
-    mStHalService = ISoundTriggerHw::getService();
-    if (mStHalService != nullptr) {
-      LOGI("Connected to ST HAL service");
-      mStHalService->linkToDeath(mDeathRecipient, 0 /* flags */);
-    }
+bool StHalLpmaHandler::start() {
+#ifdef CHRE_LPMA_REQUEST_START_RECOGNITION
+  // mLpmaHandle
+  ISoundTriggerHw::RecognitionConfig config = {};
+
+  Return<int32_t> hidlResult = mStHalService->startRecognition(
+      mLpmaHandle, config, nullptr /* callback */, 0 /* cookie */);
+
+  int32_t result = hidlResult.withDefault(-EPIPE);
+  if (result != 0) {
+    LOGE("Failed to start LPMA: %" PRId32, result);
   }
+  return (result == 0);
+#else
+  return true;
+#endif  // CHRE_LPMA_REQUEST_START_RECOGNITION
 }
 
-bool StHalLpmaHandler::waitOnStHalRequestAndProcess() {
-  bool noDelayNeeded = true;
-  std::unique_lock<std::mutex> lock(mMutex);
+void StHalLpmaHandler::stop() {
+#ifdef CHRE_LPMA_REQUEST_START_RECOGNITION
+  Return<int32_t> hidlResult = mStHalService->stopRecognition(mLpmaHandle);
 
-  if (mCurrentLpmaEnabled == mTargetLpmaEnabled) {
-    mRetryDelay = 0;
-    mRetryCount = 0;
-    releaseWakeLock();  // Allow the system to suspend while waiting.
-    mCondVar.wait(lock, [this] { return mCondVarPredicate; });
-    mCondVarPredicate = false;
-    acquireWakeLock();  // Ensure the system stays up while retrying.
-  } else if (mTargetLpmaEnabled && load()) {
-    mCurrentLpmaEnabled = mTargetLpmaEnabled;
-  } else if (!mTargetLpmaEnabled) {
-    // Regardless of whether the use case fails to unload, set the
-    // currentLpmaEnabled to the targetLpmaEnabled. This will allow the next
-    // enable request to proceed. After a failure to unload occurs, the
-    // supplied handle is invalid and should not be unloaded again.
-    unload();
-    mCurrentLpmaEnabled = mTargetLpmaEnabled;
-  } else {
-    noDelayNeeded = false;
+  int32_t result = hidlResult.withDefault(-EPIPE);
+  if (result != 0) {
+    LOGW("Failed to stop LPMA: %" PRId32, result);
   }
-
-  return noDelayNeeded;
+#endif  // CHRE_LPMA_REQUEST_START_RECOGNITION
 }
 
-void StHalLpmaHandler::delay() {
-  constexpr useconds_t kInitialRetryDelayUs = 500000;
-  constexpr int kRetryGrowthFactor = 2;
-  constexpr int kRetryGrowthLimit = 5;     // Terminates at 8s retry interval.
-  constexpr int kRetryWakeLockLimit = 10;  // Retry with a wakelock 10 times.
-
-  if (mRetryDelay == 0) {
-    mRetryDelay = kInitialRetryDelayUs;
-  } else if (mRetryCount < kRetryGrowthLimit) {
-    mRetryDelay *= kRetryGrowthFactor;
-  }
-  usleep(mRetryDelay);
-  mRetryCount++;
-  if (mRetryCount > kRetryWakeLockLimit) {
-    releaseWakeLock();
-  }
-}
-
-void StHalLpmaHandler::StHalLpmaHandlerThreadEntry() {
-  LOGD("Starting LPMA thread");
-
-  while (true) {
-    if (!waitOnStHalRequestAndProcess()) {
-      // processing an LPMA state update failed, retry after a little while
-      delay();
-    }
-  }
-}
-
-void StHalLpmaHandler::onStHalServiceDeath() {
-  LOGE("ST HAL Service Died");
-  std::lock_guard<std::mutex> lock(mMutex);
-  mStHalService = nullptr;
-  if (mTargetLpmaEnabled) {
-    // ST HAL has died, so assume that the sound model is no longer active,
-    // and trigger a reload of the sound model.
-    mCurrentLpmaEnabled = false;
-    mCondVarPredicate = true;
-    mCondVar.notify_one();
-  }
-}
+#endif  // CHRE_ST_LPMA_HANDLER_AIDL
 
 }  // namespace chre
 }  // namespace android
diff --git a/host/common/test/chre_test_client.cc b/host/common/test/chre_test_client.cc
index 304ea6b..57da705 100644
--- a/host/common/test/chre_test_client.cc
+++ b/host/common/test/chre_test_client.cc
@@ -16,6 +16,7 @@
 
 #include "chre/util/nanoapp/app_id.h"
 #include "chre/util/system/napp_header_utils.h"
+#include "chre_host/file_stream.h"
 #include "chre_host/host_protocol_host.h"
 #include "chre_host/log.h"
 #include "chre_host/napp_header.h"
@@ -51,6 +52,7 @@
 using android::chre::HostProtocolHost;
 using android::chre::IChreMessageHandlers;
 using android::chre::NanoAppBinaryHeader;
+using android::chre::readFileContents;
 using android::chre::SocketClient;
 using flatbuffers::FlatBufferBuilder;
 
@@ -182,27 +184,6 @@
   }
 }
 
-bool readFileContents(const char *filename, std::vector<uint8_t> *buffer) {
-  bool success = false;
-  std::ifstream file(filename, std::ios::binary | std::ios::ate);
-  if (!file) {
-    LOGE("Couldn't open file '%s': %d (%s)", filename, errno, strerror(errno));
-  } else {
-    ssize_t size = file.tellg();
-    file.seekg(0, std::ios::beg);
-
-    buffer->resize(size);
-    if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) {
-      LOGE("Couldn't read from file '%s': %d (%s)", filename, errno,
-           strerror(errno));
-    } else {
-      success = true;
-    }
-  }
-
-  return success;
-}
-
 void sendNanoappLoad(SocketClient &client, uint64_t appId, uint32_t appVersion,
                      uint32_t apiVersion, uint32_t appFlags,
                      const std::vector<uint8_t> &binary) {
@@ -227,8 +208,8 @@
                             const char *binaryPath) {
   std::vector<uint8_t> headerBuffer;
   std::vector<uint8_t> binaryBuffer;
-  if (readFileContents(headerPath, &headerBuffer) &&
-      readFileContents(binaryPath, &binaryBuffer)) {
+  if (readFileContents(headerPath, headerBuffer) &&
+      readFileContents(binaryPath, binaryBuffer)) {
     if (headerBuffer.size() != sizeof(NanoAppBinaryHeader)) {
       LOGE("Header size mismatch");
     } else {
@@ -250,7 +231,7 @@
                             uint64_t appId, uint32_t appVersion,
                             uint32_t apiVersion, bool tcmApp) {
   std::vector<uint8_t> buffer;
-  if (readFileContents(filename, &buffer)) {
+  if (readFileContents(filename, buffer)) {
     // All loaded nanoapps must be signed currently.
     uint32_t appFlags = CHRE_NAPP_HEADER_SIGNED;
     if (tcmApp) {
diff --git a/host/common/test/power_test/chre_power_test_client.cc b/host/common/test/power_test/chre_power_test_client.cc
index 7e9cba2..344944d 100644
--- a/host/common/test/power_test/chre_power_test_client.cc
+++ b/host/common/test/power_test/chre_power_test_client.cc
@@ -35,6 +35,7 @@
 #include "chre/util/nanoapp/app_id.h"
 #include "chre/util/system/napp_header_utils.h"
 #include "chre/version.h"
+#include "chre_host/file_stream.h"
 #include "chre_host/host_protocol_host.h"
 #include "chre_host/log.h"
 #include "chre_host/napp_header.h"
@@ -127,6 +128,7 @@
 using android::chre::HostProtocolHost;
 using android::chre::IChreMessageHandlers;
 using android::chre::NanoAppBinaryHeader;
+using android::chre::readFileContents;
 using android::chre::SocketClient;
 using chre::power_test::MessageType;
 using chre::power_test::SensorType;
@@ -357,29 +359,6 @@
   return true;
 }
 
-bool readFileContents(const std::string filename,
-                      std::vector<uint8_t> *buffer) {
-  bool success = false;
-  std::ifstream file(filename.c_str(), std::ios::binary | std::ios::ate);
-  if (!file) {
-    LOGE("Couldn't open file '%s': %d (%s)", filename.c_str(), errno,
-         strerror(errno));
-  } else {
-    ssize_t size = file.tellg();
-    file.seekg(0, std::ios::beg);
-
-    buffer->resize(size);
-    if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) {
-      LOGE("Couldn't read from file '%s': %d (%s)", filename.c_str(), errno,
-           strerror(errno));
-    } else {
-      success = true;
-    }
-  }
-
-  return success;
-}
-
 bool sendNanoappLoad(SocketClient &client, uint64_t appId, uint32_t appVersion,
                      uint32_t apiVersion, uint32_t appFlags,
                      const std::vector<uint8_t> &binary) {
@@ -403,8 +382,10 @@
   bool success = false;
   std::vector<uint8_t> headerBuffer;
   std::vector<uint8_t> binaryBuffer;
-  if (readFileContents(filenameNoExtension + ".napp_header", &headerBuffer) &&
-      readFileContents(filenameNoExtension + ".so", &binaryBuffer)) {
+  std::string headerName = filenameNoExtension + ".napp_header";
+  std::string binaryName = filenameNoExtension + ".so";
+  if (readFileContents(headerName.c_str(), headerBuffer) &&
+      readFileContents(binaryName.c_str(), binaryBuffer)) {
     if (headerBuffer.size() != sizeof(NanoAppBinaryHeader)) {
       LOGE("Header size mismatch");
     } else {
diff --git a/host/common/time_syncer.cc b/host/common/time_syncer.cc
new file mode 100644
index 0000000..e6d2792
--- /dev/null
+++ b/host/common/time_syncer.cc
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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_host/time_syncer.h"
+#include <chre_host/host_protocol_host.h>
+#include "chre_host/log.h"
+
+namespace android::chre {
+// TODO(b/247124878): Can we add a static assert to make sure these functions
+//  are not called when mConnection->isTimeSyncNeeded() returns false?
+bool TimeSyncer::sendTimeSync() {
+  if (!mConnection->isTimeSyncNeeded()) {
+    LOGW("Platform doesn't require time sync. Ignore the request.");
+    return true;
+  }
+  int64_t timeOffsetUs = 0;
+  if (!mConnection->getTimeOffset(&timeOffsetUs)) {
+    LOGE("Failed to get time offset.");
+    return false;
+  }
+  flatbuffers::FlatBufferBuilder builder(64);
+  // clientId doesn't matter for time sync request so the default id is used.
+  HostProtocolHost::encodeTimeSyncMessage(builder, timeOffsetUs);
+  return mConnection->sendMessage(builder.GetBufferPointer(),
+                                  builder.GetSize());
+}
+
+bool TimeSyncer::sendTimeSyncWithRetry(size_t numOfRetries,
+                                       useconds_t retryDelayUs) {
+  if (!mConnection->isTimeSyncNeeded()) {
+    LOGW("Platform doesn't require time sync. Ignore the request.");
+    return true;
+  }
+  bool success = false;
+  while (!success && (numOfRetries-- > 0)) {
+    success = sendTimeSync();
+    if (!success) {
+      usleep(retryDelayUs);
+    }
+  }
+  return success;
+}
+}  // namespace android::chre
\ No newline at end of file
diff --git a/host/common/wifi_ext_hal_handler.cc b/host/common/wifi_ext_hal_handler.cc
index b963062..d1138ef 100644
--- a/host/common/wifi_ext_hal_handler.cc
+++ b/host/common/wifi_ext_hal_handler.cc
@@ -28,9 +28,9 @@
     const std::function<void(bool)> &statusChangeCallback) {
   mEnableConfig.reset();
   mThread = std::thread(&WifiExtHalHandler::wifiExtHandlerThreadEntry, this);
-  auto cb = [&]() { onWifiExtHalServiceDeath(); };
-  mDeathRecipient = new WifiExtHalDeathRecipient(cb);
-  mCallback = new WifiExtCallback(statusChangeCallback);
+  mDeathRecipient =
+      AIBinder_DeathRecipient_new(WifiExtHalHandler::onWifiExtHalServiceDeath);
+  mCallback = ndk::SharedRefBase::make<WifiExtCallback>(statusChangeCallback);
 }
 
 void WifiExtHalHandler::handleConfigurationRequest(bool enable) {
@@ -40,44 +40,31 @@
 }
 
 void WifiExtHalHandler::dispatchConfigurationRequest(bool enable) {
-  auto hidlCb = [this, enable](const WifiStatus &status) {
-    bool success = (status.code == WifiStatusCode::SUCCESS) ? true : false;
-    if (!success) {
-      LOGE("wifi ext hal config request for %s failed with code: %d (%s)",
-           (enable == true) ? "Enable" : "Disable", status.code,
-           status.description.c_str());
-    }
-    mCallback->onStatusChanged(success);
-  };
-
   if (checkWifiExtHalConnected()) {
-    auto result = mService->requestWifiChreNanRtt(enable, hidlCb);
+    auto result = mService->requestWifiChreNanRtt(enable);
     if (!result.isOk()) {
-      LOGE("Failed to %s NAN: %s", (enable == true) ? "Enable" : "Disable",
-           result.description().c_str());
+      LOGE("wifi ext hal config request for %s failed with code: %d",
+           (enable == true) ? "Enable" : "Disable",
+           result.getServiceSpecificError());
     }
+    mCallback->onStatusChanged(result.isOk());
   }
 }
 
 bool WifiExtHalHandler::checkWifiExtHalConnected() {
   bool success = false;
   if (mService == nullptr) {
-    mService = IWifiExt::getService();
+    auto serviceName = std::string() + IWifiExt::descriptor + "/default";
+    mService = IWifiExt::fromBinder(
+        ndk::SpAIBinder(AServiceManager_waitForService(serviceName.c_str())));
     if (mService != nullptr) {
       LOGD("Connected to Wifi Ext HAL service");
-      mService->linkToDeath(mDeathRecipient, 0 /*cookie*/);
-
-      auto hidlCb = [&success](const WifiStatus &status) {
-        success = (status.code == WifiStatusCode::SUCCESS);
-        if (!success) {
-          LOGE("Failed to register CHRE callback with WifiExt: %s",
-               status.description.c_str());
-        }
-      };
-      auto result = mService->registerChreCallback(mCallback, hidlCb);
+      AIBinder_linkToDeath(mService->asBinder().get(), mDeathRecipient,
+                           reinterpret_cast<void *>(&mService) /* cookie */);
+      auto result = mService->registerChreCallback(mCallback);
       if (!result.isOk()) {
-        LOGE("Failed to register CHRE callback with WifiEmDeathRecipientxt: %s",
-             result.description().c_str());
+        LOGE("Failed to register CHRE callback with WifiExt, code: %d",
+             result.getServiceSpecificError());
       } else {
         success = true;
       }
@@ -88,9 +75,10 @@
   return success;
 }
 
-void WifiExtHalHandler::onWifiExtHalServiceDeath() {
+void WifiExtHalHandler::onWifiExtHalServiceDeath(void *cookie) {
   LOGI("WiFi Ext HAL service died");
-  mService = nullptr;
+  // Cookie is expected to be a pointer to mService.
+  *((std::shared_ptr<IWifiExt> *)cookie) = nullptr;
   // TODO(b/204226580): Figure out if wifi ext HAL is stateful and if it
   // isn't, notify CHRE of a NAN disabled status change to enable nanoapps
   // to not expect NAN data until the service is back up, and expect it to
diff --git a/host/exynos/chre_daemon_exynos.rc b/host/exynos/chre_daemon_exynos.rc
new file mode 100644
index 0000000..daa741f
--- /dev/null
+++ b/host/exynos/chre_daemon_exynos.rc
@@ -0,0 +1,23 @@
+#
+# Copyright (C) 2022 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.
+#
+
+service vendor.chre /vendor/bin/chre_daemon_exynos
+    class late_start
+    user context_hub
+    group wakelock context_hub
+    capabilities BLOCK_SUSPEND
+    socket chre seqpacket 0660 root context_hub
+    shutdown critical
diff --git a/host/exynos/exynos_daemon.cc b/host/exynos/exynos_daemon.cc
new file mode 100644
index 0000000..b2e7481
--- /dev/null
+++ b/host/exynos/exynos_daemon.cc
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2022 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 "exynos_daemon.h"
+#include <sys/epoll.h>
+#include <utils/SystemClock.h>
+#include <array>
+#include <csignal>
+#include "chre_host/file_stream.h"
+
+// Aliased for consistency with the way these symbols are referenced in
+// CHRE-side code
+namespace fbs = ::chre::fbs;
+
+namespace android {
+namespace chre {
+
+namespace {
+
+int createEpollFd(int fdToEpoll) {
+  struct epoll_event event;
+  event.data.fd = fdToEpoll;
+  event.events = EPOLLIN | EPOLLWAKEUP;
+  int epollFd = epoll_create1(EPOLL_CLOEXEC);
+  if (epoll_ctl(epollFd, EPOLL_CTL_ADD, event.data.fd, &event) != 0) {
+    LOGE("Failed to add control interface to msg read fd errno: %s",
+         strerror(errno));
+    epollFd = -1;
+  }
+  return epollFd;
+}
+
+}  // anonymous namespace
+
+ExynosDaemon::ExynosDaemon() : mLpmaHandler(true /* LPMA enabled */) {
+  // TODO(b/235631242): Implement this.
+}
+
+bool ExynosDaemon::init() {
+  constexpr size_t kMaxTimeSyncRetries = 5;
+  constexpr useconds_t kTimeSyncRetryDelayUs = 50000;  // 50 ms
+  bool success = false;
+  mNativeThreadHandle = 0;
+  siginterrupt(SIGINT, true);
+  std::signal(SIGINT, signalHandler);
+  if ((mCommsReadFd = open(kCommsDeviceFilename, O_RDONLY | O_CLOEXEC)) < 0) {
+    LOGE("Read FD open failed: %s", strerror(errno));
+  } else if ((mCommsWriteFd =
+                  open(kCommsDeviceFilename, O_WRONLY | O_CLOEXEC)) < 0) {
+    LOGE("Write FD open failed: %s", strerror(errno));
+  } else {
+    mProcessThreadRunning = true;
+    mIncomingMsgProcessThread =
+        std::thread([&] { this->processIncomingMsgs(); });
+    mNativeThreadHandle = mIncomingMsgProcessThread.native_handle();
+
+    if (!sendTimeSyncWithRetry(kMaxTimeSyncRetries, kTimeSyncRetryDelayUs,
+                               true /* logOnError */)) {
+      LOGE("Failed to send initial time sync message");
+    } else {
+      loadPreloadedNanoapps();
+      success = true;
+      LOGD("CHRE daemon initialized successfully");
+    }
+  }
+  return success;
+}
+
+void ExynosDaemon::deinit() {
+  stopMsgProcessingThread();
+
+  close(mCommsWriteFd);
+  mCommsWriteFd = kInvalidFd;
+
+  close(mCommsReadFd);
+  mCommsReadFd = kInvalidFd;
+}
+
+void ExynosDaemon::run() {
+  constexpr char kChreSocketName[] = "chre";
+  auto serverCb = [&](uint16_t clientId, void *data, size_t len) {
+    sendMessageToChre(clientId, data, len);
+  };
+
+  mServer.run(kChreSocketName, true /* allowSocketCreation */, serverCb);
+}
+
+void ExynosDaemon::stopMsgProcessingThread() {
+  if (mProcessThreadRunning) {
+    mProcessThreadRunning = false;
+    pthread_kill(mNativeThreadHandle, SIGINT);
+    if (mIncomingMsgProcessThread.joinable()) {
+      mIncomingMsgProcessThread.join();
+    }
+  }
+}
+
+void ExynosDaemon::processIncomingMsgs() {
+  std::array<uint8_t, kIpcMsgSizeMax> message;
+  int epollFd = createEpollFd(mCommsReadFd);
+
+  while (mProcessThreadRunning) {
+    struct epoll_event retEvent;
+    int nEvents = epoll_wait(epollFd, &retEvent, 1 /* maxEvents */,
+                             -1 /* infinite timeout */);
+    if (nEvents < 0) {
+      // epoll_wait will get interrupted if the CHRE daemon is shutting down,
+      // check this condition before logging an error.
+      if (mProcessThreadRunning) {
+        LOGE("Epolling failed: %s", strerror(errno));
+      }
+    } else if (nEvents == 0) {
+      LOGW("Epoll returned with 0 FDs ready despite no timeout (errno: %s)",
+           strerror(errno));
+    } else {
+      int bytesRead = read(mCommsReadFd, message.data(), message.size());
+      if (bytesRead < 0) {
+        LOGE("Failed to read from fd: %s", strerror(errno));
+      } else if (bytesRead == 0) {
+        LOGE("Read 0 bytes from fd");
+      } else {
+        onMessageReceived(message.data(), bytesRead);
+      }
+    }
+  }
+}
+
+bool ExynosDaemon::doSendMessage(void *data, size_t length) {
+  bool success = false;
+  if (length > kIpcMsgSizeMax) {
+    LOGE("Msg size %zu larger than max msg size %zu", length, kIpcMsgSizeMax);
+  } else {
+    ssize_t rv = write(mCommsWriteFd, data, length);
+
+    if (rv < 0) {
+      LOGE("Failed to send message: %s", strerror(errno));
+    } else if (rv != length) {
+      LOGW("Msg send data loss: %zd of %zu bytes were written", rv, length);
+    } else {
+      success = true;
+    }
+  }
+  return success;
+}
+
+int64_t ExynosDaemon::getTimeOffset(bool *success) {
+  // TODO(b/235631242): Implement this.
+  *success = false;
+  return 0;
+}
+
+void ExynosDaemon::loadPreloadedNanoapp(const std::string &directory,
+                                        const std::string &name,
+                                        uint32_t transactionId) {
+  std::vector<uint8_t> headerBuffer;
+  std::vector<uint8_t> nanoappBuffer;
+
+  std::string headerFilename = directory + "/" + name + ".napp_header";
+  std::string nanoappFilename = directory + "/" + name + ".so";
+
+  if (readFileContents(headerFilename.c_str(), headerBuffer) &&
+      readFileContents(nanoappFilename.c_str(), nanoappBuffer) &&
+      !loadNanoapp(headerBuffer, nanoappBuffer, transactionId)) {
+    LOGE("Failed to load nanoapp: '%s'", name.c_str());
+  }
+}
+
+bool ExynosDaemon::loadNanoapp(const std::vector<uint8_t> &header,
+                               const std::vector<uint8_t> &nanoapp,
+                               uint32_t transactionId) {
+  // This struct comes from build/build_template.mk and must not be modified.
+  // Refer to that file for more details.
+  struct NanoAppBinaryHeader {
+    uint32_t headerVersion;
+    uint32_t magic;
+    uint64_t appId;
+    uint32_t appVersion;
+    uint32_t flags;
+    uint64_t hwHubType;
+    uint8_t targetChreApiMajorVersion;
+    uint8_t targetChreApiMinorVersion;
+    uint8_t reserved[6];
+  } __attribute__((packed));
+
+  bool success = false;
+  if (header.size() != sizeof(NanoAppBinaryHeader)) {
+    LOGE("Header size mismatch");
+  } else {
+    // The header blob contains the struct above.
+    const auto *appHeader =
+        reinterpret_cast<const NanoAppBinaryHeader *>(header.data());
+
+    // Build the target API version from major and minor.
+    uint32_t targetApiVersion = (appHeader->targetChreApiMajorVersion << 24) |
+                                (appHeader->targetChreApiMinorVersion << 16);
+
+    success = sendFragmentedNanoappLoad(
+        appHeader->appId, appHeader->appVersion, appHeader->flags,
+        targetApiVersion, nanoapp.data(), nanoapp.size(), transactionId);
+  }
+
+  return success;
+}
+
+bool ExynosDaemon::sendFragmentedNanoappLoad(
+    uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+    uint32_t appTargetApiVersion, const uint8_t *appBinary, size_t appSize,
+    uint32_t transactionId) {
+  std::vector<uint8_t> binary(appSize);
+  std::copy(appBinary, appBinary + appSize, binary.begin());
+
+  FragmentedLoadTransaction transaction(transactionId, appId, appVersion,
+                                        appFlags, appTargetApiVersion, binary);
+
+  bool success = true;
+
+  while (success && !transaction.isComplete()) {
+    // Pad the builder to avoid allocation churn.
+    const auto &fragment = transaction.getNextRequest();
+    flatbuffers::FlatBufferBuilder builder(fragment.binary.size() + 128);
+    HostProtocolHost::encodeFragmentedLoadNanoappRequest(
+        builder, fragment, true /* respondBeforeStart */);
+    success = sendFragmentAndWaitOnResponse(transactionId, builder,
+                                            fragment.fragmentId, appId);
+  }
+
+  return success;
+}
+
+bool ExynosDaemon::sendFragmentAndWaitOnResponse(
+    uint32_t transactionId, flatbuffers::FlatBufferBuilder &builder,
+    uint32_t fragmentId, uint64_t appId) {
+  bool success = true;
+  std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
+
+  mPreloadedNanoappPendingTransaction = {
+      .transactionId = transactionId,
+      .fragmentId = fragmentId,
+      .nanoappId = appId,
+  };
+  mPreloadedNanoappPending = sendMessageToChre(
+      kHostClientIdDaemon, builder.GetBufferPointer(), builder.GetSize());
+  if (!mPreloadedNanoappPending) {
+    LOGE("Failed to send nanoapp fragment");
+    success = false;
+  } else {
+    std::chrono::seconds timeout(2);
+    bool signaled = mPreloadedNanoappsCond.wait_for(
+        lock, timeout, [this] { return !mPreloadedNanoappPending; });
+
+    if (!signaled) {
+      LOGE("Nanoapp fragment load timed out");
+      success = false;
+    }
+  }
+  return success;
+}
+
+void ExynosDaemon::handleDaemonMessage(const uint8_t *message) {
+  std::unique_ptr<fbs::MessageContainerT> container =
+      fbs::UnPackMessageContainer(message);
+  if (container->message.type != fbs::ChreMessage::LoadNanoappResponse) {
+    LOGE("Invalid message from CHRE directed to daemon");
+  } else {
+    const auto *response = container->message.AsLoadNanoappResponse();
+    std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
+
+    if (!mPreloadedNanoappPending) {
+      LOGE("Received nanoapp load response with no pending load");
+    } else if (mPreloadedNanoappPendingTransaction.transactionId !=
+               response->transaction_id) {
+      LOGE("Received nanoapp load response with invalid transaction id");
+    } else if (mPreloadedNanoappPendingTransaction.fragmentId !=
+               response->fragment_id) {
+      LOGE("Received nanoapp load response with invalid fragment id");
+    } else if (!response->success) {
+#ifdef CHRE_DAEMON_METRIC_ENABLED
+      std::vector<VendorAtomValue> values(3);
+      values[0].set<VendorAtomValue::longValue>(
+          mPreloadedNanoappPendingTransaction.nanoappId);
+      values[1].set<VendorAtomValue::intValue>(
+          Atoms::ChreHalNanoappLoadFailed::TYPE_PRELOADED);
+      values[2].set<VendorAtomValue::intValue>(
+          Atoms::ChreHalNanoappLoadFailed::REASON_ERROR_GENERIC);
+      const VendorAtom atom{
+          .atomId = Atoms::CHRE_HAL_NANOAPP_LOAD_FAILED,
+          .values{std::move(values)},
+      };
+      ChreDaemonBase::reportMetric(atom);
+#endif  // CHRE_DAEMON_METRIC_ENABLED
+
+    } else {
+      mPreloadedNanoappPending = false;
+    }
+
+    mPreloadedNanoappsCond.notify_all();
+  }
+}
+
+}  // namespace chre
+}  // namespace android
diff --git a/host/exynos/exynos_daemon.h b/host/exynos/exynos_daemon.h
new file mode 100644
index 0000000..0041832
--- /dev/null
+++ b/host/exynos/exynos_daemon.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 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_EXYNOS_DAEMON_H_
+#define CHRE_EXYNOS_DAEMON_H_
+
+#include <atomic>
+#include <thread>
+
+#include "chre_host/fbs_daemon_base.h"
+#include "chre_host/st_hal_lpma_handler.h"
+
+namespace android {
+namespace chre {
+
+class ExynosDaemon : public FbsDaemonBase {
+ public:
+  ExynosDaemon();
+  ~ExynosDaemon() {
+    deinit();
+  }
+
+  //! EXYNOS's shared memory size for CHRE <-> AP is 4KB.
+  static constexpr size_t kIpcMsgSizeMax = 4096;
+
+  /**
+   * Initializes the CHRE daemon.
+   *
+   * @return true on successful init
+   */
+  bool init();
+
+  /**
+   * Starts a socket server receive loop for inbound messages.
+   */
+  void run();
+
+  void processIncomingMsgs();
+
+  int64_t getTimeOffset(bool *success) override;
+
+ protected:
+  void loadPreloadedNanoapp(const std::string &directory,
+                            const std::string &name,
+                            uint32_t transactionId) override;
+  void handleDaemonMessage(const uint8_t *message) override;
+  bool doSendMessage(void *data, size_t length) override;
+
+  void configureLpma(bool enabled) override {
+    mLpmaHandler.enable(enabled);
+  }
+
+ private:
+  static constexpr char kCommsDeviceFilename[] = "/dev/nanohub_comms";
+  static constexpr int kInvalidFd = -1;
+
+  int mCommsReadFd = kInvalidFd;
+  int mCommsWriteFd = kInvalidFd;
+  std::thread mIncomingMsgProcessThread;
+  std::thread::native_handle_type mNativeThreadHandle;
+  std::atomic<bool> mProcessThreadRunning = false;
+
+  StHalLpmaHandler mLpmaHandler;
+
+  //! Set to the expected transaction, fragment, app ID for loading a nanoapp.
+  struct Transaction {
+    uint32_t transactionId;
+    uint32_t fragmentId;
+    uint64_t nanoappId;
+  };
+  Transaction mPreloadedNanoappPendingTransaction;
+
+  //! The mutex used to guard state between the nanoapp messaging thread
+  //! and loading preloaded nanoapps.
+  std::mutex mPreloadedNanoappsMutex;
+
+  //! The condition variable used to wait for a nanoapp to finish loading.
+  std::condition_variable mPreloadedNanoappsCond;
+
+  //! Set to true when a preloaded nanoapp is pending load.
+  bool mPreloadedNanoappPending;
+
+  /**
+   * Perform a graceful shutdown of the daemon
+   */
+  void deinit();
+
+  /**
+   * Stops the inbound message processing thread (forcibly).
+   * Since the message read mechanism uses blocking system calls (poll, read),
+   * and since there's no timeout on the system calls (to avoid waking the AP
+   * up to handle timeouts), we forcibly terminate the thread on a daemon
+   * deinit. POSIX semantics are used since the C++20 threading interface does
+   * not provide an API to accomplish this.
+   */
+  void stopMsgProcessingThread();
+
+  /**
+   * Sends a preloaded nanoapp to CHRE.
+   *
+   * @param header The nanoapp header binary blob.
+   * @param nanoapp The nanoapp binary blob.
+   * @param transactionId The transaction ID to use when loading the app.
+   * @return true if successful, false otherwise.
+   */
+  bool loadNanoapp(const std::vector<uint8_t> &header,
+                   const std::vector<uint8_t> &nanoapp, uint32_t transactionId);
+
+  /**
+   * Loads a nanoapp using fragments.
+   *
+   * @param appId The ID of the nanoapp to load.
+   * @param appVersion The version of the nanoapp to load.
+   * @param appFlags The flags specified by the nanoapp to be loaded.
+   * @param appTargetApiVersion The version of the CHRE API that the app
+   * targets.
+   * @param appBinary The application binary code.
+   * @param appSize The size of the appBinary.
+   * @param transactionId The transaction ID to use when loading.
+   * @return true if successful, false otherwise.
+   */
+  bool sendFragmentedNanoappLoad(uint64_t appId, uint32_t appVersion,
+                                 uint32_t appFlags,
+                                 uint32_t appTargetApiVersion,
+                                 const uint8_t *appBinary, size_t appSize,
+                                 uint32_t transactionId);
+
+  bool sendFragmentAndWaitOnResponse(uint32_t transactionId,
+                                     flatbuffers::FlatBufferBuilder &builder,
+                                     uint32_t fragmentId, uint64_t appId);
+
+  /**
+   * Empty signal handler to handle SIGINT
+   */
+  static void signalHandler(int /*signal*/) {}
+};
+
+}  // namespace chre
+}  // namespace android
+
+#endif  // CHRE_EXYNOS_DAEMON_H_
diff --git a/host/exynos/main.cc b/host/exynos/main.cc
new file mode 100644
index 0000000..fc536d4
--- /dev/null
+++ b/host/exynos/main.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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 "exynos_daemon.h"
+
+int main() {
+  android::chre::ExynosDaemon daemon;
+
+  if (!daemon.init()) {
+    LOGE("failed to init the daemon");
+  } else {
+    daemon.run();
+  }
+
+  return 0;
+}
diff --git a/host/hal_generic/Android.bp b/host/hal_generic/Android.bp
new file mode 100644
index 0000000..caef528
--- /dev/null
+++ b/host/hal_generic/Android.bp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+soong_namespace {
+}
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_chre_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_chre_license"],
+}
+
+cc_binary {
+    name: "android.hardware.contexthub-service.generic",
+    defaults: ["hidl_defaults"],
+    vendor: true,
+    relative_install_path: "hw",
+    srcs: [
+        "aidl/generic_context_hub_aidl.cc",
+        "aidl/service.cc",
+        "common/hal_chre_socket_connection.cc",
+        "common/permissions_util.cc",
+    ],
+    include_dirs: [
+        "system/chre/util/include",
+    ],
+    local_include_dirs: [
+        "common/",
+    ],
+    init_rc: ["aidl/android.hardware.contexthub-service.generic.rc"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DCHRE_MESSAGE_TO_HOST_MAX_SIZE=4000", // Needed to import CHRE APIs.
+        "-DCHRE_HAL_SOCKET_METRICS_ENABLED",
+        "-DCHRE_IS_HOST_BUILD",
+    ],
+    shared_libs: [
+        "android.frameworks.stats-V1-ndk",
+        "libcutils",
+        "libjsoncpp",
+        "liblog",
+        "libprotobuf-cpp-lite",
+        "libutils",
+        "libbase",
+        "libbinder_ndk",
+        "android.hardware.contexthub-V2-ndk",
+        "chremetrics-cpp",
+        "chre_atoms_log",
+    ],
+    header_libs: [
+        "chre_api",
+    ],
+    static_libs: [
+        "chre_client",
+        "chre_config_util",
+        "event_logger",
+    ],
+    vintf_fragments: ["aidl/android.hardware.contexthub-service.generic.xml"],
+}
diff --git a/host/hal_generic/aidl/android.hardware.contexthub-service.generic.xml b/host/hal_generic/aidl/android.hardware.contexthub-service.generic.xml
index e383c50..930f672 100644
--- a/host/hal_generic/aidl/android.hardware.contexthub-service.generic.xml
+++ b/host/hal_generic/aidl/android.hardware.contexthub-service.generic.xml
@@ -1,7 +1,10 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.contexthub</name>
-        <version>1</version>
-        <fqname>IContextHub/default</fqname>
+        <version>2</version>
+        <interface>
+            <name>IContextHub</name>
+            <instance>default</instance>
+        </interface>
     </hal>
 </manifest>
diff --git a/host/hal_generic/aidl/event_logger.cc b/host/hal_generic/aidl/event_logger.cc
index b262a9b..257b8f7 100644
--- a/host/hal_generic/aidl/event_logger.cc
+++ b/host/hal_generic/aidl/event_logger.cc
@@ -89,6 +89,15 @@
   });
 }
 
+void EventLogger::logMessageFromNanoapp(const ContextHubMessage &message) {
+  std::lock_guard<std::mutex> lock(mQueuesMutex);
+  mMsgFromNanoapp.kick_push({
+      .timestampMs = getTimeMs(),
+      .id = message.nanoappId,
+      .sizeBytes = message.messageBody.size(),
+  });
+}
+
 std::string EventLogger::dump() const {
   constexpr int kBufferSize = 100;
   char buffer[kBufferSize];
diff --git a/host/hal_generic/aidl/event_logger.h b/host/hal_generic/aidl/event_logger.h
index fa71110..a085481 100644
--- a/host/hal_generic/aidl/event_logger.h
+++ b/host/hal_generic/aidl/event_logger.h
@@ -51,6 +51,8 @@
 
   void logMessageFromNanoapp(const ::chre::fbs::NanoappMessageT &message);
 
+  void logMessageFromNanoapp(const ContextHubMessage &message);
+
   /** Returns a textual representation of the logged events. */
   std::string dump() const;
 
diff --git a/host/hal_generic/aidl/generic_context_hub_aidl.cc b/host/hal_generic/aidl/generic_context_hub_aidl.cc
index 1814bb3..dae0d6f 100644
--- a/host/hal_generic/aidl/generic_context_hub_aidl.cc
+++ b/host/hal_generic/aidl/generic_context_hub_aidl.cc
@@ -17,21 +17,29 @@
 #include "generic_context_hub_aidl.h"
 
 #include "chre_api/chre/event.h"
+#include "chre_host/config_util.h"
+#include "chre_host/file_stream.h"
 #include "chre_host/fragmented_load_transaction.h"
 #include "chre_host/host_protocol_host.h"
+#include "chre_host/log.h"
+#include "chre_host/napp_header.h"
 #include "permissions_util.h"
 
-namespace aidl {
-namespace android {
-namespace hardware {
-namespace contexthub {
+#include <algorithm>
+#include <chrono>
+#include <limits>
+
+namespace aidl::android::hardware::contexthub {
 
 // Aliased for consistency with the way these symbols are referenced in
 // CHRE-side code
 namespace fbs = ::chre::fbs;
 
 using ::android::chre::FragmentedLoadTransaction;
+using ::android::chre::getPreloadedNanoappsFromConfigFile;
 using ::android::chre::getStringFromByteVector;
+using ::android::chre::NanoAppBinaryHeader;
+using ::android::chre::readFileContents;
 using ::android::hardware::contexthub::common::implementation::
     chreToAndroidPermissions;
 using ::android::hardware::contexthub::common::implementation::
@@ -40,13 +48,25 @@
 
 namespace {
 constexpr uint32_t kDefaultHubId = 0;
+constexpr char kPreloadedNanoappsConfigPath[] =
+    "/vendor/etc/chre/preloaded_nanoapps.json";
+constexpr std::chrono::duration kTestModeTimeout = std::chrono::seconds(10);
 
-inline constexpr uint8_t extractChreApiMajorVersion(uint32_t chreVersion) {
-  return static_cast<uint8_t>(chreVersion >> 24);
+/*
+ * The starting transaction ID for internal transactions. We choose
+ * the limit + 1 here as any client will only pass non-negative values up to the
+ * limit. The socket connection to CHRE accepts a uint32_t for the transaction
+ * ID, so we can use the value below up to std::numeric_limits<uint32_t>::max()
+ * for internal transaction IDs.
+ */
+constexpr int32_t kStartingInternalTransactionId = 0x80000000;
+
+inline constexpr int8_t extractChreApiMajorVersion(uint32_t chreVersion) {
+  return static_cast<int8_t>(chreVersion >> 24);
 }
 
-inline constexpr uint8_t extractChreApiMinorVersion(uint32_t chreVersion) {
-  return static_cast<uint8_t>(chreVersion >> 16);
+inline constexpr int8_t extractChreApiMinorVersion(uint32_t chreVersion) {
+  return static_cast<int8_t>(chreVersion >> 16);
 }
 
 inline constexpr uint16_t extractChrePatchVersion(uint32_t chreVersion) {
@@ -68,7 +88,7 @@
       break;
     default:
       foundSetting = false;
-      ALOGE("Setting update with invalid enum value %hhu", setting);
+      LOGE("Setting update with invalid enum value %hhu", setting);
       break;
   }
 
@@ -114,45 +134,39 @@
                                       const NanoappBinary &appBinary,
                                       int32_t transactionId) {
   if (contextHubId != kDefaultHubId) {
-    ALOGE("Invalid ID %" PRId32, contextHubId);
+    LOGE("Invalid ID %" PRId32, contextHubId);
     return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
-  } else {
-    uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) |
-                                (appBinary.targetChreApiMinorVersion << 16);
-    FragmentedLoadTransaction transaction(
-        transactionId, appBinary.nanoappId, appBinary.nanoappVersion,
-        appBinary.flags, targetApiVersion, appBinary.customBinary);
-    const bool success = mConnection.loadNanoapp(transaction);
-    mEventLogger.logNanoappLoad(appBinary, success);
-    return toServiceSpecificError(success);
   }
+
+  std::lock_guard<std::mutex> lock(mTestModeMutex);
+  bool success = loadNanoappInternal(appBinary, transactionId);
+  return toServiceSpecificError(success);
 }
 
 ScopedAStatus ContextHub::unloadNanoapp(int32_t contextHubId, int64_t appId,
                                         int32_t transactionId) {
   if (contextHubId != kDefaultHubId) {
-    ALOGE("Invalid ID %" PRId32, contextHubId);
+    LOGE("Invalid ID %" PRId32, contextHubId);
     return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
-  } else {
-    const bool success = mConnection.unloadNanoapp(appId, transactionId);
-    mEventLogger.logNanoappUnload(appId, success);
-    return toServiceSpecificError(success);
   }
+
+  std::lock_guard<std::mutex> lock(mTestModeMutex);
+  bool success = unloadNanoappInternal(appId, transactionId);
+  return toServiceSpecificError(success);
 }
 
 ScopedAStatus ContextHub::disableNanoapp(int32_t /* contextHubId */,
                                          int64_t appId,
                                          int32_t /* transactionId */) {
-  ALOGW("Attempted to disable app ID 0x%016" PRIx64 ", but not supported",
-        appId);
+  LOGW("Attempted to disable app ID 0x%016" PRIx64 ", but not supported",
+       appId);
   return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
 }
 
 ScopedAStatus ContextHub::enableNanoapp(int32_t /* contextHubId */,
                                         int64_t appId,
                                         int32_t /* transactionId */) {
-  ALOGW("Attempted to enable app ID 0x%016" PRIx64 ", but not supported",
-        appId);
+  LOGW("Attempted to enable app ID 0x%016" PRIx64 ", but not supported", appId);
   return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
 }
 
@@ -201,51 +215,76 @@
 
 ScopedAStatus ContextHub::queryNanoapps(int32_t contextHubId) {
   if (contextHubId != kDefaultHubId) {
-    ALOGE("Invalid ID %" PRId32, contextHubId);
+    LOGE("Invalid ID %" PRId32, contextHubId);
     return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
-  } else {
-    return toServiceSpecificError(mConnection.queryNanoapps());
   }
+  return toServiceSpecificError(mConnection.queryNanoapps());
+}
+
+::ndk::ScopedAStatus ContextHub::getPreloadedNanoappIds(
+    int32_t contextHubId, std::vector<int64_t> *out_preloadedNanoappIds) {
+  if (contextHubId != kDefaultHubId) {
+    LOGE("Invalid ID %" PRId32, contextHubId);
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+
+  if (out_preloadedNanoappIds == nullptr) {
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+
+  std::unique_lock<std::mutex> lock(mPreloadedNanoappIdsMutex);
+  if (mPreloadedNanoappIds.has_value()) {
+    *out_preloadedNanoappIds = *mPreloadedNanoappIds;
+    return ScopedAStatus::ok();
+  }
+
+  std::vector<chrePreloadedNanoappInfo> preloadedNanoapps;
+  if (!getPreloadedNanoappIdsFromConfigFile(preloadedNanoapps, nullptr)) {
+    return ScopedAStatus::fromExceptionCode(EX_SERVICE_SPECIFIC);
+  }
+
+  mPreloadedNanoappIds = std::vector<int64_t>();
+  for (const auto &preloadedNanoapp : preloadedNanoapps) {
+    mPreloadedNanoappIds->push_back(preloadedNanoapp.id);
+    out_preloadedNanoappIds->push_back(preloadedNanoapp.id);
+  }
+
+  return ScopedAStatus::ok();
 }
 
 ScopedAStatus ContextHub::registerCallback(
     int32_t contextHubId, const std::shared_ptr<IContextHubCallback> &cb) {
   if (contextHubId != kDefaultHubId) {
-    ALOGE("Invalid ID %" PRId32, contextHubId);
+    LOGE("Invalid ID %" PRId32, contextHubId);
     return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
-  } else {
-    std::lock_guard<std::mutex> lock(mCallbackMutex);
-    if (mCallback != nullptr) {
-      binder_status_t binder_status = AIBinder_unlinkToDeath(
-          mCallback->asBinder().get(), mDeathRecipient.get(), this);
-      if (binder_status != STATUS_OK) {
-        ALOGE("Failed to unlink to death");
-      }
-    }
-
-    mCallback = cb;
-
-    if (cb != nullptr) {
-      binder_status_t binder_status = AIBinder_linkToDeath(
-          cb->asBinder().get(), mDeathRecipient.get(), this);
-
-      if (binder_status != STATUS_OK) {
-        ALOGE("Failed to link to death");
-      }
-    }
-
-    return ScopedAStatus::ok();
   }
+  std::lock_guard<std::mutex> lock(mCallbackMutex);
+  if (mCallback != nullptr) {
+    binder_status_t binder_status = AIBinder_unlinkToDeath(
+        mCallback->asBinder().get(), mDeathRecipient.get(), this);
+    if (binder_status != STATUS_OK) {
+      LOGE("Failed to unlink to death");
+    }
+  }
+  mCallback = cb;
+  if (cb != nullptr) {
+    binder_status_t binder_status =
+        AIBinder_linkToDeath(cb->asBinder().get(), mDeathRecipient.get(), this);
+    if (binder_status != STATUS_OK) {
+      LOGE("Failed to link to death");
+    }
+  }
+  return ScopedAStatus::ok();
 }
 
 ScopedAStatus ContextHub::sendMessageToHub(int32_t contextHubId,
                                            const ContextHubMessage &message) {
   if (contextHubId != kDefaultHubId) {
-    ALOGE("Invalid ID %" PRId32, contextHubId);
+    LOGE("Invalid ID %" PRId32, contextHubId);
     return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
   }
 
-  const bool success = mConnection.sendMessageToHub(
+  bool success = mConnection.sendMessageToHub(
       message.nanoappId, message.messageType, message.hostEndPoint,
       message.messageBody.data(), message.messageBody.size());
   mEventLogger.logMessageToNanoapp(message, success);
@@ -253,19 +292,32 @@
   return toServiceSpecificError(success);
 }
 
+ScopedAStatus ContextHub::setTestMode(bool enable) {
+  return enable ? enableTestMode() : disableTestMode();
+}
+
 ScopedAStatus ContextHub::onHostEndpointConnected(
     const HostEndpointInfo &in_info) {
   std::lock_guard<std::mutex> lock(mConnectedHostEndpointsMutex);
+  uint8_t type;
+  switch (in_info.type) {
+    case HostEndpointInfo::Type::APP:
+      type = CHRE_HOST_ENDPOINT_TYPE_APP;
+      break;
+    case HostEndpointInfo::Type::NATIVE:
+      type = CHRE_HOST_ENDPOINT_TYPE_NATIVE;
+      break;
+    case HostEndpointInfo::Type::FRAMEWORK:
+      type = CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK;
+      break;
+    default:
+      LOGE("Unsupported host endpoint type %" PRIu32, type);
+      return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
   mConnectedHostEndpoints.insert(in_info.hostEndpointId);
-
-  uint8_t type = (in_info.type == HostEndpointInfo::Type::FRAMEWORK)
-                     ? CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK
-                     : CHRE_HOST_ENDPOINT_TYPE_APP;
-
   mConnection.onHostEndpointConnected(
       in_info.hostEndpointId, type, in_info.packageName.value_or(std::string()),
       in_info.attributionTag.value_or(std::string()));
-
   return ndk::ScopedAStatus::ok();
 }
 
@@ -277,13 +329,19 @@
 
     mConnection.onHostEndpointDisconnected(in_hostEndpointId);
   } else {
-    ALOGE("Unknown host endpoint disconnected (ID: %" PRIu16 ")",
-          in_hostEndpointId);
+    LOGE("Unknown host endpoint disconnected (ID: %" PRIu16 ")",
+         in_hostEndpointId);
   }
 
   return ndk::ScopedAStatus::ok();
 }
 
+ScopedAStatus ContextHub::onNanSessionStateChanged(
+    const NanSessionStateUpdate & /*in_update*/) {
+  // TODO(271471342): Add support for NAN session management.
+  return ndk::ScopedAStatus::ok();
+}
+
 void ContextHub::onNanoappMessage(const ::chre::fbs::NanoappMessageT &message) {
   std::lock_guard<std::mutex> lock(mCallbackMutex);
   if (mCallback != nullptr) {
@@ -303,51 +361,65 @@
 
 void ContextHub::onNanoappListResponse(
     const ::chre::fbs::NanoappListResponseT &response) {
-  std::lock_guard<std::mutex> lock(mCallbackMutex);
-  if (mCallback != nullptr) {
-    std::vector<NanoappInfo> appInfoList;
-
-    for (const std::unique_ptr<::chre::fbs::NanoappListEntryT> &nanoapp :
-         response.nanoapps) {
-      // TODO: determine if this is really required, and if so, have
-      // HostProtocolHost strip out null entries as part of decode
-      if (nanoapp == nullptr) {
-        continue;
-      }
-
-      ALOGV("App 0x%016" PRIx64 " ver 0x%" PRIx32 " permissions 0x%" PRIx32
-            " enabled %d system %d",
-            nanoapp->app_id, nanoapp->version, nanoapp->permissions,
-            nanoapp->enabled, nanoapp->is_system);
-      if (!nanoapp->is_system) {
-        NanoappInfo appInfo;
-
-        appInfo.nanoappId = nanoapp->app_id;
-        appInfo.nanoappVersion = nanoapp->version;
-        appInfo.enabled = nanoapp->enabled;
-        appInfo.permissions = chreToAndroidPermissions(nanoapp->permissions);
-
-        std::vector<NanoappRpcService> rpcServices;
-        for (const auto &service : nanoapp->rpc_services) {
-          NanoappRpcService aidlService;
-          aidlService.id = service->id;
-          aidlService.version = service->version;
-          rpcServices.emplace_back(aidlService);
-        }
-        appInfo.rpcServices = rpcServices;
-
-        appInfoList.push_back(appInfo);
-      }
+  std::vector<NanoappInfo> appInfoList;
+  for (const std::unique_ptr<::chre::fbs::NanoappListEntryT> &nanoapp :
+       response.nanoapps) {
+    // TODO(b/245202050): determine if this is really required, and if so, have
+    // HostProtocolHost strip out null entries as part of decode
+    if (nanoapp == nullptr) {
+      continue;
     }
 
+    LOGV("App 0x%016" PRIx64 " ver 0x%" PRIx32 " permissions 0x%" PRIx32
+         " enabled %d system %d",
+         nanoapp->app_id, nanoapp->version, nanoapp->permissions,
+         nanoapp->enabled, nanoapp->is_system);
+    if (!nanoapp->is_system) {
+      NanoappInfo appInfo;
+
+      appInfo.nanoappId = nanoapp->app_id;
+      appInfo.nanoappVersion = nanoapp->version;
+      appInfo.enabled = nanoapp->enabled;
+      appInfo.permissions = chreToAndroidPermissions(nanoapp->permissions);
+
+      std::vector<NanoappRpcService> rpcServices;
+      for (const auto &service : nanoapp->rpc_services) {
+        NanoappRpcService aidlService;
+        aidlService.id = service->id;
+        aidlService.version = service->version;
+        rpcServices.emplace_back(aidlService);
+      }
+      appInfo.rpcServices = rpcServices;
+
+      appInfoList.push_back(appInfo);
+    }
+  }
+
+  {
+    std::lock_guard<std::mutex> lock(mQueryNanoappsInternalMutex);
+    if (!mQueryNanoappsInternalList) {
+      mQueryNanoappsInternalList = appInfoList;
+      mQueryNanoappsInternalCondVar.notify_all();
+    }
+  }
+
+  std::lock_guard<std::mutex> lock(mCallbackMutex);
+  if (mCallback != nullptr) {
     mCallback->handleNanoappInfo(appInfoList);
   }
 }
 
 void ContextHub::onTransactionResult(uint32_t transactionId, bool success) {
-  std::lock_guard<std::mutex> lock(mCallbackMutex);
-  if (mCallback != nullptr) {
-    mCallback->handleTransactionResult(transactionId, success);
+  std::unique_lock<std::mutex> lock(mSynchronousLoadUnloadMutex);
+  if (mSynchronousLoadUnloadTransactionId &&
+      transactionId == *mSynchronousLoadUnloadTransactionId) {
+    mSynchronousLoadUnloadSuccess = success;
+    mSynchronousLoadUnloadCondVar.notify_all();
+  } else {
+    std::lock_guard<std::mutex> lock(mCallbackMutex);
+    if (mCallback != nullptr) {
+      mCallback->handleTransactionResult(transactionId, success);
+    }
   }
 }
 
@@ -355,7 +427,7 @@
   std::lock_guard<std::mutex> lock(mCallbackMutex);
   mIsWifiAvailable.reset();
   {
-    std::lock_guard<std::mutex> lock(mConnectedHostEndpointsMutex);
+    std::lock_guard<std::mutex> endpointLock(mConnectedHostEndpointsMutex);
     mConnectedHostEndpoints.clear();
     mEventLogger.logContextHubRestart();
   }
@@ -365,27 +437,18 @@
 }
 
 void ContextHub::onDebugDumpData(const ::chre::fbs::DebugDumpDataT &data) {
-  if (mDebugFd == kInvalidFd) {
-    ALOGW("Got unexpected debug dump data message");
-  } else {
-    writeToDebugFile(reinterpret_cast<const char *>(data.debug_str.data()),
-                     data.debug_str.size());
-  }
+  auto str = std::string(reinterpret_cast<const char *>(data.debug_str.data()),
+                         data.debug_str.size());
+  debugDumpAppend(str);
 }
 
 void ContextHub::onDebugDumpComplete(
     const ::chre::fbs::DebugDumpResponseT & /* response */) {
-  std::lock_guard<std::mutex> lock(mDebugDumpMutex);
-  if (!mDebugDumpPending) {
-    ALOGI("Ignoring duplicate/unsolicited debug dump response");
-  } else {
-    mDebugDumpPending = false;
-    mDebugDumpCond.notify_all();
-  }
+  debugDumpComplete();
 }
 
 void ContextHub::handleServiceDeath() {
-  ALOGI("Context Hub Service died ...");
+  LOGI("Context Hub Service died ...");
   {
     std::lock_guard<std::mutex> lock(mCallbackMutex);
     mCallback.reset();
@@ -403,41 +466,327 @@
 
 binder_status_t ContextHub::dump(int fd, const char ** /* args */,
                                  uint32_t /* numArgs */) {
-  // Timeout inside CHRE is typically 5 seconds, grant 500ms extra here to let
-  // the data reach us
-  constexpr auto kDebugDumpTimeout = std::chrono::milliseconds(5500);
-
-  mDebugFd = fd;
-  if (mDebugFd < 0) {
-    ALOGW("Can't dump debug info to invalid fd %d", mDebugFd);
-  } else {
-    writeToDebugFile("-- Dumping CHRE/ASH debug info --\n");
-
-    ALOGV("Sending debug dump request");
-    std::unique_lock<std::mutex> lock(mDebugDumpMutex);
-    mDebugDumpPending = true;
-    if (!mConnection.requestDebugDump()) {
-      ALOGW("Couldn't send debug dump request");
-    } else {
-      mDebugDumpCond.wait_for(lock, kDebugDumpTimeout,
-                              [this]() { return !mDebugDumpPending; });
-      if (mDebugDumpPending) {
-        ALOGE("Timed out waiting on debug dump data");
-        mDebugDumpPending = false;
-      }
-    }
-
-    writeToDebugFile(mEventLogger.dump());
-    writeToDebugFile("\n-- End of CHRE/ASH debug info --\n");
-
-    mDebugFd = kInvalidFd;
-    ALOGV("Debug dump complete");
-  }
-
+  debugDumpStart(fd);
+  debugDumpFinish();
   return STATUS_OK;
 }
 
-}  // namespace contexthub
-}  // namespace hardware
-}  // namespace android
-}  // namespace aidl
+void ContextHub::debugDumpFinish() {
+  if (checkDebugFd()) {
+    const std::string &dump = mEventLogger.dump();
+    writeToDebugFile(dump.c_str());
+    writeToDebugFile("\n-- End of CHRE/ASH debug info --\n");
+    invalidateDebugFd();
+  }
+}
+
+void ContextHub::writeToDebugFile(const char *str) {
+  if (!::android::base::WriteStringToFd(std::string(str), getDebugFd())) {
+    LOGW("Failed to write %zu bytes to debug dump fd", strlen(str));
+  }
+}
+
+ScopedAStatus ContextHub::enableTestMode() {
+  std::unique_lock<std::mutex> lock(mTestModeMutex);
+
+  bool success = false;
+  std::vector<int64_t> loadedNanoappIds;
+  std::vector<int64_t> preloadedNanoappIds;
+  std::vector<int64_t> nanoappIdsToUnload;
+  if (mIsTestModeEnabled) {
+    success = true;
+  } else if (mConnection.isLoadTransactionPending()) {
+    /**
+     * There is already a pending load transaction. We cannot change the test
+     * mode state if there is a pending load transaction. We do not consider
+     * pending unload transactions as they can happen asynchronously and
+     * multiple at a time.
+     */
+    LOGE("There exists a pending load transaction. Cannot enable test mode.");
+  } else if (!queryNanoappsInternal(kDefaultHubId, &loadedNanoappIds)) {
+    LOGE("Could not query nanoapps to enable test mode.");
+  } else if (!getPreloadedNanoappIds(kDefaultHubId, &preloadedNanoappIds)
+                  .isOk()) {
+    LOGE("Unable to get preloaded nanoapp IDs from the config file.");
+  } else {
+    std::sort(loadedNanoappIds.begin(), loadedNanoappIds.end());
+    std::sort(preloadedNanoappIds.begin(), preloadedNanoappIds.end());
+
+    // Calculate the system nanoapp IDs. They are preloaded, but not loaded.
+    mSystemNanoappIds.clear();
+    std::set_difference(preloadedNanoappIds.begin(), preloadedNanoappIds.end(),
+                        loadedNanoappIds.begin(), loadedNanoappIds.end(),
+                        std::back_inserter(mSystemNanoappIds));
+
+    /*
+     * Unload all preloaded and loaded nanoapps (set intersection).
+     * Both vectors need to be sorted for std::set_intersection to work.
+     * We explicitly choose not to use std::set here to avoid the
+     * copying cost as well as the tree balancing cost for the
+     * red-black tree.
+     */
+    std::set_intersection(loadedNanoappIds.begin(), loadedNanoappIds.end(),
+                          preloadedNanoappIds.begin(),
+                          preloadedNanoappIds.end(),
+                          std::back_inserter(nanoappIdsToUnload));
+    if (!unloadNanoappsInternal(kDefaultHubId, nanoappIdsToUnload)) {
+      LOGE("Unable to unload all loaded and preloaded nanoapps.");
+    } else {
+      success = true;
+    }
+  }
+
+  if (success) {
+    mIsTestModeEnabled = true;
+    LOGI("Successfully enabled test mode.");
+    return ScopedAStatus::ok();
+  } else {
+    return ScopedAStatus::fromExceptionCode(EX_SERVICE_SPECIFIC);
+  }
+}
+
+ScopedAStatus ContextHub::disableTestMode() {
+  std::unique_lock<std::mutex> lock(mTestModeMutex);
+
+  bool success = false;
+  std::vector<chrePreloadedNanoappInfo> preloadedNanoapps;
+  std::string preloadedNanoappDirectory;
+  if (!mIsTestModeEnabled) {
+    success = true;
+  } else if (mConnection.isLoadTransactionPending()) {
+    /**
+     * There is already a pending load transaction. We cannot change the test
+     * mode state if there is a pending load transaction. We do not consider
+     * pending unload transactions as they can happen asynchronously and
+     * multiple at a time.
+     */
+    LOGE("There exists a pending load transaction. Cannot disable test mode.");
+  } else if (!getPreloadedNanoappIdsFromConfigFile(
+                 preloadedNanoapps, &preloadedNanoappDirectory)) {
+    LOGE("Unable to get preloaded nanoapp IDs from the config file.");
+  } else {
+    std::vector<NanoappBinary> nanoappsToLoad = selectPreloadedNanoappsToLoad(
+        preloadedNanoapps, preloadedNanoappDirectory);
+
+    if (!loadNanoappsInternal(kDefaultHubId, nanoappsToLoad)) {
+      LOGE("Unable to load all preloaded, non-system nanoapps.");
+    } else {
+      success = true;
+    }
+  }
+
+  if (success) {
+    mIsTestModeEnabled = false;
+    LOGI("Successfully disabled test mode.");
+    return ScopedAStatus::ok();
+  } else {
+    return ScopedAStatus::fromExceptionCode(EX_SERVICE_SPECIFIC);
+  }
+}
+
+bool ContextHub::queryNanoappsInternal(int32_t contextHubId,
+                                       std::vector<int64_t> *nanoappIdList) {
+  if (contextHubId != kDefaultHubId) {
+    LOGE("Invalid ID %" PRId32, contextHubId);
+    return false;
+  }
+
+  std::unique_lock<std::mutex> lock(mQueryNanoappsInternalMutex);
+  mQueryNanoappsInternalList.reset();
+
+  bool success =
+      queryNanoapps(contextHubId).isOk() &&
+      mQueryNanoappsInternalCondVar.wait_for(lock, kTestModeTimeout, [this]() {
+        return mQueryNanoappsInternalList.has_value();
+      });
+  if (success && nanoappIdList != nullptr) {
+    std::transform(
+        mQueryNanoappsInternalList->begin(), mQueryNanoappsInternalList->end(),
+        std::back_inserter(*nanoappIdList),
+        [](const NanoappInfo &nanoapp) { return nanoapp.nanoappId; });
+  }
+  return success;
+}
+
+bool ContextHub::loadNanoappInternal(const NanoappBinary &appBinary,
+                                     int32_t transactionId) {
+  uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) |
+                              (appBinary.targetChreApiMinorVersion << 16);
+  FragmentedLoadTransaction transaction(
+      transactionId, appBinary.nanoappId, appBinary.nanoappVersion,
+      appBinary.flags, targetApiVersion, appBinary.customBinary);
+  bool success = mConnection.loadNanoapp(transaction);
+  mEventLogger.logNanoappLoad(appBinary, success);
+  return success;
+}
+
+bool ContextHub::loadNanoappsInternal(
+    int32_t contextHubId, const std::vector<NanoappBinary> &nanoappBinaryList) {
+  if (contextHubId != kDefaultHubId) {
+    LOGE("Invalid ID %" PRId32, contextHubId);
+    return false;
+  }
+
+  std::unique_lock<std::mutex> lock(mSynchronousLoadUnloadMutex);
+  mSynchronousLoadUnloadTransactionId = kStartingInternalTransactionId;
+
+  for (const NanoappBinary &nanoappToLoad : nanoappBinaryList) {
+    LOGI("Loading nanoapp with ID: 0x%016" PRIx64, nanoappToLoad.nanoappId);
+
+    bool success = false;
+    if (!loadNanoappInternal(nanoappToLoad,
+                             *mSynchronousLoadUnloadTransactionId)) {
+      LOGE("Failed to request loading nanoapp with ID 0x%" PRIx64,
+           nanoappToLoad.nanoappId);
+    } else {
+      mSynchronousLoadUnloadSuccess.reset();
+      mSynchronousLoadUnloadCondVar.wait_for(lock, kTestModeTimeout, [this]() {
+        return mSynchronousLoadUnloadSuccess.has_value();
+      });
+      if (mSynchronousLoadUnloadSuccess.has_value() &&
+          *mSynchronousLoadUnloadSuccess) {
+        LOGI("Successfully loaded nanoapp with ID: 0x%016" PRIx64,
+             nanoappToLoad.nanoappId);
+        ++(*mSynchronousLoadUnloadTransactionId);
+        success = true;
+      }
+    }
+
+    if (!success) {
+      LOGE("Failed to load nanoapp with ID 0x%" PRIx64,
+           nanoappToLoad.nanoappId);
+      mSynchronousLoadUnloadTransactionId.reset();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool ContextHub::unloadNanoappInternal(int64_t appId, int32_t transactionId) {
+  bool success = mConnection.unloadNanoapp(appId, transactionId);
+  mEventLogger.logNanoappUnload(appId, success);
+  return success;
+}
+
+bool ContextHub::unloadNanoappsInternal(
+    int32_t contextHubId, const std::vector<int64_t> &nanoappIdList) {
+  if (contextHubId != kDefaultHubId) {
+    LOGE("Invalid ID %" PRId32, contextHubId);
+    return false;
+  }
+
+  std::unique_lock<std::mutex> lock(mSynchronousLoadUnloadMutex);
+  mSynchronousLoadUnloadTransactionId = kStartingInternalTransactionId;
+
+  for (int64_t nanoappIdToUnload : nanoappIdList) {
+    LOGI("Unloading nanoapp with ID: 0x%016" PRIx64, nanoappIdToUnload);
+
+    bool success = false;
+    if (!unloadNanoappInternal(nanoappIdToUnload,
+                               *mSynchronousLoadUnloadTransactionId)) {
+      LOGE("Failed to request unloading nanoapp with ID 0x%" PRIx64,
+           nanoappIdToUnload);
+    } else {
+      mSynchronousLoadUnloadSuccess.reset();
+      mSynchronousLoadUnloadCondVar.wait_for(lock, kTestModeTimeout, [this]() {
+        return mSynchronousLoadUnloadSuccess.has_value();
+      });
+      if (mSynchronousLoadUnloadSuccess.has_value() &&
+          *mSynchronousLoadUnloadSuccess) {
+        LOGI("Successfully unloaded nanoapp with ID: 0x%016" PRIx64,
+             nanoappIdToUnload);
+        ++(*mSynchronousLoadUnloadTransactionId);
+        success = true;
+      }
+    }
+
+    if (!success) {
+      LOGE("Failed to unload nanoapp with ID 0x%" PRIx64, nanoappIdToUnload);
+      mSynchronousLoadUnloadTransactionId.reset();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool ContextHub::getPreloadedNanoappIdsFromConfigFile(
+    std::vector<chrePreloadedNanoappInfo> &out_preloadedNanoapps,
+    std::string *out_directory) const {
+  std::vector<std::string> nanoappNames;
+  std::string directory;
+
+  bool success = getPreloadedNanoappsFromConfigFile(
+      kPreloadedNanoappsConfigPath, directory, nanoappNames);
+  if (!success) {
+    LOGE("Failed to parse preloaded nanoapps config file");
+  }
+
+  for (const std::string &nanoappName : nanoappNames) {
+    std::string headerFile = directory + "/" + nanoappName + ".napp_header";
+    std::vector<uint8_t> headerBuffer;
+    if (!readFileContents(headerFile.c_str(), headerBuffer)) {
+      LOGE("Cannot read header file: %s", headerFile.c_str());
+      continue;
+    }
+
+    if (headerBuffer.size() != sizeof(NanoAppBinaryHeader)) {
+      LOGE("Header size mismatch");
+      continue;
+    }
+
+    const auto *appHeader =
+        reinterpret_cast<const NanoAppBinaryHeader *>(headerBuffer.data());
+    out_preloadedNanoapps.emplace_back(static_cast<int64_t>(appHeader->appId),
+                                       nanoappName, *appHeader);
+  }
+
+  if (out_directory != nullptr) {
+    *out_directory = directory;
+  }
+
+  return true;
+}
+
+std::vector<NanoappBinary> ContextHub::selectPreloadedNanoappsToLoad(
+    std::vector<chrePreloadedNanoappInfo> &preloadedNanoapps,
+    const std::string &preloadedNanoappDirectory) {
+  std::vector<NanoappBinary> nanoappsToLoad;
+
+  for (auto &preloadedNanoapp : preloadedNanoapps) {
+    int64_t nanoappId = preloadedNanoapp.id;
+
+    // A nanoapp is a system nanoapp if it is in the preloaded nanoapp list
+    // but not in the loaded nanoapp list as CHRE hides system nanoapps
+    // from the HAL.
+    bool isSystemNanoapp =
+        std::any_of(mSystemNanoappIds.begin(), mSystemNanoappIds.end(),
+                    [nanoappId](int64_t systemNanoappId) {
+                      return systemNanoappId == nanoappId;
+                    });
+    if (!isSystemNanoapp) {
+      std::vector<uint8_t> nanoappBuffer;
+      std::string nanoappFile =
+          preloadedNanoappDirectory + "/" + preloadedNanoapp.name + ".so";
+      if (!readFileContents(nanoappFile.c_str(), nanoappBuffer)) {
+        LOGE("Cannot read header file: %s", nanoappFile.c_str());
+      } else {
+        NanoappBinary nanoapp;
+        nanoapp.nanoappId = preloadedNanoapp.header.appId;
+        nanoapp.nanoappVersion = preloadedNanoapp.header.appVersion;
+        nanoapp.flags = preloadedNanoapp.header.flags;
+        nanoapp.targetChreApiMajorVersion =
+            preloadedNanoapp.header.targetChreApiMajorVersion;
+        nanoapp.targetChreApiMinorVersion =
+            preloadedNanoapp.header.targetChreApiMinorVersion;
+        nanoapp.customBinary = nanoappBuffer;
+
+        nanoappsToLoad.push_back(nanoapp);
+      }
+    }
+  }
+  return nanoappsToLoad;
+}
+
+}  // namespace aidl::android::hardware::contexthub
diff --git a/host/hal_generic/aidl/generic_context_hub_aidl.h b/host/hal_generic/aidl/generic_context_hub_aidl.h
index ea3a9fc..9c12c16 100644
--- a/host/hal_generic/aidl/generic_context_hub_aidl.h
+++ b/host/hal_generic/aidl/generic_context_hub_aidl.h
@@ -18,29 +18,45 @@
 #define ANDROID_HARDWARE_CONTEXTHUB_AIDL_CONTEXTHUB_H
 
 #include <aidl/android/hardware/contexthub/BnContextHub.h>
-#include <android-base/file.h>
 #include <log/log.h>
+#include <atomic>
+#include <future>
 #include <map>
 #include <mutex>
 #include <optional>
 #include <unordered_set>
 
+#include "chre_host/napp_header.h"
+#include "debug_dump_helper.h"
 #include "event_logger.h"
 #include "hal_chre_socket_connection.h"
 
-namespace aidl {
-namespace android {
-namespace hardware {
-namespace contexthub {
+namespace aidl::android::hardware::contexthub {
+
+using ::android::chre::NanoAppBinaryHeader;
+
+/**
+ * Contains information about a preloaded nanoapp. Used when getting
+ * preloaded nanoapp information from the config.
+ */
+struct chrePreloadedNanoappInfo {
+  chrePreloadedNanoappInfo(int64_t _id, const std::string &_name,
+                           const NanoAppBinaryHeader &_header)
+      : id(_id), name(_name), header(_header) {}
+
+  int64_t id;
+  std::string name;
+  NanoAppBinaryHeader header;
+};
 
 class ContextHub : public BnContextHub,
+                   public ::android::hardware::contexthub::DebugDumpHelper,
                    public ::android::hardware::contexthub::common::
                        implementation::IChreSocketCallback {
  public:
   ContextHub()
       : mDeathRecipient(
             AIBinder_DeathRecipient_new(ContextHub::onServiceDied)) {}
-
   ::ndk::ScopedAStatus getContextHubs(
       std::vector<ContextHubInfo> *out_contextHubInfos) override;
   ::ndk::ScopedAStatus loadNanoapp(int32_t contextHubId,
@@ -54,15 +70,21 @@
                                      int32_t transactionId) override;
   ::ndk::ScopedAStatus onSettingChanged(Setting setting, bool enabled) override;
   ::ndk::ScopedAStatus queryNanoapps(int32_t contextHubId) override;
+  ::ndk::ScopedAStatus getPreloadedNanoappIds(
+      int32_t contextHubId,
+      std::vector<int64_t> *out_preloadedNanoappIds) override;
   ::ndk::ScopedAStatus registerCallback(
       int32_t contextHubId,
       const std::shared_ptr<IContextHubCallback> &cb) override;
   ::ndk::ScopedAStatus sendMessageToHub(
       int32_t contextHubId, const ContextHubMessage &message) override;
+  ::ndk::ScopedAStatus setTestMode(bool enable) override;
   ::ndk::ScopedAStatus onHostEndpointConnected(
       const HostEndpointInfo &in_info) override;
   ::ndk::ScopedAStatus onHostEndpointDisconnected(
       char16_t in_hostEndpointId) override;
+  ::ndk::ScopedAStatus onNanSessionStateChanged(
+      const NanSessionStateUpdate &in_update) override;
 
   void onNanoappMessage(const ::chre::fbs::NanoappMessageT &message) override;
 
@@ -83,7 +105,126 @@
 
   binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
 
+  bool requestDebugDump() override {
+    return mConnection.requestDebugDump();
+  }
+
+  void debugDumpFinish() override;
+
+  void writeToDebugFile(const char *str) override;
+
  private:
+  /**
+   * Enables test mode on the context hub. This unloads all nanoapps and puts
+   * CHRE in a state that is consistent for testing.
+   *
+   * @return                            the status.
+   */
+  ::ndk::ScopedAStatus enableTestMode();
+
+  /**
+   * Disables test mode. Reverses the affects of enableTestMode() by loading all
+   * preloaded nanoapps. This puts CHRE back in a normal state.
+   *
+   * @return                            the status.
+   */
+  ::ndk::ScopedAStatus disableTestMode();
+
+  /**
+   * Queries the list of loaded nanoapps in a synchronous manner.
+   * The list is stored in the mQueryNanoappsInternalList variable.
+   *
+   * @param contextHubId                the ID of the context hub.
+   * @param nanoappIdList               (out) optional out parameter that
+   *                                    contains the nanoapp IDs.
+   *
+   * @return true                       the operation was successful.
+   * @return false                      the operation was not successful.
+   */
+  bool queryNanoappsInternal(int32_t contextHubId,
+                             std::vector<int64_t> *nanoappIdList);
+
+  /**
+   * Loads a nanoapp.
+   *
+   * @param appBinary                   the nanoapp binary to load.
+   * @param transactionId               the transaction ID.
+   *
+   * @return true                       the operation was successful.
+   * @return false                      the operation was not successful.
+   */
+  bool loadNanoappInternal(const NanoappBinary &appBinary,
+                           int32_t transactionId);
+
+  /**
+   * Loads the nanoapps in a synchronous manner.
+   *
+   * @param contextHubId                the ID of the context hub.
+   * @param nanoappBinaryList           the list of NanoappBinary's to load.
+   * @return true                       the operation was successful.
+   * @return false                      the operation was not successful.
+   */
+  bool loadNanoappsInternal(
+      int32_t contextHubId,
+      const std::vector<NanoappBinary> &nanoappBinaryList);
+
+  /**
+   * Unloads a nanoapp.
+   *
+   * @param appId                       the nanoapp ID to unload.
+   * @param transactionId               the transaction ID.
+   *
+   * @return true                       the operation was successful.
+   * @return false                      the operation was not successful.
+   */
+  bool unloadNanoappInternal(int64_t appId, int32_t transactionId);
+
+  /**
+   * Unloads the nanoapps in a synchronous manner.
+   *
+   * @param contextHubId                the ID of the context hub.
+   * @param nanoappIdsToUnload          the list of nanoapp IDs to unload.
+   * @return true                       the operation was successful.
+   * @return false                      the operation was not successful.
+   */
+  bool unloadNanoappsInternal(int32_t contextHubId,
+                              const std::vector<int64_t> &nanoappIdList);
+
+  /**
+   * Get the preloaded nanoapp IDs from the config file and headers. All IDs,
+   * names and headers are in the same order (one nanoapp has the same index in
+   * each).
+   *
+   * @param out_preloadedNanoapps       out parameter, the nanoapp information.
+   * @param out_directory               out parameter, optional, the directory
+   * that contains the nanoapps.
+   * @return true                       the operation was successful.
+   * @return false                      the operation was not successful.
+   */
+  bool getPreloadedNanoappIdsFromConfigFile(
+      std::vector<chrePreloadedNanoappInfo> &out_preloadedNanoapps,
+      std::string *out_directory) const;
+
+  /**
+   * Selects the nanoapps to load -> all preloaded and non-system nanoapps.
+   *
+   * @param preloadedNanoapps           the preloaded nanoapps.
+   * @param preloadedNanoappDirectory   the preloaded nanoapp directory.
+   * @return                            the nanoapps to load.
+   */
+  std::vector<NanoappBinary> selectPreloadedNanoappsToLoad(
+      std::vector<chrePreloadedNanoappInfo> &preloadedNanoapps,
+      const std::string &preloadedNanoappDirectory);
+
+  bool isSettingEnabled(Setting setting) {
+    return mSettingEnabled.count(setting) > 0 && mSettingEnabled[setting];
+  }
+
+  chre::fbs::SettingState toFbsSettingState(bool enabled) const {
+    return enabled ? chre::fbs::SettingState::ENABLED
+                   : chre::fbs::SettingState::DISABLED;
+  }
+
   ::android::hardware::contexthub::common::implementation::
       HalChreSocketConnection mConnection{this};
 
@@ -101,46 +242,33 @@
   std::mutex mConnectedHostEndpointsMutex;
   std::unordered_set<char16_t> mConnectedHostEndpoints;
 
-  // Variables related to debug dump.
-  static constexpr int kInvalidFd = -1;
-  int mDebugFd = kInvalidFd;
-  bool mDebugDumpPending = false;
-  std::mutex mDebugDumpMutex;
-  std::condition_variable mDebugDumpCond;
-
   // Logs events to be reported in debug dumps.
   EventLogger mEventLogger;
 
-  bool isSettingEnabled(Setting setting) {
-    return mSettingEnabled.count(setting) > 0 ? mSettingEnabled[setting]
-                                              : false;
-  }
+  // A mutex to synchronize access to the list of preloaded nanoapp IDs.
+  std::mutex mPreloadedNanoappIdsMutex;
+  std::optional<std::vector<int64_t>> mPreloadedNanoappIds;
 
-  chre::fbs::SettingState toFbsSettingState(bool enabled) const {
-    return enabled ? chre::fbs::SettingState::ENABLED
-                   : chre::fbs::SettingState::DISABLED;
-  }
+  // A mutex and condition variable to synchronize queryNanoappsInternal.
+  std::mutex mQueryNanoappsInternalMutex;
+  std::condition_variable mQueryNanoappsInternalCondVar;
+  std::optional<std::vector<NanoappInfo>> mQueryNanoappsInternalList;
 
-  // Write a string to mDebugFd
-  void writeToDebugFile(const std::string &str) {
-    if (!::android::base::WriteStringToFd(str, mDebugFd)) {
-      ALOGW("Failed to write %zu bytes to debug dump fd", str.size());
-    }
-  }
+  // State for synchronous loads and unloads. Primarily used for test mode.
+  std::mutex mSynchronousLoadUnloadMutex;
+  std::condition_variable mSynchronousLoadUnloadCondVar;
+  std::optional<bool> mSynchronousLoadUnloadSuccess;
+  std::optional<int32_t> mSynchronousLoadUnloadTransactionId;
 
-  void writeToDebugFile(const char *str) {
-    writeToDebugFile(str, strlen(str));
-  }
+  // A boolean and mutex to synchronize test mode state changes and
+  // load/unloads.
+  std::mutex mTestModeMutex;
+  bool mIsTestModeEnabled = false;
 
-  void writeToDebugFile(const char *str, size_t len) {
-    std::string s(str, len);
-    writeToDebugFile(s);
-  }
+  // List of system nanoapp Ids.
+  std::vector<int64_t> mSystemNanoappIds;
 };
 
-}  // namespace contexthub
-}  // namespace hardware
-}  // namespace android
-}  // namespace aidl
+}  // namespace aidl::android::hardware::contexthub
 
 #endif  // ANDROID_HARDWARE_CONTEXTHUB_AIDL_CONTEXTHUB_H
diff --git a/host/hal_generic/common/chre_connection.h b/host/hal_generic/common/chre_connection.h
new file mode 100644
index 0000000..8f15345
--- /dev/null
+++ b/host/hal_generic/common/chre_connection.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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 ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_CONNECTION_H_
+#define ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_CONNECTION_H_
+
+#include <flatbuffers/flatbuffers.h>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <future>
+#include <string>
+#include "chre_host/fragmented_load_transaction.h"
+#include "hal_client_id.h"
+
+namespace android::hardware::contexthub::common::implementation {
+
+/** This abstract class defines the interface between HAL and CHRE. */
+class ChreConnection {
+ public:
+  virtual ~ChreConnection() = default;
+
+  /**
+   * Initializes the connection between HAL and CHRE.
+   *
+   * @return true if success, otherwise false.
+   */
+  virtual bool init() = 0;
+
+  /**
+   * Sends a message to CHRE.
+   *
+   * @return true if success, otherwise false.
+   */
+  virtual bool sendMessage(void *data, size_t length) = 0;
+
+  /**
+   * @return The nanoapp loading fragment size in bytes.
+   */
+  virtual size_t getLoadFragmentSizeBytes() const {
+    static_assert(CHRE_HOST_DEFAULT_FRAGMENT_SIZE > 0);
+    return CHRE_HOST_DEFAULT_FRAGMENT_SIZE;
+  }
+
+  /**
+   * Sends a message encapsulated in a FlatBufferBuilder to CHRE.
+   *
+   * @return true if success, otherwise false.
+   */
+  inline bool sendMessage(const flatbuffers::FlatBufferBuilder &builder) {
+    return sendMessage(builder.GetBufferPointer(), builder.GetSize());
+  }
+
+  /**
+   * Gets the offset between the Context hub and Android time in nanoseconds.
+   *
+   * This function may be used for synchronizing timestamps between the Context
+   * hub and Android.
+   *
+   * @param offset points to the address storing the offset in nanoseconds which
+   * is defined as android time - context hub time.
+   * @return true on success, otherwise false.
+   */
+  virtual bool getTimeOffset(int64_t * /*offset*/) {
+    return false;
+  }
+
+  /**
+   * Returns true if time sync is required by the platform, false otherwise.
+   *
+   * When this function returns false, getTimeOffset may not be implemented.
+   */
+  virtual bool isTimeSyncNeeded() {
+    return false;
+  }
+};
+
+}  // namespace android::hardware::contexthub::common::implementation
+#endif  // ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_CONNECTION_H_
diff --git a/host/hal_generic/common/chre_connection_callback.h b/host/hal_generic/common/chre_connection_callback.h
new file mode 100644
index 0000000..e751db6
--- /dev/null
+++ b/host/hal_generic/common/chre_connection_callback.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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 ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_CONNECTION_CALLBACK_H_
+#define ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_CONNECTION_CALLBACK_H_
+
+namespace android::hardware::contexthub::common::implementation {
+
+/**
+ * The callback interface for ChreConnection.
+ *
+ * A Context Hub HAL should implement this interface so that the ChreConnection
+ * has an API to pass message to.
+ */
+class ChreConnectionCallback {
+ public:
+  virtual ~ChreConnectionCallback() = default;
+  virtual void handleMessageFromChre(const unsigned char *message,
+                                     size_t messageLen) = 0;
+
+  /** This method should be called when CHRE is reconnected to HAL and ready to
+   * accept new messages. */
+  virtual void onChreRestarted(){};
+};
+}  // namespace android::hardware::contexthub::common::implementation
+#endif  // ANDROID_HARDWARE_CONTEXTHUB_COMMON_CHRE_CONNECTION_CALLBACK_H_
\ No newline at end of file
diff --git a/host/hal_generic/common/debug_dump_helper.h b/host/hal_generic/common/debug_dump_helper.h
new file mode 100644
index 0000000..748a161
--- /dev/null
+++ b/host/hal_generic/common/debug_dump_helper.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 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 <android-base/file.h>
+#include <log/log.h>
+#include <mutex>
+
+namespace android {
+namespace hardware {
+namespace contexthub {
+
+/**
+ * Class to help request and synchronize dumping CHRE debug information.
+ */
+class DebugDumpHelper {
+ public:
+  virtual ~DebugDumpHelper() {}
+
+  /**
+   * Implementation specific method to send a debug dump request.
+   *
+   * @return true on a successful request, false otherwise.
+   */
+  virtual bool requestDebugDump() = 0;
+
+  /**
+   * Implementation specific method to write a string to a debug file.
+   * Note that this function must only be called after a debug dump request
+   * has been initiated via 'startDebugDump()'.
+   *
+   * @param str String to be written to the debug dump file. MUST be NULL
+   *        terminated.
+   */
+  virtual void writeToDebugFile(const char *str) = 0;
+
+  /**
+   * Optional implementation specific method to write any debug info private to
+   * said implementation.
+   * Note that this function must only be called after a debug dump request
+   * has been initiated via 'startDebugDump()'.
+   */
+  virtual void debugDumpFinish() {}
+
+  bool checkDebugFd() {
+    return mDebugFd != kInvalidFd;
+  }
+
+  int getDebugFd() const {
+    return mDebugFd;
+  }
+
+  void invalidateDebugFd() {
+    mDebugFd = kInvalidFd;
+  }
+
+  /**
+   * Initiate a debug dump request.
+   *
+   * @param fd Posix file descriptor to write debug information into.
+   */
+  void debugDumpStart(int fd) {
+    // Timeout inside CHRE is typically 5 seconds, grant 500ms extra here to let
+    // the data reach us
+    constexpr auto kDebugDumpTimeout = std::chrono::milliseconds(5500);
+    mDebugFd = fd;
+    if (mDebugFd < 0) {
+      ALOGW("Can't dump debug info to invalid fd %d", mDebugFd);
+    } else {
+      writeToDebugFile("-- Dumping CHRE debug info --\n");
+
+      ALOGV("Sending debug dump request");
+      std::unique_lock<std::mutex> lock(mDebugDumpMutex);
+      mDebugDumpPending = true;
+      if (!requestDebugDump()) {
+        ALOGW("Couldn't send debug dump request");
+      } else {
+        mDebugDumpCond.wait_for(lock, kDebugDumpTimeout,
+                                [this]() { return !mDebugDumpPending; });
+        if (mDebugDumpPending) {
+          ALOGE("Timed out waiting on debug dump data");
+          mDebugDumpPending = false;
+        }
+      }
+    }
+  }
+
+  /**
+   * Append to a debug dump file asynchronously. Note that a call to this
+   * function will only go through if a debug dump request was already
+   * initiated via debugDumpStart().
+   *
+   * @param str Debug string to append to the file.
+   */
+  void debugDumpAppend(std::string &str) {
+    if (mDebugFd == kInvalidFd) {
+      ALOGW("Got unexpected debug dump data message");
+    } else {
+      writeToDebugFile(str.c_str());
+    }
+  }
+
+  /**
+   * Called at the end of a debug dump request.
+   */
+  void debugDumpComplete() {
+    std::lock_guard<std::mutex> lock(mDebugDumpMutex);
+    if (!mDebugDumpPending) {
+      ALOGI("Ignoring duplicate/unsolicited debug dump response");
+    } else {
+      mDebugDumpPending = false;
+      mDebugDumpCond.notify_all();
+      invalidateDebugFd();
+    }
+  }
+
+ private:
+  static constexpr int kInvalidFd = -1;
+  int mDebugFd = kInvalidFd;
+  bool mDebugDumpPending = false;
+  std::mutex mDebugDumpMutex;
+  std::condition_variable mDebugDumpCond;
+};
+
+}  // namespace contexthub
+}  // namespace hardware
+}  // namespace android
\ No newline at end of file
diff --git a/host/hal_generic/common/generic_context_hub_base.h b/host/hal_generic/common/generic_context_hub_base.h
index aa08a0c..64b4a24 100644
--- a/host/hal_generic/common/generic_context_hub_base.h
+++ b/host/hal_generic/common/generic_context_hub_base.h
@@ -29,6 +29,7 @@
 #include <log/log.h>
 
 #include "IContextHubCallbackWrapper.h"
+#include "debug_dump_helper.h"
 #include "hal_chre_socket_connection.h"
 #include "permissions_util.h"
 
@@ -46,6 +47,7 @@
 using ::android::hardware::hidl_string;
 using ::android::hardware::hidl_vec;
 using ::android::hardware::Return;
+using ::android::hardware::contexthub::DebugDumpHelper;
 using ::android::hardware::contexthub::common::implementation::
     chreToAndroidPermissions;
 using ::android::hardware::contexthub::V1_0::AsyncEventType;
@@ -94,43 +96,21 @@
 }
 
 template <class IContexthubT>
-class GenericContextHubBase : public IContexthubT, public IChreSocketCallback {
+class GenericContextHubBase : public IContexthubT,
+                              public IChreSocketCallback,
+                              public DebugDumpHelper {
  public:
   GenericContextHubBase() {
     mDeathRecipient = new DeathRecipient(this);
   }
 
+  bool requestDebugDump() override {
+    return mConnection.requestDebugDump();
+  }
+
   Return<void> debug(const hidl_handle &fd,
                      const hidl_vec<hidl_string> & /* options */) override {
-    // Timeout inside CHRE is typically 5 seconds, grant 500ms extra here to let
-    // the data reach us
-    constexpr auto kDebugDumpTimeout = std::chrono::milliseconds(5500);
-
-    mDebugFd = hidlHandleToFileDescriptor(fd);
-    if (mDebugFd < 0) {
-      ALOGW("Can't dump debug info to invalid fd");
-    } else {
-      writeToDebugFile("-- Dumping CHRE/ASH debug info --\n");
-
-      ALOGV("Sending debug dump request");
-      std::unique_lock<std::mutex> lock(mDebugDumpMutex);
-      mDebugDumpPending = true;
-      if (!mConnection.requestDebugDump()) {
-        ALOGW("Couldn't send debug dump request");
-      } else {
-        mDebugDumpCond.wait_for(lock, kDebugDumpTimeout,
-                                [this]() { return !mDebugDumpPending; });
-        if (mDebugDumpPending) {
-          ALOGI("Timed out waiting on debug dump data");
-          mDebugDumpPending = false;
-        }
-      }
-      writeToDebugFile("\n-- End of CHRE/ASH debug info --\n");
-
-      mDebugFd = kInvalidFd;
-      ALOGV("Debug dump complete");
-    }
-
+    debugDumpStart(hidlHandleToFileDescriptor(fd));
     return Void();
   }
 
@@ -359,22 +339,30 @@
   }
 
   void onDebugDumpData(const ::chre::fbs::DebugDumpDataT &data) override {
-    if (mDebugFd == kInvalidFd) {
-      ALOGW("Got unexpected debug dump data message");
-    } else {
-      writeToDebugFile(reinterpret_cast<const char *>(data.debug_str.data()),
-                       data.debug_str.size());
-    }
+    auto str =
+        std::string(reinterpret_cast<const char *>(data.debug_str.data()),
+                    data.debug_str.size());
+    debugDumpAppend(str);
   }
 
   void onDebugDumpComplete(
       const ::chre::fbs::DebugDumpResponseT & /* response */) override {
-    std::lock_guard<std::mutex> lock(mDebugDumpMutex);
-    if (!mDebugDumpPending) {
-      ALOGI("Ignoring duplicate/unsolicited debug dump response");
+    debugDumpComplete();
+  }
+
+  // Write a string to the debug file.
+  void writeToDebugFile(const char *str) override {
+    if (checkDebugFd()) {
+      size_t len = strlen(str);
+      ssize_t written = write(getDebugFd(), str, len);
+      if (written != (ssize_t)len) {
+        ALOGW(
+            "Couldn't write to debug header: returned %zd, expected %zu (errno "
+            "%d)",
+            written, len, errno);
+      }
     } else {
-      mDebugDumpPending = false;
-      mDebugDumpCond.notify_all();
+      ALOGE("Attempted to write to an invalid FD");
     }
   }
 
@@ -408,27 +396,6 @@
   std::mutex mHubInfoMutex;
   std::condition_variable mHubInfoCond;
 
-  static constexpr int kInvalidFd = -1;
-  int mDebugFd = kInvalidFd;
-  bool mDebugDumpPending = false;
-  std::mutex mDebugDumpMutex;
-  std::condition_variable mDebugDumpCond;
-
-  // Write a string to mDebugFd
-  void writeToDebugFile(const char *str) {
-    writeToDebugFile(str, strlen(str));
-  }
-
-  void writeToDebugFile(const char *str, size_t len) {
-    ssize_t written = write(mDebugFd, str, len);
-    if (written != (ssize_t)len) {
-      ALOGW(
-          "Couldn't write to debug header: returned %zd, expected %zu (errno "
-          "%d)",
-          written, len, errno);
-    }
-  }
-
   // Unregisters callback when context hub service dies
   void handleServiceDeath(uint32_t hubId) {
     std::lock_guard<std::mutex> lock(mCallbacksLock);
diff --git a/host/hal_generic/common/hal_chre_socket_connection.cc b/host/hal_generic/common/hal_chre_socket_connection.cc
index 2070508..b113e44 100644
--- a/host/hal_generic/common/hal_chre_socket_connection.cc
+++ b/host/hal_generic/common/hal_chre_socket_connection.cc
@@ -24,7 +24,7 @@
 #ifdef CHRE_HAL_SOCKET_METRICS_ENABLED
 #include <aidl/android/frameworks/stats/IStats.h>
 #include <android/binder_manager.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+#include <chre_atoms_log.h>
 #include <utils/SystemClock.h>
 #endif  // CHRE_HAL_SOCKET_METRICS_ENABLED
 
@@ -43,7 +43,6 @@
 using ::aidl::android::frameworks::stats::IStats;
 using ::aidl::android::frameworks::stats::VendorAtom;
 using ::aidl::android::frameworks::stats::VendorAtomValue;
-namespace PixelAtoms = ::android::hardware::google::pixel::PixelAtoms;
 #endif  // CHRE_HAL_SOCKET_METRICS_ENABLED
 
 HalChreSocketConnection::HalChreSocketConnection(
@@ -95,7 +94,13 @@
   return mHubInfoValid;
 }
 
-bool HalChreSocketConnection::sendMessageToHub(long nanoappId,
+bool HalChreSocketConnection::sendDebugConfiguration() {
+  FlatBufferBuilder builder;
+  HostProtocolHost::encodeDebugConfiguration(builder);
+  return mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize());
+}
+
+bool HalChreSocketConnection::sendMessageToHub(uint64_t nanoappId,
                                                uint32_t messageType,
                                                uint16_t hostEndpointId,
                                                const unsigned char *payload,
@@ -168,6 +173,11 @@
   return mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize());
 }
 
+bool HalChreSocketConnection::isLoadTransactionPending() {
+  std::lock_guard<std::mutex> lock(mPendingLoadTransactionMutex);
+  return mPendingLoadTransaction.has_value();
+}
+
 HalChreSocketConnection::SocketCallbacks::SocketCallbacks(
     HalChreSocketConnection &parent, IChreSocketCallback *callback)
     : mParent(parent), mCallback(callback) {
@@ -189,6 +199,7 @@
     ALOGI("Reconnected to CHRE daemon");
     mCallback->onContextHubRestarted();
   }
+  mParent.sendDebugConfiguration();
   mHaveConnected = true;
 }
 
@@ -220,8 +231,7 @@
       values[0].set<VendorAtomValue::longValue>(nanoappId);
 
       const VendorAtom atom{
-          .reverseDomainName = "",
-          .atomId = PixelAtoms::Atom::kChreApWakeUpOccurred,
+          .atomId = chre::Atoms::CHRE_AP_WAKE_UP_OCCURRED,
           .values{std::move(values)},
       };
 
@@ -341,13 +351,12 @@
     std::vector<VendorAtomValue> values(3);
     values[0].set<VendorAtomValue::longValue>(request.appId);
     values[1].set<VendorAtomValue::intValue>(
-        PixelAtoms::ChreHalNanoappLoadFailed::TYPE_DYNAMIC);
+        chre::Atoms::ChreHalNanoappLoadFailed::TYPE_DYNAMIC);
     values[2].set<VendorAtomValue::intValue>(
-        PixelAtoms::ChreHalNanoappLoadFailed::REASON_ERROR_GENERIC);
+        chre::Atoms::ChreHalNanoappLoadFailed::REASON_ERROR_GENERIC);
 
     const VendorAtom atom{
-        .reverseDomainName = "",
-        .atomId = PixelAtoms::Atom::kChreHalNanoappLoadFailed,
+        .atomId = chre::Atoms::CHRE_HAL_NANOAPP_LOAD_FAILED,
         .values{std::move(values)},
     };
     reportMetric(atom);
@@ -363,7 +372,6 @@
 
 #ifdef CHRE_HAL_SOCKET_METRICS_ENABLED
 void HalChreSocketConnection::reportMetric(const VendorAtom atom) {
-  // check service availability
   const std::string statsServiceName =
       std::string(IStats::descriptor).append("/default");
   if (!AServiceManager_isDeclared(statsServiceName.c_str())) {
@@ -371,9 +379,12 @@
     return;
   }
 
-  // obtain the service
   std::shared_ptr<IStats> stats_client = IStats::fromBinder(ndk::SpAIBinder(
       AServiceManager_waitForService(statsServiceName.c_str())));
+  if (stats_client == nullptr) {
+    ALOGE("Failed to get IStats service");
+    return;
+  }
 
   const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(atom);
   if (!ret.isOk()) {
diff --git a/host/hal_generic/common/hal_chre_socket_connection.h b/host/hal_generic/common/hal_chre_socket_connection.h
index 983816c..b3bb294 100644
--- a/host/hal_generic/common/hal_chre_socket_connection.h
+++ b/host/hal_generic/common/hal_chre_socket_connection.h
@@ -26,7 +26,6 @@
 
 #ifdef CHRE_HAL_SOCKET_METRICS_ENABLED
 #include <aidl/android/frameworks/stats/IStats.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
 #endif  // CHRE_HAL_SOCKET_METRICS_ENABLED
 
 namespace android {
@@ -99,10 +98,12 @@
 
   bool getContextHubs(::chre::fbs::HubInfoResponseT *response);
 
-  bool sendMessageToHub(long nanoappId, uint32_t messageType,
+  bool sendMessageToHub(uint64_t nanoappId, uint32_t messageType,
                         uint16_t hostEndpointId, const unsigned char *payload,
                         size_t payloadLength);
 
+  bool sendDebugConfiguration();
+
   bool loadNanoapp(chre::FragmentedLoadTransaction &transaction);
 
   bool unloadNanoapp(uint64_t appId, uint32_t transactionId);
@@ -120,6 +121,15 @@
 
   bool onHostEndpointDisconnected(uint16_t hostEndpointId);
 
+  /**
+   * Returns true if there exists a pending load transaction; false otherwise.
+   *
+   * @return true                     there exists a pending load transaction.
+   * @return false                    there does not exist a pending load
+   * transaction.
+   */
+  bool isLoadTransactionPending();
+
  private:
   class SocketCallbacks : public ::android::chre::SocketClient::ICallbacks,
                           public ::android::chre::IChreMessageHandlers {
diff --git a/host/hal_generic/common/hal_client_id.h b/host/hal_generic/common/hal_client_id.h
new file mode 100644
index 0000000..fb6e2a5
--- /dev/null
+++ b/host/hal_generic/common/hal_client_id.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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 ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_ID_H_
+#define ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_ID_H_
+
+#include <limits>
+
+namespace android::hardware::contexthub::common::implementation {
+
+using HalClientId = uint16_t;
+
+/** The max HAL client Id. */
+constexpr HalClientId kMaxHalClientId = 0x1ff;
+
+/** Max number of HAL clients supported. */
+constexpr uint16_t kMaxNumOfHalClients = kMaxHalClientId - 1;
+
+/** The default HAL client id indicating the id is not assigned. */
+constexpr HalClientId kDefaultHalClientId = 0;
+
+/**
+ * The HAL client id indicating the message is actually sent to the HAL itself.
+ */
+constexpr HalClientId kHalId = kMaxHalClientId;
+
+}  // namespace android::hardware::contexthub::common::implementation
+
+#endif  // ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_ID_H_
diff --git a/host/hal_generic/common/hal_client_manager.cc b/host/hal_generic/common/hal_client_manager.cc
new file mode 100644
index 0000000..f6945eb
--- /dev/null
+++ b/host/hal_generic/common/hal_client_manager.cc
@@ -0,0 +1,502 @@
+/*
+ * Copyright (C) 2022 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 "hal_client_manager.h"
+#include <aidl/android/hardware/contexthub/AsyncEventType.h>
+#include <android-base/strings.h>
+#include <json/json.h>
+#include <utils/SystemClock.h>
+#include <fstream>
+
+namespace android::hardware::contexthub::common::implementation {
+
+using aidl::android::hardware::contexthub::AsyncEventType;
+using aidl::android::hardware::contexthub::ContextHubMessage;
+using aidl::android::hardware::contexthub::HostEndpointInfo;
+using aidl::android::hardware::contexthub::IContextHubCallback;
+
+namespace {
+bool getClientMappingsFromFile(const char *filePath, Json::Value &mappings) {
+  std::fstream file(filePath);
+  Json::CharReaderBuilder builder;
+  return file.good() &&
+         Json::parseFromStream(builder, file, &mappings, /* errs= */ nullptr);
+}
+}  // namespace
+
+std::optional<HalClientId> HalClientManager::createClientIdLocked(
+    const std::string &processName) {
+  if (mPIdsToClientIds.size() > kMaxNumOfHalClients ||
+      mNextClientId > kMaxHalClientId) {
+    LOGE("Too many HAL clients registered which should never happen.");
+    return std::nullopt;
+  }
+  if (mProcessNamesToClientIds.find(processName) !=
+      mProcessNamesToClientIds.end()) {
+    return mProcessNamesToClientIds[processName];
+  }
+  // Update the json list with the new mapping
+  mProcessNamesToClientIds.emplace(processName, mNextClientId);
+  Json::Value mappings;
+  for (const auto &[name, clientId] : mProcessNamesToClientIds) {
+    Json::Value mapping;
+    mapping[kJsonProcessName] = name;
+    mapping[kJsonClientId] = clientId;
+    mappings.append(mapping);
+  }
+  // write to the file; Create the file if it doesn't exist
+  Json::StreamWriterBuilder factory;
+  std::unique_ptr<Json::StreamWriter> const writer(factory.newStreamWriter());
+  std::ofstream fileStream(kClientMappingFilePath);
+  writer->write(mappings, &fileStream);
+  fileStream << std::endl;
+  return {mNextClientId++};
+}
+
+HalClientId HalClientManager::getClientId() {
+  pid_t pid = AIBinder_getCallingPid();
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (isKnownPIdLocked(pid)) {
+    return mPIdsToClientIds[pid];
+  }
+  LOGE("Failed to find the client id for pid %d", pid);
+  return kDefaultHalClientId;
+}
+
+std::shared_ptr<IContextHubCallback> HalClientManager::getCallback(
+    HalClientId clientId) {
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (isAllocatedClientIdLocked(clientId)) {
+    return mClientIdsToClientInfo.at(clientId).callback;
+  }
+  LOGE("Failed to find the callback for the client id %" PRIu16, clientId);
+  return nullptr;
+}
+
+bool HalClientManager::registerCallback(
+    const std::shared_ptr<IContextHubCallback> &callback,
+    const ndk::ScopedAIBinder_DeathRecipient &deathRecipient,
+    void *deathRecipientCookie) {
+  pid_t pid = AIBinder_getCallingPid();
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (AIBinder_linkToDeath(callback->asBinder().get(), deathRecipient.get(),
+                           deathRecipientCookie) != STATUS_OK) {
+    LOGE("Failed to link client binder to death recipient.");
+    return false;
+  }
+  if (isKnownPIdLocked(pid)) {
+    LOGW("The pid %d has already registered. Overriding its callback.", pid);
+    return overrideCallbackLocked(pid, callback, deathRecipient,
+                                  deathRecipientCookie);
+  }
+  std::string processName = getProcessName(pid);
+  std::optional<HalClientId> clientIdOptional =
+      createClientIdLocked(processName);
+  if (clientIdOptional == std::nullopt) {
+    LOGE("Failed to generate a valid client id for process %s",
+         processName.c_str());
+    return false;
+  }
+  HalClientId clientId = clientIdOptional.value();
+  if (mClientIdsToClientInfo.find(clientId) != mClientIdsToClientInfo.end()) {
+    LOGE("Process %s already has a connection to HAL.", processName.c_str());
+    return false;
+  }
+  mPIdsToClientIds[pid] = clientId;
+  mClientIdsToClientInfo.emplace(clientId,
+                                 HalClientInfo(callback, deathRecipientCookie));
+  if (mFrameworkServiceClientId == kDefaultHalClientId &&
+      processName == kSystemServerName) {
+    mFrameworkServiceClientId = clientId;
+  }
+  return true;
+}
+
+bool HalClientManager::overrideCallbackLocked(
+    pid_t pid, const std::shared_ptr<IContextHubCallback> &callback,
+    const ndk::ScopedAIBinder_DeathRecipient &deathRecipient,
+    void *deathRecipientCookie) {
+  LOGI("Overriding the callback for pid %d", pid);
+  HalClientInfo &clientInfo =
+      mClientIdsToClientInfo.at(mPIdsToClientIds.at(pid));
+  if (AIBinder_unlinkToDeath(clientInfo.callback->asBinder().get(),
+                             deathRecipient.get(),
+                             clientInfo.deathRecipientCookie) != STATUS_OK) {
+    LOGE("Unable to unlink the old callback for pid %d", pid);
+    return false;
+  }
+  clientInfo.callback.reset();
+  clientInfo.callback = callback;
+  clientInfo.deathRecipientCookie = deathRecipientCookie;
+  return true;
+}
+
+void HalClientManager::handleClientDeath(
+    pid_t pid, const ndk::ScopedAIBinder_DeathRecipient &deathRecipient) {
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (!isKnownPIdLocked(pid)) {
+    LOGE("Failed to locate the dead pid %d", pid);
+    return;
+  }
+  HalClientId clientId = mPIdsToClientIds[pid];
+  mPIdsToClientIds.erase(mPIdsToClientIds.find(pid));
+  if (!isAllocatedClientIdLocked(clientId)) {
+    LOGE("Failed to locate the dead client id %" PRIu16, clientId);
+    return;
+  }
+
+  for (const auto &[procName, id] : mProcessNamesToClientIds) {
+    if (id == clientId && procName == kSystemServerName) {
+      LOGE("System server is disconnected");
+      mIsFirstClient = true;
+    }
+  }
+
+  HalClientInfo &clientInfo = mClientIdsToClientInfo.at(clientId);
+  if (AIBinder_unlinkToDeath(clientInfo.callback->asBinder().get(),
+                             deathRecipient.get(),
+                             clientInfo.deathRecipientCookie) != STATUS_OK) {
+    LOGE("Unable to unlink the old callback for pid %d in death handler", pid);
+  }
+  clientInfo.callback.reset();
+  if (mPendingLoadTransaction.has_value() &&
+      mPendingLoadTransaction->clientId == clientId) {
+    mPendingLoadTransaction.reset();
+  }
+  if (mPendingUnloadTransaction.has_value() &&
+      mPendingUnloadTransaction->clientId == clientId) {
+    mPendingLoadTransaction.reset();
+  }
+  mClientIdsToClientInfo.erase(clientId);
+  if (mFrameworkServiceClientId == clientId) {
+    mFrameworkServiceClientId = kDefaultHalClientId;
+  }
+  LOGI("Process %" PRIu32 " is disconnected from HAL.", pid);
+}
+
+bool HalClientManager::registerPendingLoadTransaction(
+    std::unique_ptr<chre::FragmentedLoadTransaction> transaction) {
+  if (transaction->isComplete()) {
+    LOGW("No need to register a completed load transaction.");
+    return false;
+  }
+  pid_t pid = AIBinder_getCallingPid();
+
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (!isKnownPIdLocked(pid)) {
+    LOGE("Unknown HAL client when registering its pending load transaction.");
+    return false;
+  }
+  auto clientId = mPIdsToClientIds[pid];
+  if (!isNewTransactionAllowedLocked(clientId)) {
+    return false;
+  }
+  mPendingLoadTransaction.emplace(
+      clientId, /* registeredTimeMs= */ android::elapsedRealtime(),
+      /* currentFragmentId= */ 0, std::move(transaction));
+  return true;
+}
+
+std::optional<chre::FragmentedLoadRequest>
+HalClientManager::getNextFragmentedLoadRequest() {
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (mPendingLoadTransaction->transaction->isComplete()) {
+    LOGI("Pending load transaction %" PRIu32
+         " is finished with client %" PRIu16,
+         mPendingLoadTransaction->transaction->getTransactionId(),
+         mPendingLoadTransaction->clientId);
+    mPendingLoadTransaction.reset();
+    return std::nullopt;
+  }
+  auto request = mPendingLoadTransaction->transaction->getNextRequest();
+  mPendingLoadTransaction->currentFragmentId = request.fragmentId;
+  LOGD("Client %" PRIu16 " has fragment #%zu ready",
+       mPendingLoadTransaction->clientId, request.fragmentId);
+  return request;
+}
+
+bool HalClientManager::registerPendingUnloadTransaction(
+    uint32_t transactionId) {
+  pid_t pid = AIBinder_getCallingPid();
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (!isKnownPIdLocked(pid)) {
+    LOGE("Unknown HAL client when registering its pending unload transaction.");
+    return false;
+  }
+  auto clientId = mPIdsToClientIds[pid];
+  if (!isNewTransactionAllowedLocked(clientId)) {
+    return false;
+  }
+  mPendingUnloadTransaction.emplace(
+      clientId, transactionId,
+      /* registeredTimeMs= */ android::elapsedRealtime());
+  return true;
+}
+
+bool HalClientManager::isNewTransactionAllowedLocked(HalClientId clientId) {
+  if (mPendingLoadTransaction.has_value()) {
+    auto timeElapsedMs =
+        android::elapsedRealtime() - mPendingLoadTransaction->registeredTimeMs;
+    if (timeElapsedMs < kTransactionTimeoutThresholdMs) {
+      LOGE("Rejects client %" PRIu16
+           "'s transaction because an active load "
+           "transaction %" PRIu32 " with current fragment id %" PRIu32
+           " from client %" PRIu16 " exists. Try again later.",
+           clientId, mPendingLoadTransaction->transaction->getTransactionId(),
+           mPendingLoadTransaction->currentFragmentId,
+           mPendingLoadTransaction->clientId);
+      return false;
+    }
+    LOGE("Client %" PRIu16 "'s pending load transaction %" PRIu32
+         " with current fragment id %" PRIu32
+         " is overridden by client %" PRIu16
+         " after holding the slot for %" PRIu64 " ms",
+         mPendingLoadTransaction->clientId,
+         mPendingLoadTransaction->transaction->getTransactionId(),
+         mPendingLoadTransaction->currentFragmentId, clientId, timeElapsedMs);
+    mPendingLoadTransaction.reset();
+    return true;
+  }
+  if (mPendingUnloadTransaction.has_value()) {
+    auto timeElapsedMs = android::elapsedRealtime() -
+                         mPendingUnloadTransaction->registeredTimeMs;
+    if (timeElapsedMs < kTransactionTimeoutThresholdMs) {
+      LOGE("Rejects client %" PRIu16
+           "'s transaction because an active unload "
+           "transaction %" PRIu32 " from client %" PRIu16
+           " exists. Try again later.",
+           clientId, mPendingUnloadTransaction->transactionId,
+           mPendingUnloadTransaction->clientId);
+      return false;
+    }
+    LOGE("A pending unload transaction %" PRIu32
+         " registered by client %" PRIu16
+         " is overridden by a new transaction from client %" PRIu16
+         " after holding the slot for %" PRIu64 "ms",
+         mPendingUnloadTransaction->transactionId,
+         mPendingUnloadTransaction->clientId, clientId, timeElapsedMs);
+    mPendingUnloadTransaction.reset();
+    return true;
+  }
+  return true;
+}
+
+bool HalClientManager::registerEndpointId(const HostEndpointId &endpointId) {
+  pid_t pid = AIBinder_getCallingPid();
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (!isKnownPIdLocked(pid)) {
+    LOGE(
+        "Unknown HAL client (pid %d). Register the callback before registering "
+        "an endpoint.",
+        pid);
+    return false;
+  }
+  HalClientId clientId = mPIdsToClientIds[pid];
+  if (!isValidEndpointId(clientId, endpointId)) {
+    LOGE("Endpoint id %" PRIu16 " from process %d is out of range.", endpointId,
+         pid);
+    return false;
+  }
+  if (mClientIdsToClientInfo[clientId].endpointIds.find(endpointId) !=
+      mClientIdsToClientInfo[clientId].endpointIds.end()) {
+    LOGW("The endpoint %" PRIu16 " is already connected.", endpointId);
+    return false;
+  }
+  mClientIdsToClientInfo[clientId].endpointIds.insert(endpointId);
+  LOGI("Endpoint id %" PRIu16 " is connected to client %" PRIu16, endpointId,
+       clientId);
+  return true;
+}
+
+bool HalClientManager::removeEndpointId(const HostEndpointId &endpointId) {
+  pid_t pid = AIBinder_getCallingPid();
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (!isKnownPIdLocked(pid)) {
+    LOGE(
+        "Unknown HAL client (pid %d). A callback should have been registered "
+        "before removing an endpoint.",
+        pid);
+    return false;
+  }
+  HalClientId clientId = mPIdsToClientIds[pid];
+  if (!isValidEndpointId(clientId, endpointId)) {
+    LOGE("Endpoint id %" PRIu16 " from process %d is out of range.", endpointId,
+         pid);
+    return false;
+  }
+  if (mClientIdsToClientInfo[clientId].endpointIds.find(endpointId) ==
+      mClientIdsToClientInfo[clientId].endpointIds.end()) {
+    LOGW("The endpoint %" PRIu16 " is not connected.", endpointId);
+    return false;
+  }
+  mClientIdsToClientInfo[clientId].endpointIds.erase(endpointId);
+  LOGI("Endpoint id %" PRIu16 " is removed from client %" PRIu16, endpointId,
+       clientId);
+  return true;
+}
+
+std::shared_ptr<IContextHubCallback> HalClientManager::getCallbackForEndpoint(
+    const HostEndpointId &endpointId) {
+  const std::lock_guard<std::mutex> lock(mLock);
+  HalClientId clientId = getClientIdFromEndpointId(endpointId);
+  if (!isAllocatedClientIdLocked(clientId)) {
+    LOGE("Unknown endpoint id %" PRIu16 ". Please register the callback first.",
+         endpointId);
+    return nullptr;
+  }
+  return mClientIdsToClientInfo[clientId].callback;
+}
+
+void HalClientManager::sendMessageForAllCallbacks(
+    const ContextHubMessage &message,
+    const std::vector<std::string> &messageParams) {
+  const std::lock_guard<std::mutex> lock(mLock);
+  for (const auto &[_, clientInfo] : mClientIdsToClientInfo) {
+    if (clientInfo.callback != nullptr) {
+      clientInfo.callback->handleContextHubMessage(message, messageParams);
+    }
+  }
+}
+
+const std::unordered_set<HostEndpointId>
+    *HalClientManager::getAllConnectedEndpoints(pid_t pid) {
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (!isKnownPIdLocked(pid)) {
+    LOGE("Unknown HAL client with pid %d", pid);
+    return nullptr;
+  }
+  HalClientId clientId = mPIdsToClientIds[pid];
+  if (mClientIdsToClientInfo.find(clientId) == mClientIdsToClientInfo.end()) {
+    LOGE("Can't find any information for client id %" PRIu16, clientId);
+    return nullptr;
+  }
+  return &mClientIdsToClientInfo[clientId].endpointIds;
+}
+
+bool HalClientManager::mutateEndpointIdFromHostIfNeeded(
+    const pid_t &pid, HostEndpointId &endpointId) {
+  const std::lock_guard<std::mutex> lock(mLock);
+  if (!isKnownPIdLocked(pid)) {
+    LOGE("Unknown HAL client with pid %d", pid);
+    return false;
+  }
+  // no need to mutate client id for framework service
+  if (mPIdsToClientIds[pid] != mFrameworkServiceClientId) {
+    HalClientId clientId = mPIdsToClientIds[pid];
+    endpointId = kVendorEndpointIdBitMask |
+                 clientId << kNumOfBitsForEndpointId | endpointId;
+  }
+  return true;
+}
+
+HostEndpointId HalClientManager::convertToOriginalEndpointId(
+    const HostEndpointId &endpointId) {
+  if (endpointId & kVendorEndpointIdBitMask) {
+    return endpointId & kMaxVendorEndpointId;
+  }
+  return endpointId;
+}
+
+HalClientManager::HalClientManager() {
+  // Parses the file to construct a mapping from process names to client ids.
+  Json::Value mappings;
+  if (!getClientMappingsFromFile(kClientMappingFilePath, mappings)) {
+    // TODO(b/247124878): When the device was firstly booted up the file doesn't
+    //   exist which is expected. Consider to create a default file to avoid
+    //   confusions.
+    LOGW("Unable to find and read %s.", kClientMappingFilePath);
+    return;
+  }
+  for (int i = 0; i < mappings.size(); i++) {
+    Json::Value mapping = mappings[i];
+    if (!mapping.isMember(kJsonClientId) ||
+        !mapping.isMember(kJsonProcessName)) {
+      LOGE("Unable to find expected key name for the entry %d", i);
+      continue;
+    }
+    std::string processName = mapping[kJsonProcessName].asString();
+    auto clientId = static_cast<HalClientId>(mapping[kJsonClientId].asUInt());
+    mProcessNamesToClientIds[processName] = clientId;
+    // mNextClientId should always hold the next available client id
+    if (mNextClientId <= clientId) {
+      mNextClientId = clientId + 1;
+    }
+  }
+}
+
+bool HalClientManager::isPendingLoadTransactionMatchedLocked(
+    HalClientId clientId, uint32_t transactionId, uint32_t currentFragmentId) {
+  bool success =
+      isPendingTransactionMatchedLocked(clientId, transactionId,
+                                        mPendingLoadTransaction) &&
+      mPendingLoadTransaction->currentFragmentId == currentFragmentId;
+  if (!success) {
+    if (mPendingLoadTransaction.has_value()) {
+      LOGE("Transaction of client %" PRIu16 " transaction %" PRIu32
+           " fragment %" PRIu32
+           " doesn't match the current pending transaction (client %" PRIu16
+           " transaction %" PRIu32 " fragment %" PRIu32 ").",
+           clientId, transactionId, currentFragmentId,
+           mPendingLoadTransaction->clientId,
+           mPendingLoadTransaction->transactionId,
+           mPendingLoadTransaction->currentFragmentId);
+    } else {
+      LOGE("Transaction of client %" PRIu16 " transaction %" PRIu32
+           " fragment %" PRIu32 " doesn't match any pending transaction.",
+           clientId, transactionId, currentFragmentId);
+    }
+  }
+  return success;
+}
+
+void HalClientManager::resetPendingLoadTransaction() {
+  const std::lock_guard<std::mutex> lock(mLock);
+  mPendingLoadTransaction.reset();
+}
+
+bool HalClientManager::resetPendingUnloadTransaction(HalClientId clientId,
+                                                     uint32_t transactionId) {
+  const std::lock_guard<std::mutex> lock(mLock);
+  // Only clear a pending transaction when the client id and the transaction id
+  // are both matched
+  if (isPendingTransactionMatchedLocked(clientId, transactionId,
+                                        mPendingUnloadTransaction)) {
+    LOGI("Clears out the pending unload transaction: client id %" PRIu16
+         ", transaction id %" PRIu32,
+         clientId, transactionId);
+    mPendingUnloadTransaction.reset();
+    return true;
+  }
+  LOGW("Client %" PRIu16 " doesn't have a pending unload transaction %" PRIu32
+       ". Skip resetting",
+       clientId, transactionId);
+  return false;
+}
+
+void HalClientManager::handleChreRestart() {
+  {
+    const std::lock_guard<std::mutex> lock(mLock);
+    mPendingLoadTransaction.reset();
+    mPendingUnloadTransaction.reset();
+    for (auto &[_, clientInfo] : mClientIdsToClientInfo) {
+      clientInfo.endpointIds.clear();
+    }
+  }
+  // Incurs callbacks without holding the lock to avoid deadlocks.
+  for (auto &[_, clientInfo] : mClientIdsToClientInfo) {
+    clientInfo.callback->handleContextHubAsyncEvent(AsyncEventType::RESTARTED);
+  }
+}
+}  // namespace android::hardware::contexthub::common::implementation
\ No newline at end of file
diff --git a/host/hal_generic/common/hal_client_manager.h b/host/hal_generic/common/hal_client_manager.h
new file mode 100644
index 0000000..66ca859
--- /dev/null
+++ b/host/hal_generic/common/hal_client_manager.h
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2022 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 ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_
+#define ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_
+
+#include <aidl/android/hardware/contexthub/ContextHubMessage.h>
+#include <aidl/android/hardware/contexthub/IContextHub.h>
+#include <aidl/android/hardware/contexthub/IContextHubCallback.h>
+#include <chre_host/fragmented_load_transaction.h>
+#include <chre_host/preloaded_nanoapp_loader.h>
+#include <sys/types.h>
+#include <cstddef>
+#include <unordered_map>
+#include <unordered_set>
+#include "chre_host/log.h"
+#include "hal_client_id.h"
+
+using aidl::android::hardware::contexthub::ContextHubMessage;
+using aidl::android::hardware::contexthub::HostEndpointInfo;
+using aidl::android::hardware::contexthub::IContextHubCallback;
+using android::chre::FragmentedLoadTransaction;
+using HostEndpointId = uint16_t;
+
+namespace android::hardware::contexthub::common::implementation {
+
+/**
+ * A class managing clients for Context Hub HAL.
+ *
+ * A HAL client is defined as a user calling the IContextHub API. The main
+ * purpose of this class are:
+ *   - to assign a unique HalClientId identifying each client;
+ *   - to maintain a mapping between client ids and HalClientInfos;
+ *   - to maintain a mapping between client ids and their endpoint ids.
+ *
+ * There are two types of ids HalClientManager will track, host endpoint id and
+ * client id. A host endpoint id, which is defined at
+ * hardware/interfaces/contexthub/aidl/android/hardware/contexthub/ContextHubMessage.aidl,
+ * identifies a host app that communicates with a HAL client. A client id
+ * identifies a HAL client, which is the layer beneath the host apps, such as
+ * ContextHubService. Multiple apps with different host endpoint IDs can have
+ * the same client ID.
+ *
+ * For a host endpoint connected to ContextHubService, its endpoint id is kept
+ *in the form below during the communication with CHRE.
+ *
+ *  0                   1
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |0|      endpoint_id            |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * For vendor host endpoints,  the client id is embedded into the endpoint id
+ * before sending a message to CHRE. When that happens, the highest bit is set
+ * to 1 and the endpoint id is mutated to the format below:
+ *
+ *  0                   1
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |1|   client_id     |endpoint_id|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Note that HalClientManager is not responsible for generating endpoint ids,
+ * which should be managed by HAL clients themselves.
+ */
+class HalClientManager {
+ public:
+  HalClientManager();
+  virtual ~HalClientManager() = default;
+
+  /** Disable copy constructor and copy assignment to avoid duplicates. */
+  HalClientManager(HalClientManager &) = delete;
+  void operator=(const HalClientManager &) = delete;
+
+  /**
+   * Gets the client id allocated to the current HAL client.
+   *
+   * The current HAL client is identified by its process id, which is retrieved
+   * by calling AIBinder_getCallingPid(). If the process doesn't have any client
+   * id assigned, HalClientManager will create one mapped to its process id.
+   *
+   * @return client id assigned to the calling process, or kDefaultHalClientId
+   * if the process id is not found.
+   */
+  HalClientId getClientId();
+
+  /**
+   * Gets the callback for the current HAL client identified by the clientId.
+   *
+   * @return callback previously registered. nullptr is returned if the clientId
+   * is not found.
+   */
+  std::shared_ptr<IContextHubCallback> getCallback(HalClientId clientId);
+
+  /**
+   * Registers a IContextHubCallback function mapped to the current client's
+   * client id. @p deathRecipient and @p deathRecipientCookie are used to unlink
+   * the previous registered callback for the same client, if any.
+   *
+   * @param callback a function incurred to handle the client death event.
+   * @param deathRecipient a handle on the death notification.
+   * @param deathRecipientCookie the data used by the callback.
+   *
+   * @return true if success, otherwise false.
+   */
+  bool registerCallback(
+      const std::shared_ptr<IContextHubCallback> &callback,
+      const ndk::ScopedAIBinder_DeathRecipient &deathRecipient,
+      void *deathRecipientCookie);
+
+  /**
+   * Registers a FragmentedLoadTransaction for the current HAL client.
+   *
+   * At this moment only one active transaction, either load or unload, is
+   * supported.
+   *
+   * @return true if success, otherwise false.
+   */
+  bool registerPendingLoadTransaction(
+      std::unique_ptr<FragmentedLoadTransaction> transaction);
+
+  /**
+   * Returns true if the load transaction matches the arguments provided.
+   */
+  bool isPendingLoadTransactionExpected(HalClientId clientId,
+                                        uint32_t transactionId,
+                                        uint32_t currentFragmentId) {
+    const std::lock_guard<std::mutex> lock(mLock);
+    return isPendingLoadTransactionMatchedLocked(clientId, transactionId,
+                                                 currentFragmentId);
+  }
+
+  /**
+   * Clears the pending load transaction.
+   *
+   * This function is called to proactively clear out a pending load transaction
+   * that is not timed out yet.
+   *
+   */
+  void resetPendingLoadTransaction();
+
+  /**
+   * Gets the next FragmentedLoadRequest from PendingLoadTransaction if it's
+   * available.
+   *
+   * @return an optional FragmentedLoadRequest, std::nullopt if unavailable.
+   */
+
+  std::optional<chre::FragmentedLoadRequest> getNextFragmentedLoadRequest();
+
+  /**
+   * Registers the current HAL client as having a pending unload transaction.
+   *
+   * At this moment only one active transaction, either load or unload, is
+   * supported.
+   *
+   * @return true if success, otherwise false.
+   */
+  bool registerPendingUnloadTransaction(uint32_t transactionId);
+
+  /**
+   * Clears the pending unload transaction.
+   *
+   * This function is called to proactively clear out a pending unload
+   * transaction that is not timed out yet. @p clientId and @p
+   * transactionId must match the existing pending transaction.
+   *
+   * @param clientId the client id of the caller.
+   * @param transactionId unique id of the transaction.
+   *
+   * @return true if the pending transaction is cleared, otherwise false.
+   */
+  bool resetPendingUnloadTransaction(HalClientId clientId,
+                                     uint32_t transactionId);
+
+  /**
+   * Registers an endpoint id when it is connected to HAL.
+   *
+   * @return true if success, otherwise false.
+   */
+  bool registerEndpointId(const HostEndpointId &endpointId);
+
+  /**
+   * Removes an endpoint id when it is disconnected to HAL.
+   *
+   * @return true if success, otherwise false.
+   */
+  bool removeEndpointId(const HostEndpointId &endpointId);
+
+  /**
+   * Mutates the endpoint id if the hal client is not the framework service.
+   *
+   * @return true if success, otherwise false.
+   */
+  bool mutateEndpointIdFromHostIfNeeded(const pid_t &pid,
+                                        HostEndpointId &endpointId);
+
+  /** Returns the original endpoint id sent by the host client. */
+  static HostEndpointId convertToOriginalEndpointId(
+      const HostEndpointId &endpointId);
+
+  /**
+   * Gets all the connected endpoints for the client identified by the pid.
+   *
+   * @return the pointer to the endpoint id set if the client is identifiable,
+   * otherwise nullptr.
+   */
+  const std::unordered_set<HostEndpointId> *getAllConnectedEndpoints(pid_t pid);
+
+  /** Sends a message to every connected endpoints. */
+  void sendMessageForAllCallbacks(
+      const ContextHubMessage &message,
+      const std::vector<std::string> &messageParams);
+
+  std::shared_ptr<IContextHubCallback> getCallbackForEndpoint(
+      const HostEndpointId &endpointId);
+
+  /**
+   * Handles the client death event.
+   *
+   * @param pid of the client that loses the binder connection to the HAL.
+   * @param deathRecipient to be unlinked with the client's callback
+   */
+  void handleClientDeath(
+      pid_t pid, const ndk::ScopedAIBinder_DeathRecipient &deathRecipient);
+
+  /** Handles CHRE restart event. */
+  void handleChreRestart();
+
+ protected:
+  static constexpr char kSystemServerName[] = "system_server";
+  static constexpr char kClientMappingFilePath[] =
+      "/data/vendor/chre/chre_hal_clients.json";
+  static constexpr char kJsonClientId[] = "ClientId";
+  static constexpr char kJsonProcessName[] = "ProcessName";
+  static constexpr int64_t kTransactionTimeoutThresholdMs = 5000;  // 5 seconds
+  static constexpr uint8_t kNumOfBitsForEndpointId = 6;
+  static constexpr HostEndpointId kMaxVendorEndpointId =
+      (1 << kNumOfBitsForEndpointId) - 1;
+  // The endpoint id is from a vendor client if the highest bit is set to 1.
+  static constexpr HostEndpointId kVendorEndpointIdBitMask = 0x8000;
+
+  struct HalClientInfo {
+    explicit HalClientInfo(const std::shared_ptr<IContextHubCallback> &callback,
+                           void *cookie) {
+      this->callback = callback;
+      this->deathRecipientCookie = cookie;
+    }
+    HalClientInfo() = default;
+    std::shared_ptr<IContextHubCallback> callback;
+    // cookie is used by the death recipient's linked callback
+    void *deathRecipientCookie{};
+    std::unordered_set<HostEndpointId> endpointIds{};
+  };
+
+  struct PendingTransaction {
+    PendingTransaction(HalClientId clientId, uint32_t transactionId,
+                       int64_t registeredTimeMs) {
+      this->clientId = clientId;
+      this->transactionId = transactionId;
+      this->registeredTimeMs = registeredTimeMs;
+    }
+    HalClientId clientId;
+    uint32_t transactionId;
+    int64_t registeredTimeMs;
+  };
+
+  /**
+   * PendingLoadTransaction tracks ongoing load transactions.
+   */
+  struct PendingLoadTransaction : public PendingTransaction {
+    PendingLoadTransaction(
+        HalClientId clientId, int64_t registeredTimeMs,
+        uint32_t currentFragmentId,
+        std::unique_ptr<chre::FragmentedLoadTransaction> transaction)
+        : PendingTransaction(clientId, transaction->getTransactionId(),
+                             registeredTimeMs) {
+      this->currentFragmentId = currentFragmentId;
+      this->transaction = std::move(transaction);
+    }
+    uint32_t currentFragmentId;  // the fragment id being sent out.
+    std::unique_ptr<chre::FragmentedLoadTransaction> transaction;
+
+    [[nodiscard]] std::string toString() const {
+      using android::internal::ToString;
+      return "[Load transaction: client id " + ToString(clientId) +
+             ", Transaction id " + ToString(transaction->getTransactionId()) +
+             ", fragment id " + ToString(currentFragmentId) + "]";
+    }
+  };
+
+  /**
+   * Creates a client id to uniquely identify a HAL client.
+   *
+   * A file is maintained on the device for the mappings between client names
+   * and client ids so that if a client has connected to HAL before the same
+   * client id is always assigned to it.
+   *
+   * mLock must be held when this function is called.
+   *
+   * @param processName the process name of the client
+   */
+  virtual std::optional<HalClientId> createClientIdLocked(
+      const std::string &processName);
+
+  /**
+   * Returns true if @p clientId and @p transactionId match the
+   * corresponding values in @p transaction.
+   *
+   * mLock must be held when this function is called.
+   */
+  static bool isPendingTransactionMatchedLocked(
+      HalClientId clientId, uint32_t transactionId,
+      const std::optional<PendingTransaction> &transaction) {
+    return transaction.has_value() && transaction->clientId == clientId &&
+           transaction->transactionId == transactionId;
+  }
+
+  /**
+   * Returns true if the load transaction is expected.
+   *
+   * mLock must be held when this function is called.
+   */
+  bool isPendingLoadTransactionMatchedLocked(HalClientId clientId,
+                                             uint32_t transactionId,
+                                             uint32_t currentFragmentId);
+
+  /**
+   * Checks if the transaction registration is allowed and clears out any stale
+   * pending transaction if possible.
+   *
+   * This function is called when registering a new transaction. The reason that
+   * we still proceed when there is already a pending transaction is because we
+   * don't want a stale one, for whatever reason, to block future transactions.
+   * However, every transaction is guaranteed to have up to
+   * kTransactionTimeoutThresholdMs to finish.
+   *
+   * mLock must be held when this function is called.
+   *
+   * @param clientId id of the client trying to register the transaction
+   *
+   * @return true if registration is allowed, otherwise false.
+   */
+  bool isNewTransactionAllowedLocked(HalClientId clientId);
+
+  /**
+   * Returns true if the clientId is being used.
+   *
+   * mLock must be held when this function is called.
+   */
+  inline bool isAllocatedClientIdLocked(HalClientId clientId) {
+    return mClientIdsToClientInfo.find(clientId) !=
+           mClientIdsToClientInfo.end();
+  }
+
+  /**
+   * Returns true if the pid is being used to identify a client.
+   *
+   * mLock must be held when this function is called.
+   */
+  inline bool isKnownPIdLocked(pid_t pid) {
+    return mPIdsToClientIds.find(pid) != mPIdsToClientIds.end();
+  }
+
+  /** Returns true if the endpoint id is within the accepted range. */
+  [[nodiscard]] inline bool isValidEndpointId(
+      const HalClientId &clientId, const HostEndpointId &endpointId) const {
+    if (clientId != mFrameworkServiceClientId) {
+      return endpointId <= kMaxVendorEndpointId;
+    }
+    return true;
+  }
+
+  /**
+   * Overrides the old callback registered with the client.
+   *
+   * @return true if success, otherwise false
+   */
+  bool overrideCallbackLocked(
+      pid_t pid, const std::shared_ptr<IContextHubCallback> &callback,
+      const ndk::ScopedAIBinder_DeathRecipient &deathRecipient,
+      void *deathRecipientCookie);
+
+  /**
+   * Extracts the client id from the endpoint id.
+   *
+   * @param endpointId the endpoint id received from CHRE, before any conversion
+   */
+  [[nodiscard]] inline HalClientId getClientIdFromEndpointId(
+      const HostEndpointId &endpointId) const {
+    if (endpointId & kVendorEndpointIdBitMask) {
+      return endpointId >> kNumOfBitsForEndpointId & kMaxHalClientId;
+    }
+    return mFrameworkServiceClientId;
+  }
+
+  std::string getProcessName(pid_t /*pid*/) {
+    // TODO(b/274597758): this is a temporary solution that should be updated
+    //   after b/274597758 is resolved.
+    if (mIsFirstClient) {
+      mIsFirstClient = false;
+      return kSystemServerName;
+    }
+    return "the_vendor_client";
+  }
+
+  bool mIsFirstClient = true;
+
+  // next available client id
+  HalClientId mNextClientId = kDefaultHalClientId + 1;
+  // framework service client id
+  HalClientId mFrameworkServiceClientId = kDefaultHalClientId;
+
+  // The lock guarding the access to clients' states and pending transactions
+  std::mutex mLock;
+
+  // Map from process name to client id which stays consistent with the file
+  // stored at kClientMappingFilePath
+  std::unordered_map<std::string, HalClientId> mProcessNamesToClientIds{};
+  // Map from pids to client ids
+  std::unordered_map<pid_t, HalClientId> mPIdsToClientIds{};
+  // Map from client ids to ClientInfos
+  std::unordered_map<HalClientId, HalClientInfo> mClientIdsToClientInfo{};
+
+  // States tracking pending transactions
+  std::optional<PendingLoadTransaction> mPendingLoadTransaction = std::nullopt;
+  std::optional<PendingTransaction> mPendingUnloadTransaction = std::nullopt;
+};
+}  // namespace android::hardware::contexthub::common::implementation
+
+#endif  // ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_
diff --git a/host/hal_generic/common/multi_client_context_hub_base.cc b/host/hal_generic/common/multi_client_context_hub_base.cc
new file mode 100644
index 0000000..950c09f
--- /dev/null
+++ b/host/hal_generic/common/multi_client_context_hub_base.cc
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2022 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 "multi_client_context_hub_base.h"
+#include <chre/platform/shared/host_protocol_common.h>
+#include <chre_host/generated/host_messages_generated.h>
+#include <chre_host/log.h>
+#include "chre/event.h"
+#include "chre_host/config_util.h"
+#include "chre_host/file_stream.h"
+#include "chre_host/fragmented_load_transaction.h"
+#include "chre_host/host_protocol_host.h"
+#include "permissions_util.h"
+
+namespace android::hardware::contexthub::common::implementation {
+
+using ::android::chre::FragmentedLoadTransaction;
+using ::android::chre::getStringFromByteVector;
+using ::ndk::ScopedAStatus;
+namespace fbs = ::chre::fbs;
+
+namespace {
+constexpr uint32_t kDefaultHubId = 0;
+
+// timeout for calling getContextHubs(), which is synchronous
+constexpr auto kHubInfoQueryTimeout = std::chrono::seconds(5);
+
+enum class HalErrorCode : int32_t {
+  OPERATION_FAILED = -1,
+  INVALID_RESULT = -2,
+  INVALID_ARGUMENT = -3,
+};
+
+bool isValidContextHubId(uint32_t hubId) {
+  if (hubId != kDefaultHubId) {
+    LOGE("Invalid context hub ID %" PRId32, hubId);
+    return false;
+  }
+  return true;
+}
+
+bool getFbsSetting(const Setting &setting, fbs::Setting *fbsSetting) {
+  bool foundSetting = true;
+  switch (setting) {
+    case Setting::LOCATION:
+      *fbsSetting = fbs::Setting::LOCATION;
+      break;
+    case Setting::AIRPLANE_MODE:
+      *fbsSetting = fbs::Setting::AIRPLANE_MODE;
+      break;
+    case Setting::MICROPHONE:
+      *fbsSetting = fbs::Setting::MICROPHONE;
+      break;
+    default:
+      foundSetting = false;
+      LOGE("Setting update with invalid enum value %hhu", setting);
+      break;
+  }
+  return foundSetting;
+}
+
+chre::fbs::SettingState toFbsSettingState(bool enabled) {
+  return enabled ? chre::fbs::SettingState::ENABLED
+                 : chre::fbs::SettingState::DISABLED;
+}
+
+// functions that extract different version numbers
+inline constexpr int8_t extractChreApiMajorVersion(uint32_t chreVersion) {
+  return static_cast<int8_t>(chreVersion >> 24);
+}
+inline constexpr int8_t extractChreApiMinorVersion(uint32_t chreVersion) {
+  return static_cast<int8_t>(chreVersion >> 16);
+}
+inline constexpr uint16_t extractChrePatchVersion(uint32_t chreVersion) {
+  return static_cast<uint16_t>(chreVersion);
+}
+
+// functions that help to generate ScopedAStatus from different values.
+inline ScopedAStatus fromServiceError(HalErrorCode errorCode) {
+  return ScopedAStatus::fromServiceSpecificError(
+      static_cast<int32_t>(errorCode));
+}
+inline ScopedAStatus fromResult(bool result) {
+  return result ? ScopedAStatus::ok()
+                : fromServiceError(HalErrorCode::OPERATION_FAILED);
+}
+}  // anonymous namespace
+
+ScopedAStatus MultiClientContextHubBase::getContextHubs(
+    std::vector<ContextHubInfo> *contextHubInfos) {
+  std::unique_lock<std::mutex> lock(mHubInfoMutex);
+  if (mContextHubInfo == nullptr) {
+    fbs::HubInfoResponseT response;
+    flatbuffers::FlatBufferBuilder builder;
+    HostProtocolHost::encodeHubInfoRequest(builder);
+    if (!mConnection->sendMessage(builder)) {
+      LOGE("Failed to send a message to CHRE to get context hub info.");
+      return fromServiceError(HalErrorCode::OPERATION_FAILED);
+    }
+    mHubInfoCondition.wait_for(lock, kHubInfoQueryTimeout,
+                               [this]() { return mContextHubInfo != nullptr; });
+  }
+  if (mContextHubInfo != nullptr) {
+    contextHubInfos->push_back(*mContextHubInfo);
+    return ScopedAStatus::ok();
+  }
+  LOGE("Unable to get a valid context hub info for PID %d",
+       AIBinder_getCallingPid());
+  return fromServiceError(HalErrorCode::INVALID_RESULT);
+}
+
+ScopedAStatus MultiClientContextHubBase::loadNanoapp(
+    int32_t contextHubId, const NanoappBinary &appBinary,
+    int32_t transactionId) {
+  if (!isValidContextHubId(contextHubId)) {
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  LOGI("Loading nanoapp 0x%" PRIx64, appBinary.nanoappId);
+  uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) |
+                              (appBinary.targetChreApiMinorVersion << 16);
+  auto transaction = std::make_unique<FragmentedLoadTransaction>(
+      transactionId, appBinary.nanoappId, appBinary.nanoappVersion,
+      appBinary.flags, targetApiVersion, appBinary.customBinary,
+      mConnection->getLoadFragmentSizeBytes());
+  if (!mHalClientManager->registerPendingLoadTransaction(
+          std::move(transaction))) {
+    return fromResult(false);
+  }
+  auto clientId = mHalClientManager->getClientId();
+  auto request = mHalClientManager->getNextFragmentedLoadRequest();
+
+  if (request.has_value() &&
+      sendFragmentedLoadRequest(clientId, request.value())) {
+    return ScopedAStatus::ok();
+  }
+  LOGE("Failed to send the first load request for nanoapp 0x%" PRIx64,
+       appBinary.nanoappId);
+  mHalClientManager->resetPendingLoadTransaction();
+  return fromResult(false);
+}
+
+bool MultiClientContextHubBase::sendFragmentedLoadRequest(
+    HalClientId clientId, FragmentedLoadRequest &request) {
+  flatbuffers::FlatBufferBuilder builder(128 + request.binary.size());
+  HostProtocolHost::encodeFragmentedLoadNanoappRequest(
+      builder, request, /* respondBeforeStart= */ false);
+  HostProtocolHost::mutateHostClientId(builder.GetBufferPointer(),
+                                       builder.GetSize(), clientId);
+  return mConnection->sendMessage(builder);
+}
+
+ScopedAStatus MultiClientContextHubBase::unloadNanoapp(int32_t contextHubId,
+                                                       int64_t appId,
+                                                       int32_t transactionId) {
+  if (!isValidContextHubId(contextHubId)) {
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  if (!mHalClientManager->registerPendingUnloadTransaction(transactionId)) {
+    return fromResult(false);
+  }
+  HalClientId clientId = mHalClientManager->getClientId();
+  flatbuffers::FlatBufferBuilder builder(64);
+  HostProtocolHost::encodeUnloadNanoappRequest(
+      builder, transactionId, appId, /* allowSystemNanoappUnload= */ false);
+  HostProtocolHost::mutateHostClientId(builder.GetBufferPointer(),
+                                       builder.GetSize(), clientId);
+
+  bool result = mConnection->sendMessage(builder);
+  if (!result) {
+    mHalClientManager->resetPendingUnloadTransaction(clientId, transactionId);
+  }
+  return fromResult(result);
+}
+
+ScopedAStatus MultiClientContextHubBase::disableNanoapp(
+    int32_t /* contextHubId */, int64_t appId, int32_t /* transactionId */) {
+  LOGW("Attempted to disable app ID 0x%016" PRIx64 ", but not supported",
+       appId);
+  return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ScopedAStatus MultiClientContextHubBase::enableNanoapp(
+    int32_t /* contextHubId */, int64_t appId, int32_t /* transactionId */) {
+  LOGW("Attempted to enable app ID 0x%016" PRIx64 ", but not supported", appId);
+  return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ScopedAStatus MultiClientContextHubBase::onSettingChanged(Setting setting,
+                                                          bool enabled) {
+  mSettingEnabled[setting] = enabled;
+  fbs::Setting fbsSetting;
+  bool isWifiOrBtSetting =
+      (setting == Setting::WIFI_MAIN || setting == Setting::WIFI_SCANNING ||
+       setting == Setting::BT_MAIN || setting == Setting::BT_SCANNING);
+  if (!isWifiOrBtSetting && getFbsSetting(setting, &fbsSetting)) {
+    flatbuffers::FlatBufferBuilder builder(64);
+    HostProtocolHost::encodeSettingChangeNotification(
+        builder, fbsSetting, toFbsSettingState(enabled));
+    mConnection->sendMessage(builder);
+  }
+
+  bool isWifiMainEnabled = isSettingEnabled(Setting::WIFI_MAIN);
+  bool isWifiScanEnabled = isSettingEnabled(Setting::WIFI_SCANNING);
+  bool isAirplaneModeEnabled = isSettingEnabled(Setting::AIRPLANE_MODE);
+
+  // Because the airplane mode impact on WiFi is not standardized in Android,
+  // we write a specific handling in the Context Hub HAL to inform CHRE.
+  // The following definition is a default one, and can be adjusted
+  // appropriately if necessary.
+  bool isWifiAvailable = isAirplaneModeEnabled
+                             ? (isWifiMainEnabled)
+                             : (isWifiMainEnabled || isWifiScanEnabled);
+  if (!mIsWifiAvailable.has_value() || (isWifiAvailable != mIsWifiAvailable)) {
+    flatbuffers::FlatBufferBuilder builder(64);
+    HostProtocolHost::encodeSettingChangeNotification(
+        builder, fbs::Setting::WIFI_AVAILABLE,
+        toFbsSettingState(isWifiAvailable));
+    mConnection->sendMessage(builder);
+    mIsWifiAvailable = isWifiAvailable;
+  }
+
+  // The BT switches determine whether we can BLE scan which is why things are
+  // mapped like this into CHRE.
+  bool isBtMainEnabled = isSettingEnabled(Setting::BT_MAIN);
+  bool isBtScanEnabled = isSettingEnabled(Setting::BT_SCANNING);
+  bool isBleAvailable = isBtMainEnabled || isBtScanEnabled;
+  if (!mIsBleAvailable.has_value() || (isBleAvailable != mIsBleAvailable)) {
+    flatbuffers::FlatBufferBuilder builder(64);
+    HostProtocolHost::encodeSettingChangeNotification(
+        builder, fbs::Setting::BLE_AVAILABLE,
+        toFbsSettingState(isBleAvailable));
+    mConnection->sendMessage(builder);
+    mIsBleAvailable = isBleAvailable;
+  }
+
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus MultiClientContextHubBase::queryNanoapps(int32_t contextHubId) {
+  if (!isValidContextHubId(contextHubId)) {
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  flatbuffers::FlatBufferBuilder builder(64);
+  HostProtocolHost::encodeNanoappListRequest(builder);
+  HostProtocolHost::mutateHostClientId(builder.GetBufferPointer(),
+                                       builder.GetSize(),
+                                       mHalClientManager->getClientId());
+  return fromResult(mConnection->sendMessage(builder));
+}
+
+ScopedAStatus MultiClientContextHubBase::getPreloadedNanoappIds(
+    int32_t contextHubId, std::vector<int64_t> *out_preloadedNanoappIds) {
+  if (contextHubId != kDefaultHubId) {
+    LOGE("Invalid ID %" PRId32, contextHubId);
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  if (out_preloadedNanoappIds == nullptr) {
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  std::unique_lock<std::mutex> lock(mPreloadedNanoappIdsMutex);
+  if (!mPreloadedNanoappIds.has_value()) {
+    mPreloadedNanoappIds = std::vector<uint64_t>{};
+    mPreloadedNanoappLoader->getPreloadedNanoappIds(*mPreloadedNanoappIds);
+  }
+  for (const auto &nanoappId : mPreloadedNanoappIds.value()) {
+    out_preloadedNanoappIds->emplace_back(static_cast<uint64_t>(nanoappId));
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus MultiClientContextHubBase::registerCallback(
+    int32_t contextHubId,
+    const std::shared_ptr<IContextHubCallback> &callback) {
+  if (!isValidContextHubId(contextHubId)) {
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  if (callback == nullptr) {
+    LOGE("Callback of context hub HAL must not be null");
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  // If everything is successful cookie will be released by the callback of
+  // binder unlinking (callback overridden).
+  auto *cookie = new HalDeathRecipientCookie(this, AIBinder_getCallingPid());
+  if (!mHalClientManager->registerCallback(callback, mDeathRecipient, cookie)) {
+    LOGE("Unable to register the callback");
+    delete cookie;
+    return fromResult(false);
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus MultiClientContextHubBase::sendMessageToHub(
+    int32_t contextHubId, const ContextHubMessage &message) {
+  if (!isValidContextHubId(contextHubId)) {
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+  }
+  HostEndpointId hostEndpointId = message.hostEndPoint;
+  if (!mHalClientManager->mutateEndpointIdFromHostIfNeeded(
+          AIBinder_getCallingPid(), hostEndpointId)) {
+    return fromResult(false);
+  }
+  flatbuffers::FlatBufferBuilder builder(1024);
+  HostProtocolHost::encodeNanoappMessage(
+      builder, message.nanoappId, message.messageType, hostEndpointId,
+      message.messageBody.data(), message.messageBody.size());
+  return fromResult(mConnection->sendMessage(builder));
+}
+
+ScopedAStatus MultiClientContextHubBase::onHostEndpointConnected(
+    const HostEndpointInfo &info) {
+  uint8_t type;
+  switch (info.type) {
+    case HostEndpointInfo::Type::APP:
+      type = CHRE_HOST_ENDPOINT_TYPE_APP;
+      break;
+    case HostEndpointInfo::Type::NATIVE:
+      type = CHRE_HOST_ENDPOINT_TYPE_NATIVE;
+      break;
+    case HostEndpointInfo::Type::FRAMEWORK:
+      type = CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK;
+      break;
+    default:
+      LOGE("Unsupported host endpoint type %" PRIu32, info.type);
+      return fromServiceError(HalErrorCode::INVALID_ARGUMENT);
+  }
+
+  uint16_t endpointId = info.hostEndpointId;
+  if (!mHalClientManager->registerEndpointId(info.hostEndpointId) ||
+      !mHalClientManager->mutateEndpointIdFromHostIfNeeded(
+          AIBinder_getCallingPid(), endpointId)) {
+    return fromServiceError(HalErrorCode::INVALID_ARGUMENT);
+  }
+  flatbuffers::FlatBufferBuilder builder(64);
+  HostProtocolHost::encodeHostEndpointConnected(
+      builder, endpointId, type, info.packageName.value_or(std::string()),
+      info.attributionTag.value_or(std::string()));
+  return fromResult(mConnection->sendMessage(builder));
+}
+
+ScopedAStatus MultiClientContextHubBase::onHostEndpointDisconnected(
+    char16_t in_hostEndpointId) {
+  HostEndpointId hostEndpointId = in_hostEndpointId;
+  bool isSuccessful = false;
+  if (mHalClientManager->removeEndpointId(hostEndpointId) &&
+      mHalClientManager->mutateEndpointIdFromHostIfNeeded(
+          AIBinder_getCallingPid(), hostEndpointId)) {
+    flatbuffers::FlatBufferBuilder builder(64);
+    HostProtocolHost::encodeHostEndpointDisconnected(builder, hostEndpointId);
+    isSuccessful = mConnection->sendMessage(builder);
+  }
+  if (!isSuccessful) {
+    LOGW("Unable to remove host endpoint id %" PRIu16, in_hostEndpointId);
+  }
+  return ScopedAStatus::ok();
+}
+
+ScopedAStatus MultiClientContextHubBase::onNanSessionStateChanged(
+    const NanSessionStateUpdate & /*in_update*/) {
+  // TODO(271471342): Add support for NAN session management.
+  return ndk::ScopedAStatus::ok();
+}
+
+ScopedAStatus MultiClientContextHubBase::setTestMode(bool /*enable*/) {
+  // To be implemented.
+  return ScopedAStatus::ok();
+}
+
+void MultiClientContextHubBase::handleMessageFromChre(
+    const unsigned char *messageBuffer, size_t messageLen) {
+  if (!::chre::HostProtocolCommon::verifyMessage(messageBuffer, messageLen)) {
+    LOGE("Invalid message received from CHRE.");
+    return;
+  }
+  std::unique_ptr<fbs::MessageContainerT> container =
+      fbs::UnPackMessageContainer(messageBuffer);
+  fbs::ChreMessageUnion &message = container->message;
+  HalClientId clientId = container->host_addr->client_id();
+
+  switch (container->message.type) {
+    case fbs::ChreMessage::HubInfoResponse: {
+      handleHubInfoResponse(*message.AsHubInfoResponse());
+      break;
+    }
+    case fbs::ChreMessage::NanoappListResponse: {
+      onNanoappListResponse(*message.AsNanoappListResponse(), clientId);
+      break;
+    }
+    case fbs::ChreMessage::LoadNanoappResponse: {
+      onNanoappLoadResponse(*message.AsLoadNanoappResponse(), clientId);
+      break;
+    }
+    case fbs::ChreMessage::UnloadNanoappResponse: {
+      onNanoappUnloadResponse(*message.AsUnloadNanoappResponse(), clientId);
+      break;
+    }
+    case fbs::ChreMessage::NanoappMessage: {
+      onNanoappMessage(*message.AsNanoappMessage());
+      break;
+    }
+    default:
+      LOGW("Got unexpected message type %" PRIu8,
+           static_cast<uint8_t>(message.type));
+  }
+}
+
+void MultiClientContextHubBase::handleHubInfoResponse(
+    const fbs::HubInfoResponseT &response) {
+  std::unique_lock<std::mutex> lock(mHubInfoMutex);
+  mContextHubInfo = std::make_unique<ContextHubInfo>();
+  mContextHubInfo->name = getStringFromByteVector(response.name);
+  mContextHubInfo->vendor = getStringFromByteVector(response.vendor);
+  mContextHubInfo->toolchain = getStringFromByteVector(response.toolchain);
+  mContextHubInfo->id = kDefaultHubId;
+  mContextHubInfo->peakMips = response.peak_mips;
+  mContextHubInfo->maxSupportedMessageLengthBytes = response.max_msg_len;
+  mContextHubInfo->chrePlatformId = response.platform_id;
+  uint32_t version = response.chre_platform_version;
+  mContextHubInfo->chreApiMajorVersion = extractChreApiMajorVersion(version);
+  mContextHubInfo->chreApiMinorVersion = extractChreApiMinorVersion(version);
+  mContextHubInfo->chrePatchVersion = extractChrePatchVersion(version);
+  mContextHubInfo->supportedPermissions = kSupportedPermissions;
+  mHubInfoCondition.notify_all();
+}
+
+void MultiClientContextHubBase::onNanoappListResponse(
+    const fbs::NanoappListResponseT &response, HalClientId clientId) {
+  std::shared_ptr<IContextHubCallback> callback =
+      mHalClientManager->getCallback(clientId);
+  if (callback == nullptr) {
+    return;
+  }
+  std::vector<NanoappInfo> appInfoList;
+  for (const auto &nanoapp : response.nanoapps) {
+    if (nanoapp == nullptr || nanoapp->is_system) {
+      continue;
+    }
+    NanoappInfo appInfo;
+    appInfo.nanoappId = nanoapp->app_id;
+    appInfo.nanoappVersion = nanoapp->version;
+    appInfo.enabled = nanoapp->enabled;
+    appInfo.permissions = chreToAndroidPermissions(nanoapp->permissions);
+
+    std::vector<NanoappRpcService> rpcServices;
+    for (const auto &service : nanoapp->rpc_services) {
+      NanoappRpcService aidlService;
+      aidlService.id = service->id;
+      aidlService.version = service->version;
+      rpcServices.emplace_back(aidlService);
+    }
+    appInfo.rpcServices = rpcServices;
+    appInfoList.push_back(appInfo);
+  }
+  callback->handleNanoappInfo(appInfoList);
+}
+
+void MultiClientContextHubBase::onNanoappLoadResponse(
+    const fbs::LoadNanoappResponseT &response, HalClientId clientId) {
+  LOGD("Received nanoapp load response for client %" PRIu16
+       " transaction %" PRIu32 " fragment %" PRIu32,
+       clientId, response.transaction_id, response.fragment_id);
+  if (mPreloadedNanoappLoader->isPreloadOngoing()) {
+    mPreloadedNanoappLoader->onLoadNanoappResponse(response, clientId);
+    return;
+  }
+  if (!mHalClientManager->isPendingLoadTransactionExpected(
+          clientId, response.transaction_id, response.fragment_id)) {
+    LOGW("Received a response for client %" PRIu16 " transaction %" PRIu32
+         " fragment %" PRIu32
+         " that doesn't match the existing transaction. Skipped.",
+         clientId, response.transaction_id, response.fragment_id);
+    return;
+  }
+  if (response.success) {
+    auto nextFragmentedRequest =
+        mHalClientManager->getNextFragmentedLoadRequest();
+    if (nextFragmentedRequest.has_value()) {
+      // nextFragmentedRequest will only have a value if the pending transaction
+      // matches the response and there are more fragments to send. Hold off on
+      // calling the callback in this case.
+      LOGD("Sending next FragmentedLoadRequest for client %" PRIu16
+           ": (transaction: %" PRIu32 ", fragment %zu)",
+           clientId, nextFragmentedRequest->transactionId,
+           nextFragmentedRequest->fragmentId);
+      sendFragmentedLoadRequest(clientId, nextFragmentedRequest.value());
+      return;
+    }
+  } else {
+    LOGE("Loading nanoapp fragment for client %" PRIu16 " transaction %" PRIu32
+         " fragment %" PRIu32 " failed",
+         clientId, response.transaction_id, response.fragment_id);
+    mHalClientManager->resetPendingLoadTransaction();
+  }
+  // At this moment the current pending transaction should either have no more
+  // fragment to send or the response indicates its last nanoapp fragment fails
+  // to get loaded.
+  if (auto callback = mHalClientManager->getCallback(clientId);
+      callback != nullptr) {
+    callback->handleTransactionResult(response.transaction_id,
+                                      /* in_success= */ response.success);
+  }
+}
+
+void MultiClientContextHubBase::onNanoappUnloadResponse(
+    const fbs::UnloadNanoappResponseT &response, HalClientId clientId) {
+  if (mHalClientManager->resetPendingUnloadTransaction(
+          clientId, response.transaction_id)) {
+    if (auto callback = mHalClientManager->getCallback(clientId);
+        callback != nullptr) {
+      callback->handleTransactionResult(response.transaction_id,
+                                        /* in_success= */ response.success);
+    }
+  }
+}
+
+void MultiClientContextHubBase::onNanoappMessage(
+    const ::chre::fbs::NanoappMessageT &message) {
+  ContextHubMessage outMessage;
+  outMessage.nanoappId = message.app_id;
+  outMessage.hostEndPoint = message.host_endpoint;
+  outMessage.messageType = message.message_type;
+  outMessage.messageBody = message.message;
+  outMessage.permissions = chreToAndroidPermissions(message.permissions);
+  auto messageContentPerms =
+      chreToAndroidPermissions(message.message_permissions);
+  // broadcast message is sent to every connected endpoint
+  if (message.host_endpoint == CHRE_HOST_ENDPOINT_BROADCAST) {
+    mHalClientManager->sendMessageForAllCallbacks(outMessage,
+                                                  messageContentPerms);
+  } else if (auto callback = mHalClientManager->getCallbackForEndpoint(
+                 message.host_endpoint);
+             callback != nullptr) {
+    outMessage.hostEndPoint =
+        HalClientManager::convertToOriginalEndpointId(message.host_endpoint);
+    callback->handleContextHubMessage(outMessage, messageContentPerms);
+  }
+}
+
+void MultiClientContextHubBase::onClientDied(void *cookie) {
+  auto *info = static_cast<HalDeathRecipientCookie *>(cookie);
+  info->hal->handleClientDeath(info->clientPid);
+}
+
+void MultiClientContextHubBase::handleClientDeath(pid_t clientPid) {
+  LOGI("Process %d is dead. Cleaning up.", clientPid);
+  if (auto endpoints = mHalClientManager->getAllConnectedEndpoints(clientPid)) {
+    for (auto endpointId : *endpoints) {
+      LOGI("Sending message to remove endpoint 0x%" PRIx16, endpointId);
+      if (!mHalClientManager->mutateEndpointIdFromHostIfNeeded(clientPid,
+                                                               endpointId)) {
+        continue;
+      }
+      flatbuffers::FlatBufferBuilder builder(64);
+      HostProtocolHost::encodeHostEndpointDisconnected(builder, endpointId);
+      mConnection->sendMessage(builder);
+    }
+  }
+  mHalClientManager->handleClientDeath(clientPid, mDeathRecipient);
+}
+
+void MultiClientContextHubBase::onChreRestarted() {
+  mIsWifiAvailable.reset();
+  mHalClientManager->handleChreRestart();
+}
+}  // namespace android::hardware::contexthub::common::implementation
diff --git a/host/hal_generic/common/multi_client_context_hub_base.h b/host/hal_generic/common/multi_client_context_hub_base.h
new file mode 100644
index 0000000..8fa00e5
--- /dev/null
+++ b/host/hal_generic/common/multi_client_context_hub_base.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 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 ANDROID_HARDWARE_CONTEXTHUB_COMMON_MULTICLIENTS_HAL_BASE_H_
+#define ANDROID_HARDWARE_CONTEXTHUB_COMMON_MULTICLIENTS_HAL_BASE_H_
+
+#ifndef LOG_TAG
+#define LOG_TAG "CHRE.HAL"
+#endif
+
+#include <aidl/android/hardware/contexthub/BnContextHub.h>
+#include <chre_host/generated/host_messages_generated.h>
+
+#include "chre_connection_callback.h"
+#include "chre_host/napp_header.h"
+#include "chre_host/preloaded_nanoapp_loader.h"
+#include "hal_client_id.h"
+#include "hal_client_manager.h"
+
+namespace android::hardware::contexthub::common::implementation {
+
+using namespace aidl::android::hardware::contexthub;
+using namespace android::chre;
+using ::ndk::ScopedAStatus;
+
+/**
+ * The base class of multiclients HAL.
+ *
+ * A subclass should initiate mConnection, mHalClientManager and
+ * mPreloadedNanoappLoader in its constructor.
+ *
+ * TODO(b/247124878): A few things are pending:
+ *   - Some APIs of IContextHub are not implemented yet;
+ *   - onHostEndpointConnected/Disconnected now returns an error if the endpoint
+ *     id is illegal or already connected/disconnected. The doc of
+ *     IContextHub.aidl should be updated accordingly.
+ *   - registerCallback() can fail if mHalClientManager sees an error during
+ *     registration. The doc of IContextHub.aidl should be updated accordingly.
+ *   - Involve EventLogger to log API calls;
+ *   - extends DebugDumpHelper to ease debugging
+ */
+class MultiClientContextHubBase
+    : public BnContextHub,
+      public ::android::hardware::contexthub::common::implementation::
+          ChreConnectionCallback {
+ public:
+  /** The entry point of death recipient for a disconnected client. */
+  static void onClientDied(void *cookie);
+
+  // functions implementing IContextHub
+  ScopedAStatus getContextHubs(
+      std::vector<ContextHubInfo> *contextHubInfos) override;
+  ScopedAStatus loadNanoapp(int32_t contextHubId,
+                            const NanoappBinary &appBinary,
+                            int32_t transactionId) override;
+  ScopedAStatus unloadNanoapp(int32_t contextHubId, int64_t appId,
+                              int32_t transactionId) override;
+  ScopedAStatus disableNanoapp(int32_t contextHubId, int64_t appId,
+                               int32_t transactionId) override;
+  ScopedAStatus enableNanoapp(int32_t contextHubId, int64_t appId,
+                              int32_t transactionId) override;
+  ScopedAStatus onSettingChanged(Setting setting, bool enabled) override;
+  ScopedAStatus queryNanoapps(int32_t contextHubId) override;
+  ScopedAStatus registerCallback(
+      int32_t contextHubId,
+      const std::shared_ptr<IContextHubCallback> &callback) override;
+  ScopedAStatus sendMessageToHub(int32_t contextHubId,
+                                 const ContextHubMessage &message) override;
+  ScopedAStatus onHostEndpointConnected(const HostEndpointInfo &info) override;
+  ScopedAStatus onHostEndpointDisconnected(char16_t in_hostEndpointId) override;
+  ScopedAStatus getPreloadedNanoappIds(int32_t contextHubId,
+                                       std::vector<int64_t> *result) override;
+  ScopedAStatus onNanSessionStateChanged(
+      const NanSessionStateUpdate &in_update) override;
+  ScopedAStatus setTestMode(bool enable) override;
+
+  // The callback function implementing ChreConnectionCallback
+  void handleMessageFromChre(const unsigned char *messageBuffer,
+                             size_t messageLen) override;
+  void onChreRestarted() override;
+
+ protected:
+  // The data needed by the death client to clear states of a client.
+  struct HalDeathRecipientCookie {
+    MultiClientContextHubBase *hal;
+    pid_t clientPid;
+    HalDeathRecipientCookie(MultiClientContextHubBase *hal, pid_t pid) {
+      this->hal = hal;
+      this->clientPid = pid;
+    }
+  };
+  MultiClientContextHubBase() = default;
+
+  bool sendFragmentedLoadRequest(HalClientId clientId,
+                                 FragmentedLoadRequest &fragmentedLoadRequest);
+
+  // Functions handling various types of messages
+  void handleHubInfoResponse(const ::chre::fbs::HubInfoResponseT &message);
+  void onNanoappListResponse(const ::chre::fbs::NanoappListResponseT &response,
+                             HalClientId clientid);
+  void onNanoappLoadResponse(const ::chre::fbs::LoadNanoappResponseT &response,
+                             HalClientId clientId);
+  void onNanoappUnloadResponse(
+      const ::chre::fbs::UnloadNanoappResponseT &response,
+      HalClientId clientId);
+  void onNanoappMessage(const ::chre::fbs::NanoappMessageT &message);
+
+  void handleClientDeath(pid_t pid);
+
+  inline bool isSettingEnabled(Setting setting) {
+    return mSettingEnabled.find(setting) != mSettingEnabled.end() &&
+           mSettingEnabled[setting];
+  }
+
+  // HAL is the unique owner of the communication channel to CHRE.
+  std::unique_ptr<ChreConnection> mConnection{};
+
+  // HalClientManager maintains states of hal clients. Each HAL should only have
+  // one instance of a HalClientManager.
+  std::unique_ptr<HalClientManager> mHalClientManager{};
+
+  std::unique_ptr<PreloadedNanoappLoader> mPreloadedNanoappLoader{};
+
+  std::unique_ptr<ContextHubInfo> mContextHubInfo;
+
+  // Mutex and CV are used to get context hub info synchronously.
+  std::mutex mHubInfoMutex;
+  std::condition_variable mHubInfoCondition;
+
+  // Death recipient handling clients' disconnections
+  ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
+
+  // States of settings
+  std::unordered_map<Setting, bool> mSettingEnabled;
+  std::optional<bool> mIsWifiAvailable;
+  std::optional<bool> mIsBleAvailable;
+
+  // A mutex to synchronize access to the list of preloaded nanoapp IDs.
+  std::mutex mPreloadedNanoappIdsMutex;
+  std::optional<std::vector<uint64_t>> mPreloadedNanoappIds{};
+};
+}  // namespace android::hardware::contexthub::common::implementation
+#endif  // ANDROID_HARDWARE_CONTEXTHUB_COMMON_MULTICLIENTS_HAL_BASE_H_
diff --git a/host/msm/daemon/fastrpc_daemon.h b/host/msm/daemon/fastrpc_daemon.h
index 1fc8b95..a688391 100644
--- a/host/msm/daemon/fastrpc_daemon.h
+++ b/host/msm/daemon/fastrpc_daemon.h
@@ -24,7 +24,7 @@
 #define CHRE_FASTRPC_DAEMON_H_
 
 #include "chre/platform/slpi/fastrpc.h"
-#include "chre_host/daemon_base.h"
+#include "chre_host/fbs_daemon_base.h"
 #include "chre_host/st_hal_lpma_handler.h"
 
 #include <utils/SystemClock.h>
@@ -35,7 +35,7 @@
 namespace android {
 namespace chre {
 
-class FastRpcChreDaemon : public ChreDaemonBase {
+class FastRpcChreDaemon : public FbsDaemonBase {
  public:
   FastRpcChreDaemon();
 
@@ -82,7 +82,7 @@
    *
    * @return clock drift offset in nanoseconds
    */
-  int64_t getTimeOffset(bool *success);
+  int64_t getTimeOffset(bool *success) override;
 
   /**
    * Entry point for the thread that blocks in a FastRPC call to monitor for
diff --git a/host/test/hal_generic/aidl/event_logger_test.cc b/host/test/hal_generic/aidl/event_logger_test.cc
index d8e7f17..0b4c9d9 100644
--- a/host/test/hal_generic/aidl/event_logger_test.cc
+++ b/host/test/hal_generic/aidl/event_logger_test.cc
@@ -161,4 +161,4 @@
 }
 
 }  // namespace
-}  // namespace aidl::android::hardware::contexthub
\ No newline at end of file
+}  // namespace aidl::android::hardware::contexthub
diff --git a/host/tinysys/hal/Android.bp b/host/tinysys/hal/Android.bp
new file mode 100644
index 0000000..b78c29d
--- /dev/null
+++ b/host/tinysys/hal/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_chre_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_chre_license"],
+}
+
+cc_binary {
+    name: "android.hardware.contexthub-service.tinysys",
+    defaults: ["hidl_defaults"],
+    vendor: true,
+    relative_install_path: "hw",
+    srcs: [
+        "service.cc",
+        "tinysys_chre_connection.cc",
+        "tinysys_context_hub.cc",
+        ":st_hal_lpma_handler",
+        ":contexthub_generic_aidl_hal_core",
+    ],
+    include_dirs: [
+        "system/chre/util/include/",
+        "system/chre/host/common/include/",
+        "system/chre/host/hal_generic/aidl/",
+        "system/chre/host/hal_generic/common/",
+        "system/chre/platform/shared/include/",
+    ],
+    init_rc: ["android.hardware.contexthub-service.tinysys.rc"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-DCHRE_MESSAGE_TO_HOST_MAX_SIZE=4000",
+        "-DCHRE_IS_HOST_BUILD",
+        "-DCHRE_HOST_DEFAULT_FRAGMENT_SIZE=2048",
+        "-DCHRE_ST_LPMA_HANDLER_AIDL",
+    ],
+    shared_libs: [
+        "android.media.soundtrigger.types-V1-ndk",
+        "android.hardware.contexthub-V2-ndk",
+        "android.hardware.soundtrigger3-V1-ndk",
+        "libcutils",
+        "liblog",
+        "libutils",
+        "libbase",
+        "libbinder_ndk",
+        "libpower",
+        "libjsoncpp",
+    ],
+    header_libs: [
+        "chre_api",
+        "pw_span_headers",
+        "pw_polyfill_headers",
+    ],
+    static_libs: [
+        "chre_client",
+        "event_logger",
+        "pw_varint",
+        "pw_detokenizer",
+    ],
+    vintf_fragments: ["android.hardware.contexthub-service.tinysys.xml"],
+}
diff --git a/host/tinysys/hal/android.hardware.contexthub-service.tinysys.rc b/host/tinysys/hal/android.hardware.contexthub-service.tinysys.rc
new file mode 100644
index 0000000..04ff8c5
--- /dev/null
+++ b/host/tinysys/hal/android.hardware.contexthub-service.tinysys.rc
@@ -0,0 +1,9 @@
+on post-fs-data
+    mkdir /data/vendor/chre 0770 context_hub context_hub
+
+service vendor.contexthub-default /vendor/bin/hw/android.hardware.contexthub-service.tinysys
+    class hal late_start
+    user context_hub
+    group wakelock context_hub system readproc
+    capabilities BLOCK_SUSPEND
+    shutdown critical
diff --git a/host/tinysys/hal/android.hardware.contexthub-service.tinysys.xml b/host/tinysys/hal/android.hardware.contexthub-service.tinysys.xml
new file mode 100644
index 0000000..54d4592
--- /dev/null
+++ b/host/tinysys/hal/android.hardware.contexthub-service.tinysys.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.contexthub</name>
+        <version>2</version>
+        <fqname>IContextHub/default</fqname>
+    </hal>
+</manifest>
diff --git a/host/tinysys/hal/service.cc b/host/tinysys/hal/service.cc
new file mode 100644
index 0000000..5f67f95
--- /dev/null
+++ b/host/tinysys/hal/service.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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 "tinysys_context_hub.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#ifndef LOG_TAG
+#define LOG_TAG "android.hardware.contexthub-service"
+#endif
+
+using aidl::android::hardware::contexthub::TinysysContextHub;
+
+int main() {
+  ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+  // Make a default contexthub service
+  auto contextHub = ndk::SharedRefBase::make<TinysysContextHub>();
+  const std::string contextHubName =
+      std::string() + TinysysContextHub::descriptor + "/default";
+  binder_status_t status = AServiceManager_addService(
+      contextHub->asBinder().get(), contextHubName.c_str());
+  CHECK(status == STATUS_OK);
+
+  ABinderProcess_joinThreadPool();
+  return EXIT_FAILURE;  // should not reach
+}
diff --git a/host/tinysys/hal/tinysys_chre_connection.cc b/host/tinysys/hal/tinysys_chre_connection.cc
new file mode 100644
index 0000000..22ef714
--- /dev/null
+++ b/host/tinysys/hal/tinysys_chre_connection.cc
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 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 "tinysys_chre_connection.h"
+#include "chre_host/file_stream.h"
+#include "chre_host/generated/host_messages_generated.h"
+#include "chre_host/host_protocol_host.h"
+
+#include <hardware_legacy/power.h>
+#include <sys/ioctl.h>
+#include <cerrno>
+#include <thread>
+
+/* The definitions below must be the same as the ones defined in kernel. */
+#define SCP_CHRE_MANAGER_STAT_UNINIT _IOW('a', 0, unsigned int)
+#define SCP_CHRE_MANAGER_STAT_STOP _IOW('a', 1, unsigned int)
+#define SCP_CHRE_MANAGER_STAT_START _IOW('a', 2, unsigned int)
+
+namespace aidl::android::hardware::contexthub {
+
+using namespace ::android::chre;
+namespace fbs = ::chre::fbs;
+
+namespace {
+
+// The ChreStateMessage defines the message written by kernel indicating the
+// current state of SCP. It must be consistent with the definition in the
+// kernel.
+struct ChreStateMessage {
+  long nextStateAddress;
+};
+
+// Possible states of SCP.
+enum ChreState {
+  SCP_CHRE_UNINIT = 0,
+  SCP_CHRE_STOP = 1,
+  SCP_CHRE_START = 2,
+};
+
+ChreState chreCurrentState = SCP_CHRE_UNINIT;
+
+unsigned getRequestCode(ChreState chreState) {
+  switch (chreState) {
+    case SCP_CHRE_UNINIT:
+      return SCP_CHRE_MANAGER_STAT_UNINIT;
+    case SCP_CHRE_STOP:
+      return SCP_CHRE_MANAGER_STAT_STOP;
+    case SCP_CHRE_START:
+      return SCP_CHRE_MANAGER_STAT_START;
+    default:
+      LOGE("Unexpected CHRE state: %" PRIu32, chreState);
+      assert(false);
+  }
+}
+}  // namespace
+
+bool TinysysChreConnection::init() {
+  // Make sure the payload size is large enough for nanoapp binary fragment
+  static_assert(kMaxPayloadBytes > CHRE_HOST_DEFAULT_FRAGMENT_SIZE &&
+                kMaxPayloadBytes - CHRE_HOST_DEFAULT_FRAGMENT_SIZE >
+                    kMaxPayloadOverheadBytes);
+  mChreFileDescriptor =
+      TEMP_FAILURE_RETRY(open(kChreFileDescriptorPath, O_RDWR));
+  if (mChreFileDescriptor < 0) {
+    LOGE("open chre device failed err=%d errno=%d\n", mChreFileDescriptor,
+         errno);
+    return false;
+  }
+  mLogger.init();
+  // launch the tasks
+  mMessageListener = std::thread(messageListenerTask, this);
+  mMessageSender = std::thread(messageSenderTask, this);
+  mStateListener = std::thread(chreStateMonitorTask, this);
+  mLpmaHandler.init();
+  return true;
+}
+
+[[noreturn]] void TinysysChreConnection::messageListenerTask(
+    TinysysChreConnection *chreConnection) {
+  auto chreFd = chreConnection->getChreFileDescriptor();
+  while (true) {
+    {
+      ssize_t payloadSize = TEMP_FAILURE_RETRY(
+          read(chreFd, chreConnection->mPayload.get(), kMaxPayloadBytes));
+      if (payloadSize == 0) {
+        // Payload size 0 is a fake signal from kernel which is normal if the
+        // device is in sleep.
+        LOGW("%s: Received a payload size 0. Ignored. errno=%d", __func__,
+             errno);
+        continue;
+      }
+      if (payloadSize < 0) {
+        LOGE("%s: read failed. payload size: %zu. errno=%d", __func__,
+             payloadSize, errno);
+        continue;
+      }
+      handleMessageFromChre(chreConnection, chreConnection->mPayload.get(),
+                            payloadSize);
+    }
+  }
+}
+
+[[noreturn]] void TinysysChreConnection::chreStateMonitorTask(
+    TinysysChreConnection *chreConnection) {
+  int chreFd = chreConnection->getChreFileDescriptor();
+  uint32_t nextState = 0;
+  ChreStateMessage chreMessage{.nextStateAddress =
+                                   reinterpret_cast<long>(&nextState)};
+  while (true) {
+    LOGI("The current CHRE state is %" PRIu32, chreCurrentState);
+    if (TEMP_FAILURE_RETRY(ioctl(chreFd, getRequestCode(chreCurrentState),
+                                 (unsigned long)&chreMessage)) < 0) {
+      LOGE("Unable to get an update for the CHRE state: errno=%d", errno);
+      continue;
+    }
+    LOGI("Retrieved the next state: %" PRIu32, nextState);
+    auto chreNextState = static_cast<ChreState>(nextState);
+    if (chreCurrentState == SCP_CHRE_STOP && chreNextState == SCP_CHRE_START) {
+      // TODO(b/277128368): We should have an explicit indication from CHRE for
+      // restart recovery.
+      LOGW("SCP restarted. Give it 5s for recovery before notifying clients");
+      std::this_thread::sleep_for(std::chrono::milliseconds(5000));
+      chreConnection->getCallback()->onChreRestarted();
+    }
+    chreCurrentState = chreNextState;
+  }
+}
+
+[[noreturn]] void TinysysChreConnection::messageSenderTask(
+    TinysysChreConnection *chreConnection) {
+  LOGI("Message sender task is launched.");
+  int chreFd = chreConnection->getChreFileDescriptor();
+  while (true) {
+    chreConnection->mQueue.waitForMessage();
+    ChreConnectionMessage &message = chreConnection->mQueue.front();
+    auto size =
+        TEMP_FAILURE_RETRY(write(chreFd, &message, message.getMessageSize()));
+    if (size < 0) {
+      LOGE("Failed to write to chre file descriptor. errno=%d\n", errno);
+    }
+    chreConnection->mQueue.pop();
+  }
+}
+
+bool TinysysChreConnection::sendMessage(void *data, size_t length) {
+  if (length <= 0 || length > kMaxPayloadBytes) {
+    LOGE("length %zu is not within the accepted range.", length);
+    return false;
+  }
+  return mQueue.emplace(data, length);
+}
+
+void TinysysChreConnection::handleMessageFromChre(
+    TinysysChreConnection *chreConnection, const unsigned char *messageBuffer,
+    size_t messageLen) {
+  // TODO(b/267188769): Move the wake lock acquisition/release to RAII
+  // pattern.
+  bool isWakelockAcquired =
+      acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakeLock) == 0;
+  if (!isWakelockAcquired) {
+    LOGE("Failed to acquire the wakelock before handling a message.");
+  } else {
+    LOGV("Wakelock is acquired before handling a message.");
+  }
+  HalClientId hostClientId;
+  fbs::ChreMessage messageType = fbs::ChreMessage::NONE;
+  if (!HostProtocolHost::extractHostClientIdAndType(
+          messageBuffer, messageLen, &hostClientId, &messageType)) {
+    LOGW("Failed to extract host client ID from message - sending broadcast");
+    hostClientId = ::chre::kHostClientIdUnspecified;
+  }
+  LOGV("Received a message (type: %hhu, len: %zu) from CHRE for client %d",
+       messageType, messageLen, hostClientId);
+
+  switch (messageType) {
+    case fbs::ChreMessage::LogMessageV2: {
+      std::unique_ptr<fbs::MessageContainerT> container =
+          fbs::UnPackMessageContainer(messageBuffer);
+      const auto *logMessage = container->message.AsLogMessageV2();
+      const std::vector<int8_t> &buffer = logMessage->buffer;
+      const auto *logData = reinterpret_cast<const uint8_t *>(buffer.data());
+      uint32_t numLogsDropped = logMessage->num_logs_dropped;
+      chreConnection->mLogger.logV2(logData, buffer.size(), numLogsDropped);
+      break;
+    }
+    case fbs::ChreMessage::LowPowerMicAccessRequest: {
+      chreConnection->getLpmaHandler()->enable(/* enabled= */ true);
+      break;
+    }
+    case fbs::ChreMessage::LowPowerMicAccessRelease: {
+      chreConnection->getLpmaHandler()->enable(/* enabled= */ false);
+      break;
+    }
+    case fbs::ChreMessage::MetricLog:
+    case fbs::ChreMessage::NanConfigurationRequest:
+    case fbs::ChreMessage::TimeSyncRequest:
+    case fbs::ChreMessage::LogMessage: {
+      LOGE("Unsupported message type %hhu received from CHRE.", messageType);
+      break;
+    }
+    default: {
+      chreConnection->getCallback()->handleMessageFromChre(messageBuffer,
+                                                           messageLen);
+      break;
+    }
+  }
+  if (isWakelockAcquired) {
+    if (release_wake_lock(kWakeLock)) {
+      LOGE("Failed to release the wake lock");
+    } else {
+      LOGV("The wake lock is released after handling a message.");
+    }
+  }
+}
+}  // namespace aidl::android::hardware::contexthub
diff --git a/host/tinysys/hal/tinysys_chre_connection.h b/host/tinysys/hal/tinysys_chre_connection.h
new file mode 100644
index 0000000..e42b0c4
--- /dev/null
+++ b/host/tinysys/hal/tinysys_chre_connection.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2023 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 TINYSYS_CHRE_CONNECTION_H_
+#define TINYSYS_CHRE_CONNECTION_H_
+
+#include "chre_connection.h"
+#include "chre_connection_callback.h"
+#include "chre_host/fragmented_load_transaction.h"
+#include "chre_host/log.h"
+#include "chre_host/log_message_parser.h"
+#include "chre_host/st_hal_lpma_handler.h"
+
+#include <unistd.h>
+#include <cassert>
+#include <future>
+#include <queue>
+#include <thread>
+
+using ::android::chre::StHalLpmaHandler;
+
+namespace aidl::android::hardware::contexthub {
+
+using namespace ::android::hardware::contexthub::common::implementation;
+
+/** A class handling message transmission between context hub HAL and CHRE. */
+// TODO(b/267188769): We should add comments explaining how IPI works.
+class TinysysChreConnection : public ChreConnection {
+ public:
+  TinysysChreConnection(ChreConnectionCallback *callback)
+      : mCallback(callback), mLpmaHandler(/* allowed= */ true) {
+    mPayload = std::make_unique<uint8_t[]>(kMaxPayloadBytes);
+  };
+
+  ~TinysysChreConnection() override {
+    // TODO(b/264308286): Need a decent way to terminate the listener thread.
+    close(mChreFileDescriptor);
+    if (mMessageListener.joinable()) {
+      mMessageListener.join();
+    }
+    if (mMessageSender.joinable()) {
+      mMessageSender.join();
+    }
+    if (mStateListener.joinable()) {
+      mStateListener.join();
+    }
+  }
+
+  static void handleMessageFromChre(TinysysChreConnection *chreConnection,
+                                    const unsigned char *messageBuffer,
+                                    size_t messageLen);
+
+  bool init() override;
+
+  bool sendMessage(void *data, size_t length) override;
+
+  inline ChreConnectionCallback *getCallback() {
+    return mCallback;
+  }
+
+  inline StHalLpmaHandler *getLpmaHandler() {
+    return &mLpmaHandler;
+  }
+
+ private:
+  // The wakelock used to keep device awake while handleUsfMsgAsync() is being
+  // called.
+  static constexpr char kWakeLock[] = "tinysys_chre_hal_wakelock";
+
+  // Max payload size that can be sent to CHRE
+  // TODO(b/277235389): Adjust max payload size (AP -> SCP and SCP -> AP)
+  // as appropriate. This is a temp/quick fix for b/272311907 and b/270758946
+  // setting max payload allowed to CHRE_MESSAGE_TO_HOST_MAX_SIZE + 128 byte
+  // to account for transport overhead.
+  static constexpr uint32_t kMaxPayloadBytes = 4224;  // 4096 + 128
+
+  // Max overhead of the nanoapp binary payload caused by the fbs encapsulation
+  static constexpr uint32_t kMaxPayloadOverheadBytes = 1024;
+
+  // The path to CHRE file descriptor
+  static constexpr char kChreFileDescriptorPath[] = "/dev/scp_chre_manager";
+
+  // Max queue size for sending messages to CHRE
+  static constexpr size_t kMaxSynchronousMessageQueueSize = 64;
+
+  // Wrapper for a message sent to CHRE
+  struct ChreConnectionMessage {
+    // This magic number is the SCP_CHRE_MAGIC constant defined by kernel
+    // scp_chre_manager service. The value is embedded in the payload as a
+    // security check for proper use of the device node.
+    uint32_t magic = 0x67728269;
+    uint32_t payloadSize = 0;
+    uint8_t payload[kMaxPayloadBytes];
+
+    ChreConnectionMessage(void *data, size_t length) {
+      assert(length <= kMaxPayloadBytes);
+      memcpy(payload, data, length);
+      payloadSize = static_cast<uint32_t>(length);
+    }
+
+    uint32_t getMessageSize() {
+      return sizeof(magic) + sizeof(payloadSize) + payloadSize;
+    }
+  };
+
+  // A queue suitable for multiple producers and a single consumer.
+  class SynchronousMessageQueue {
+   public:
+    bool emplace(void *data, size_t length) {
+      std::unique_lock<std::mutex> lock(mMutex);
+      if (mQueue.size() >= kMaxSynchronousMessageQueueSize) {
+        LOGE("Message queue from HAL to CHRE is full!");
+        return false;
+      }
+      mQueue.emplace(data, length);
+      mCv.notify_all();
+      return true;
+    }
+
+    void pop() {
+      std::unique_lock<std::mutex> lock(mMutex);
+      mQueue.pop();
+    }
+
+    ChreConnectionMessage &front() {
+      std::unique_lock<std::mutex> lock(mMutex);
+      return mQueue.front();
+    }
+
+    void waitForMessage() {
+      std::unique_lock<std::mutex> lock(mMutex);
+      mCv.wait(lock, [&]() { return !mQueue.empty(); });
+    }
+
+   private:
+    std::mutex mMutex;
+    std::condition_variable mCv;
+    std::queue<ChreConnectionMessage> mQueue;
+  };
+
+  // The task receiving message from CHRE
+  [[noreturn]] static void messageListenerTask(
+      TinysysChreConnection *chreConnection);
+
+  // The task sending message to CHRE
+  [[noreturn]] static void messageSenderTask(
+      TinysysChreConnection *chreConnection);
+
+  // The task receiving CHRE state update
+  [[noreturn]] static void chreStateMonitorTask(
+      TinysysChreConnection *chreConnection);
+
+  [[nodiscard]] inline int getChreFileDescriptor() const {
+    return mChreFileDescriptor;
+  }
+
+  // The parser of buffered logs from CHRE
+  ::android::chre::LogMessageParser mLogger{};
+
+  // The file descriptor for communication with CHRE
+  int mChreFileDescriptor;
+
+  // The calback function that should be implemented by HAL
+  ChreConnectionCallback *mCallback;
+
+  // the message listener thread that receives messages from CHRE
+  std::thread mMessageListener;
+  // the message sender thread that sends messages to CHRE
+  std::thread mMessageSender;
+  // the status listener thread that hosts chreStateMonitorTask
+  std::thread mStateListener;
+
+  // Payload received from CHRE
+  std::unique_ptr<uint8_t[]> mPayload;
+
+  // The LPMA handler to talk to the ST HAL
+  StHalLpmaHandler mLpmaHandler;
+
+  // For messages sent to CHRE
+  SynchronousMessageQueue mQueue;
+};
+}  // namespace aidl::android::hardware::contexthub
+
+#endif  // TINYSYS_CHRE_CONNECTION_H_
diff --git a/host/tinysys/hal/tinysys_context_hub.cc b/host/tinysys/hal/tinysys_context_hub.cc
new file mode 100644
index 0000000..2f54aba
--- /dev/null
+++ b/host/tinysys/hal/tinysys_context_hub.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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 "tinysys_context_hub.h"
+
+namespace aidl::android::hardware::contexthub {
+TinysysContextHub::TinysysContextHub() {
+  mDeathRecipient = ndk::ScopedAIBinder_DeathRecipient(
+      AIBinder_DeathRecipient_new(onClientDied));
+  AIBinder_DeathRecipient_setOnUnlinked(
+      mDeathRecipient.get(), [](void *cookie) {
+        LOGI("Callback is unlinked. Releasing the death recipient cookie.");
+        delete static_cast<HalDeathRecipientCookie *>(cookie);
+      });
+  mConnection = std::make_unique<TinysysChreConnection>(this);
+  mHalClientManager = std::make_unique<HalClientManager>();
+  mPreloadedNanoappLoader = std::make_unique<PreloadedNanoappLoader>(
+      mConnection.get(), kPreloadedNanoappsConfigPath);
+  if (mConnection->init()) {
+    if (!kPreloadedNanoappsConfigPath.empty()) {
+      mPreloadedNanoappLoader->loadPreloadedNanoapps();
+    }
+  }
+}
+
+void TinysysContextHub::onChreRestarted() {
+  if (!kPreloadedNanoappsConfigPath.empty()) {
+    mPreloadedNanoappLoader->loadPreloadedNanoapps();
+  }
+  MultiClientContextHubBase::onChreRestarted();
+}
+}  // namespace aidl::android::hardware::contexthub
\ No newline at end of file
diff --git a/host/tinysys/hal/tinysys_context_hub.h b/host/tinysys/hal/tinysys_context_hub.h
new file mode 100644
index 0000000..4be2d93
--- /dev/null
+++ b/host/tinysys/hal/tinysys_context_hub.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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 ANDROID_HARDWARE_CONTEXTHUB_AIDL_CONTEXTHUB_H
+#define ANDROID_HARDWARE_CONTEXTHUB_AIDL_CONTEXTHUB_H
+
+#include "chre_host/preloaded_nanoapp_loader.h"
+#include "chre_host/time_syncer.h"
+#include "multi_client_context_hub_base.h"
+#include "tinysys_chre_connection.h"
+
+namespace aidl::android::hardware::contexthub {
+
+using namespace ::android::hardware::contexthub::common::implementation;
+using namespace ::android::chre;
+
+/** The implementation of HAL for Tinysys. */
+class TinysysContextHub : public MultiClientContextHubBase {
+ public:
+  TinysysContextHub();
+
+ protected:
+  void onChreRestarted() override;
+  const std::string kPreloadedNanoappsConfigPath =
+      "/vendor/etc/chre/preloaded_nanoapps.json";
+};
+}  // namespace aidl::android::hardware::contexthub
+#endif  // ANDROID_HARDWARE_CONTEXTHUB_AIDL_CONTEXTHUB_H
diff --git a/java/test/audio_concurrency/src/com/google/android/chre/test/audioconcurrency/ContextHubAudioConcurrencyTestExecutor.java b/java/test/audio_concurrency/src/com/google/android/chre/test/audioconcurrency/ContextHubAudioConcurrencyTestExecutor.java
index c82b19a..b534586 100644
--- a/java/test/audio_concurrency/src/com/google/android/chre/test/audioconcurrency/ContextHubAudioConcurrencyTestExecutor.java
+++ b/java/test/audio_concurrency/src/com/google/android/chre/test/audioconcurrency/ContextHubAudioConcurrencyTestExecutor.java
@@ -30,6 +30,7 @@
 import com.google.android.chre.nanoapp.proto.ChreAudioConcurrencyTest;
 import com.google.android.chre.nanoapp.proto.ChreTestCommon;
 import com.google.android.utils.chre.ChreTestUtil;
+import com.google.protobuf.ByteString;
 import com.google.protobuf.InvalidProtocolBufferException;
 
 import org.junit.Assert;
@@ -68,6 +69,12 @@
 
     private final AtomicReference<ChreTestCommon.TestResult> mTestResult = new AtomicReference<>();
 
+    // Options for updating test results
+    private enum UpdateOption {
+        ALWAYS,
+        SKIP_IF_EXISTS
+    }
+
     public ContextHubAudioConcurrencyTestExecutor(
             ContextHubManager manager, ContextHubInfo info, NanoAppBinary binary) {
         mContextHubManager = manager;
@@ -76,20 +83,18 @@
         mNanoAppId = mNanoAppBinary.getNanoAppId();
 
         mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
-        Assert.assertTrue(mContextHubClient != null);
     }
 
     @Override
     public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {
         if (message.getNanoAppId() == mNanoAppId) {
             boolean valid = true;
+            ChreTestCommon.TestResult result = null;
             switch (message.getMessageType()) {
                 case ChreAudioConcurrencyTest.MessageType.TEST_RESULT_VALUE: {
                     try {
-                        mTestResult.set(
-                                ChreTestCommon.TestResult.parseFrom(message.getMessageBody()));
-                        Log.d(TAG, "Got test result message with code: "
-                                + mTestResult.get().getCode());
+                        result = ChreTestCommon.TestResult.parseFrom(message.getMessageBody());
+                        Log.d(TAG, "Got test result message with code: " + result.getCode());
                     } catch (InvalidProtocolBufferException e) {
                         Log.e(TAG, "Failed to parse message: " + e.getMessage());
                     }
@@ -106,15 +111,18 @@
                 }
             }
 
-            if (valid && mCountDownLatch != null) {
-                mCountDownLatch.countDown();
+            if (valid) {
+                updateTestResult(result, UpdateOption.SKIP_IF_EXISTS);
             }
         }
     }
 
     @Override
     public void onHubReset(ContextHubClient client) {
-        // TODO: Handle Reset
+        updateTestResult(ChreTestCommon.TestResult.newBuilder()
+                .setCode(ChreTestCommon.TestResult.Code.FAILED)
+                .setErrorMessage(ByteString.copyFromUtf8("Hub Reset"))
+                .build(), UpdateOption.ALWAYS);
     }
 
     /**
@@ -210,4 +218,17 @@
             Assert.fail("Failed to send message: result = " + result);
         }
     }
+
+    /**
+     * Set test result and update count
+     */
+    private void updateTestResult(ChreTestCommon.TestResult result, UpdateOption option) {
+        if (result != null && (mTestResult.get() == null || option == UpdateOption.ALWAYS)) {
+            mTestResult.set(result);
+        }
+
+        if (mCountDownLatch != null) {
+            mCountDownLatch.countDown();
+        }
+    }
 }
diff --git a/java/test/ble_concurrency/Android.bp b/java/test/ble_concurrency/Android.bp
new file mode 100644
index 0000000..eed3c99
--- /dev/null
+++ b/java/test/ble_concurrency/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_chre_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_chre_license"],
+}
+
+java_library {
+    name: "context-hub-ble-concurrency-test",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.test.rules",
+        "chre-test-utils",
+        "chqts",
+    ],
+
+    sdk_version: "test_current",
+}
diff --git a/java/test/ble_concurrency/src/com/google/android/chre/test/bleconcurrency/ContextHubBleConcurrencyTestExecutor.java b/java/test/ble_concurrency/src/com/google/android/chre/test/bleconcurrency/ContextHubBleConcurrencyTestExecutor.java
new file mode 100644
index 0000000..b961f64
--- /dev/null
+++ b/java/test/ble_concurrency/src/com/google/android/chre/test/bleconcurrency/ContextHubBleConcurrencyTestExecutor.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.bleconcurrency;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.hardware.location.NanoAppBinary;
+
+import com.google.android.chre.test.chqts.ContextHubChreApiTestExecutor;
+import com.google.android.utils.chre.ChreApiTestUtil;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+
+import org.junit.Assert;
+
+import java.util.HexFormat;
+import java.util.List;
+
+import dev.chre.rpc.proto.ChreApiTest;
+
+/**
+ * A class that can execute the CHRE BLE concurrency test.
+ */
+public class ContextHubBleConcurrencyTestExecutor extends ContextHubChreApiTestExecutor {
+    private static final String TAG = "ContextHubBleConcurrencyTestExecutor";
+
+    /**
+     * The delay to report results in milliseconds.
+     */
+    private static final int REPORT_DELAY_MS = 0;
+
+    /**
+     * The RSSI threshold for the BLE scan filter.
+     */
+    private static final int RSSI_THRESHOLD = -128;
+
+    /**
+     * The advertisement type for service data.
+     */
+    private static final int CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16 = 0x16;
+
+    /**
+     * CHRE BLE capabilities and filter capabilities.
+     */
+    private static final int CHRE_BLE_CAPABILITIES_SCAN = 1 << 0;
+    private static final int CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA = 1 << 7;
+
+    private BluetoothLeScanner mBluetoothLeScanner = null;
+
+    private final ScanCallback mScanCallback = new ScanCallback() {
+        @Override
+        public void onBatchScanResults(List<ScanResult> results) {
+            // do nothing
+        }
+
+        @Override
+        public void onScanFailed(int errorCode) {
+            Assert.fail("Failed to start a BLE scan on the host");
+        }
+
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            // do nothing
+        }
+    };
+
+    public ContextHubBleConcurrencyTestExecutor(NanoAppBinary nanoapp) {
+        super(nanoapp);
+
+        BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+        assertThat(bluetoothManager).isNotNull();
+        BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
+        if (bluetoothAdapter != null) {
+            mBluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
+        }
+    }
+
+    /**
+     * Runs the test.
+     */
+    public void run() throws Exception {
+        if (doesNecessaryBleCapabilitiesExist()) {
+            testHostScanFirst();
+            Thread.sleep(1000);
+            testChreScanFirst();
+        }
+    }
+
+    /**
+     * Generates a BLE scan filter that filters only for the known Google beacons:
+     * Google Eddystone and Nearby Fastpair.
+     */
+    private static ChreApiTest.ChreBleScanFilter getDefaultScanFilter() {
+        ChreApiTest.ChreBleGenericFilter eddystoneFilter =
+                ChreApiTest.ChreBleGenericFilter.newBuilder()
+                        .setType(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16)
+                        .setLength(2)
+                        .setData(ByteString.copyFrom(HexFormat.of().parseHex("AAFE")))
+                        .setMask(ByteString.copyFrom(HexFormat.of().parseHex("FFFF")))
+                        .build();
+        ChreApiTest.ChreBleGenericFilter nearbyFastpairFilter =
+                ChreApiTest.ChreBleGenericFilter.newBuilder()
+                        .setType(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16)
+                        .setLength(2)
+                        .setData(ByteString.copyFrom(HexFormat.of().parseHex("2CFE")))
+                        .setMask(ByteString.copyFrom(HexFormat.of().parseHex("FFFF")))
+                        .build();
+
+        return ChreApiTest.ChreBleScanFilter.newBuilder()
+                .setRssiThreshold(RSSI_THRESHOLD)
+                .setScanFilterCount(2)
+                .addScanFilters(eddystoneFilter)
+                .addScanFilters(nearbyFastpairFilter)
+                .build();
+    }
+
+    /**
+     * Generates a BLE scan filter that filters only for the known Google beacons:
+     * Google Eddystone and Nearby Fastpair. We specify the filter data in (little-endian) LE
+     * here as the CHRE code will take BE input and transform it to LE.
+     */
+    private static List<ScanFilter> getDefaultScanFilterHost() {
+        assertThat(CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16)
+                .isEqualTo(ScanRecord.DATA_TYPE_SERVICE_DATA_16_BIT);
+
+        ScanFilter scanFilter = new ScanFilter.Builder()
+                .setAdvertisingDataTypeWithData(
+                        ScanRecord.DATA_TYPE_SERVICE_DATA_16_BIT,
+                        ByteString.copyFrom(HexFormat.of().parseHex("AAFE")).toByteArray(),
+                        ByteString.copyFrom(HexFormat.of().parseHex("FFFF")).toByteArray())
+                .build();
+        ScanFilter scanFilter2 = new ScanFilter.Builder()
+                .setAdvertisingDataTypeWithData(
+                        ScanRecord.DATA_TYPE_SERVICE_DATA_16_BIT,
+                        ByteString.copyFrom(HexFormat.of().parseHex("2CFE")).toByteArray(),
+                        ByteString.copyFrom(HexFormat.of().parseHex("FFFF")).toByteArray())
+                .build();
+
+        return ImmutableList.of(scanFilter, scanFilter2);
+    }
+
+    /**
+     * Starts a BLE scan and asserts it was started successfully in a synchronous manner.
+     * This waits for the event to be received and returns the status in the event.
+     *
+     * @param scanFilter                The scan filter.
+     */
+    private void chreBleStartScanSync(ChreApiTest.ChreBleScanFilter scanFilter) throws Exception {
+        ChreApiTest.ChreBleStartScanAsyncInput.Builder inputBuilder =
+                ChreApiTest.ChreBleStartScanAsyncInput.newBuilder()
+                        .setMode(ChreApiTest.ChreBleScanMode.CHRE_BLE_SCAN_MODE_FOREGROUND)
+                        .setReportDelayMs(REPORT_DELAY_MS)
+                        .setHasFilter(scanFilter != null);
+        if (scanFilter != null) {
+            inputBuilder.setFilter(scanFilter);
+        }
+
+        ChreApiTestUtil util = new ChreApiTestUtil();
+        List<ChreApiTest.GeneralSyncMessage> response =
+                util.callServerStreamingRpcMethodSync(getRpcClient(),
+                        "chre.rpc.ChreApiTestService.ChreBleStartScanSync",
+                        inputBuilder.build());
+        assertThat(response).isNotEmpty();
+        for (ChreApiTest.GeneralSyncMessage status: response) {
+            assertThat(status.getStatus()).isTrue();
+        }
+    }
+
+    /**
+     * Stops a BLE scan and asserts it was started successfully in a synchronous manner.
+     * This waits for the event to be received and returns the status in the event.
+     */
+    private void chreBleStopScanSync() throws Exception {
+        ChreApiTestUtil util = new ChreApiTestUtil();
+        List<ChreApiTest.GeneralSyncMessage> response =
+                util.callServerStreamingRpcMethodSync(getRpcClient(),
+                        "chre.rpc.ChreApiTestService.ChreBleStopScanSync");
+        assertThat(response).isNotEmpty();
+        for (ChreApiTest.GeneralSyncMessage status: response) {
+            assertThat(status.getStatus()).isTrue();
+        }
+    }
+
+    /**
+     * Returns true if the required BLE capabilities and filter capabilities exist,
+     * otherwise returns false.
+     */
+    private boolean doesNecessaryBleCapabilitiesExist() throws Exception {
+        if (mBluetoothLeScanner == null) {
+            return false;
+        }
+
+        ChreApiTest.Capabilities capabilitiesResponse =
+                ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(),
+                        "chre.rpc.ChreApiTestService.ChreBleGetCapabilities");
+        int capabilities = capabilitiesResponse.getCapabilities();
+        if ((capabilities & CHRE_BLE_CAPABILITIES_SCAN) != 0) {
+            ChreApiTest.Capabilities filterCapabilitiesResponse =
+                    ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(),
+                            "chre.rpc.ChreApiTestService.ChreBleGetFilterCapabilities");
+            int filterCapabilities = filterCapabilitiesResponse.getCapabilities();
+            return (filterCapabilities & CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA) != 0;
+        }
+        return false;
+    }
+
+    /**
+     * Starts a BLE scan on the host side with known Google beacon filters.
+     */
+    private void startBleScanOnHost() {
+        ScanSettings scanSettings = new ScanSettings.Builder()
+                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+                .build();
+        mBluetoothLeScanner.startScan(getDefaultScanFilterHost(),
+                scanSettings, mScanCallback);
+    }
+
+    /**
+     * Stops a BLE scan on the host side.
+     */
+    private void stopBleScanOnHost() {
+        mBluetoothLeScanner.stopScan(mScanCallback);
+    }
+
+    /**
+     * Tests with the host starting scanning first.
+     */
+    private void testHostScanFirst() throws Exception {
+        startBleScanOnHost();
+        chreBleStartScanSync(getDefaultScanFilter());
+        Thread.sleep(1000);
+        chreBleStopScanSync();
+        stopBleScanOnHost();
+    }
+
+    /**
+     * Tests with CHRE starting scanning first.
+     */
+    private void testChreScanFirst() throws Exception {
+        chreBleStartScanSync(getDefaultScanFilter());
+        startBleScanOnHost();
+        Thread.sleep(1000);
+        stopBleScanOnHost();
+        chreBleStopScanSync();
+    }
+}
diff --git a/java/test/chqts/Android.bp b/java/test/chqts/Android.bp
index 398b98bb..306d0dc 100644
--- a/java/test/chqts/Android.bp
+++ b/java/test/chqts/Android.bp
@@ -29,7 +29,12 @@
     static_libs: [
         "androidx.test.rules",
         "chre-test-utils",
+        "chre_pigweed_utils",
+        "truth-prebuilt",
+        "guava",
+        "chre_api_test_proto_java_lite",
+        "pw_rpc_java_client",
     ],
 
-    sdk_version: "system_current",
+    sdk_version: "test_current",
 }
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubChreApiTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubChreApiTestExecutor.java
new file mode 100644
index 0000000..28b363f
--- /dev/null
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubChreApiTestExecutor.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.chqts;
+
+import android.content.Context;
+import android.hardware.location.ContextHubClient;
+import android.hardware.location.ContextHubClientCallback;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.NanoAppBinary;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.google.android.chre.utils.pigweed.ChreRpcClient;
+import com.google.android.utils.chre.ChreApiTestUtil;
+import com.google.android.utils.chre.ChreTestUtil;
+
+import org.junit.Assert;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import dev.pigweed.pw_rpc.Service;
+
+/**
+ * A base class for test executors that uses RPC-Based nanoapp.
+ */
+public class ContextHubChreApiTestExecutor extends ContextHubClientCallback {
+    private final List<NanoAppBinary> mNanoAppBinaries;
+    private final List<Long> mNanoAppIds = new ArrayList<Long>();
+    private final ContextHubClient mContextHubClient;
+    private final AtomicBoolean mChreReset = new AtomicBoolean(false);
+    protected final Context mContext = InstrumentationRegistry.getTargetContext();
+    protected final ContextHubManager mContextHubManager;
+    protected final ContextHubInfo mContextHub;
+    protected final List<ChreRpcClient> mRpcClients = new ArrayList<ChreRpcClient>();
+
+    public ContextHubChreApiTestExecutor(NanoAppBinary nanoapp) {
+        this(List.of(nanoapp));
+    }
+
+    public ContextHubChreApiTestExecutor(List<NanoAppBinary> nanoapps) {
+        mNanoAppBinaries = nanoapps;
+        mContextHubManager = mContext.getSystemService(ContextHubManager.class);
+        Assert.assertTrue(mContextHubManager != null);
+        List<ContextHubInfo> contextHubs = mContextHubManager.getContextHubs();
+        Assert.assertTrue(contextHubs.size() > 0);
+        mContextHub = contextHubs.get(0);
+        mContextHubClient = mContextHubManager.createClient(mContextHub, this);
+
+        for (NanoAppBinary nanoapp: nanoapps) {
+            mNanoAppIds.add(nanoapp.getNanoAppId());
+            Service chreApiService = ChreApiTestUtil.getChreApiService();
+            mRpcClients.add(new ChreRpcClient(
+                    mContextHubManager, mContextHub, nanoapp.getNanoAppId(),
+                    List.of(chreApiService), this));
+        }
+    }
+
+    @Override
+    public void onHubReset(ContextHubClient client) {
+        mChreReset.set(true);
+    }
+
+    /** Should be invoked before run() is invoked to set up the test, e.g. in a @Before method. */
+    public void init() {
+        mContextHubManager.enableTestMode();
+        for (NanoAppBinary nanoapp: mNanoAppBinaries) {
+            ChreTestUtil.loadNanoAppAssertSuccess(mContextHubManager, mContextHub, nanoapp);
+        }
+    }
+
+    /** Cleans up the test, should be invoked in e.g. @After method. */
+    public void deinit() {
+        if (mChreReset.get()) {
+            Assert.fail("CHRE reset during the test");
+        }
+
+        for (Long nanoappId: mNanoAppIds) {
+            ChreTestUtil.unloadNanoAppAssertSuccess(mContextHubManager, mContextHub, nanoappId);
+        }
+        mContextHubManager.disableTestMode();
+        mContextHubClient.close();
+    }
+
+    /**
+     * Gets the first RPC client in the list or returns null if the list is null or empty.
+     * This is useful for tests with only one nanoapp/client.
+     */
+    public ChreRpcClient getRpcClient() {
+        return mRpcClients != null && !mRpcClients.isEmpty() ? mRpcClients.get(0) : null;
+    }
+}
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubClientSendMessageTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubClientSendMessageTestExecutor.java
new file mode 100644
index 0000000..1a2ea1c
--- /dev/null
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubClientSendMessageTestExecutor.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.chqts;
+
+import static com.google.android.utils.chre.ChreTestUtil.assertLatchCountedDown;
+import static com.google.android.utils.chre.ContextHubServiceTestHelper.TIMEOUT_SECONDS_MESSAGE;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.hardware.location.ContextHubClient;
+import android.hardware.location.ContextHubClientCallback;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.google.android.utils.chre.ContextHubClientMessageValidator;
+import com.google.android.utils.chre.ContextHubServiceTestHelper;
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * An executor running tests by sending messages to the nanoapp in different ways.
+ *
+ * <p>To use this executor, a test should run {@link #init()} as part of the set-up annotated by
+ * {@code @Before} and {@link #deinit()} as part of tearing down annotated by {@code @After}.
+ */
+public class ContextHubClientSendMessageTestExecutor {
+    private static final String TAG = "ContextHubClientSendMessageExecutor";
+    private static final int MESSAGE_TYPE =
+            ContextHubTestConstants.MessageType.SERVICE_MESSAGE.asInt();
+    private final Random mRandom = new Random();
+    private final NanoAppBinary mNanoAppBinary;
+    private final ContextHubInfo mContextHubInfo;
+    private final ContextHubServiceTestHelper mTestHelper;
+
+    public ContextHubClientSendMessageTestExecutor(
+            ContextHubManager contextHubManager,
+            ContextHubInfo contextHubInfo,
+            NanoAppBinary nanoAppBinary) {
+        mNanoAppBinary = nanoAppBinary;
+        mContextHubInfo = contextHubInfo;
+        mTestHelper = new ContextHubServiceTestHelper(contextHubInfo, contextHubManager);
+    }
+
+    public void init() throws InterruptedException, TimeoutException {
+        mTestHelper.init();
+        mTestHelper.loadNanoAppAssertSuccess(mNanoAppBinary);
+    }
+
+    public void deinit() {
+        mTestHelper.unloadNanoAppAssertSuccess(mNanoAppBinary.getNanoAppId());
+        mTestHelper.deinit();
+    }
+
+    /** Generates a {@link NanoAppMessage} with randomized message body. */
+    private NanoAppMessage createNanoAppMessage() {
+        int maxMessageLength = mContextHubInfo.getMaxPacketLengthBytes();
+        byte[] payload = new byte[maxMessageLength];
+        mRandom.nextBytes(payload);
+        Log.d(TAG, "Created a nanoapp message: " + Base64.getEncoder().encodeToString(payload));
+        return NanoAppMessage.createMessageToNanoApp(
+                mNanoAppBinary.getNanoAppId(), MESSAGE_TYPE, payload);
+    }
+
+    /** Generates a list of {@link NanoAppMessage} with randomized message bodies. */
+    private List<NanoAppMessage> createNanoAppMessages(int numOfMessages) {
+        List<NanoAppMessage> result = new ArrayList<>(numOfMessages);
+        for (int i = 0; i < numOfMessages; i++) {
+            result.add(createNanoAppMessage());
+        }
+        return result;
+    }
+
+    /**
+     * Registers a client with a callback that verifies the incoming messages from nanoapps.
+     *
+     * @param latch the latch counted down when all the expected messages are received
+     * @param messagesToTheApp list of messages that the nanoapp is expected to receive
+     */
+    private ContextHubClient registerMessageClient(
+            CountDownLatch latch, List<NanoAppMessage> messagesToTheApp) {
+        ContextHubClientMessageValidator validator =
+                new ContextHubClientMessageValidator(
+                        messagesToTheApp, /* onComplete= */ latch::countDown);
+        ContextHubClientCallback callback =
+                new ContextHubClientCallback() {
+                    @Override
+                    public void onMessageFromNanoApp(
+                            ContextHubClient client, NanoAppMessage message) {
+                        if (message.getNanoAppId() == mNanoAppBinary.getNanoAppId()) {
+                            validator.assertMessageReceivedIsExpected(message);
+                        }
+                    }
+                };
+        return mTestHelper.createClient(callback);
+    }
+
+    /**
+     * Sends a message to an echo_message nanoapp, and verify that the client receives the same
+     * message back.
+     */
+    public void testSingleMessage(int numOfTestCycles) throws InterruptedException {
+        NanoAppMessage mNanoAppMessage = createNanoAppMessage();
+        for (int i = 0; i < numOfTestCycles; i++) {
+            CountDownLatch latch = new CountDownLatch(1);
+            ContextHubClient contextHubClient =
+                    registerMessageClient(latch, ImmutableList.of(mNanoAppMessage));
+            long startTimeMillis = SystemClock.elapsedRealtime();
+
+            int result = contextHubClient.sendMessageToNanoApp(mNanoAppMessage);
+            assertWithMessage("Send message failed with error code " + result)
+                    .that(result)
+                    .isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+            assertLatchCountedDown(latch, TIMEOUT_SECONDS_MESSAGE);
+
+            long roundTripTime = SystemClock.elapsedRealtime() - startTimeMillis;
+            Log.d(TAG, String.format("RTT = %s ms for round %s", roundTripTime, i));
+            unregisterMessageClient(contextHubClient);
+        }
+    }
+
+    /**
+     * Sends distinct messages from a ContextHubClient to the echo_message nanoapp, and verify that
+     * the client receives all messages back.
+     *
+     * @param numofTestCycles number of times the test is run
+     * @param numOfMessages number of messages sent to the nanoapp
+     */
+    public void testBurstMessages(int numofTestCycles, int numOfMessages)
+            throws InterruptedException {
+        for (int i = 0; i < numofTestCycles; i++) {
+            List<NanoAppMessage> messagesToNanoapp = createNanoAppMessages(numOfMessages);
+            CountDownLatch latch = new CountDownLatch(1);
+            ContextHubClient client = registerMessageClient(latch, messagesToNanoapp);
+            long startTimeMillis = SystemClock.elapsedRealtime();
+            for (NanoAppMessage message : messagesToNanoapp) {
+                int result = client.sendMessageToNanoApp(message);
+                assertWithMessage("Send message failed with error code " + result)
+                        .that(result)
+                        .isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+            }
+            long timeoutThreshold = numOfMessages * TIMEOUT_SECONDS_MESSAGE;
+            assertLatchCountedDown(latch, timeoutThreshold);
+            Log.d(
+                    TAG,
+                    String.format(
+                            "RTT (1 clients %s messages) is %s ms in round %s",
+                            numOfMessages, SystemClock.elapsedRealtime() - startTimeMillis, i));
+            unregisterMessageClient(client);
+        }
+    }
+
+    /**
+     * Creates a Runnable function to send messages for a ContextHubClient
+     *
+     * @param client the client to send message for
+     * @param messages the list of message to send
+     * @return the Runnable function
+     */
+    private static Runnable createMessageRunnable(
+            ContextHubClient client, List<NanoAppMessage> messages) {
+        return () -> {
+            for (NanoAppMessage message : messages) {
+                int result = client.sendMessageToNanoApp(message);
+                assertWithMessage("Send message failed with error code " + result)
+                        .that(result)
+                        .isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+            }
+        };
+    }
+
+    /**
+     * Sends different messages from multiple ContextHubClients concurrently to the nanoapp, and
+     * verify that each client receives its own messages back.
+     *
+     * @param numofTestCycles number of times the test is run
+     * @param numOfClients number of {@link ContextHubClient}
+     * @param numOfMessages number of messages sent to the nanoapp
+     */
+    public void testConcurrentMessages(int numofTestCycles, int numOfClients, int numOfMessages)
+            throws InterruptedException {
+        for (int i = 0; i < numofTestCycles; i++) {
+            CountDownLatch latch = new CountDownLatch(numOfClients);
+            ExecutorService executorService = Executors.newCachedThreadPool();
+            long startTimeMillis = SystemClock.elapsedRealtime();
+            List<ContextHubClient> clients = new ArrayList<>(numOfClients);
+
+            // for each ContextHubClient, create a runnable to send messages to the nanoapp
+            for (int j = 0; j < numOfClients; j++) {
+                List<NanoAppMessage> messagesToNanoapp = createNanoAppMessages(numOfMessages);
+                ContextHubClient client = registerMessageClient(latch, messagesToNanoapp);
+                clients.add(client);
+                executorService.submit(createMessageRunnable(client, messagesToNanoapp));
+            }
+            executorService.shutdown();
+            boolean isTerminated =
+                    executorService.awaitTermination(TIMEOUT_SECONDS_MESSAGE, TimeUnit.SECONDS);
+            assertWithMessage("ExecutorService is not terminated").that(isTerminated).isTrue();
+
+            // wait for all the clients to finish
+            long timeoutThreshold = numOfMessages * TIMEOUT_SECONDS_MESSAGE;
+            assertLatchCountedDown(latch, timeoutThreshold);
+            Log.d(
+                    TAG,
+                    String.format(
+                            "RTT (%s clients %s messages) is %s ms in round %s",
+                            numOfClients,
+                            numOfMessages,
+                            SystemClock.elapsedRealtime() - startTimeMillis,
+                            i));
+            // unregister all the clients
+            for (ContextHubClient client : clients) {
+                unregisterMessageClient(client);
+            }
+        }
+    }
+
+    private void unregisterMessageClient(ContextHubClient client) {
+        if (client != null) {
+            client.close();
+            Log.d(TAG, "Sending message after closing client, "
+                    + "expecting a error message from sendMessageToNanoApp");
+            int result = client.sendMessageToNanoApp(createNanoAppMessage());
+            assertWithMessage("Send message succeeded after client close")
+                    .that(result)
+                    .isNotEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+        }
+    }
+}
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubEstimatedHostTimeTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubEstimatedHostTimeTestExecutor.java
index e067940..a657c95 100644
--- a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubEstimatedHostTimeTestExecutor.java
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubEstimatedHostTimeTestExecutor.java
@@ -31,8 +31,14 @@
  * app to host: CONTINUE
  * host to app: CONTINUE, 64-bit time
  * app to host: SUCCESS
+ *
  */
 public class ContextHubEstimatedHostTimeTestExecutor extends ContextHubGeneralTestExecutor {
+    private static final long MAX_ALLOWED_TIME_DELTA_NS = 10000000;     // 10 ms.
+    private static final int NUM_RTT_SAMPLES = 5;
+    private long mMsgSendTimestampNs = 0;
+    private long mSmallestDelta = Long.MAX_VALUE;
+    private int mSamplesReceived = 0;
 
     public ContextHubEstimatedHostTimeTestExecutor(ContextHubManager manager, ContextHubInfo info,
             NanoAppBinary binary) {
@@ -46,13 +52,40 @@
         if (type != ContextHubTestConstants.MessageType.CONTINUE) {
             fail("Unexpected message type " + type);
         } else {
-            ByteBuffer buffer = ByteBuffer.allocate(8)
-                    .order(ByteOrder.LITTLE_ENDIAN)
-                    .putLong(SystemClock.elapsedRealtimeNanos());
+            if (data.length != 0 && mMsgSendTimestampNs != 0) {
+                long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
+                long chreTimestampNs = ByteBuffer.wrap(data)
+                        .order(ByteOrder.LITTLE_ENDIAN)
+                        .getLong();
 
-            sendMessageToNanoAppOrFail(nanoAppId,
-                    ContextHubTestConstants.MessageType.CONTINUE.asInt(),
-                    buffer.array());
+                // Identify the closest CHRE timestamp to the midpoint of RTT.
+                // This needs to be done across multiple rounds since RTT may not
+                // be evenly distributed in a single round
+                long middleTimestamp = (currentTimestampNs - mMsgSendTimestampNs) / 2
+                                       + mMsgSendTimestampNs;
+                long deltaNs = java.lang.Math.abs(chreTimestampNs - middleTimestamp);
+
+                mSmallestDelta = java.lang.Math.min(mSmallestDelta, deltaNs);
+                mSamplesReceived += 1;
+
+                if (mSamplesReceived == NUM_RTT_SAMPLES) {
+                    if (mSmallestDelta < MAX_ALLOWED_TIME_DELTA_NS) {
+                        pass();
+                    } else {
+                        fail("Inconsistent CHRE/AP timestamps- Current TS: "
+                                + currentTimestampNs + " CHRE TS: " + chreTimestampNs
+                                + " start TS: " + mMsgSendTimestampNs + " Smallest Delta: "
+                                + mSmallestDelta);
+                    }
+                }
+            }
+
+            if (mSamplesReceived < NUM_RTT_SAMPLES) {
+                mMsgSendTimestampNs = SystemClock.elapsedRealtimeNanos();
+                sendMessageToNanoAppOrFail(nanoAppId,
+                        ContextHubTestConstants.MessageType.CONTINUE.asInt(),
+                        new byte[0] /* data */);
+            }
         }
     }
 }
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java
index 07b6f69..10680f2 100644
--- a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubGeneralTestExecutor.java
@@ -198,7 +198,6 @@
         mCountDownLatch = new CountDownLatch(1);
 
         mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
-        Assert.assertTrue(mContextHubClient != null);
 
         for (GeneralTestNanoApp test : mGeneralTestNanoAppList) {
             if (test.loadAtInit()) {
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubHostEndpointInfoTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubHostEndpointInfoTestExecutor.java
new file mode 100644
index 0000000..7290ab8
--- /dev/null
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubHostEndpointInfoTestExecutor.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.chqts;
+
+import android.hardware.location.ContextHubClient;
+import android.hardware.location.NanoAppBinary;
+
+import com.google.android.utils.chre.ChreApiTestUtil;
+
+import org.junit.Assert;
+
+import dev.chre.rpc.proto.ChreApiTest;
+
+/**
+ * Test to ensure CHRE HostEndpoint related API works as expected.
+ */
+public class ContextHubHostEndpointInfoTestExecutor extends ContextHubChreApiTestExecutor {
+    public ContextHubHostEndpointInfoTestExecutor(NanoAppBinary nanoapp) {
+        super(nanoapp);
+    }
+
+    /**
+     * Validates if the host endpoint info stored in ChreApiTest Nanoapp is as expected.
+     *
+     * @param id the host endpoint id of the host endpoint.
+     * @param success true if we are expecting to retrieve host endpoint info by this id, otherwise
+     *     false.
+     */
+    private void validateHostEndpointInfoById(int id, boolean success) throws Exception {
+        ChreApiTest.ChreGetHostEndpointInfoInput input =
+                ChreApiTest.ChreGetHostEndpointInfoInput.newBuilder().setHostEndpointId(id).build();
+        ChreApiTest.ChreGetHostEndpointInfoOutput response =
+                ChreApiTestUtil.callUnaryRpcMethodSync(
+                        getRpcClient(), "chre.rpc.ChreApiTestService.ChreGetHostEndpointInfo",
+                        input);
+        if (!success) {
+            Assert.assertFalse(
+                    "Received host endpoint info for not connected id", response.getStatus());
+        } else {
+            Assert.assertTrue("Did not receive host endpoint info", response.getStatus());
+            Assert.assertEquals("Host endpoint Id mismatch", response.getHostEndpointId(), id);
+            Assert.assertTrue("IsNameValid should be true", response.getIsNameValid());
+            Assert.assertFalse("IsTagValid should be false", response.getIsTagValid());
+            Assert.assertEquals("Host endpoint name mismatch", mContext.getPackageName(),
+                    response.getEndpointName());
+        }
+    }
+
+    /**
+     * Test if we can get host endpoint info by id for connected host endpoint, and if we get
+     * failure result to query unconnected host endpoint info.
+     */
+    public void testGetHostEndpointInfo() throws Exception {
+        ContextHubClient firstClient = mContextHubManager.createClient(mContextHub, this);
+        ContextHubClient secondClient = mContextHubManager.createClient(mContextHub, this);
+        int firstClientHubId = firstClient.getId();
+        int secondClientHubId = secondClient.getId();
+        validateHostEndpointInfoById(firstClientHubId, true);
+        validateHostEndpointInfoById(secondClientHubId, true);
+
+        secondClient.close();
+
+        validateHostEndpointInfoById(firstClientHubId, true);
+        validateHostEndpointInfoById(secondClientHubId, false);
+
+        firstClient.close();
+    }
+
+    /**
+     * Validates if the nanoapp received a proper host endpoint notification disconnection event.
+     *
+     * @param id host endpoint id for the most recent disconnected host endpoint.
+     */
+    private void validateLatestHostEndpointNotification(int id) throws Exception {
+        // TODO(b/274791978): Deprecate this once we can capture event in test mode.
+        ChreApiTest.RetrieveLatestDisconnectedHostEndpointEventOutput response =
+                ChreApiTestUtil.callUnaryRpcMethodSync(
+                        getRpcClient(),
+                        "chre.rpc.ChreApiTestService.RetrieveLatestDisconnectedHostEndpointEvent");
+        Assert.assertEquals(
+                "Should have exactly receive 1 host endpoint notification",
+                1,
+                response.getDisconnectedCount());
+        Assert.assertEquals("Host endpoint Id mismatch", response.getHostEndpointId(), id);
+    }
+
+    /**
+     * Asks the test nanoapp to configure host endpoint notification.
+     *
+     * @param hostEndpointId which host endpoint to get notification.
+     * @param enable true to enable host endpoint notification, false to disable.
+     */
+    private void configureHostEndpointNotification(int hostEndpointId, boolean enable)
+            throws Exception {
+        ChreApiTest.ChreConfigureHostEndpointNotificationsInput request =
+                ChreApiTest.ChreConfigureHostEndpointNotificationsInput.newBuilder()
+                        .setHostEndpointId(hostEndpointId)
+                        .setEnable(enable)
+                        .build();
+        ChreApiTest.Status response =
+                ChreApiTestUtil.callUnaryRpcMethodSync(
+                        getRpcClient(),
+                        "chre.rpc.ChreApiTestService.ChreConfigureHostEndpointNotifications",
+                        request);
+        Assert.assertTrue("Failed to configure host endpoint notification", response.getStatus());
+    }
+
+    /**
+     * Test if we can register host endpoint notification and if it receives the event once a
+     * registered host endpoint got disconnected.
+     */
+    public void testGetHostEndpointNotificationOnDisconnect() throws Exception {
+        ContextHubClient client = mContextHubManager.createClient(mContextHub, this);
+        int clientHostEndpointId = client.getId();
+        configureHostEndpointNotification(clientHostEndpointId, true);
+        client.close();
+        Thread.sleep(1000);
+        validateLatestHostEndpointNotification(clientHostEndpointId);
+    }
+}
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubLoadAndUnloadNanoAppsTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubLoadAndUnloadNanoAppsTestExecutor.java
new file mode 100644
index 0000000..4f775af
--- /dev/null
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubLoadAndUnloadNanoAppsTestExecutor.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.chqts;
+
+import static com.google.android.utils.chre.ContextHubServiceTestHelper.TIMEOUT_SECONDS_LOAD;
+import static com.google.android.utils.chre.ContextHubServiceTestHelper.TIMEOUT_SECONDS_UNLOAD;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.hardware.location.ContextHubClient;
+import android.hardware.location.ContextHubClientCallback;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppBinary;
+
+import com.google.android.utils.chre.ContextHubServiceTestHelper;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ContextHubLoadAndUnloadNanoAppsTestExecutor {
+    private static final int NUM_TEST_CYCLES = 10;
+    private final ContextHubServiceTestHelper mTestHelper;
+
+    private static class OnLoadUnloadCompleteListener
+            implements ContextHubTransaction.OnCompleteListener<Void> {
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+        @Override
+        public void onComplete(
+                ContextHubTransaction<Void> transaction,
+                ContextHubTransaction.Response<Void> response) {
+            int result = response.getResult();
+            String type =
+                    ContextHubTransaction.typeToString(transaction.getType(), true /* upperCase */);
+            assertWithMessage("%s transaction failed with error code %s", type, result)
+                    .that(result)
+                    .isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+            mCountDownLatch.countDown();
+        }
+
+        CountDownLatch getCountDownLatch() {
+            return mCountDownLatch;
+        }
+    }
+
+    private static class ContextHubClientTestCallback extends ContextHubClientCallback {
+        private final AtomicInteger mNanoAppLoadedCount = new AtomicInteger(0);
+        private final AtomicInteger mRemainingTransactionCount;
+        private final long mExpectedNanoAppId;
+        final CountDownLatch mDoneCountDownLatch = new CountDownLatch(1);
+
+        ContextHubClientTestCallback(long expectedNanoAppId, int numOfTestCycles) {
+            mExpectedNanoAppId = expectedNanoAppId;
+            mRemainingTransactionCount = new AtomicInteger(numOfTestCycles * 2);
+        }
+
+        @Override
+        public void onNanoAppLoaded(ContextHubClient client, long nanoAppId) {
+            if (nanoAppId == mExpectedNanoAppId) {
+                // After loading the nanoapp, the count must be 1
+                assertThat(mNanoAppLoadedCount.incrementAndGet()).isEqualTo(1);
+                // At least one remaining transaction to unload the nanoapp
+                assertThat(mRemainingTransactionCount.decrementAndGet()).isGreaterThan(0);
+            }
+        }
+
+        @Override
+        public void onNanoAppUnloaded(ContextHubClient client, long nanoAppId) {
+            if (nanoAppId == mExpectedNanoAppId) {
+                // After unloading the nanoapp, the count must be back to 0
+                assertThat(mNanoAppLoadedCount.decrementAndGet()).isEqualTo(0);
+                // Declare the test is done after all the expected transactions are fulfilled
+                if (mRemainingTransactionCount.decrementAndGet() == 0) {
+                    mDoneCountDownLatch.countDown();
+                }
+            }
+        }
+
+        CountDownLatch getDoneCountDownLatch() {
+            return mDoneCountDownLatch;
+        }
+    }
+
+    public ContextHubLoadAndUnloadNanoAppsTestExecutor(
+            ContextHubManager contextHubManager, ContextHubInfo contextHubInfo) {
+        mTestHelper = new ContextHubServiceTestHelper(contextHubInfo, contextHubManager);
+    }
+
+    public void init() throws Exception {
+        mTestHelper.init();
+    }
+
+    public void deinit() {
+        mTestHelper.deinit();
+    }
+
+    /**
+     * Repeatedly loads and unloads a nanoapp synchronously, and verifies that the naonapp is loaded
+     * successfully.
+     */
+    public void loadUnloadSyncTest(NanoAppBinary nanoAppBinary) throws Exception {
+        List<Long> nanoAppIds = Collections.singletonList(nanoAppBinary.getNanoAppId());
+        for (int i = 0; i < NUM_TEST_CYCLES; i++) {
+            mTestHelper.loadNanoAppAssertSuccess(nanoAppBinary);
+            mTestHelper.assertNanoAppsLoaded(nanoAppIds);
+
+            mTestHelper.unloadNanoAppAssertSuccess(nanoAppBinary.getNanoAppId());
+            mTestHelper.assertNanoAppsNotLoaded(nanoAppIds);
+        }
+    }
+
+    /**
+     * Repeatedly loads and unloads a nanoapp asynchronously, and verifies that the naonapp is
+     * loaded successfully.
+     */
+    public void loadUnloadAsyncTest(NanoAppBinary nanoAppBinary) throws Exception {
+        List<Long> nanoAppIds = Collections.singletonList(nanoAppBinary.getNanoAppId());
+        ContextHubTransaction<Void> transaction;
+        for (int i = 0; i < NUM_TEST_CYCLES; i++) {
+            transaction = mTestHelper.loadNanoApp(nanoAppBinary);
+            waitForCompleteAsync(transaction);
+            mTestHelper.assertNanoAppsLoaded(nanoAppIds);
+
+            transaction = mTestHelper.unloadNanoApp(nanoAppBinary.getNanoAppId());
+            waitForCompleteAsync(transaction);
+            mTestHelper.assertNanoAppsLoaded(Collections.emptyList());
+        }
+    }
+
+    /** Load and unload 2 nanoapps concurrently and verify that we can find them through a query. */
+    public void loadUnloadConcurrentTest(
+            NanoAppBinary nanoAppBinary1, NanoAppBinary nanoAppBinary2, int numOfTestCycles)
+            throws InterruptedException, TimeoutException {
+        long nanoAppId1 = nanoAppBinary1.getNanoAppId();
+        long nanoAppId2 = nanoAppBinary2.getNanoAppId();
+        List<Long> nanoAppIdList = Arrays.asList(nanoAppId1, nanoAppId2);
+        List<Runnable> loadRunnables =
+                Arrays.asList(
+                        () -> mTestHelper.loadNanoAppAssertSuccess(nanoAppBinary1),
+                        () -> mTestHelper.loadNanoAppAssertSuccess(nanoAppBinary2));
+        List<Runnable> unloadRunnables =
+                Arrays.asList(
+                        () -> mTestHelper.unloadNanoAppAssertSuccess(nanoAppId1),
+                        () -> mTestHelper.unloadNanoAppAssertSuccess(nanoAppId2));
+
+        for (int i = 0; i < numOfTestCycles; i++) {
+            mTestHelper.runConcurrentTasks(
+                    loadRunnables, 2 * TIMEOUT_SECONDS_LOAD, TimeUnit.SECONDS);
+            mTestHelper.assertNanoAppsLoaded(nanoAppIdList);
+
+            mTestHelper.runConcurrentTasks(
+                    unloadRunnables, 2 * TIMEOUT_SECONDS_UNLOAD, TimeUnit.SECONDS);
+            mTestHelper.assertNanoAppsNotLoaded(nanoAppIdList);
+        }
+    }
+
+    /**
+     * Starts multiple load and unload transactions asynchronously (queued up at the service), and
+     * verifies all transactions succeed.
+     */
+    public void queuedLoadUnloadTest(NanoAppBinary nanoAppBinary, int numOfTestCycles)
+            throws InterruptedException {
+        ContextHubClientTestCallback callback =
+                new ContextHubClientTestCallback(nanoAppBinary.getNanoAppId(), numOfTestCycles);
+        CountDownLatch latch = callback.getDoneCountDownLatch();
+        // create the client to activate the callback
+        ContextHubClient client = mTestHelper.createClient(callback);
+
+        for (int i = 0; i < numOfTestCycles; i++) {
+            mTestHelper.loadNanoApp(nanoAppBinary);
+            mTestHelper.unloadNanoApp(nanoAppBinary.getNanoAppId());
+        }
+
+        long timeoutThreshold = numOfTestCycles * (TIMEOUT_SECONDS_LOAD + TIMEOUT_SECONDS_UNLOAD);
+        boolean isCountedDown = latch.await(timeoutThreshold, TimeUnit.SECONDS);
+        assertWithMessage(
+                        "Waiting for latch to count down timeout after %s seconds",
+                        timeoutThreshold)
+                .that(isCountedDown)
+                .isTrue();
+
+        client.close();
+    }
+
+    private void waitForCompleteAsync(ContextHubTransaction<Void> transaction)
+            throws InterruptedException {
+        OnLoadUnloadCompleteListener listener = new OnLoadUnloadCompleteListener();
+        transaction.setOnCompleteListener(listener);
+        String type =
+                ContextHubTransaction.typeToString(transaction.getType(), /* upperCase= */ false);
+        long timeoutThreshold =
+                (transaction.getType() == ContextHubTransaction.TYPE_LOAD_NANOAPP)
+                        ? TIMEOUT_SECONDS_LOAD
+                        : TIMEOUT_SECONDS_UNLOAD;
+        boolean isCountedDown =
+                listener.getCountDownLatch().await(timeoutThreshold, TimeUnit.SECONDS);
+        assertWithMessage(
+                        "Waiting for %s transaction timeout after %s seconds",
+                        type, timeoutThreshold)
+                .that(isCountedDown)
+                .isTrue();
+    }
+}
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubNanoAppRequirementsTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubNanoAppRequirementsTestExecutor.java
new file mode 100644
index 0000000..5a590d4
--- /dev/null
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubNanoAppRequirementsTestExecutor.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.chqts;
+
+import android.hardware.location.NanoAppBinary;
+
+import com.google.android.utils.chre.ChreApiTestUtil;
+
+import org.junit.Assert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dev.chre.rpc.proto.ChreApiTest;
+public class ContextHubNanoAppRequirementsTestExecutor extends ContextHubChreApiTestExecutor {
+    private final List<Long> mPreloadedNanoappIds;
+
+    private static final int CHRE_SENSOR_ACCELEROMETER_INTERVAL_NS = 20000000;
+    private static final int CHRE_SENSOR_GYROSCOPE_INTERVAL_NS = 2500000;
+
+    // TODO(b/262043286): Enable this once BLE is available
+    /*
+    private static final int CHRE_BLE_CAPABILITIES_SCAN = 1 << 0;
+    private static final int CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA = 1 << 7;
+    */
+
+    private static final int CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT = 2;
+    private static final int CHRE_SENSOR_TYPE_ACCELEROMETER = 1;
+    private static final int CHRE_SENSOR_TYPE_GYROSCOPE = 6;
+
+    private static final int CHRE_AUDIO_MIN_BUFFER_SIZE_NS = 2000000000;
+
+    private static final int RPC_TIMEOUT_IN_SECONDS = 2;
+    private static final int MAX_AUDIO_SOURCES_TO_TRY = 10;
+
+    /**
+     * Formats for audio that can be provided to a nanoapp. See enum chreAudioDataFormat in the
+     * CHRE API.
+     */
+    public enum ChreAudioDataFormat {
+        /**
+         * Unsigned, 8-bit u-Law encoded data as specified by ITU-T G.711.
+         */
+        CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW(0),
+
+        /**
+         * Signed, 16-bit linear PCM data. Endianness must be native to the local
+         * processor.
+         */
+        CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM(1);
+
+        private final int mId;
+
+        ChreAudioDataFormat(int id) {
+            mId = id;
+        }
+
+        /**
+         * Returns the ID.
+         *
+         * @return int      the ID
+         */
+        public int getId() {
+            return mId;
+        }
+    }
+
+    public ContextHubNanoAppRequirementsTestExecutor(NanoAppBinary nanoapp) {
+        super(nanoapp);
+        mPreloadedNanoappIds = new ArrayList<Long>();
+        for (long nanoappId: mContextHubManager.getPreloadedNanoAppIds(mContextHub)) {
+            mPreloadedNanoappIds.add(nanoappId);
+        }
+    }
+
+    /**
+     * Tests for specific sensors for activity.
+     */
+    public void assertActivitySensors() throws Exception {
+        findDefaultSensorAndAssertItExists(CHRE_SENSOR_TYPE_INSTANT_MOTION_DETECT);
+        int accelerometerHandle =
+                findDefaultSensorAndAssertItExists(CHRE_SENSOR_TYPE_ACCELEROMETER);
+        getSensorInfoAndVerifyInterval(accelerometerHandle,
+                CHRE_SENSOR_ACCELEROMETER_INTERVAL_NS);
+    }
+
+    /**
+     * Tests for specific sensors for movement.
+     */
+    public void assertMovementSensors() throws Exception {
+        findDefaultSensorAndAssertItExists(CHRE_SENSOR_TYPE_ACCELEROMETER);
+        int gyroscopeHandle =
+                findDefaultSensorAndAssertItExists(CHRE_SENSOR_TYPE_GYROSCOPE);
+        getSensorInfoAndVerifyInterval(gyroscopeHandle,
+                CHRE_SENSOR_GYROSCOPE_INTERVAL_NS);
+
+        findAudioSourceAndAssertItExists(CHRE_AUDIO_MIN_BUFFER_SIZE_NS,
+                ChreAudioDataFormat.CHRE_AUDIO_DATA_FORMAT_16_BIT_SIGNED_PCM);
+    }
+
+    /**
+     * Tests for specific BLE capabilities.
+     */
+    public void assertBleSensors() throws Exception {
+        // TODO(b/262043286): Enable this once BLE is available
+        /*
+        mExecutor.getBleCapabilitiesAndAssertCapabilityExists(CHRE_BLE_CAPABILITIES_SCAN);
+        mExecutor.getBleFilterCapabilitiesAndAssertCapabilityExists(
+                CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA);
+        */
+    }
+
+    /**
+     * Returns true if the nanoappId represents a preloaded nanoapp; false otherwise.
+     */
+    public boolean isNanoappPreloaded(long nanoappId) {
+        return mPreloadedNanoappIds.contains(nanoappId);
+    }
+
+    /**
+     * Finds the default sensor for the given type and asserts that it exists.
+     *
+     * @param sensorType        the type of the sensor (constant)
+     *
+     * @return                  the handle of the sensor
+     */
+    public int findDefaultSensorAndAssertItExists(int sensorType) throws Exception {
+        ChreApiTest.ChreSensorFindDefaultInput input = ChreApiTest.ChreSensorFindDefaultInput
+                .newBuilder().setSensorType(sensorType).build();
+        ChreApiTest.ChreSensorFindDefaultOutput response =
+                ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(),
+                        "chre.rpc.ChreApiTestService.ChreSensorFindDefault", input);
+        Assert.assertTrue("Did not find sensor with type: " + sensorType,
+                response.getFoundSensor());
+        return response.getSensorHandle();
+    }
+
+    /**
+     * Gets the sensor samping status and verifies the minimum interval from chreGetSensorInfo
+     * is less than or equal to the expected interval -> the sensor is at least as fast at sampling
+     * as is required.
+     *
+     * @param sensorHandle          the handle to the sensor
+     * @param expectedInterval      the true sampling interval
+     */
+    public void getSensorInfoAndVerifyInterval(int sensorHandle, long expectedInterval)
+            throws Exception {
+        ChreApiTest.ChreHandleInput input =
+                ChreApiTest.ChreHandleInput.newBuilder()
+                .setHandle(sensorHandle).build();
+        ChreApiTest.ChreGetSensorInfoOutput response =
+                ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(),
+                        "chre.rpc.ChreApiTestService.ChreGetSensorInfo", input);
+        Assert.assertTrue("Failed to get sensor info for sensor with handle: " + sensorHandle,
+                response.getStatus());
+        Assert.assertTrue("The sensor with handle: " + sensorHandle
+                + " does not sample at a fast enough rate.",
+                response.getMinInterval() <= expectedInterval);
+    }
+
+    /**
+     * Iterates through possible audio sources to find a source that has a minimum buffer
+     * size in ns of expectedMinBufferSizeNs and a format of format.
+     *
+     * @param expectedMinBufferSizeInNs         the minimum buffer size in nanoseconds (ns)
+     * @param format                            the audio format enum
+     */
+    public void findAudioSourceAndAssertItExists(long expectedMinBufferSizeNs,
+            ChreAudioDataFormat format) throws Exception {
+        boolean foundAcceptableAudioSource = false;
+        for (int i = 0; i < MAX_AUDIO_SOURCES_TO_TRY; ++i) {
+            ChreApiTest.ChreHandleInput input =
+                    ChreApiTest.ChreHandleInput.newBuilder()
+                    .setHandle(i).build();
+            ChreApiTest.ChreAudioGetSourceOutput response =
+                    ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(),
+                            "chre.rpc.ChreApiTestService.ChreAudioGetSource", input);
+            if (response.getStatus()
+                    && response.getMinBufferDuration() >= expectedMinBufferSizeNs
+                    && response.getFormat() == format.getId()) {
+                foundAcceptableAudioSource = true;
+                break;
+            }
+        }
+        Assert.assertTrue("Did not find an acceptable audio source with a minimum buffer "
+                + "size of " + expectedMinBufferSizeNs
+                + " ns and format: " + format.name(),
+                foundAcceptableAudioSource);
+    }
+
+    // TODO(b/262043286): Enable this once BLE is available
+    /*
+    /**
+     * Gets the BLE capabilities and asserts the capability exists.
+     *
+     * @param capability        the capability to assert exists
+     *
+    public void getBleCapabilitiesAndAssertCapabilityExists(int capability) throws Exception {
+        getCapabilitiesAndAssertCapabilityExists(
+                "chre.rpc.ChreApiTestService.ChreBleGetCapabilities",
+                capability,
+                "Did not find the BLE capabilities");
+    }
+
+    /**
+     * Gets the BLE filter capabilities and asserts the capability exists.
+     *
+     * @param capability        the capability to assert exists
+     *
+    public void getBleFilterCapabilitiesAndAssertCapabilityExists(int capability) throws Exception {
+        getCapabilitiesAndAssertCapabilityExists(
+                "chre.rpc.ChreApiTestService.ChreBleGetFilterCapabilities",
+                capability,
+                "Did not find the BLE filter capabilities");
+    }
+    */
+
+    // TODO(b/262043286): Enable this once BLE is available
+    /*
+    /**
+     * Gets the capabilities returned by RPC function: function and asserts that
+     * capability exists with a failure message: errorMessage.
+     *
+     * @param function          the function to call
+     * @param capability        the capability to assert exists
+     * @param errorMessage      the error message to show when there is an assertion failure
+     *
+    private void getCapabilitiesAndAssertCapabilityExists(String function,
+            int capability, String errorMessage) throws Exception {
+        ChreApiTest.Capabilities capabilitiesResponse =
+                ChreApiTestUtil.callUnaryRpcMethodSync(getRpcClient(), function);
+        int capabilities = capabilitiesResponse.getCapabilities();
+        Assert.assertTrue(errorMessage + ": " + capability,
+                (capabilities & capability) != 0);
+    }
+    */
+}
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubPendingIntentTestExecutor.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubPendingIntentTestExecutor.java
new file mode 100644
index 0000000..272de45
--- /dev/null
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubPendingIntentTestExecutor.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.chqts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.android.utils.chre.ContextHubServiceTestHelper.TIMEOUT_SECONDS_LOAD;
+import static com.google.android.utils.chre.ContextHubServiceTestHelper.TIMEOUT_SECONDS_MESSAGE;
+import static com.google.android.utils.chre.ContextHubServiceTestHelper.TIMEOUT_SECONDS_UNLOAD;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.location.ContextHubClient;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubIntentEvent;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.os.Build;
+import android.util.Log;
+
+import com.google.android.utils.chre.ContextHubBroadcastReceiver;
+import com.google.android.utils.chre.ContextHubServiceTestHelper;
+
+import org.junit.Assert;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class ContextHubPendingIntentTestExecutor {
+    public static final String ACTION = "com.google.android.chre.test.chqts.ACTION";
+
+    private static final String TAG = "ContextHubPendingIntentTest";
+    // Additional timeout to delay receiving Intent events.
+    private static final int TIMEOUT_INTENT_EVENT_SECONDS = 5;
+    private static final int MESSAGE_TYPE =
+            ContextHubTestConstants.MessageType.SERVICE_MESSAGE.asInt();
+
+    private final long mNanoAppId;
+    private final NanoAppBinary mNanoAppBinary;
+    private final Context mContext = getInstrumentation().getTargetContext();
+    private final BroadcastReceiver mReceiver = new ContextHubBroadcastReceiver();
+    private final PendingIntent mPendingIntent;
+    private final ContextHubInfo mContextHubInfo;
+    private final ContextHubManager mContextHubManager;
+    private final ContextHubServiceTestHelper mTestHelper;
+
+    private ContextHubClient mContextHubClient = null;
+
+    public ContextHubPendingIntentTestExecutor(ContextHubManager contextHubManager,
+            ContextHubInfo contextHubInfo, NanoAppBinary nanoAppBinary) {
+        mNanoAppBinary = nanoAppBinary;
+        mContextHubInfo = contextHubInfo;
+        mContextHubManager = contextHubManager;
+        mTestHelper = new ContextHubServiceTestHelper(contextHubInfo, contextHubManager);
+        mNanoAppId = mNanoAppBinary.getNanoAppId();
+        IntentFilter filter = new IntentFilter(ACTION);
+        mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED/*UNAUDITED*/);
+        Intent intent = new Intent(ACTION).setPackage(mContext.getPackageName());
+        mPendingIntent = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
+    }
+
+    public void init() throws InterruptedException, TimeoutException {
+        mTestHelper.initAndUnloadAllNanoApps();
+    }
+
+    /**
+     * This test does the following:
+     * - Loads the who_am_i nanoapp, creates a ContextHubClient, and asks the nanoapp for the host
+     * endpoint ID.
+     * - Regenerates the ContextHubClient and asks the host endpoint ID, and checks if they are the
+     * same.
+     * - Unloads the nanoapp and closes the ContextHubClient.
+     * - Creates a ContextHubClient associated with a different nanoapp, and checks that Intent
+     * events are not received for the who_am_i nanoapp.
+     */
+    public void basicPendingIntentTest(long sampleNanoAppId) {
+        createClient(mNanoAppId);
+        checkLoadNanoApp();
+
+        for (int i = 0; i < 10; i++) {
+            short hostEndpointId = getIdFromNanoApp();
+            Log.d(TAG, "My host endpoint ID is " + hostEndpointId);
+
+            mContextHubClient = mTestHelper.createClient(mPendingIntent, mNanoAppId);
+            assertWithMessage("Failed to regenerate PendingIntent client").that(
+                    mContextHubClient).isNotNull();
+            assertThat(getIdFromNanoApp()).isEqualTo(hostEndpointId);
+            // ContextHubClient.getId() was introduced in Android T. Check version before calling.
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                Assert.assertEquals(mContextHubClient.getId(), hostEndpointId);
+            }
+        }
+
+        checkUnloadNanoApp();
+        mContextHubClient.close();
+
+        mContextHubClient = mTestHelper.createClient(mPendingIntent, sampleNanoAppId);
+        mTestHelper.loadNanoAppAssertSuccess(mNanoAppBinary);
+
+        NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(mNanoAppId, MESSAGE_TYPE,
+                new byte[0]);
+        int result = mContextHubClient.sendMessageToNanoApp(message);
+        assertThat(result).isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+
+        mTestHelper.unloadNanoAppAssertSuccess(mNanoAppBinary.getNanoAppId());
+        ContextHubBroadcastReceiver.assertNoIntentEventReceived(
+                TIMEOUT_SECONDS_MESSAGE + TIMEOUT_INTENT_EVENT_SECONDS, TimeUnit.SECONDS);
+    }
+
+    public void deinit() {
+        if (mContextHubClient != null) {
+            mContextHubClient.close();
+            mContextHubClient = null;
+        }
+        mContext.unregisterReceiver(mReceiver);
+        mTestHelper.deinit();
+    }
+
+    /** Loads a nanoapp and asserts that a load Intent event is received. */
+    private void checkLoadNanoApp() {
+        mContextHubManager.loadNanoApp(mContextHubInfo, mNanoAppBinary);
+        ContextHubBroadcastReceiver.pollIntentEvent(
+                TIMEOUT_SECONDS_LOAD + TIMEOUT_INTENT_EVENT_SECONDS, TimeUnit.SECONDS,
+                ContextHubManager.EVENT_NANOAPP_LOADED, mContextHubInfo, mNanoAppId);
+    }
+
+    /** Asks the who_am_i nanoapp for a ContextHubClient's host endpoint ID via an Intent event. */
+    public short getIdFromNanoApp() {
+        NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(mNanoAppId, MESSAGE_TYPE,
+                new byte[0]);
+        int result = mContextHubClient.sendMessageToNanoApp(message);
+        assertThat(result).isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+
+        return waitForIdFromNanoApp();
+    }
+
+    /**
+     * Waits for a Intent event message from the who_am_i nanoapp indicating the host endpoint ID.
+     */
+    public short waitForIdFromNanoApp() {
+        ContextHubIntentEvent event = ContextHubBroadcastReceiver.pollIntentEvent(
+                TIMEOUT_SECONDS_MESSAGE + TIMEOUT_INTENT_EVENT_SECONDS, TimeUnit.SECONDS,
+                ContextHubManager.EVENT_NANOAPP_MESSAGE, mContextHubInfo, mNanoAppId);
+        byte[] appMessage = event.getNanoAppMessage().getMessageBody();
+        return ByteBuffer.wrap(appMessage).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(0);
+    }
+
+    public ContextHubClient createClient(long nanoAppId) {
+        mContextHubClient = mTestHelper.createClient(mPendingIntent, nanoAppId);
+        Assert.assertNotNull("Failed to register PendingIntent client", mContextHubClient);
+        return mContextHubClient;
+    }
+
+    /** Unloads a nanoapp and asserts that an unload Intent event is received. */
+    private void checkUnloadNanoApp() {
+        mTestHelper.unloadNanoApp(mNanoAppId);
+        ContextHubBroadcastReceiver.pollIntentEvent(
+                TIMEOUT_SECONDS_UNLOAD + TIMEOUT_INTENT_EVENT_SECONDS, TimeUnit.SECONDS,
+                ContextHubManager.EVENT_NANOAPP_UNLOADED, mContextHubInfo, mNanoAppId);
+    }
+}
diff --git a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubTestConstants.java b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubTestConstants.java
index b2fa1c3..4d46faa 100644
--- a/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubTestConstants.java
+++ b/java/test/chqts/src/com/google/android/chre/test/chqts/ContextHubTestConstants.java
@@ -223,7 +223,12 @@
         /**
          * Test: ContextHubSimpleGeneralNanoAppTests[BasicSensorFlushAsyncTest]
          */
-        BASIC_SENSOR_FLUSH_ASYNC_TEST(0x0426);
+        BASIC_SENSOR_FLUSH_ASYNC_TEST(0x0426),
+
+        /**
+         * Test: ContextHubSimpleGeneralNanoAppTests[BasicBleTest]
+         */
+        BASIC_BLE_TEST(0x0427);
 
         private final int mValue;
         TestNames(int value) {
diff --git a/java/test/chre_concurrency/Android.bp b/java/test/chre_concurrency/Android.bp
new file mode 100644
index 0000000..5e6d7ca
--- /dev/null
+++ b/java/test/chre_concurrency/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_chre_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_chre_license"],
+}
+
+java_library {
+    name: "context-hub-chre-concurrency-test",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.test.rules",
+        "chre-test-utils",
+        "chqts",
+    ],
+
+    sdk_version: "test_current",
+}
diff --git a/java/test/chre_concurrency/src/com/google/android/chre/test/chreconcurrency/ContextHubChreConcurrencyTestExecutor.java b/java/test/chre_concurrency/src/com/google/android/chre/test/chreconcurrency/ContextHubChreConcurrencyTestExecutor.java
new file mode 100644
index 0000000..4610179
--- /dev/null
+++ b/java/test/chre_concurrency/src/com/google/android/chre/test/chreconcurrency/ContextHubChreConcurrencyTestExecutor.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.chreconcurrency;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.location.NanoAppBinary;
+
+import com.google.android.chre.test.chqts.ContextHubChreApiTestExecutor;
+import com.google.android.utils.chre.ChreApiTestUtil;
+
+import org.junit.Assert;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import dev.chre.rpc.proto.ChreApiTest;
+
+/**
+ * A class that can execute the CHRE BLE concurrency test.
+ */
+public class ContextHubChreConcurrencyTestExecutor extends ContextHubChreApiTestExecutor {
+    private static final String TAG = "ContextHubChreConcurrencyTestExecutor";
+
+    private static final int CHRE_EVENT_SENSOR_ACCELEROMETER_DATA = 0x0100 + 1;
+
+    private static final int CHRE_EVENT_SENSOR_SAMPLING_CHANGE = 0x200;
+
+    private static final int CHRE_SENSOR_CONFIGURE_RAW_POWER_ON = 1 << 0;
+
+    private static final int CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS = 1 << 1;
+
+    private static final int CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS =
+            CHRE_SENSOR_CONFIGURE_RAW_POWER_ON | CHRE_SENSOR_CONFIGURE_RAW_REPORT_CONTINUOUS;
+
+    private static final int CHRE_SENSOR_TYPE_ACCELEROMETER = 1;
+
+    private static final int NUM_EVENTS_TO_GATHER = 10;
+
+    private static final long TIMEOUT_IN_NS = 5000000000L;
+
+    /**
+     * The multiplier used to allow for jitter in the timestamps of the samples. This is
+     * multiplied against the requested interval between samples to produce the final interval
+     * that is compared with the real interval between samples. This allows for 5% of jitter.
+     */
+    private static final double JITTER_MULTIPLIER = 1.05;
+
+    public ContextHubChreConcurrencyTestExecutor(NanoAppBinary nanoapp, NanoAppBinary nanoapp2) {
+        super(Arrays.asList(nanoapp, nanoapp2));
+    }
+
+    /**
+     * Tests for accelerometer data concurrency.
+     */
+    public void runAccelerometerConcurrencyTest() throws Exception {
+        Future<List<List<ChreApiTest.GeneralEventsMessage>>> eventsFuture =
+                new ChreApiTestUtil().gatherEventsConcurrent(mRpcClients,
+                        Arrays.asList(CHRE_EVENT_SENSOR_ACCELEROMETER_DATA,
+                                      CHRE_EVENT_SENSOR_SAMPLING_CHANGE),
+                        NUM_EVENTS_TO_GATHER,
+                        TIMEOUT_IN_NS);
+
+        int accelerometerHandle = findDefaultAccelerometer();
+        long minInterval = getMinIntervalForAccelerometer(accelerometerHandle);
+
+        List<ChreApiTest.ChreSensorConfigureInput> configureInputs =
+                assertAccelerometerIsConfigured(accelerometerHandle, minInterval);
+
+        List<List<ChreApiTest.GeneralEventsMessage>> events =
+                eventsFuture.get(2 * TIMEOUT_IN_NS, TimeUnit.NANOSECONDS);
+        assertThat(events).isNotNull();
+
+        for (int i = 0; i < events.size(); ++i) {
+            List<ChreApiTest.GeneralEventsMessage> eventsFromNanoapp = events.get(i);
+            assertThat(eventsFromNanoapp).isNotNull();
+            Assert.assertEquals(eventsFromNanoapp.size(), NUM_EVENTS_TO_GATHER);
+            assertSensorIntervalsAreCorrect(accelerometerHandle, eventsFromNanoapp,
+                    configureInputs.get(i).getInterval());
+        }
+    }
+
+    /**
+     * Finds the default accelerometer for each nanoapp then asserts that it exists and both
+     * handles are the same.
+     *
+     * @return                              the accelerometer handle.
+     */
+    private int findDefaultAccelerometer() throws Exception {
+        ChreApiTest.ChreSensorFindDefaultInput input = ChreApiTest.ChreSensorFindDefaultInput
+                .newBuilder().setSensorType(CHRE_SENSOR_TYPE_ACCELEROMETER).build();
+        List<ChreApiTest.ChreSensorFindDefaultOutput> responses =
+                ChreApiTestUtil.callConcurrentUnaryRpcMethodSync(mRpcClients,
+                        "chre.rpc.ChreApiTestService.ChreSensorFindDefault", input);
+        assertThat(responses).isNotNull();
+
+        Integer accelerometerHandle = null;
+        for (ChreApiTest.ChreSensorFindDefaultOutput response: responses) {
+            Assert.assertTrue(response.getFoundSensor());
+            if (accelerometerHandle == null) {
+                accelerometerHandle = response.getSensorHandle();
+            } else {
+                Assert.assertEquals(accelerometerHandle.intValue(), response.getSensorHandle());
+            }
+        }
+        assertThat(accelerometerHandle).isNotNull();
+        return accelerometerHandle;
+    }
+
+    /**
+     * Gets the minimum interval for the accelerometer and asserts that it is the same
+     * for all nanoapps.
+     *
+     * @param accelerometerHandle           the accelerometer handle.
+     * @return                              the minimum interval.
+     */
+    private long getMinIntervalForAccelerometer(int accelerometerHandle) throws Exception {
+        ChreApiTest.ChreHandleInput input = ChreApiTest.ChreHandleInput.newBuilder()
+                .setHandle(accelerometerHandle)
+                .build();
+        List<ChreApiTest.ChreGetSensorInfoOutput> accelerometerInfos =
+                ChreApiTestUtil.callConcurrentUnaryRpcMethodSync(mRpcClients,
+                        "chre.rpc.ChreApiTestService.ChreGetSensorInfo", input);
+        assertThat(accelerometerInfos).isNotNull();
+
+        Long minInterval = null;
+        for (ChreApiTest.ChreGetSensorInfoOutput accelerometerInfo: accelerometerInfos) {
+            if (minInterval == null) {
+                minInterval = accelerometerInfo.getMinInterval();
+            } else {
+                Assert.assertEquals(minInterval.longValue(), accelerometerInfo.getMinInterval());
+            }
+        }
+
+        assertThat(minInterval).isNotNull();
+        return minInterval;
+    }
+
+    /**
+     * Configures the accelerometer with two different intervals corresponding to each
+     * nanoapp. Asserts this was successful and returns the input used to configure.
+     *
+     * @param accelerometerHandle           the accelerometer handle.
+     * @param minInterval                   the minimum interval for the accelerometer.
+     *
+     * @return                              the configuration input.
+     */
+    private List<ChreApiTest.ChreSensorConfigureInput> assertAccelerometerIsConfigured(
+                int accelerometerHandle, long minInterval) throws Exception {
+        List<ChreApiTest.ChreSensorConfigureInput> configureInputs = Arrays.asList(
+                ChreApiTest.ChreSensorConfigureInput.newBuilder()
+                        .setSensorHandle(accelerometerHandle)
+                        .setMode(CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS)
+                        .setLatency(0)
+                        .setInterval(minInterval)
+                        .build(),
+                ChreApiTest.ChreSensorConfigureInput.newBuilder()
+                    .setSensorHandle(accelerometerHandle)
+                    .setMode(CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS)
+                    .setLatency(0)
+                    .setInterval(minInterval * 2)
+                    .build()
+        );
+
+        List<ChreApiTest.Status> responses =
+                ChreApiTestUtil.callConcurrentUnaryRpcMethodSync(mRpcClients,
+                "chre.rpc.ChreApiTestService.ChreSensorConfigure", configureInputs);
+        assertThat(responses).isNotNull();
+        for (ChreApiTest.Status response: responses) {
+            Assert.assertTrue(response.getStatus());
+        }
+
+        return configureInputs;
+    }
+
+    /**
+     * Verifies the intervals between different samples and different readings in each sample
+     * are less than the requested interval.
+     *
+     * @param accelerometerHandle           the handle for the accelerometer.
+     * @param events                        the events received from the nanoapps.
+     * @param requestedIntervalNs           the interval requested when configuring the
+     *                                      accelerometers.
+     */
+    private void assertSensorIntervalsAreCorrect(
+            int accelerometerHandle,
+            List<ChreApiTest.GeneralEventsMessage> events,
+            long requestedIntervalNs) {
+        assertThat(events).isNotNull();
+        Long lastReadingTimestamp = null;
+        for (ChreApiTest.GeneralEventsMessage event: events) {
+            Assert.assertTrue(event.getStatus());
+            if (event.hasChreSensorThreeAxisData()) {
+                lastReadingTimestamp = handleChreSensorThreeAxisData(event, requestedIntervalNs,
+                        lastReadingTimestamp, accelerometerHandle);
+            } else if (event.hasChreSensorSamplingStatusEvent()) {
+                requestedIntervalNs = handleChreSensorSamplingStatusEvent(event,
+                        requestedIntervalNs, accelerometerHandle);
+            } else {
+                Assert.fail("Event does not contain any requested data.");
+            }
+        }
+    }
+
+    /**
+     * Handles ChreSensorThreeAxisData events.
+     *
+     * @param event                         the general event.
+     * @param requestedIntervalNs           the requestd interval in ns.
+     * @param lastReadingTimestamp          the timestamp of the last reading.
+     * @param accelerometerHandle           the accelerometer handle.
+     * @return                              the last reading timestamp.
+     */
+    private Long handleChreSensorThreeAxisData(ChreApiTest.GeneralEventsMessage event,
+            long requestedIntervalNs, Long lastReadingTimestamp, int accelerometerHandle) {
+        ChreApiTest.ChreSensorThreeAxisData data = event.getChreSensorThreeAxisData();
+        Assert.assertEquals(data.getHeader().getSensorHandle(), accelerometerHandle);
+
+        assertThat(data.getReadingsCount()).isGreaterThan(0);
+        for (int i = 0; i < data.getReadingsCount(); ++i) {
+            ChreApiTest.ChreSensorThreeAxisSampleData sampleData = data.getReadings(i);
+
+            // baseTimestamp is the timestamp provided in the event, and to calculate
+            // each reading's timestamp, one must sum, in order, the timestampDelta
+            // up to the current reading.
+            long readingTimestamp = (i == 0
+                    ? data.getHeader().getBaseTimestamp()
+                    : lastReadingTimestamp) + sampleData.getTimestampDelta();
+            if (lastReadingTimestamp != null) {
+                Assert.assertTrue("Invalid timestamp between samples: interval: "
+                        + (readingTimestamp - lastReadingTimestamp)
+                        + ", requestedIntervalNs: " + requestedIntervalNs,
+                        readingTimestamp <= requestedIntervalNs * JITTER_MULTIPLIER
+                                + lastReadingTimestamp);
+            }
+            lastReadingTimestamp = readingTimestamp;
+        }
+        return lastReadingTimestamp;
+    }
+
+    /**
+     * Handles ChreSensorSamplingStatusEvent events.
+     *
+     * @param event                         the general event.
+     * @param requestedIntervalNs           the requested interval in ns.
+     * @param accelerometerHandle           the accelerometer handle.
+     * @return                              the new interval determined by
+     *                                      the sampling status data.
+     */
+    private long handleChreSensorSamplingStatusEvent(ChreApiTest.GeneralEventsMessage event,
+            long requestedIntervalNs, int accelerometerHandle) {
+        ChreApiTest.ChreSensorSamplingStatusEvent samplingStatusEvent =
+                event.getChreSensorSamplingStatusEvent();
+        Assert.assertEquals(samplingStatusEvent.getSensorHandle(), accelerometerHandle);
+        ChreApiTest.ChreSensorSamplingStatus samplingStatus = samplingStatusEvent.getStatus();
+        return samplingStatus.getEnabled() ? samplingStatus.getInterval() : requestedIntervalNs;
+    }
+}
diff --git a/java/test/cross_validation/src/com/google/android/chre/test/crossvalidator/ChreCrossValidatorSensor.java b/java/test/cross_validation/src/com/google/android/chre/test/crossvalidator/ChreCrossValidatorSensor.java
index 11a965e..d53f408 100644
--- a/java/test/cross_validation/src/com/google/android/chre/test/crossvalidator/ChreCrossValidatorSensor.java
+++ b/java/test/cross_validation/src/com/google/android/chre/test/crossvalidator/ChreCrossValidatorSensor.java
@@ -77,7 +77,7 @@
 
     private static final long SAMPLING_LATENCY_IN_MS = 0;
 
-    private static final long MAX_TIMESTAMP_DIFF_NS = 10000000L;
+    private static final long MAX_TIMESTAMP_DIFF_NS = 4000000L;
 
     private static final float AP_PROXIMITY_SENSOR_FAR_DISTANCE_IN_CM = 5f;
 
@@ -291,8 +291,8 @@
                     if (valuesLength != mSensorConfig.expectedValuesLength) {
                         setErrorStr(String.format(kParseDataErrorPrefix
                                         + "incorrect sensor datapoints values length %d when "
-                                        + "expecing %d",
-                                sensorType, valuesLength, mSensorConfig.expectedValuesLength));
+                                        + "expecting %d",
+                                valuesLength, mSensorConfig.expectedValuesLength));
                         break;
                     }
                     mChreDatapointsQueue.add(new ChreSensorDatapoint(datapoint));
diff --git a/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubChrePermissionsTestExecutor.java b/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubChrePermissionsTestExecutor.java
index 0d313f9..84b1c13 100644
--- a/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubChrePermissionsTestExecutor.java
+++ b/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubChrePermissionsTestExecutor.java
@@ -68,7 +68,6 @@
         mNanoAppId = mNanoAppBinary.getNanoAppId();
 
         mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
-        Assert.assertTrue(mContextHubClient != null);
     }
 
     @Override
diff --git a/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubFrameworkPermissionsTestExecutor.java b/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubFrameworkPermissionsTestExecutor.java
index f0f9cd6..09a42b2 100644
--- a/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubFrameworkPermissionsTestExecutor.java
+++ b/java/test/permissions/src/com/google/android/chre/test/permissions/ContextHubFrameworkPermissionsTestExecutor.java
@@ -15,6 +15,9 @@
  */
 package com.google.android.chre.test.permissions;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.app.Instrumentation;
 import android.content.Context;
 import android.hardware.location.ContextHubClient;
@@ -31,8 +34,6 @@
 import com.google.android.chre.nanoapp.proto.PingTest;
 import com.google.android.utils.chre.ChreTestUtil;
 
-import org.junit.Assert;
-
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -75,7 +76,7 @@
         mPackageName = mContext.getPackageName();
 
         mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
-        Assert.assertTrue(mContextHubClient != null);
+        assertThat(mContextHubClient).isNotNull();
     }
 
     @Override
@@ -118,17 +119,13 @@
                 mNanoAppId, PingTest.MessageType.PING_COMMAND_VALUE,
                 command.toByteArray());
         int result = mContextHubClient.sendMessageToNanoApp(message);
-        if (result != ContextHubTransaction.RESULT_SUCCESS) {
-            Assert.fail("Failed to send message: result = " + result);
-        }
+        assertWithMessage("Failed to send message: result = %s ", result)
+                .that(result)
+                .isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
 
-        try {
-            NanoAppMessage msg = mMessageQueue.poll(2, TimeUnit.SECONDS);
-            Assert.assertNotNull("Timed out waiting for a message", msg);
-            Log.d(TAG, "Got message from nanoapp: " + msg);
-        } catch (InterruptedException e) {
-            Assert.fail(e.getMessage());
-        }
+        NanoAppMessage msg = mMessageQueue.poll(2, TimeUnit.SECONDS);
+        assertWithMessage("Timed out waiting for a message").that(msg).isNotNull();
+        Log.d(TAG, "Got message from nanoapp: " + msg);
 
         // No need to grant our package again since the denial will be tied to
         // the current contexthubclient which is only used by this test.
@@ -136,17 +133,21 @@
                 "cmd contexthub deny " + mContextHubInfo.getId()
                 + " " + mContext.getPackageName() + " " + mNanoAppId);
 
-        int authorization = mAuthorizationUpdateQueue.poll(2, TimeUnit.SECONDS);
-        Assert.assertEquals(authorization, ContextHubManager.AUTHORIZATION_DENIED);
+        Integer authorization = mAuthorizationUpdateQueue.poll(2, TimeUnit.SECONDS);
+        assertWithMessage("Timed out waiting on denied authorization update")
+                .that(authorization)
+                .isNotNull();
+        assertThat(authorization).isEqualTo(ContextHubManager.AUTHORIZATION_DENIED);
 
         try {
             mContextHubClient.sendMessageToNanoApp(message);
-            Assert.fail("Sent message to nanoapp even though permissions were denied");
+            assertWithMessage("Sent message to nanoapp even though permissions were denied").fail();
         } catch (SecurityException e) {
             // Expected
         }
-        Assert.assertTrue(mAuthorizationUpdateQueue.isEmpty());
-        Assert.assertFalse(mHubResetDuringTest.get());
+
+        assertThat(mAuthorizationUpdateQueue).isEmpty();
+        assertThat(mHubResetDuringTest.get()).isFalse();
     }
 
     /**
@@ -162,17 +163,13 @@
                 mNanoAppId, PingTest.MessageType.PING_COMMAND_VALUE,
                 command.toByteArray());
         int result = mContextHubClient.sendMessageToNanoApp(message);
-        if (result != ContextHubTransaction.RESULT_SUCCESS) {
-            Assert.fail("Failed to send message: result = " + result);
-        }
+        assertWithMessage("Failed to send message: result = %s ", result)
+                .that(result)
+                .isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
 
-        try {
-            NanoAppMessage msg = mMessageQueue.poll(2, TimeUnit.SECONDS);
-            Assert.assertNotNull("Timed out waiting for a message", msg);
-            Log.d(TAG, "Got message from nanoapp: " + msg);
-        } catch (InterruptedException e) {
-            Assert.fail(e.getMessage());
-        }
+        NanoAppMessage msg = mMessageQueue.poll(2, TimeUnit.SECONDS);
+        assertWithMessage("Timed out waiting for a message").that(msg).isNotNull();
+        Log.d(TAG, "Got message from nanoapp: " + msg);
     }
 
     /**
diff --git a/java/test/rpc_service/src/com/google/android/chre/test/rpc_service/ContextHubRpcServiceTestExecutor.java b/java/test/rpc_service/src/com/google/android/chre/test/rpc_service/ContextHubRpcServiceTestExecutor.java
index bcb1f7f..b6da0f1 100644
--- a/java/test/rpc_service/src/com/google/android/chre/test/rpc_service/ContextHubRpcServiceTestExecutor.java
+++ b/java/test/rpc_service/src/com/google/android/chre/test/rpc_service/ContextHubRpcServiceTestExecutor.java
@@ -15,16 +15,22 @@
  */
 package com.google.android.chre.test.rpc_service;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.location.ContextHubClient;
 import android.hardware.location.ContextHubClientCallback;
 import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubIntentEvent;
 import android.hardware.location.ContextHubManager;
 import android.hardware.location.NanoAppBinary;
-import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
 
-import com.google.android.chre.utils.pigweed.ChreCallbackHandler;
-import com.google.android.chre.utils.pigweed.ChreChannelOutput;
+import com.google.android.chre.utils.pigweed.ChreRpcClient;
 import com.google.android.utils.chre.ChreTestUtil;
 
 import org.junit.Assert;
@@ -34,8 +40,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import dev.pigweed.pw_rpc.Call.UnaryFuture;
-import dev.pigweed.pw_rpc.Channel;
-import dev.pigweed.pw_rpc.Client;
 import dev.pigweed.pw_rpc.MethodClient;
 import dev.pigweed.pw_rpc.Service;
 import dev.pigweed.pw_rpc.UnaryResult;
@@ -45,108 +49,99 @@
  * A class that can execute the test for the RPC service test nanoapp.
  */
 public class ContextHubRpcServiceTestExecutor extends ContextHubClientCallback {
+    public static final String ACTION = "com.google.android.chre.test.chqts.ACTION";
     private static final String TAG = "ContextHubRpcServiceTestExecutor";
-
-    private final NanoAppBinary mNanoAppBinary;
-
-    private final long mNanoAppId;
-
-    private final ContextHubClient mContextHubClient;
-
-    private final AtomicBoolean mChreReset = new AtomicBoolean(false);
-
-    private final ContextHubManager mContextHubManager;
-
-    private final ContextHubInfo mContextHubInfo;
-
-    private final Client mRpcClient;
-    private final Channel mChannel;
-    private final ChreCallbackHandler mCallbackHandler;
-
-    // TODO(b/218677634): Remove flag once pigweed RPC can be used in nanoapps.
-    private final boolean mPwRpcEnabled = false;
-
     // The ID and version of the "rpc_service_test" nanoapp. Must be synchronized with the
     // value defined in the nanoapp code.
-    private static final int NUM_RPC_SERVICES = 1;
     private static final long RPC_SERVICE_ID = 0xca8f7150a3f05847L;
     private static final int RPC_SERVICE_VERSION = 0x01020034;
     private static final String RPC_ECHO_STRING = "HELLO_WORLD";
+    private final NanoAppBinary mNanoAppBinary;
+    private final long mNanoAppId;
+    private final AtomicBoolean mChreReset = new AtomicBoolean(false);
+    private final ContextHubManager mContextHubManager;
+    private final ContextHubInfo mContextHubInfo;
+    private final Service mEchoService;
+    private final Context mContext = getInstrumentation().getTargetContext();
+    private ChreRpcClient mRpcClient;
 
-    public ContextHubRpcServiceTestExecutor(
-                ContextHubManager manager, ContextHubInfo info, NanoAppBinary binary) {
+    public ContextHubRpcServiceTestExecutor(ContextHubManager manager, ContextHubInfo info,
+            NanoAppBinary binary) {
         mContextHubManager = manager;
         mContextHubInfo = info;
         mNanoAppBinary = binary;
         mNanoAppId = mNanoAppBinary.getNanoAppId();
 
-        mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
-        Assert.assertTrue(mContextHubClient != null);
-
-        Service echoService = new Service("pw.rpc.EchoService",
-                Service.unaryMethod("Echo", Echo.EchoMessage.class,
-                        Echo.EchoMessage.class));
-        ChreChannelOutput channelOutput = new ChreChannelOutput(mContextHubClient, mNanoAppId);
-        mChannel = new Channel(channelOutput.getChannelId(), channelOutput);
-        mRpcClient = Client.create(List.of(mChannel), List.of(echoService));
-        mCallbackHandler =
-                new ChreCallbackHandler(mContextHubClient, mNanoAppId, mRpcClient, channelOutput);
+        mEchoService = new Service("pw.rpc.EchoService",
+                Service.unaryMethod("Echo", Echo.EchoMessage.class, Echo.EchoMessage.class));
     }
 
     @Override
     public void onHubReset(ContextHubClient client) {
         mChreReset.set(true);
-        mCallbackHandler.onHubReset();
-    }
-
-    @Override
-    public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {
-        mCallbackHandler.onMessageFromNanoApp(message);
     }
 
     /**
-     * Should be invoked before run() is invoked to set up the test, e.g. in a @Before method.
+     * Should be invoked before each test, e.g. in a @Before method.
      */
     public void init() {
         ChreTestUtil.loadNanoAppAssertSuccess(mContextHubManager, mContextHubInfo, mNanoAppBinary);
+        mRpcClient = null;
     }
 
-    /**
-     * The test code, e.g. run in @Test method
-     */
-    public void run() throws Exception {
-        List<NanoAppState> stateList =
-                    ChreTestUtil.queryNanoAppsAssertSuccess(mContextHubManager, mContextHubInfo);
+    public void findServiceTest() throws Exception {
+        List<NanoAppState> stateList = ChreTestUtil.queryNanoAppsAssertSuccess(mContextHubManager,
+                mContextHubInfo);
         boolean serviceFound = false;
         for (NanoAppState state : stateList) {
-            if (state.getNanoAppId() == mNanoAppId) {
-                Assert.assertEquals(state.getRpcServices().size(), NUM_RPC_SERVICES);
-
-                Assert.assertEquals(state.getRpcServices().get(0).getId(), RPC_SERVICE_ID);
-                Assert.assertEquals(
-                            state.getRpcServices().get(0).getVersion(), RPC_SERVICE_VERSION);
+            if (ChreRpcClient.hasService(state, mNanoAppId, RPC_SERVICE_ID, RPC_SERVICE_VERSION)) {
+                // The service is provided only by the test nanoapp.
+                Assert.assertFalse(serviceFound);
                 serviceFound = true;
-                break;
             }
         }
         Assert.assertTrue(serviceFound);
+    }
 
-        if (mPwRpcEnabled) {
-            MethodClient methodClient = mRpcClient.method(mChannel.id(), "pw.rpc.EchoService.Echo");
-            Assert.assertNotNull(methodClient);
+    /**
+     * Using callbacks (persistent service).
+     */
+    public void callbackModeTest() throws Exception {
+        mRpcClient = new ChreRpcClient(mContextHubManager, mContextHubInfo, mNanoAppId,
+                List.of(mEchoService), this);
 
-            Echo.EchoMessage message =
-                    Echo.EchoMessage.newBuilder().setMsg(RPC_ECHO_STRING).build();
-            UnaryFuture<Echo.EchoMessage> responseFuture = methodClient.invokeUnaryFuture(message);
+        invokeRpc(mRpcClient);
+    }
 
-            UnaryResult<Echo.EchoMessage> responseResult = responseFuture.get(2, TimeUnit.SECONDS);
-            Assert.assertNotNull(responseResult);
-            Assert.assertTrue(responseResult.status().ok());
+    /**
+     * Using intents (non-persistent service).
+     */
+    public void pendingIntentModeTest() throws Exception {
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                ContextHubIntentEvent event = ContextHubIntentEvent.fromIntent(intent);
+                if (event.getEventType() == ContextHubManager.EVENT_HUB_RESET) {
+                    mChreReset.set(true);
+                }
+                mRpcClient.handleIntent(intent);
+            }
+        };
 
-            Echo.EchoMessage response = responseResult.response();
-            Assert.assertNotNull(response);
-            Assert.assertEquals(RPC_ECHO_STRING, response.getMsg());
-        }
+        IntentFilter filter = new IntentFilter(ACTION);
+        mContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
+        Intent intent = new Intent(ACTION).setPackage(mContext.getPackageName());
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
+                intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
+
+        ContextHubClient contextHubClient = mContextHubManager.createClient(mContextHubInfo,
+                pendingIntent, mNanoAppId);
+
+        mRpcClient = new ChreRpcClient(contextHubClient, mNanoAppId, List.of(mEchoService));
+
+        invokeRpc(mRpcClient);
+
+        mContext.unregisterReceiver(receiver);
     }
 
     /**
@@ -158,6 +153,24 @@
         }
 
         ChreTestUtil.unloadNanoAppAssertSuccess(mContextHubManager, mContextHubInfo, mNanoAppId);
-        mContextHubClient.close();
+
+        if (mRpcClient != null) {
+            mRpcClient.close();
+        }
+    }
+
+    private void invokeRpc(ChreRpcClient rpcClient) throws Exception {
+        MethodClient methodClient = rpcClient.getMethodClient("pw.rpc.EchoService.Echo");
+
+        Echo.EchoMessage message = Echo.EchoMessage.newBuilder().setMsg(RPC_ECHO_STRING).build();
+        UnaryFuture<Echo.EchoMessage> responseFuture = methodClient.invokeUnaryFuture(message);
+
+        UnaryResult<Echo.EchoMessage> responseResult = responseFuture.get(2, TimeUnit.SECONDS);
+        Assert.assertNotNull(responseResult);
+        Assert.assertTrue(responseResult.status().ok());
+
+        Echo.EchoMessage response = responseResult.response();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(RPC_ECHO_STRING, response.getMsg());
     }
 }
\ No newline at end of file
diff --git a/java/test/settings/src/com/google/android/chre/test/setting/ContextHubBleSettingsTestExecutor.java b/java/test/settings/src/com/google/android/chre/test/setting/ContextHubBleSettingsTestExecutor.java
new file mode 100644
index 0000000..4ada672
--- /dev/null
+++ b/java/test/settings/src/com/google/android/chre/test/setting/ContextHubBleSettingsTestExecutor.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.chre.test.setting;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.app.Instrumentation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.location.NanoAppBinary;
+import android.util.Log;
+
+import com.google.android.chre.nanoapp.proto.ChreSettingsTest;
+import com.google.android.utils.chre.SettingsUtil;
+
+import org.junit.Assert;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A test to check for behavior when Bluetooth settings are changed.
+ */
+public class ContextHubBleSettingsTestExecutor {
+    private static final String TAG = "ContextHubBleSettingsTestExecutor";
+    private final ContextHubSettingsTestExecutor mExecutor;
+
+    private final Instrumentation mInstrumentation =
+            androidx.test.platform.app.InstrumentationRegistry.getInstrumentation();
+
+    private final Context mContext = mInstrumentation.getTargetContext();
+
+    private final SettingsUtil mSettingsUtil;
+
+    private boolean mInitialBluetoothEnabled;
+
+    private boolean mInitialAirplaneMode;
+
+    private boolean mInitialBluetoothScanningEnabled;
+
+    public static class BluetoothUpdateListener {
+        public BluetoothUpdateListener(int state) {
+            mExpectedState = state;
+        }
+
+        // Expected state of the BT Adapter
+        private final int mExpectedState;
+
+        public CountDownLatch mBluetoothLatch = new CountDownLatch(1);
+
+        public BroadcastReceiver mBluetoothUpdateReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
+                        || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
+                                intent.getAction())) {
+                    if (mExpectedState == intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
+                        mBluetoothLatch.countDown();
+                    }
+                }
+            }
+        };
+    }
+
+    public ContextHubBleSettingsTestExecutor(NanoAppBinary binary) {
+        mExecutor = new ContextHubSettingsTestExecutor(binary);
+        mSettingsUtil = new SettingsUtil(mContext);
+    }
+
+    /**
+     * Should be called in a @Before method.
+     */
+    public void setUp() {
+        mInitialBluetoothEnabled = mSettingsUtil.isBluetoothEnabled();
+        mInitialBluetoothScanningEnabled = mSettingsUtil.isBluetoothScanningAlwaysEnabled();
+        mInitialAirplaneMode = mSettingsUtil.isAirplaneModeOn();
+        Log.d(TAG, "isBluetoothEnabled=" + mInitialBluetoothEnabled
+                    + "; isBluetoothScanningEnabled=" + mInitialBluetoothScanningEnabled
+                    + "; isAirplaneModeOn=" + mInitialAirplaneMode);
+        mSettingsUtil.setAirplaneMode(false /* enable */);
+        mExecutor.init();
+    }
+
+    public void runBleScanningTest() {
+        runBleScanningTest(false /* enableBluetooth */, false /* enableBluetoothScanning */);
+        runBleScanningTest(true /* enableBluetooth */, false /* enableBluetoothScanning */);
+        runBleScanningTest(false /* enableBluetooth */, true /* enableBluetoothScanning */);
+        runBleScanningTest(true /* enableBluetooth */, true /* enableBluetoothScanning */);
+    }
+
+    /**
+     * Should be called in an @After method.
+     */
+    public void tearDown() {
+        mExecutor.deinit();
+        mSettingsUtil.setBluetooth(mInitialBluetoothEnabled);
+        mSettingsUtil.setBluetoothScanningSettings(mInitialBluetoothScanningEnabled);
+        mSettingsUtil.setAirplaneMode(mInitialAirplaneMode);
+    }
+
+    /**
+     * Sets the BLE scanning settings on the device.
+     * @param enable                    true to enable Bluetooth settings, false to disable it.
+     * @param enableBluetoothScanning   if true, enable BLE scanning; false, otherwise
+     */
+    private void setBluetoothSettings(boolean enable, boolean enableBluetoothScanning) {
+        int state = BluetoothAdapter.STATE_OFF;
+        if (enable) {
+            state = BluetoothAdapter.STATE_ON;
+        } else if (enableBluetoothScanning) {
+            state = BluetoothAdapter.STATE_BLE_ON;
+        }
+
+        BluetoothUpdateListener bluetoothUpdateListener = new BluetoothUpdateListener(state);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
+        mContext.registerReceiver(bluetoothUpdateListener.mBluetoothUpdateReceiver, filter);
+
+        mSettingsUtil.setBluetooth(enable);
+        mSettingsUtil.setBluetoothScanningSettings(enableBluetoothScanning);
+        if (!enable && enableBluetoothScanning) {
+            BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+            Assert.assertTrue(bluetoothManager != null);
+            BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
+            Assert.assertTrue(bluetoothAdapter != null);
+            mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+            Assert.assertTrue(bluetoothAdapter.enableBLE());
+        }
+        try {
+            bluetoothUpdateListener.mBluetoothLatch.await(10, TimeUnit.SECONDS);
+            Assert.assertTrue(enable == mSettingsUtil.isBluetoothEnabled());
+            Assert.assertTrue(enableBluetoothScanning
+                    == mSettingsUtil.isBluetoothScanningAlwaysEnabled());
+
+            // Wait a few seconds to ensure setting is propagated to CHRE path
+            Thread.sleep(2000);
+        } catch (InterruptedException e) {
+            Assert.fail(e.getMessage());
+        }
+
+        mContext.unregisterReceiver(bluetoothUpdateListener.mBluetoothUpdateReceiver);
+    }
+
+    /**
+     * Helper function to run the test
+     *
+     * @param enableBluetooth         if bluetooth is enabled
+     * @param enableBluetoothScanning if bluetooth scanning is always enabled
+     */
+    private void runBleScanningTest(boolean enableBluetooth,
+            boolean enableBluetoothScanning) {
+        setBluetoothSettings(enableBluetooth, enableBluetoothScanning);
+
+        boolean enableFeature = enableBluetooth || enableBluetoothScanning;
+        ChreSettingsTest.TestCommand.State state = enableFeature
+                ? ChreSettingsTest.TestCommand.State.ENABLED
+                : ChreSettingsTest.TestCommand.State.DISABLED;
+        mExecutor.startTestAssertSuccess(
+                ChreSettingsTest.TestCommand.Feature.BLE_SCANNING, state);
+    }
+}
diff --git a/java/test/settings/src/com/google/android/chre/test/setting/ContextHubSettingsTestExecutor.java b/java/test/settings/src/com/google/android/chre/test/setting/ContextHubSettingsTestExecutor.java
index 3ee611c..78a2f1b 100644
--- a/java/test/settings/src/com/google/android/chre/test/setting/ContextHubSettingsTestExecutor.java
+++ b/java/test/settings/src/com/google/android/chre/test/setting/ContextHubSettingsTestExecutor.java
@@ -81,7 +81,6 @@
         mContextHubInfo = info.get(0);
 
         mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
-        Assert.assertTrue(mContextHubClient != null);
     }
 
     @Override
@@ -207,13 +206,10 @@
      * Cleans up the test, should be invoked in e.g. @After method.
      */
     public void deinit() {
-        Assert.assertTrue("deinit() must be invoked after init()", mInitialized);
-
         if (mChreReset.get()) {
             Assert.fail("CHRE reset during the test");
         }
-
-        ChreTestUtil.unloadNanoAppAssertSuccess(mContextHubManager, mContextHubInfo, mNanoAppId);
+        ChreTestUtil.unloadNanoApp(mContextHubManager, mContextHubInfo, mNanoAppId);
         mContextHubClient.close();
 
         mInitialized = false;
diff --git a/java/test/settings/src/com/google/android/chre/test/setting/ContextHubWwanSettingsTestExecutor.java b/java/test/settings/src/com/google/android/chre/test/setting/ContextHubWwanSettingsTestExecutor.java
index 02a3398..e6d82e7 100644
--- a/java/test/settings/src/com/google/android/chre/test/setting/ContextHubWwanSettingsTestExecutor.java
+++ b/java/test/settings/src/com/google/android/chre/test/setting/ContextHubWwanSettingsTestExecutor.java
@@ -16,21 +16,13 @@
 package com.google.android.chre.test.setting;
 
 import android.app.Instrumentation;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.hardware.location.NanoAppBinary;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.google.android.chre.nanoapp.proto.ChreSettingsTest;
-import com.google.android.utils.chre.ChreTestUtil;
-
-import org.junit.Assert;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
+import com.google.android.utils.chre.SettingsUtil;
 
 /**
  * A test to check for behavior when WWAN settings are changed.
@@ -42,28 +34,20 @@
 
     private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
 
-    private class AirplaneModeListener {
-        protected CountDownLatch mAirplaneModeLatch = new CountDownLatch(1);
+    private final Context mContext = mInstrumentation.getTargetContext();
 
-        protected BroadcastReceiver mAirplaneModeReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
-                    mAirplaneModeLatch.countDown();
-                }
-            }
-        };
-    }
+    private final SettingsUtil mSettingsUtil;
 
     public ContextHubWwanSettingsTestExecutor(NanoAppBinary binary) {
         mExecutor = new ContextHubSettingsTestExecutor(binary);
+        mSettingsUtil = new SettingsUtil(mContext);
     }
 
     /**
      * Should be called in a @Before method.
      */
     public void setUp() {
-        mInitialAirplaneMode = isAirplaneModeOn();
+        mInitialAirplaneMode = mSettingsUtil.isAirplaneModeOn();
         mExecutor.init();
     }
 
@@ -77,21 +61,7 @@
      */
     public void tearDown() {
         mExecutor.deinit();
-        setAirplaneMode(mInitialAirplaneMode);
-    }
-
-    /**
-     * Sets the airplane mode on the device.
-     * @param enable True to enable airplane mode, false to disable it.
-     */
-    private void setAirplaneMode(boolean enable) {
-        if (enable) {
-            ChreTestUtil.executeShellCommand(
-                    mInstrumentation, "cmd connectivity airplane-mode enable");
-        } else {
-            ChreTestUtil.executeShellCommand(
-                    mInstrumentation, "cmd connectivity airplane-mode disable");
-        }
+        mSettingsUtil.setAirplaneMode(mInitialAirplaneMode);
     }
 
     /**
@@ -99,30 +69,8 @@
      * @param enableFeature True for enable.
      */
     private void runTest(boolean enableFeature) {
-        Context context = InstrumentationRegistry.getTargetContext();
-        AirplaneModeListener listener = new AirplaneModeListener();
-        context.registerReceiver(
-                listener.mAirplaneModeReceiver,
-                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
-
         boolean airplaneModeExpected = !enableFeature;
-        setAirplaneMode(airplaneModeExpected);
-
-        if (isAirplaneModeOn() != airplaneModeExpected) {
-            try {
-                listener.mAirplaneModeLatch.await(5, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-                Assert.fail(e.getMessage());
-            }
-        }
-        context.unregisterReceiver(listener.mAirplaneModeReceiver);
-        Assert.assertTrue(isAirplaneModeOn() == airplaneModeExpected);
-
-        try {
-            Thread.sleep(5000);  // wait for setting to propagate
-        } catch (InterruptedException e) {
-            Assert.fail(e.getMessage());
-        }
+        mSettingsUtil.setAirplaneMode(airplaneModeExpected);
 
         ChreSettingsTest.TestCommand.State state = enableFeature
                 ? ChreSettingsTest.TestCommand.State.ENABLED
@@ -130,13 +78,4 @@
         mExecutor.startTestAssertSuccess(
                 ChreSettingsTest.TestCommand.Feature.WWAN_CELL_INFO, state);
     }
-
-    /**
-     * @return true if the airplane mode is currently enabled.
-     */
-    private boolean isAirplaneModeOn() {
-        String out = ChreTestUtil.executeShellCommand(
-                mInstrumentation, "settings get global airplane_mode_on");
-        return ChreTestUtil.convertToIntegerOrFail(out) > 0;
-    }
 }
diff --git a/java/test/stress/src/com/google/android/chre/test/stress/ContextHubStressTestExecutor.java b/java/test/stress/src/com/google/android/chre/test/stress/ContextHubStressTestExecutor.java
index c3ddfb7..378640a 100644
--- a/java/test/stress/src/com/google/android/chre/test/stress/ContextHubStressTestExecutor.java
+++ b/java/test/stress/src/com/google/android/chre/test/stress/ContextHubStressTestExecutor.java
@@ -48,6 +48,12 @@
 public class ContextHubStressTestExecutor extends ContextHubClientCallback {
     private static final String TAG = "ContextHubStressTestExecutor";
 
+    /**
+     * Wifi capabilities flags listed in
+     * //system/chre/chre_api/include/chre_api/chre/wifi.h
+     */
+    private static final int WIFI_CAPABILITIES_SCAN_MONITORING = 1;
+
     private final NanoAppBinary mNanoAppBinary;
 
     private final long mNanoAppId;
@@ -65,6 +71,8 @@
 
     private CountDownLatch mCountDownLatch;
 
+    private ChreStressTest.Capabilities mCapabilities;
+
     // Set to true to have the test suite only load the nanoapp and start the test.
     // This can be useful for long-running stress tests, where we do not want to wait a fixed
     // time to wait for successful completion.
@@ -103,6 +111,16 @@
                     valid = true;
                     break;
                 }
+                case ChreStressTest.MessageType.CAPABILITIES_VALUE: {
+                    try {
+                        mCapabilities =
+                                ChreStressTest.Capabilities.parseFrom(message.getMessageBody());
+                        valid = true;
+                    } catch (InvalidProtocolBufferException e) {
+                        Log.e(TAG, "Failed to parse message: " + e.getMessage());
+                    }
+                    break;
+                }
                 default: {
                     Log.e(TAG, "Unknown message type " + message.getMessageType());
                 }
@@ -151,7 +169,6 @@
                     mNanoAppBinary);
         }
         mContextHubClient = mContextHubManager.createClient(mContextHubInfo, this);
-        Assert.assertTrue(mContextHubClient != null);
     }
 
     /**
@@ -164,6 +181,9 @@
                 ChreStressTest.TestCommand.Feature.GNSS_LOCATION,
                 ChreStressTest.TestCommand.Feature.GNSS_MEASUREMENT,
                 ChreStressTest.TestCommand.Feature.WWAN,
+                ChreStressTest.TestCommand.Feature.SENSORS,
+                ChreStressTest.TestCommand.Feature.AUDIO,
+                ChreStressTest.TestCommand.Feature.BLE,
         };
 
         mTestResult.set(null);
@@ -220,21 +240,36 @@
                 new byte[0]);
         sendMessageToNanoApp(message);
 
-        WifiManager manager = (WifiManager) mInstrumentation.getContext().getSystemService(
-                Context.WIFI_SERVICE);
-        Assert.assertNotNull(manager);
-
-        mWifiScanMonitorTriggered.set(false);
         mCountDownLatch = new CountDownLatch(1);
-        Assert.assertTrue(manager.startScan());
+        message = NanoAppMessage.createMessageToNanoApp(
+                mNanoAppId, ChreStressTest.MessageType.GET_CAPABILITIES_VALUE,
+                new byte[0]);
+        sendMessageToNanoApp(message);
 
         try {
             mCountDownLatch.await(30, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
             Assert.fail(e.getMessage());
         }
-        Assert.assertTrue(mWifiScanMonitorTriggered.get());
-        checkTestFailure();
+
+        if ((mCapabilities.getWifi() & WIFI_CAPABILITIES_SCAN_MONITORING) != 0) {
+            WifiManager manager =
+                    (WifiManager)
+                            mInstrumentation.getContext().getSystemService(Context.WIFI_SERVICE);
+            Assert.assertNotNull(manager);
+
+            mWifiScanMonitorTriggered.set(false);
+            mCountDownLatch = new CountDownLatch(1);
+            Assert.assertTrue(manager.startScan());
+
+            try {
+                mCountDownLatch.await(30, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Assert.fail(e.getMessage());
+            }
+            Assert.assertTrue(mWifiScanMonitorTriggered.get());
+            checkTestFailure();
+        }
 
         sendTestMessage(ChreStressTest.TestCommand.Feature.WIFI_SCAN_MONITOR, false /* start */);
 
diff --git a/java/test/utils/Android.bp b/java/test/utils/Android.bp
index 16490cc..0c842e5 100644
--- a/java/test/utils/Android.bp
+++ b/java/test/utils/Android.bp
@@ -28,6 +28,10 @@
 
     static_libs: [
         "androidx.test.rules",
+        "truth-prebuilt",
+        "chre_api_test_proto_java_lite",
+        "chre_pigweed_utils",
+        "pw_rpc_java_client",
     ],
 
     sdk_version: "system_current",
diff --git a/java/test/utils/src/com/google/android/utils/chre/ChreApiTestUtil.java b/java/test/utils/src/com/google/android/utils/chre/ChreApiTestUtil.java
new file mode 100644
index 0000000..7a0be9e
--- /dev/null
+++ b/java/test/utils/src/com/google/android/utils/chre/ChreApiTestUtil.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.utils.chre;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.chre.utils.pigweed.ChreRpcClient;
+import com.google.protobuf.MessageLite;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import dev.chre.rpc.proto.ChreApiTest;
+import dev.pigweed.pw_rpc.Call.ServerStreamingFuture;
+import dev.pigweed.pw_rpc.Call.UnaryFuture;
+import dev.pigweed.pw_rpc.MethodClient;
+import dev.pigweed.pw_rpc.Service;
+import dev.pigweed.pw_rpc.UnaryResult;
+
+/**
+ * A set of helper functions for tests that use the CHRE API Test nanoapp.
+ */
+public class ChreApiTestUtil {
+    /**
+     * The default timeout for an RPC call in seconds.
+     */
+    public static final int RPC_TIMEOUT_IN_SECONDS = 5;
+
+    /**
+     * The default timeout for an RPC call in milliseconds.
+     */
+    public static final int RPC_TIMEOUT_IN_MS = RPC_TIMEOUT_IN_SECONDS * 1000;
+
+    /**
+     * The number of threads for the executor that executes the futures.
+     * We need at least 2 here. One to process the RPCs for server streaming
+     * and one to process events (which has server streaming as a dependent).
+     * 2 is the minimum needed to run smoothly without timeout issues.
+     */
+    private static final int NUM_THREADS_FOR_EXECUTOR = 2;
+
+    /**
+     * Executor for use with server streaming RPCs.
+     */
+    private final ExecutorService mExecutor =
+            Executors.newFixedThreadPool(NUM_THREADS_FOR_EXECUTOR);
+
+    /**
+     * Storage for nanoapp streaming messages. This is a map from each RPC client to the
+     * list of messages received.
+     */
+    private final Map<ChreRpcClient, List<MessageLite>> mNanoappStreamingMessages =
+            new HashMap<ChreRpcClient, List<MessageLite>>();
+
+    /**
+     * If true, there is an active server streaming RPC ongoing.
+     */
+    private boolean mActiveServerStreamingRpc = false;
+
+    /**
+     * Calls a server streaming RPC method on multiple RPC clients. The RPC will be initiated for
+     * each client, then we will give each client a maximum of RPC_TIMEOUT_IN_SECONDS seconds of
+     * timeout, getting the futures in sequential order. The responses will have the same size
+     * as the input rpcClients size.
+     *
+     * @param <RequestType>   the type of the request (proto generated type).
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClients      the RPC clients.
+     * @param method          the fully-qualified method name.
+     * @param request         the request.
+     *
+     * @return                the proto responses or null if there was an error.
+     */
+    public <RequestType extends MessageLite, ResponseType extends MessageLite>
+            List<List<ResponseType>> callConcurrentServerStreamingRpcMethodSync(
+                    @NonNull List<ChreRpcClient> rpcClients,
+                    @NonNull String method,
+                    @NonNull RequestType request) throws Exception {
+        Objects.requireNonNull(rpcClients);
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(request);
+
+        Future<List<List<ResponseType>>> responseFuture =
+                callConcurrentServerStreamingRpcMethodAsync(rpcClients, method, request,
+                        RPC_TIMEOUT_IN_MS);
+        return responseFuture == null
+                ? null
+                : responseFuture.get(RPC_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Calls a server streaming RPC method with RPC_TIMEOUT_IN_SECONDS seconds of timeout.
+     *
+     * @param <RequestType>   the type of the request (proto generated type).
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClient       the RPC client.
+     * @param method          the fully-qualified method name.
+     * @param request         the request.
+     *
+     * @return                the proto response or null if there was an error.
+     */
+    public <RequestType extends MessageLite, ResponseType extends MessageLite> List<ResponseType>
+            callServerStreamingRpcMethodSync(
+                    @NonNull ChreRpcClient rpcClient,
+                    @NonNull String method,
+                    @NonNull RequestType request) throws Exception {
+        Objects.requireNonNull(rpcClient);
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(request);
+
+        List<List<ResponseType>> responses = callConcurrentServerStreamingRpcMethodSync(
+                Arrays.asList(rpcClient),
+                method,
+                request);
+        return responses == null || responses.isEmpty() ? null : responses.get(0);
+    }
+
+    /**
+     * Calls a server streaming RPC method with RPC_TIMEOUT_IN_SECONDS seconds of
+     * timeout with a void request.
+     *
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClient       the RPC client.
+     * @param method          the fully-qualified method name.
+     *
+     * @return                the proto response or null if there was an error.
+     */
+    public <ResponseType extends MessageLite> List<ResponseType>
+            callServerStreamingRpcMethodSync(
+                    @NonNull ChreRpcClient rpcClient,
+                    @NonNull String method) throws Exception {
+        Objects.requireNonNull(rpcClient);
+        Objects.requireNonNull(method);
+
+        ChreApiTest.Void request = ChreApiTest.Void.newBuilder().build();
+        return callServerStreamingRpcMethodSync(rpcClient, method, request);
+    }
+
+    /**
+     * Calls an RPC method with RPC_TIMEOUT_IN_SECONDS seconds of timeout for concurrent
+     * instances of the ChreApiTest nanoapp.
+     *
+     * @param <RequestType>   the type of the request (proto generated type).
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClients      the RPC clients corresponding to the instances of the
+     *                        ChreApiTest nanoapp.
+     * @param method          the fully-qualified method name.
+     * @param requests        the list of requests.
+     *
+     * @return                the proto response or null if there was an error.
+     */
+    public static <RequestType extends MessageLite, ResponseType extends MessageLite>
+            List<ResponseType> callConcurrentUnaryRpcMethodSync(
+                    @NonNull List<ChreRpcClient> rpcClients,
+                    @NonNull String method,
+                    @NonNull List<RequestType> requests) throws Exception {
+        Objects.requireNonNull(rpcClients);
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(requests);
+        if (rpcClients.size() != requests.size()) {
+            return null;
+        }
+
+        List<UnaryFuture<ResponseType>> responseFutures =
+                new ArrayList<UnaryFuture<ResponseType>>();
+        Iterator<ChreRpcClient> rpcClientsIter = rpcClients.iterator();
+        Iterator<RequestType> requestsIter = requests.iterator();
+        while (rpcClientsIter.hasNext() && requestsIter.hasNext()) {
+            ChreRpcClient rpcClient = rpcClientsIter.next();
+            RequestType request = requestsIter.next();
+            MethodClient methodClient = rpcClient.getMethodClient(method);
+            responseFutures.add(methodClient.invokeUnaryFuture(request));
+        }
+
+        List<ResponseType> responses = new ArrayList<ResponseType>();
+        boolean success = true;
+        long endTimeInMs = System.currentTimeMillis() + RPC_TIMEOUT_IN_MS;
+        for (UnaryFuture<ResponseType> responseFuture: responseFutures) {
+            try {
+                UnaryResult<ResponseType> responseResult = responseFuture.get(
+                        Math.max(0, endTimeInMs - System.currentTimeMillis()),
+                                TimeUnit.MILLISECONDS);
+                responses.add(responseResult.response());
+            } catch (Exception exception) {
+                success = false;
+            }
+        }
+        return success ? responses : null;
+    }
+
+    /**
+     * Calls an RPC method with RPC_TIMEOUT_IN_SECONDS seconds of timeout for concurrent
+     * instances of the ChreApiTest nanoapp.
+     *
+     * @param <RequestType>   the type of the request (proto generated type).
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClients      the RPC clients corresponding to the instances of the
+     *                        ChreApiTest nanoapp.
+     * @param method          the fully-qualified method name.
+     * @param request         the request.
+     *
+     * @return                the proto response or null if there was an error.
+     */
+    public static <RequestType extends MessageLite, ResponseType extends MessageLite>
+            List<ResponseType> callConcurrentUnaryRpcMethodSync(
+                    @NonNull List<ChreRpcClient> rpcClients,
+                    @NonNull String method,
+                    @NonNull RequestType request) throws Exception {
+        Objects.requireNonNull(rpcClients);
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(request);
+
+        List<RequestType> requests = new ArrayList<RequestType>();
+        for (int i = 0; i < rpcClients.size(); ++i) {
+            requests.add(request);
+        }
+        return callConcurrentUnaryRpcMethodSync(rpcClients, method, requests);
+    }
+
+    /**
+     * Calls an RPC method with RPC_TIMEOUT_IN_SECONDS seconds of timeout.
+     *
+     * @param <RequestType>   the type of the request (proto generated type).
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClient       the RPC client.
+     * @param method          the fully-qualified method name.
+     * @param request         the request.
+     *
+     * @return                the proto response or null if there was an error.
+     */
+    public static <RequestType extends MessageLite, ResponseType extends MessageLite> ResponseType
+            callUnaryRpcMethodSync(
+                    @NonNull ChreRpcClient rpcClient,
+                    @NonNull String method,
+                    @NonNull RequestType request) throws Exception {
+        Objects.requireNonNull(rpcClient);
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(request);
+
+        List<ResponseType> responses = callConcurrentUnaryRpcMethodSync(Arrays.asList(rpcClient),
+                method, request);
+        return responses == null || responses.isEmpty() ? null : responses.get(0);
+    }
+
+    /**
+     * Calls an RPC method with RPC_TIMEOUT_IN_SECONDS seconds of timeout with a void request.
+     *
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClient       the RPC client.
+     * @param method          the fully-qualified method name.
+     *
+     * @return                the proto response or null if there was an error.
+     */
+    public static <ResponseType extends MessageLite> ResponseType
+            callUnaryRpcMethodSync(@NonNull ChreRpcClient rpcClient, @NonNull String method)
+            throws Exception {
+        Objects.requireNonNull(rpcClient);
+        Objects.requireNonNull(method);
+
+        ChreApiTest.Void request = ChreApiTest.Void.newBuilder().build();
+        return callUnaryRpcMethodSync(rpcClient, method, request);
+    }
+
+    /**
+     * Gathers events that match the eventTypes for each RPC client. This gathers
+     * events until eventCount events are gathered or timeoutInNs nanoseconds has passed.
+     * The host will wait until 2 * timeoutInNs to timeout receiving the response.
+     * The responses will have the same size as the input rpcClients size.
+     *
+     * @param rpcClients      the RPC clients.
+     * @param eventTypes      the types of event to gather.
+     * @param eventCount      the number of events to gather.
+     *
+     * @return                the events future.
+     */
+    public Future<List<List<ChreApiTest.GeneralEventsMessage>>> gatherEventsConcurrent(
+            @NonNull List<ChreRpcClient> rpcClients, List<Integer> eventTypes, int eventCount,
+            long timeoutInNs) throws Exception {
+        Objects.requireNonNull(rpcClients);
+
+        ChreApiTest.GatherEventsInput input = ChreApiTest.GatherEventsInput.newBuilder()
+                .addAllEventTypes(eventTypes)
+                .setEventTypeCount(eventTypes.size())
+                .setEventCount(eventCount)
+                .setTimeoutInNs(timeoutInNs)
+                .build();
+        return callConcurrentServerStreamingRpcMethodAsync(rpcClients,
+                "chre.rpc.ChreApiTestService.GatherEvents", input,
+                TimeUnit.NANOSECONDS.toMillis(2 * timeoutInNs));
+    }
+
+    /**
+     * Gathers events that match the eventType for each RPC client. This gathers
+     * events until eventCount events are gathered or timeoutInNs nanoseconds has passed.
+     * The host will wait until 2 * timeoutInNs to timeout receiving the response.
+     * The responses will have the same size as the input rpcClients size.
+     *
+     * @param rpcClients      the RPC clients.
+     * @param eventType       the type of event to gather.
+     * @param eventCount      the number of events to gather.
+     *
+     * @return                the events future.
+     */
+    public Future<List<List<ChreApiTest.GeneralEventsMessage>>> gatherEventsConcurrent(
+            @NonNull List<ChreRpcClient> rpcClients, int eventType, int eventCount,
+            long timeoutInNs) throws Exception {
+        Objects.requireNonNull(rpcClients);
+
+        return gatherEventsConcurrent(rpcClients, Arrays.asList(eventType),
+                eventCount, timeoutInNs);
+    }
+
+    /**
+     * Gathers events that match the eventTypes for the RPC client. This gathers
+     * events until eventCount events are gathered or timeoutInNs nanoseconds has passed.
+     * The host will wait until 2 * timeoutInNs to timeout receiving the response.
+     *
+     * @param rpcClient       the RPC client.
+     * @param eventTypes      the types of event to gather.
+     * @param eventCount      the number of events to gather.
+     *
+     * @return                the events future.
+     */
+    public Future<List<ChreApiTest.GeneralEventsMessage>> gatherEvents(
+            @NonNull ChreRpcClient rpcClient, List<Integer> eventTypes, int eventCount,
+                    long timeoutInNs) throws Exception {
+        Objects.requireNonNull(rpcClient);
+
+        Future<List<List<ChreApiTest.GeneralEventsMessage>>> eventsConcurrentFuture =
+                gatherEventsConcurrent(Arrays.asList(rpcClient), eventTypes, eventCount,
+                        timeoutInNs);
+        return eventsConcurrentFuture == null ? null : mExecutor.submit(() -> {
+            List<List<ChreApiTest.GeneralEventsMessage>> events =
+                    eventsConcurrentFuture.get(2 * timeoutInNs, TimeUnit.NANOSECONDS);
+            return events == null || events.size() == 0 ? null : events.get(0);
+        });
+    }
+
+    /**
+     * Gathers events that match the eventType for the RPC client. This gathers
+     * events until eventCount events are gathered or timeoutInNs nanoseconds has passed.
+     * The host will wait until 2 * timeoutInNs to timeout receiving the response.
+     *
+     * @param rpcClient       the RPC client.
+     * @param eventType       the type of event to gather.
+     * @param eventCount      the number of events to gather.
+     *
+     * @return                the events future.
+     */
+    public Future<List<ChreApiTest.GeneralEventsMessage>> gatherEvents(
+            @NonNull ChreRpcClient rpcClient, int eventType, int eventCount,
+                    long timeoutInNs) throws Exception {
+        Objects.requireNonNull(rpcClient);
+
+        return gatherEvents(rpcClient, Arrays.asList(eventType), eventCount, timeoutInNs);
+    }
+
+    /**
+     * Gets the RPC service for the CHRE API Test nanoapp.
+     */
+    public static Service getChreApiService() {
+        return new Service("chre.rpc.ChreApiTestService",
+                Service.unaryMethod(
+                        "ChreBleGetCapabilities",
+                        ChreApiTest.Void.class,
+                        ChreApiTest.Capabilities.class),
+                Service.unaryMethod(
+                        "ChreBleGetFilterCapabilities",
+                        ChreApiTest.Void.class,
+                        ChreApiTest.Capabilities.class),
+                Service.unaryMethod(
+                        "ChreBleStartScanAsync",
+                        ChreApiTest.ChreBleStartScanAsyncInput.class,
+                        ChreApiTest.Status.class),
+                Service.serverStreamingMethod(
+                        "ChreBleStartScanSync",
+                        ChreApiTest.ChreBleStartScanAsyncInput.class,
+                        ChreApiTest.GeneralSyncMessage.class),
+                Service.unaryMethod(
+                        "ChreBleStopScanAsync",
+                        ChreApiTest.Void.class,
+                        ChreApiTest.Status.class),
+                Service.serverStreamingMethod(
+                        "ChreBleStopScanSync",
+                        ChreApiTest.Void.class,
+                        ChreApiTest.GeneralSyncMessage.class),
+                Service.unaryMethod(
+                        "ChreSensorFindDefault",
+                        ChreApiTest.ChreSensorFindDefaultInput.class,
+                        ChreApiTest.ChreSensorFindDefaultOutput.class),
+                Service.unaryMethod(
+                        "ChreGetSensorInfo",
+                        ChreApiTest.ChreHandleInput.class,
+                        ChreApiTest.ChreGetSensorInfoOutput.class),
+                Service.unaryMethod(
+                        "ChreGetSensorSamplingStatus",
+                        ChreApiTest.ChreHandleInput.class,
+                        ChreApiTest.ChreGetSensorSamplingStatusOutput.class),
+                Service.unaryMethod(
+                        "ChreSensorConfigure",
+                        ChreApiTest.ChreSensorConfigureInput.class,
+                        ChreApiTest.Status.class),
+                Service.unaryMethod(
+                        "ChreSensorConfigureModeOnly",
+                        ChreApiTest.ChreSensorConfigureModeOnlyInput.class,
+                        ChreApiTest.Status.class),
+                Service.unaryMethod(
+                        "ChreAudioGetSource",
+                        ChreApiTest.ChreHandleInput.class,
+                        ChreApiTest.ChreAudioGetSourceOutput.class),
+                Service.unaryMethod(
+                        "ChreConfigureHostEndpointNotifications",
+                        ChreApiTest.ChreConfigureHostEndpointNotificationsInput.class,
+                        ChreApiTest.Status.class),
+                Service.unaryMethod(
+                        "RetrieveLatestDisconnectedHostEndpointEvent",
+                        ChreApiTest.Void.class,
+                        ChreApiTest.RetrieveLatestDisconnectedHostEndpointEventOutput
+                                .class),
+                Service.unaryMethod(
+                        "ChreGetHostEndpointInfo",
+                        ChreApiTest.ChreGetHostEndpointInfoInput.class,
+                        ChreApiTest.ChreGetHostEndpointInfoOutput.class),
+                Service.serverStreamingMethod(
+                        "GatherEvents",
+                        ChreApiTest.GatherEventsInput.class,
+                        ChreApiTest.GeneralEventsMessage.class));
+    }
+
+    /**
+     * Calls a server streaming RPC method with timeoutInMs milliseconds of timeout on
+     * multiple RPC clients. This returns a Future for the result. The responses will have the same
+     * size as the input rpcClients size.
+     *
+     * @param <RequestType>   the type of the request (proto generated type).
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClients      the RPC clients.
+     * @param method          the fully-qualified method name.
+     * @param requests        the list of requests.
+     * @param timeoutInMs     the timeout in milliseconds.
+     *
+     * @return                the Future for the response for null if there was an error.
+     */
+    private <RequestType extends MessageLite, ResponseType extends MessageLite>
+            Future<List<List<ResponseType>>> callConcurrentServerStreamingRpcMethodAsync(
+                    @NonNull List<ChreRpcClient> rpcClients,
+                    @NonNull String method,
+                    @NonNull List<RequestType> requests,
+                    long timeoutInMs) throws Exception {
+        Objects.requireNonNull(rpcClients);
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(requests);
+        if (rpcClients.size() != requests.size()) {
+            return null;
+        }
+
+        List<ServerStreamingFuture> responseFutures = new ArrayList<ServerStreamingFuture>();
+        synchronized (mNanoappStreamingMessages) {
+            if (mActiveServerStreamingRpc) {
+                return null;
+            }
+
+            Iterator<ChreRpcClient> rpcClientsIter = rpcClients.iterator();
+            Iterator<RequestType> requestsIter = requests.iterator();
+            while (rpcClientsIter.hasNext() && requestsIter.hasNext()) {
+                ChreRpcClient rpcClient = rpcClientsIter.next();
+                RequestType request = requestsIter.next();
+                MethodClient methodClient = rpcClient.getMethodClient(method);
+                ServerStreamingFuture responseFuture = methodClient.invokeServerStreamingFuture(
+                        request,
+                        (ResponseType response) -> {
+                            synchronized (mNanoappStreamingMessages) {
+                                mNanoappStreamingMessages.putIfAbsent(rpcClient,
+                                        new ArrayList<MessageLite>());
+                                mNanoappStreamingMessages.get(rpcClient).add(response);
+                            }
+                        });
+                responseFutures.add(responseFuture);
+            }
+            mActiveServerStreamingRpc = true;
+        }
+
+        final List<ChreRpcClient> rpcClientsFinal = rpcClients;
+        Future<List<List<ResponseType>>> responseFuture = mExecutor.submit(() -> {
+            boolean success = true;
+            long endTimeInMs = System.currentTimeMillis() + timeoutInMs;
+            for (ServerStreamingFuture future: responseFutures) {
+                try {
+                    future.get(Math.max(0, endTimeInMs - System.currentTimeMillis()),
+                            TimeUnit.MILLISECONDS);
+                } catch (Exception exception) {
+                    success = false;
+                }
+            }
+
+            synchronized (mNanoappStreamingMessages) {
+                List<List<ResponseType>> responses = null;
+                if (success) {
+                    responses = new ArrayList<List<ResponseType>>();
+                    for (ChreRpcClient rpcClient: rpcClientsFinal) {
+                        List<MessageLite> messages = mNanoappStreamingMessages.get(rpcClient);
+                        List<ResponseType> responseList = new ArrayList<ResponseType>();
+                        if (messages != null) {
+                            // Only needed to cast the type.
+                            for (MessageLite message: messages) {
+                                responseList.add((ResponseType) message);
+                            }
+                        }
+
+                        responses.add(responseList);
+                    }
+                }
+
+                mNanoappStreamingMessages.clear();
+                mActiveServerStreamingRpc = false;
+                return responses;
+            }
+        });
+        return responseFuture;
+    }
+
+    /**
+     * Calls a server streaming RPC method with timeoutInMs milliseconds of timeout on
+     * multiple RPC clients. This returns a Future for the result. The responses will have the same
+     * size as the input rpcClients size.
+     *
+     * @param <RequestType>   the type of the request (proto generated type).
+     * @param <ResponseType>  the type of the response (proto generated type).
+     * @param rpcClients      the RPC clients.
+     * @param method          the fully-qualified method name.
+     * @param request         the request.
+     * @param timeoutInMs     the timeout in milliseconds.
+     *
+     * @return                the Future for the response for null if there was an error.
+     */
+    private <RequestType extends MessageLite, ResponseType extends MessageLite>
+            Future<List<List<ResponseType>>> callConcurrentServerStreamingRpcMethodAsync(
+                    @NonNull List<ChreRpcClient> rpcClients,
+                    @NonNull String method,
+                    @NonNull RequestType request,
+                    long timeoutInMs) throws Exception {
+        Objects.requireNonNull(rpcClients);
+        Objects.requireNonNull(method);
+        Objects.requireNonNull(request);
+
+        ArrayList<RequestType> requests = new ArrayList<RequestType>();
+        for (int i = 0; i < rpcClients.size(); ++i) {
+            requests.add(request);
+        }
+        return callConcurrentServerStreamingRpcMethodAsync(rpcClients, method,
+                requests, timeoutInMs);
+    }
+}
diff --git a/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java b/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java
index d69a182..f1214a5 100644
--- a/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java
+++ b/java/test/utils/src/com/google/android/utils/chre/ChreTestUtil.java
@@ -16,6 +16,8 @@
 
 package com.google.android.utils.chre;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.app.Instrumentation;
 import android.content.Context;
 import android.hardware.location.ContextHubInfo;
@@ -36,6 +38,7 @@
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -195,6 +198,18 @@
     }
 
     /**
+     * @param input The string input of an integer.
+     * @return The converted integer.
+     */
+    public static int convertToIntegerOrReturnZero(String input) {
+        try {
+            return Integer.parseInt(input);
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+    /**
      * Get all the nanoapps currently loaded on device.
      *
      * @return The nanoapps loaded currently.
@@ -288,4 +303,14 @@
         Assert.assertTrue(type + " transaction failed with error code " + response.getResult(),
                 response.getResult() == ContextHubTransaction.RESULT_SUCCESS);
     }
+
+    public static void assertLatchCountedDown(CountDownLatch latch, long timeoutThreshold)
+            throws InterruptedException {
+        boolean isCountedDown = latch.await(timeoutThreshold, TimeUnit.SECONDS);
+        assertWithMessage(
+                        "Waiting for latch to count down timeout after %s seconds",
+                        timeoutThreshold)
+                .that(isCountedDown)
+                .isTrue();
+    }
 }
diff --git a/java/test/utils/src/com/google/android/utils/chre/ContextHubBroadcastReceiver.java b/java/test/utils/src/com/google/android/utils/chre/ContextHubBroadcastReceiver.java
new file mode 100644
index 0000000..3a34d4f
--- /dev/null
+++ b/java/test/utils/src/com/google/android/utils/chre/ContextHubBroadcastReceiver.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.utils.chre;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubIntentEvent;
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/** A BroadcastReceiver that waits for Intent events from the Context Hub. */
+public class ContextHubBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = "ContextHubBroadcastReceiver";
+
+    /*
+     * The queue to store received ContextHubIntentEvents.
+     */
+    private static final ArrayBlockingQueue<ContextHubIntentEvent> sQueue =
+            new ArrayBlockingQueue<>(1);
+
+    /*
+     * Note: This method runs on the main UI thread of this app.
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        ContextHubIntentEvent event = null;
+        try {
+            event = ContextHubIntentEvent.fromIntent(intent);
+        } catch (IllegalArgumentException e) {
+            Assert.fail("Exception while parsing intent event: " + e.getMessage());
+        }
+        Log.d(TAG, "Received intent event: " + event);
+
+        Assert.assertEquals("Received too many Intent events", sQueue.size(), 0);
+        sQueue.add(event);
+    }
+
+    /**
+     * Waits for an Intent event to be delivered to an instance of this BroadcastReceiver.
+     *
+     * @param timeout the timeout to wait
+     * @param unit the unit of the timeout
+     * @param eventType the type of the event type of the expected Intent event
+     * @param contextHubInfo the ContextHubInfo of the expected Intent event
+     * @param nanoAppId the ID of the nanoapp of the expected Intent event
+     * @return the ContextHubIntentEvent generated from the Intent event
+     */
+    public static ContextHubIntentEvent pollIntentEvent(
+            long timeout,
+            TimeUnit unit,
+            int eventType,
+            ContextHubInfo contextHubInfo,
+            long nanoAppId) {
+        ContextHubIntentEvent event = null;
+        try {
+            event = sQueue.poll(timeout, unit);
+        } catch (InterruptedException e) {
+            Assert.fail("InterruptedException while waiting for Intent event");
+        }
+        if (event == null) {
+            Assert.fail("Timed out while waiting for Intent event");
+        }
+
+        if (event.getEventType() != eventType) {
+            Assert.fail(
+                    "Received unexpected event type, expected "
+                            + eventType
+                            + " received "
+                            + event.getEventType());
+        }
+        if (!event.getContextHubInfo().equals(contextHubInfo)) {
+            Assert.fail(
+                    "Received unexpected ContextHubInfo, expected "
+                            + contextHubInfo
+                            + " received "
+                            + event.getContextHubInfo());
+        }
+        if (event.getNanoAppId() != nanoAppId) {
+            Assert.fail(
+                    "Received unexpected nanoapp ID, expected "
+                            + nanoAppId
+                            + " received "
+                            + event.getNanoAppId());
+        }
+
+        return event;
+    }
+
+    /**
+     * Asserts that no Intent events are delivered to an instance of this BroadcastReceiver.
+     *
+     * @param timeout the timeout to wait
+     * @param unit the unit of the timeout
+     */
+    public static void assertNoIntentEventReceived(long timeout, TimeUnit unit) {
+        ContextHubIntentEvent event = null;
+        try {
+            event = sQueue.poll(timeout, unit);
+        } catch (InterruptedException e) {
+            Assert.fail("InterruptedException while waiting for Intent event");
+        }
+
+        Assert.assertNull("Received unexpected intent event " + event, event);
+    }
+}
diff --git a/java/test/utils/src/com/google/android/utils/chre/ContextHubClientMessageValidator.java b/java/test/utils/src/com/google/android/utils/chre/ContextHubClientMessageValidator.java
new file mode 100644
index 0000000..bf25df6
--- /dev/null
+++ b/java/test/utils/src/com/google/android/utils/chre/ContextHubClientMessageValidator.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.utils.chre;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.hardware.location.NanoAppMessage;
+
+import java.util.List;
+
+/** A helper class to handle incoming messages by comparing against a set of expected messages. */
+public class ContextHubClientMessageValidator {
+    /** The list of expected messages to received by onMessageReceipt. */
+    private final List<NanoAppMessage> mExpectedMessages;
+
+    /** The index of the next expected message. */
+    private int mExpectedMessageIndex = 0;
+
+    /** The message to invoke when all messages have been received. */
+    private final Runnable mOnComplete;
+
+    public ContextHubClientMessageValidator(
+            List<NanoAppMessage> expectedMessages, Runnable onComplete) {
+        mExpectedMessages = expectedMessages;
+        mOnComplete = onComplete;
+    }
+
+    /**
+     * asserts that the message received is expected.
+     *
+     * <p>{@link #mOnComplete} is only called when all the expected messages are received.
+     */
+    public synchronized void assertMessageReceivedIsExpected(NanoAppMessage message) {
+        assertWithMessage("Received more than expected messages from nanoapp")
+                .that(mExpectedMessageIndex)
+                .isLessThan(mExpectedMessages.size());
+
+        NanoAppMessage expectedMessage = mExpectedMessages.get(mExpectedMessageIndex);
+        assertWithMessage("Received unexpected message contents from nanoapp")
+                .that(message.getMessageBody())
+                .isEqualTo(expectedMessage.getMessageBody());
+
+        mExpectedMessageIndex++;
+        if (mExpectedMessageIndex == mExpectedMessages.size()) {
+            mOnComplete.run();
+        }
+    }
+}
diff --git a/java/test/utils/src/com/google/android/utils/chre/ContextHubServiceTestHelper.java b/java/test/utils/src/com/google/android/utils/chre/ContextHubServiceTestHelper.java
new file mode 100644
index 0000000..1c0731a
--- /dev/null
+++ b/java/test/utils/src/com/google/android/utils/chre/ContextHubServiceTestHelper.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.utils.chre;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.PendingIntent;
+import android.hardware.location.ContextHubClient;
+import android.hardware.location.ContextHubClientCallback;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppState;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * The helper class to facilitate CHQTS test.
+ *
+ * <p> A test class using this helper should run {@link #init()} before starting any test and run
+ * {@link #deinit()} after any test.</p>
+ */
+public class ContextHubServiceTestHelper {
+    private static final long TIMEOUT_SECONDS_QUERY = 5;
+    public static final long TIMEOUT_SECONDS_LOAD = 30;
+    public static final long TIMEOUT_SECONDS_UNLOAD = 5;
+    public static final long TIMEOUT_SECONDS_MESSAGE = 1;
+
+    private ContextHubClient mHubResetClient = null;
+    private final ContextHubInfo mContextHubInfo;
+    private final ContextHubManager mContextHubManager;
+    private final AtomicBoolean mChreReset = new AtomicBoolean(false);
+
+    public ContextHubServiceTestHelper(ContextHubInfo info, ContextHubManager manager) {
+        mContextHubInfo = info;
+        mContextHubManager = manager;
+    }
+
+    public void init() {
+        // Registers a client to record a hub reset.
+        registerHubResetClient();
+    }
+
+    public void initAndUnloadAllNanoApps() throws InterruptedException, TimeoutException {
+        init();
+        // Unload all nanoapps to ensure test starts at a clean state.
+        unloadAllNanoApps();
+    }
+
+    public void deinit() {
+        // unregister to detect any hub reset.
+        unregisterHubResetClient();
+    }
+
+    /** Creates a registered callback-based ContextHubClient. */
+    public ContextHubClient createClient(ContextHubClientCallback callback) {
+        return mContextHubManager.createClient(mContextHubInfo, callback);
+    }
+
+    /**
+     * Creates a PendingIntent-based ContextHubClient object.
+     *
+     * @param pendingIntent the PendingIntent object to associate with the ContextHubClient
+     * @param nanoAppId     the ID of the nanoapp to receive Intent events for
+     * @return the registered ContextHubClient object
+     */
+    public ContextHubClient createClient(PendingIntent pendingIntent, long nanoAppId) {
+        return mContextHubManager.createClient(mContextHubInfo, pendingIntent, nanoAppId);
+    }
+
+    /**
+     * Registers a {@link ContextHubClient} client with a callback so that a hub reset can be
+     * recorded.
+     *
+     * <p> Caller needs to call {@link #unregisterHubResetClient()} to check if any reset happens.
+     */
+    public void registerHubResetClient() {
+        ContextHubClientCallback callback = new ContextHubClientCallback() {
+            @Override
+            public void onHubReset(ContextHubClient client) {
+                mChreReset.set(true);
+            }
+        };
+        mHubResetClient = createClient(callback);
+    }
+
+    /** Unregisters the hub reset client after the test. */
+    public void unregisterHubResetClient() {
+        assertWithMessage("Context Hub reset unexpectedly while testing").that(
+                mChreReset.get()).isFalse();
+        if (mHubResetClient != null) {
+            mHubResetClient.close();
+            mHubResetClient = null;
+        }
+    }
+
+    /** Loads a nanoapp binary asynchronously. */
+    public ContextHubTransaction<Void> loadNanoApp(NanoAppBinary nanoAppBinary) {
+        return mContextHubManager.loadNanoApp(mContextHubInfo, nanoAppBinary);
+    }
+
+    /**
+     * Loads a nanoapp binary and asserts that it succeeded synchronously.
+     *
+     * @param nanoAppBinary the binary to load
+     */
+    public void loadNanoAppAssertSuccess(NanoAppBinary nanoAppBinary) {
+        ContextHubTransaction<Void> transaction = loadNanoApp(nanoAppBinary);
+        assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_LOAD, TimeUnit.SECONDS);
+    }
+
+    /** Unloads a nanoapp asynchronously. */
+    public ContextHubTransaction<Void> unloadNanoApp(long nanoAppId) {
+        return mContextHubManager.unloadNanoApp(mContextHubInfo, nanoAppId);
+    }
+
+    /** Unloads a nanoapp and asserts that it succeeded synchronously. */
+    public void unloadNanoAppAssertSuccess(long nanoAppId) {
+        ContextHubTransaction<Void> transaction = unloadNanoApp(nanoAppId);
+        assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_UNLOAD, TimeUnit.SECONDS);
+    }
+
+    /** Queries for a nanoapp synchronously and returns the list of loaded nanoapps. */
+    public List<NanoAppState> queryNanoApps() throws InterruptedException, TimeoutException {
+        ContextHubTransaction<List<NanoAppState>> transaction = mContextHubManager.queryNanoApps(
+                mContextHubInfo);
+
+        assertTransactionSuccessSync(transaction, TIMEOUT_SECONDS_QUERY, TimeUnit.SECONDS);
+        ContextHubTransaction.Response<List<NanoAppState>> response = transaction.waitForResponse(
+                TIMEOUT_SECONDS_QUERY, TimeUnit.SECONDS);
+
+        return response.getContents();
+    }
+
+    /** Unloads all nanoapps currently loaded at the Context Hub. */
+    public void unloadAllNanoApps() throws InterruptedException, TimeoutException {
+        List<NanoAppState> nanoAppStateList = queryNanoApps();
+        for (NanoAppState state : nanoAppStateList) {
+            unloadNanoAppAssertSuccess(state.getNanoAppId());
+        }
+    }
+
+    /** Asserts that a list of nanoapps are loaded at the hub. */
+    public void assertNanoAppsLoaded(List<Long> nanoAppIdList)
+            throws InterruptedException, TimeoutException {
+        StringBuilder unloadedApps = new StringBuilder();
+        for (Map.Entry<Long, Boolean> entry : findNanoApps(nanoAppIdList).entrySet()) {
+            if (!entry.getValue()) {
+                unloadedApps.append(Long.toHexString(entry.getKey())).append(";");
+            }
+        }
+        assertWithMessage("Did not find following nanoapps: " + unloadedApps).that(
+                unloadedApps.length()).isEqualTo(0);
+    }
+
+    /** Asserts that a list of nanoapps are not loaded at the hub. */
+    public void assertNanoAppsNotLoaded(List<Long> nanoAppIdList)
+            throws InterruptedException, TimeoutException {
+        StringBuilder loadedApps = new StringBuilder();
+        for (Map.Entry<Long, Boolean> entry : findNanoApps(nanoAppIdList).entrySet()) {
+            if (entry.getValue()) {
+                loadedApps.append(Long.toHexString(entry.getKey())).append(";");
+            }
+        }
+        assertWithMessage("Following nanoapps are loaded: " + loadedApps).that(
+                loadedApps.length()).isEqualTo(0);
+    }
+
+    /**
+     * Determines if a nanoapp is loaded at the hub using a query.
+     *
+     * @param nanoAppIds the list of nanoapps to verify as loaded
+     * @return a boolean array populated as true if the nanoapp is loaded, false otherwise
+     */
+    private Map<Long, Boolean> findNanoApps(List<Long> nanoAppIds)
+            throws InterruptedException, TimeoutException {
+        Map<Long, Boolean> foundNanoApps = new HashMap<>();
+        for (Long nanoAppId : nanoAppIds) {
+            foundNanoApps.put(nanoAppId, false);
+        }
+        List<NanoAppState> nanoAppStateList = queryNanoApps();
+        for (NanoAppState nanoAppState : nanoAppStateList) {
+            Long nanoAppId = nanoAppState.getNanoAppId();
+            if (foundNanoApps.containsKey(nanoAppId)) {
+                assertWithMessage("Nanoapp 0x" + Long.toHexString(nanoAppState.getNanoAppId())
+                        + " was found twice in query response").that(
+                        foundNanoApps.get(nanoAppId)).isFalse();
+                foundNanoApps.put(nanoAppId, true);
+            }
+        }
+        return foundNanoApps;
+    }
+
+    /**
+     * Waits for a result of a transaction synchronously, and asserts that it succeeded.
+     *
+     * @param transaction the transaction to wait on
+     * @param timeout     the timeout duration
+     * @param unit        the timeout unit
+     */
+    public void assertTransactionSuccessSync(ContextHubTransaction<?> transaction, long timeout,
+            TimeUnit unit) {
+        assertWithMessage("ContextHubTransaction cannot be null").that(transaction).isNotNull();
+        String type = ContextHubTransaction.typeToString(transaction.getType(),
+                true /* upperCase */);
+        ContextHubTransaction.Response<?> response;
+        try {
+            response = transaction.waitForResponse(timeout, unit);
+        } catch (InterruptedException | TimeoutException e) {
+            throw new AssertionError("Failed to get a response for " + type + " transaction", e);
+        }
+        assertWithMessage(
+                type + " transaction failed with error code " + response.getResult()).that(
+                response.getResult()).isEqualTo(ContextHubTransaction.RESULT_SUCCESS);
+    }
+
+    /**
+     * Run tasks in separate threads concurrently and wait until completion.
+     *
+     * @param tasks   a list of tasks to start
+     * @param timeout the timeout duration
+     * @param unit    the timeout unit
+     */
+    public void runConcurrentTasks(List<Runnable> tasks, long timeout, TimeUnit unit)
+            throws InterruptedException {
+        ExecutorService executorService = Executors.newCachedThreadPool();
+        for (Runnable task : tasks) {
+            executorService.submit(task);
+        }
+        executorService.shutdown();
+        boolean unused = executorService.awaitTermination(timeout, unit);
+    }
+}
diff --git a/java/test/utils/src/com/google/android/utils/chre/SettingsUtil.java b/java/test/utils/src/com/google/android/utils/chre/SettingsUtil.java
index 2378a4b..98a2ace 100644
--- a/java/test/utils/src/com/google/android/utils/chre/SettingsUtil.java
+++ b/java/test/utils/src/com/google/android/utils/chre/SettingsUtil.java
@@ -53,6 +53,19 @@
         };
     }
 
+    private class AirplaneModeListener {
+        protected CountDownLatch mAirplaneModeLatch = new CountDownLatch(1);
+
+        protected BroadcastReceiver mAirplaneModeReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
+                    mAirplaneModeLatch.countDown();
+                }
+            }
+        };
+    }
+
     public SettingsUtil(Context context) {
         mContext = context;
         mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
@@ -133,4 +146,79 @@
     public boolean isLocationEnabled() {
         return mLocationManager.isLocationEnabledForUser(UserHandle.CURRENT);
     }
+
+    /**
+     * Sets the bluetooth mode to be on or off
+     *
+     * @param enable        if true, turn bluetooth on; otherwise, turn bluetooth off
+     */
+    public void setBluetooth(boolean enable) {
+        String value = enable ? "enable" : "disable";
+        ChreTestUtil.executeShellCommand(mInstrumentation, "svc bluetooth " + value);
+    }
+
+    /**
+     * @param enable true to enable always bluetooth scanning.
+     */
+    public void setBluetoothScanningSettings(boolean enable) {
+        String value = enable ? "1" : "0";
+        ChreTestUtil.executeShellCommand(
+                mInstrumentation, "settings put global ble_scan_always_enabled " + value);
+    }
+
+    /**
+     * @return true if bluetooth is enabled, false otherwise
+     */
+    public boolean isBluetoothEnabled() {
+        String out = ChreTestUtil.executeShellCommand(
+                mInstrumentation, "settings get global bluetooth_on");
+        return ChreTestUtil.convertToIntegerOrFail(out) > 0;
+    }
+
+    /**
+     * @return true if the bluetooth scanning is always enabled.
+     */
+    public boolean isBluetoothScanningAlwaysEnabled() {
+        String out = ChreTestUtil.executeShellCommand(
+                mInstrumentation, "settings get global ble_scan_always_enabled");
+
+        // by default, this setting returns null, which is equivalent to 0 or disabled
+        return ChreTestUtil.convertToIntegerOrReturnZero(out) > 0;
+    }
+
+    /**
+     * Sets the airplane mode on the device.
+     * @param enable True to enable airplane mode, false to disable it.
+     */
+    public void setAirplaneMode(boolean enable) {
+        if (isAirplaneModeOn() != enable) {
+            AirplaneModeListener listener = new AirplaneModeListener();
+            mContext.registerReceiver(
+                    listener.mAirplaneModeReceiver,
+                    new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+
+            String value = enable ? "enable" : "disable";
+            ChreTestUtil.executeShellCommand(
+                    mInstrumentation, "cmd connectivity airplane-mode " + value);
+
+            try {
+                listener.mAirplaneModeLatch.await(10, TimeUnit.SECONDS);
+                // Wait 1 additional second to make sure setting gets propagated to CHRE
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                Assert.fail(e.getMessage());
+            }
+            Assert.assertTrue(isAirplaneModeOn() == enable);
+            mContext.unregisterReceiver(listener.mAirplaneModeReceiver);
+        }
+    }
+
+    /**
+     * @return true if the airplane mode is currently enabled.
+     */
+    public boolean isAirplaneModeOn() {
+        String out = ChreTestUtil.executeShellCommand(
+                mInstrumentation, "settings get global airplane_mode_on");
+        return ChreTestUtil.convertToIntegerOrFail(out) > 0;
+    }
 }
diff --git a/java/utils/pigweed/Android.bp b/java/utils/pigweed/Android.bp
index 66d7d16..e7b1572 100644
--- a/java/utils/pigweed/Android.bp
+++ b/java/utils/pigweed/Android.bp
@@ -27,6 +27,7 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
+        "androidx.annotation_annotation",
         "guava",
         "pw_rpc_java_client",
     ],
diff --git a/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreCallbackHandler.java b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreCallbackHandler.java
index 125695d..b84ebad 100644
--- a/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreCallbackHandler.java
+++ b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreCallbackHandler.java
@@ -20,109 +20,147 @@
 
 import android.hardware.location.ContextHubClient;
 import android.hardware.location.ContextHubClientCallback;
-import android.hardware.location.ContextHubIntentEvent;
 import android.hardware.location.NanoAppMessage;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
 import dev.pigweed.pw_rpc.Client;
 
 /**
  * Implementation that can be used to ensure callbacks from the ContextHub APIs are properly handled
  * when using pw_rpc.
  */
-public class ChreCallbackHandler {
-    private final ContextHubClient mHubClient;
+public class ChreCallbackHandler extends ContextHubClientCallback {
     private final long mNanoappId;
-    private final Client mRpcClient;
-    private final ChreChannelOutput mChannelOutput;
+    @Nullable
+    private final ContextHubClientCallback mCallback;
+    private Client mRpcClient;
+    private ChreChannelOutput mChannelOutput;
 
-    public ChreCallbackHandler(ContextHubClient hubClient,
-            long nanoappId, Client rpcClient, ChreChannelOutput channelOutput) {
-        mHubClient = hubClient;
+    /**
+     * @param nanoappId ID of the RPC Server nanoapp
+     * @param callback The callbacks receiving messages and life-cycle events from nanoapps
+     */
+    public ChreCallbackHandler(long nanoappId, @Nullable ContextHubClientCallback callback) {
         mNanoappId = nanoappId;
-        mRpcClient = rpcClient;
-        mChannelOutput = channelOutput;
+        mCallback = callback;
     }
 
     /**
-     * This must be called directly from the equivalent {@link ContextHubClientCallback} method
-     * or after decoding a {@link ContextHubIntentEvent} of type
-     * {@link ContextHubManager.EVENT_NANOAPP_MESSAGE}.
+     * Completes the initialization.
      *
+     * @param rpcClient The Pigweed RPC client
+     * @param channelOutput The ChannelOutput used by Pigweed
+     */
+    public void lateInit(@NonNull Client rpcClient, @NonNull ChreChannelOutput channelOutput) {
+        mRpcClient = Objects.requireNonNull(rpcClient);
+        mChannelOutput = Objects.requireNonNull(channelOutput);
+    }
+
+    /**
      * This method passes the message to pigweed RPC for decoding.
      */
-    public void onMessageFromNanoApp(NanoAppMessage message) {
-        if (message.getNanoAppId() == mNanoappId) {
+    @Override
+    public void onMessageFromNanoApp(ContextHubClient client, NanoAppMessage message) {
+        if (mRpcClient != null && message.getNanoAppId() == mNanoappId) {
             mRpcClient.processPacket(message.getMessageBody());
         }
-    }
-
-    /**
-     * This must be called directly from the equivalent {@link ContextHubClientCallback} method
-     * or after decoding a {@link ContextHubIntentEvent} of type
-     * {@link ContextHubManager.EVENT_HUB_RESET}.
-     *
-     * This method ensures all outstanding RPCs are canceled.
-     */
-    public void onHubReset() {
-        // TODO(b/210138227): Close all outsanding RPCs.
-    }
-
-
-    /**
-     * This must be called directly from the equivalent {@link ContextHubClientCallback} method
-     * or after decoding a {@link ContextHubIntentEvent} of type
-     * {@link ContextHubManager.EVENT_NANOAPP_UNLOADED}.
-     *
-     * This method ensures all outstanding RPCs are canceled.
-     */
-    public void onNanoappUnloaded(long nanoappId) {
-        if (nanoappId == mNanoappId) {
-            // TODO(b/210138227): Close all outsanding RPCs.
+        if (mCallback != null) {
+            mCallback.onMessageFromNanoApp(client, message);
         }
     }
 
     /**
-     * This must be called directly from the equivalent {@link ContextHubClientCallback} method
-     * or after decoding a {@link ContextHubIntentEvent} of type
-     * {@link ContextHubManager.EVENT_NANOAPP_DISABLED}.
-     *
      * This method ensures all outstanding RPCs are canceled.
      */
-    public void onNanoappDisabled(long nanoappId) {
-        if (nanoappId == mNanoappId) {
-            // TODO(b/210138227): Close all outsanding RPCs.
+    @Override
+    public void onHubReset(ContextHubClient client) {
+        closeChannel();
+        if (mCallback != null) {
+            mCallback.onHubReset(client);
         }
     }
 
     /**
-     * This must be called directly from the equivalent {@link ContextHubClientCallback} method
-     * or after decoding a {@link ContextHubIntentEvent} of type
-     * {@link ContextHubManager.EVENT_NANOAPP_ABORTED}.
-     *
      * This method ensures all outstanding RPCs are canceled.
      */
-    public void onNanoppAborted(long nanoappId) {
+    @Override
+    public void onNanoAppAborted(ContextHubClient client, long nanoappId, int abortCode) {
         if (nanoappId == mNanoappId) {
-            // TODO(b/210138227): Close all outsanding RPCs.
+            closeChannel();
+        }
+        if (mCallback != null) {
+            mCallback.onNanoAppAborted(client, nanoappId, abortCode);
+        }
+    }
+
+    @Override
+    public void onNanoAppLoaded(ContextHubClient client, long nanoappId) {
+        if (mCallback != null) {
+            mCallback.onNanoAppLoaded(client, nanoappId);
         }
     }
 
     /**
-     * This must be called directly from the equivalent {@link ContextHubClientCallback} method
-     * or after decoding a {@link ContextHubIntentEvent} of type
-     * {@link ContextHubManager.EVENT_CLIENT_AUTHORIZATION}.
-     *
+     * This method ensures all outstanding RPCs are canceled.
+     */
+    @Override
+    public void onNanoAppUnloaded(ContextHubClient client, long nanoappId) {
+        if (nanoappId == mNanoappId) {
+            closeChannel();
+        }
+        if (mCallback != null) {
+            mCallback.onNanoAppUnloaded(client, nanoappId);
+        }
+    }
+
+    @Override
+    public void onNanoAppEnabled(ContextHubClient client, long nanoappId) {
+        if (mCallback != null) {
+            mCallback.onNanoAppEnabled(client, nanoappId);
+        }
+    }
+
+    /**
+     * This method ensures all outstanding RPCs are canceled.
+     */
+    @Override
+    public void onNanoAppDisabled(ContextHubClient client, long nanoappId) {
+        if (nanoappId == mNanoappId) {
+            closeChannel();
+        }
+        if (mCallback != null) {
+            mCallback.onNanoAppDisabled(client, nanoappId);
+        }
+    }
+
+    /**
      * If the client is now unauthorized to communicate with the nanoapp, any future RPCs attempted
      * will fail until the client becomes authorized again.
      */
-    public void onClientAuthorizationChanged(long nanoappId, int authorization) {
-        if (nanoappId == mNanoappId) {
+    @Override
+    public void onClientAuthorizationChanged(ContextHubClient client, long nanoappId,
+            int authorization) {
+        if (mChannelOutput != null && nanoappId == mNanoappId) {
             if (authorization == AUTHORIZATION_DENIED) {
                 mChannelOutput.setAuthDenied(true /* denied */);
-                // TODO(b/210138227): Close all outsanding RPCs.
+                closeChannel();
             } else if (authorization == AUTHORIZATION_GRANTED) {
                 mChannelOutput.setAuthDenied(false /* denied */);
             }
         }
+        if (mCallback != null) {
+            mCallback.onClientAuthorizationChanged(client, nanoappId, authorization);
+        }
+    }
+
+    private void closeChannel() {
+        if (mRpcClient != null && mChannelOutput != null) {
+            mRpcClient.closeChannel(mChannelOutput.getChannelId());
+            mChannelOutput = null;
+        }
     }
 }
diff --git a/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreChannelOutput.java b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreChannelOutput.java
index 39a9160..43440b5 100644
--- a/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreChannelOutput.java
+++ b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreChannelOutput.java
@@ -54,8 +54,8 @@
      */
     @Override
     public void send(byte[] packet) throws ChannelOutputException {
-        NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
-                mNanoappId, PW_RPC_CHRE_MESSAGE_TYPE, packet);
+        NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(mNanoappId,
+                PW_RPC_CHRE_MESSAGE_TYPE, packet);
         if (mAuthDenied.get()
                 || ContextHubTransaction.RESULT_SUCCESS != mClient.sendMessageToNanoApp(message)) {
             throw new ChannelOutputException();
@@ -64,7 +64,7 @@
 
     /**
      * @return Channel ID to use for all Channels that use this output to send
-     *     messages to a nanoapp.
+     * messages to a nanoapp.
      */
     public int getChannelId() {
         return (CHANNEL_ID_HOST_CLIENT | mClient.getId());
diff --git a/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreIntentHandler.java b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreIntentHandler.java
new file mode 100644
index 0000000..bec78fc
--- /dev/null
+++ b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreIntentHandler.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.chre.utils.pigweed;
+
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_DENIED;
+import static android.hardware.location.ContextHubManager.AUTHORIZATION_GRANTED;
+
+import android.content.Intent;
+import android.hardware.location.ContextHubIntentEvent;
+import android.hardware.location.ContextHubManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+import dev.pigweed.pw_rpc.Client;
+
+/**
+ * Handles RPC events in CHRE intent.
+ */
+public class ChreIntentHandler {
+    private static final String TAG = "ChreIntentHandler";
+
+    /**
+     * Handles CHRE intents.
+     *
+     * @param intent        the intent.
+     * @param nanoappId     ID of the RPC Server nanoapp
+     * @param rpcClient     The Pigweed RPC client
+     * @param channelOutput The ChannelOutput used by Pigweed
+     */
+    public static void handle(@NonNull Intent intent, long nanoappId, @NonNull Client rpcClient,
+            @NonNull ChreChannelOutput channelOutput) {
+        Objects.requireNonNull(intent);
+        Objects.requireNonNull(rpcClient);
+        Objects.requireNonNull(channelOutput);
+
+        ContextHubIntentEvent event = ContextHubIntentEvent.fromIntent(intent);
+
+        switch (event.getEventType()) {
+            case ContextHubManager.EVENT_NANOAPP_LOADED:
+                // Nothing to do.
+                break;
+            case ContextHubManager.EVENT_NANOAPP_UNLOADED:
+                rpcClient.closeChannel(channelOutput.getChannelId());
+                break;
+            case ContextHubManager.EVENT_NANOAPP_ENABLED:
+                // Nothing to do.
+                break;
+            case ContextHubManager.EVENT_NANOAPP_DISABLED:
+                rpcClient.closeChannel(channelOutput.getChannelId());
+                break;
+            case ContextHubManager.EVENT_NANOAPP_ABORTED:
+                rpcClient.closeChannel(channelOutput.getChannelId());
+                break;
+            case ContextHubManager.EVENT_NANOAPP_MESSAGE:
+                if (event.getNanoAppId() == nanoappId) {
+                    rpcClient.processPacket(event.getNanoAppMessage().getMessageBody());
+                }
+                break;
+            case ContextHubManager.EVENT_HUB_RESET:
+                rpcClient.closeChannel(channelOutput.getChannelId());
+                break;
+            case ContextHubManager.EVENT_CLIENT_AUTHORIZATION:
+                if (event.getNanoAppId() == nanoappId) {
+                    if (event.getClientAuthorizationState() == AUTHORIZATION_DENIED) {
+                        channelOutput.setAuthDenied(true /* denied */);
+                        rpcClient.closeChannel(channelOutput.getChannelId());
+                    } else if (event.getClientAuthorizationState() == AUTHORIZATION_GRANTED) {
+                        channelOutput.setAuthDenied(false /* denied */);
+                    }
+                }
+                break;
+            default:
+                Log.e(TAG, "Unexpected event: " + event);
+        }
+    }
+}
diff --git a/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreRpcClient.java b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreRpcClient.java
new file mode 100644
index 0000000..0ba98ed
--- /dev/null
+++ b/java/utils/pigweed/src/com/google/android/chre/utils/pigweed/ChreRpcClient.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.chre.utils.pigweed;
+
+import android.content.Intent;
+import android.hardware.location.ContextHubClient;
+import android.hardware.location.ContextHubClientCallback;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.NanoAppRpcService;
+import android.hardware.location.NanoAppState;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+
+import dev.pigweed.pw_rpc.Channel;
+import dev.pigweed.pw_rpc.Client;
+import dev.pigweed.pw_rpc.MethodClient;
+import dev.pigweed.pw_rpc.Service;
+
+/**
+ * Pigweed RPC Client Helper.
+ *
+ * See https://g3doc.corp.google.com/location/lbs/contexthub/g3doc/nanoapps/pw_rpc_host.md
+ */
+public class ChreRpcClient {
+    @NonNull
+    private final Client mRpcClient;
+    @NonNull
+    private final Channel mChannel;
+    @NonNull
+    private final ChreChannelOutput mChannelOutput;
+    private final long mServerNanoappId;
+    @NonNull
+    private final ContextHubClient mContextHubClient;
+    private ChreIntentHandler mIntentHandler;
+
+    /**
+     * Creates a ContextHubClient and initializes the helper.
+     *
+     * Use this constructor for persistent clients using callbacks.
+     *
+     * @param manager         The context manager used to create a client
+     * @param serverNanoappId The ID of the RPC server nanoapp
+     * @param services        The list of services provided by the server
+     * @param callback        The callbacks receiving messages and life-cycle events from nanoapps
+     */
+    public ChreRpcClient(@NonNull ContextHubManager manager, @NonNull ContextHubInfo info,
+            long serverNanoappId, @NonNull List<Service> services,
+            @Nullable ContextHubClientCallback callback) {
+        Objects.requireNonNull(manager);
+        Objects.requireNonNull(info);
+        Objects.requireNonNull(services);
+        ChreCallbackHandler callbackHandler = new ChreCallbackHandler(serverNanoappId, callback);
+        mContextHubClient = manager.createClient(info, callbackHandler);
+        mServerNanoappId = serverNanoappId;
+        mChannelOutput = new ChreChannelOutput(mContextHubClient, serverNanoappId);
+        mChannel = new Channel(mChannelOutput.getChannelId(), mChannelOutput);
+        mRpcClient = Client.create(List.of(mChannel), services);
+        callbackHandler.lateInit(mRpcClient, mChannelOutput);
+    }
+
+    /**
+     * Initializes the helper
+     *
+     * Use this constructor for non-persistent clients using intents.
+     *
+     * handleIntent() must be called with any CHRE intent received by the BroadcastReceiver.
+     *
+     * @param contextHubClient The context hub client providing the RPC server nanoapp
+     * @param serverNanoappId  The ID of the RPC server nanoapp
+     * @param services         The list of services provided by the server
+     */
+    public ChreRpcClient(@NonNull ContextHubClient contextHubClient, long serverNanoappId,
+            @NonNull List<Service> services) {
+        mContextHubClient = Objects.requireNonNull(contextHubClient);
+        Objects.requireNonNull(services);
+        mServerNanoappId = serverNanoappId;
+        mChannelOutput = new ChreChannelOutput(contextHubClient, serverNanoappId);
+        mChannel = new Channel(mChannelOutput.getChannelId(), mChannelOutput);
+        mRpcClient = Client.create(List.of(mChannel), services);
+    }
+
+    /**
+     * Returns whether the state matches the server nanoapp and the service is provided.
+     *
+     * @param state           A nanoapp state
+     * @param serverNanoappId The ID of the RPC server nanoapp
+     * @param serviceId       ID of the service
+     * @param serviceVersion  Version of the service
+     * @return the state matches the server nanoapp and the service is provided
+     */
+    public static boolean hasService(NanoAppState state, long serverNanoappId, long serviceId,
+            int serviceVersion) {
+        if (state.getNanoAppId() != serverNanoappId) {
+            return false;
+        }
+
+        for (NanoAppRpcService service : state.getRpcServices()) {
+            if (service.getId() == serviceId) {
+                return service.getVersion() == serviceVersion;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Handles CHRE intents.
+     *
+     * @param intent The CHRE intent.
+     */
+    public void handleIntent(@NonNull Intent intent) {
+        ChreIntentHandler.handle(intent, mServerNanoappId, mRpcClient, mChannelOutput);
+    }
+
+    /**
+     * Returns the context hub client.
+     */
+    public ContextHubClient getContextHubClient() {
+        return mContextHubClient;
+    }
+
+    /**
+     * Shorthand for closing the underlying ContextHubClient.
+     */
+    public void close() {
+        mContextHubClient.close();
+    }
+
+    /**
+     * Returns a MethodClient.
+     *
+     * Use the client to invoke the service.
+     *
+     * @param methodName the method name as "package.Service.Method" or "package.Service/Method"
+     * @return The MethodClient instance
+     */
+    public MethodClient getMethodClient(String methodName) {
+        return mRpcClient.method(mChannel.id(), methodName);
+    }
+}
diff --git a/pal/include/chre/pal/ble.h b/pal/include/chre/pal/ble.h
index 52fcc38..35d5983 100644
--- a/pal/include/chre/pal/ble.h
+++ b/pal/include/chre/pal/ble.h
@@ -41,9 +41,20 @@
 #define CHRE_PAL_BLE_API_V1_6 CHRE_PAL_CREATE_API_VERSION(1, 6)
 
 /**
+ * Introduced alongside CHRE API v1.8, adds readRssi() API.
+ */
+#define CHRE_PAL_BLE_API_V1_8 CHRE_PAL_CREATE_API_VERSION(1, 8)
+
+/**
  * The version of the CHRE BLE PAL defined in this header file.
  */
-#define CHRE_PAL_BLE_API_CURRENT_VERSION CHRE_PAL_BLE_API_V1_6
+#define CHRE_PAL_BLE_API_CURRENT_VERSION CHRE_PAL_BLE_API_V1_8
+
+/**
+ * The maximum amount of time allowed to elapse between the call to
+ * readRssi() and when the readRssiCallback() is invoked.
+ */
+#define CHRE_PAL_BLE_READ_RSSI_COMPLETE_TIMEOUT_NS (2 * CHRE_NSEC_PER_SEC)
 
 struct chrePalBleCallbacks {
   /**
@@ -94,6 +105,22 @@
    * @see releaseAdvertisingEvent
    */
   void (*advertisingEventCallback)(struct chreBleAdvertisementEvent *event);
+
+  /**
+   * Callback used to pass completed BLE readRssi events up to CHRE, which hands
+   * it back to the (single) requesting client.
+   *
+   * @param errorCode An error code from enum chreError, with CHRE_ERROR_NONE
+   *        indicating a successful response.
+   * @param handle Connection handle upon which the RSSI was read.
+   * @param rssi The RSSI of the latest packet read upon this connection
+   *        (-128 to 20).
+   *
+   * @see chrePalBleApi.readRssi
+   *
+   * @since v1.8
+   */
+  void (*readRssiCallback)(uint8_t errorCode, uint16_t handle, int8_t rssi);
 };
 
 struct chrePalBleApi {
@@ -177,7 +204,7 @@
    *
    * @see chreBleStopScanAsync()
    */
-  bool (*stopScan)();
+  bool (*stopScan)(void);
 
   /**
    * Invoked when the core CHRE system no longer needs a BLE advertising event
@@ -186,6 +213,25 @@
    * @param event Event data to release
    */
   void (*releaseAdvertisingEvent)(struct chreBleAdvertisementEvent *event);
+
+  /**
+   * Reads the RSSI on a given LE-ACL connection handle.
+   *
+   * Only one call to this method may be outstanding until the
+   * readRssiCallback() is invoked. The readRssiCallback() is guaranteed to be
+   * invoked exactly one within CHRE_PAL_BLE_READ_RSSI_COMPLETE_TIMEOUT_NS of
+   * readRssi() being invoked.
+   *
+   * @param connectionHandle The LE-ACL handle upon which the RSSI is to be
+   * read.
+   *
+   * @return true if the request was accepted, in which case a subsequent call
+   *         to readRssiCallback() will be used to indicate the result of the
+   *         operation.
+   *
+   * @since v1.8
+   */
+  bool (*readRssi)(uint16_t connectionHandle);
 };
 
 /**
diff --git a/pal/include/chre/pal/sensor.h b/pal/include/chre/pal/sensor.h
index 1352e9e..9a0b6ba 100644
--- a/pal/include/chre/pal/sensor.h
+++ b/pal/include/chre/pal/sensor.h
@@ -17,8 +17,8 @@
 #ifndef CHRE_PAL_SENSOR_H_
 #define CHRE_PAL_SENSOR_H_
 
-#include <cstdbool>
-#include <cstdint>
+#include <stdbool.h>
+#include <stdint.h>
 
 #include "chre/pal/system.h"
 #include "chre/pal/version.h"
@@ -174,7 +174,7 @@
    * function. The PAL must also free any memory (e.g. the sensor array if it
    * was dynamically allocated) inside this function.
    */
-  void (*close)();
+  void (*close)(void);
 
   /**
    * Creates a chreSensorInfo struct for every CHRE-supported sensor that is
diff --git a/pal/include/chre/pal/wifi.h b/pal/include/chre/pal/wifi.h
index d58871a..bc524d7 100644
--- a/pal/include/chre/pal/wifi.h
+++ b/pal/include/chre/pal/wifi.h
@@ -61,9 +61,15 @@
 #define CHRE_PAL_WIFI_API_V1_6 CHRE_PAL_CREATE_API_VERSION(1, 6)
 
 /**
+ * Introduced alongside CHRE API v1.8, adding support for getting WiFi NAN
+ * capabilities.
+ */
+#define CHRE_PAL_WIFI_API_V1_8 CHRE_PAL_CREATE_API_VERSION(1, 8)
+
+/**
  * The version of the WiFi PAL defined in this header file.
  */
-#define CHRE_PAL_WIFI_API_CURRENT_VERSION CHRE_PAL_WIFI_API_V1_6
+#define CHRE_PAL_WIFI_API_CURRENT_VERSION CHRE_PAL_WIFI_API_V1_8
 
 struct chrePalWifiCallbacks {
   /**
@@ -497,6 +503,13 @@
    * @since v1.6
    */
   bool (*requestNanRanging)(const struct chreWifiNanRangingParams *params);
+
+  /**
+   * @see chreWifiNanGetCapabilities()
+   *
+   * @since v1.8
+   */
+  bool (*getNanCapabilities)(struct chreWifiNanCapabilities *capabilities);
 };
 
 /**
diff --git a/pal/pal.mk b/pal/pal.mk
index 16c546a..36d7f7e 100644
--- a/pal/pal.mk
+++ b/pal/pal.mk
@@ -15,6 +15,7 @@
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/pal/tests/src/wwan_test.cc
 
 GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/audio_pal_impl_test.cc
+GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/ble_pal_impl_test.cc
 GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/gnss_pal_impl_test.cc
 GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/sensor_pal_impl_test.cc
 GOOGLETEST_PAL_IMPL_SRCS += $(CHRE_PREFIX)/pal/tests/src/wifi_pal_impl_test.cc
diff --git a/pal/tests/src/audio_pal_impl_test.cc b/pal/tests/src/audio_pal_impl_test.cc
index 2e7c67a..1b3c306 100644
--- a/pal/tests/src/audio_pal_impl_test.cc
+++ b/pal/tests/src/audio_pal_impl_test.cc
@@ -18,6 +18,7 @@
 
 #include "chre/pal/audio.h"
 #include "chre/platform/condition_variable.h"
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/platform/mutex.h"
 #include "chre/platform/shared/pal_system_api.h"
 #include "chre/util/lock_guard.h"
@@ -80,6 +81,8 @@
  protected:
   void SetUp() override {
     gCallbacks = MakeUnique<Callbacks>();
+    chre::TaskManagerSingleton::deinit();
+    chre::TaskManagerSingleton::init();
     mApi = chrePalAudioGetApi(CHRE_PAL_AUDIO_API_CURRENT_VERSION);
     ASSERT_NE(mApi, nullptr);
     EXPECT_EQ(mApi->moduleVersion, CHRE_PAL_AUDIO_API_CURRENT_VERSION);
@@ -87,10 +90,11 @@
   }
 
   void TearDown() override {
-    gCallbacks = nullptr;
     if (mApi != nullptr) {
       mApi->close();
     }
+    chre::TaskManagerSingleton::deinit();
+    gCallbacks = nullptr;
   }
 
   //! CHRE PAL implementation API.
@@ -118,13 +122,12 @@
 }
 
 TEST_F(PalAudioTest, GetDataEvent) {
+  LockGuard<Mutex> lock(gCallbacks->mMutex);
   EXPECT_TRUE(mApi->requestAudioDataEvent(0 /*handle*/, 1000 /*numSamples*/,
                                           100 /*eventDelaysNs*/));
-
-  LockGuard<Mutex> lock(gCallbacks->mMutex);
   gCallbacks->mCondVarDataEvents.wait_for(
-      gCallbacks->mMutex, Nanoseconds(kOneMillisecondInNanoseconds));
-  EXPECT_TRUE(gCallbacks->mDataEvent.has_value());
+      gCallbacks->mMutex, Nanoseconds(25 * kOneMillisecondInNanoseconds));
+  ASSERT_TRUE(gCallbacks->mDataEvent.has_value());
   struct chreAudioDataEvent *event = gCallbacks->mDataEvent.value();
   EXPECT_EQ(event->handle, 0);
   EXPECT_EQ(event->sampleCount, 1000);
diff --git a/pal/tests/src/ble_pal_impl_test.cc b/pal/tests/src/ble_pal_impl_test.cc
new file mode 100644
index 0000000..0d08a87
--- /dev/null
+++ b/pal/tests/src/ble_pal_impl_test.cc
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 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 <cstdint>
+
+#include "chre/pal/ble.h"
+#include "chre/platform/condition_variable.h"
+#include "chre/platform/linux/task_util/task_manager.h"
+#include "chre/platform/log.h"
+#include "chre/platform/mutex.h"
+#include "chre/platform/shared/pal_system_api.h"
+#include "chre/util/fixed_size_vector.h"
+#include "chre/util/lock_guard.h"
+#include "chre/util/macros.h"
+#include "chre/util/nanoapp/ble.h"
+#include "chre/util/optional.h"
+#include "chre/util/unique_ptr.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using ::chre::ConditionVariable;
+using ::chre::createBleScanFilterForKnownBeacons;
+using ::chre::FixedSizeVector;
+using ::chre::gChrePalSystemApi;
+using ::chre::LockGuard;
+using ::chre::MakeUnique;
+using ::chre::Milliseconds;
+using ::chre::Mutex;
+using ::chre::Nanoseconds;
+using ::chre::Optional;
+using ::chre::Seconds;
+using ::chre::UniquePtr;
+using ::chre::ble_constants::kNumScanFilters;
+
+const Nanoseconds kBleStatusTimeoutNs = Milliseconds(200);
+const Nanoseconds kBleEventTimeoutNs = Seconds(10);
+constexpr uint32_t kBleBatchDurationMs = 0;
+
+class Callbacks {
+ public:
+  void requestStateResync() {}
+
+  void scanStatusChangeCallback(bool enabled, uint8_t errorCode) {
+    LOGI("Received scan status change with enabled %d error %d", enabled,
+         errorCode);
+    LockGuard<Mutex> lock(mMutex);
+    mEnabled = enabled;
+    mCondVarStatus.notify_one();
+  }
+
+  void advertisingEventCallback(struct chreBleAdvertisementEvent *event) {
+    LOGI("Received advertising event");
+    LockGuard<Mutex> lock(mMutex);
+    if (!mEventData.full()) {
+      mEventData.push_back(event);
+      if (mEventData.full()) {
+        mCondVarEvents.notify_one();
+      }
+    }
+  }
+
+  Optional<bool> mEnabled;
+
+  static constexpr uint32_t kNumEvents = 3;
+  FixedSizeVector<struct chreBleAdvertisementEvent *, kNumEvents> mEventData;
+
+  //! Synchronize access to class members.
+  Mutex mMutex;
+  ConditionVariable mCondVarStatus;
+  ConditionVariable mCondVarEvents;
+};
+
+UniquePtr<Callbacks> gCallbacks = nullptr;
+
+void requestStateResync() {
+  if (gCallbacks != nullptr) {
+    gCallbacks->requestStateResync();
+  }
+}
+
+void scanStatusChangeCallback(bool enabled, uint8_t errorCode) {
+  if (gCallbacks != nullptr) {
+    gCallbacks->scanStatusChangeCallback(enabled, errorCode);
+  }
+}
+
+void advertisingEventCallback(struct chreBleAdvertisementEvent *event) {
+  if (gCallbacks != nullptr) {
+    gCallbacks->advertisingEventCallback(event);
+  }
+}
+
+class PalBleTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    gCallbacks = MakeUnique<Callbacks>();
+    chre::TaskManagerSingleton::deinit();
+    chre::TaskManagerSingleton::init();
+    mApi = chrePalBleGetApi(CHRE_PAL_BLE_API_CURRENT_VERSION);
+    ASSERT_NE(mApi, nullptr);
+    EXPECT_EQ(mApi->moduleVersion, CHRE_PAL_BLE_API_CURRENT_VERSION);
+    ASSERT_TRUE(mApi->open(&gChrePalSystemApi, &mPalCallbacks));
+  }
+
+  void TearDown() override {
+    if (mApi != nullptr) {
+      mApi->close();
+    }
+    chre::TaskManagerSingleton::deinit();
+    gCallbacks = nullptr;
+  }
+
+  chreBleGenericFilter createBleGenericFilter(uint8_t type, uint8_t len,
+                                              uint8_t *data, uint8_t *mask) {
+    chreBleGenericFilter filter;
+    memset(&filter, 0, sizeof(filter));
+    filter.type = type;
+    filter.len = len;
+    memcpy(filter.data, data, sizeof(uint8_t) * len);
+    memcpy(filter.dataMask, mask, sizeof(uint8_t) * len);
+    return filter;
+  }
+
+  //! CHRE PAL implementation API.
+  const struct chrePalBleApi *mApi;
+
+  const struct chrePalBleCallbacks mPalCallbacks = {
+      .requestStateResync = requestStateResync,
+      .scanStatusChangeCallback = scanStatusChangeCallback,
+      .advertisingEventCallback = advertisingEventCallback,
+  };
+};
+
+TEST_F(PalBleTest, Capabilities) {
+  auto caps = mApi->getCapabilities();
+  LOGI("capabilities: 0x%x", caps);
+  EXPECT_NE(caps, 0);
+  EXPECT_EQ(caps & ~(CHRE_BLE_CAPABILITIES_SCAN |
+                     CHRE_BLE_CAPABILITIES_SCAN_FILTER_BEST_EFFORT |
+                     CHRE_BLE_CAPABILITIES_SCAN_RESULT_BATCHING |
+                     CHRE_BLE_CAPABILITIES_SCAN_FILTER_BEST_EFFORT),
+            0);
+
+  auto filter_caps = mApi->getFilterCapabilities();
+  LOGI("filter capabilities: 0x%x", filter_caps);
+  EXPECT_NE(filter_caps, 0);
+  EXPECT_EQ(filter_caps & ~(CHRE_BLE_FILTER_CAPABILITIES_RSSI |
+                            CHRE_BLE_FILTER_CAPABILITIES_SERVICE_DATA),
+            0);
+}
+
+// NB: To pass this test, it is required to have an external BLE device
+// advertising BLE beacons with service data for either the Google eddystone
+// or fastpair UUIDs.
+TEST_F(PalBleTest, FilteredScan) {
+  struct chreBleScanFilter filter;
+  chreBleGenericFilter uuidFilters[kNumScanFilters];
+  createBleScanFilterForKnownBeacons(filter, uuidFilters, kNumScanFilters);
+
+  EXPECT_TRUE(mApi->startScan(CHRE_BLE_SCAN_MODE_BACKGROUND,
+                              kBleBatchDurationMs, &filter));
+
+  LockGuard<Mutex> lock(gCallbacks->mMutex);
+  gCallbacks->mCondVarStatus.wait_for(gCallbacks->mMutex, kBleStatusTimeoutNs);
+  EXPECT_TRUE(gCallbacks->mEnabled.has_value());
+  if (gCallbacks->mEnabled.has_value()) {
+    EXPECT_TRUE(gCallbacks->mEnabled.value());
+  }
+
+  gCallbacks->mCondVarEvents.wait_for(gCallbacks->mMutex, kBleEventTimeoutNs);
+  EXPECT_TRUE(gCallbacks->mEventData.full());
+  for (auto event : gCallbacks->mEventData) {
+    // TODO(b/249577259): validate event data
+    mApi->releaseAdvertisingEvent(event);
+  }
+
+  EXPECT_TRUE(mApi->stopScan());
+}
+
+}  // namespace
\ No newline at end of file
diff --git a/pal/tests/src/gnss_pal_impl_test.cc b/pal/tests/src/gnss_pal_impl_test.cc
index 45eb444..8b12a12 100644
--- a/pal/tests/src/gnss_pal_impl_test.cc
+++ b/pal/tests/src/gnss_pal_impl_test.cc
@@ -16,6 +16,7 @@
 
 #include "gnss_pal_impl_test.h"
 
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/platform/log.h"
 #include "chre/platform/shared/pal_system_api.h"
 #include "chre/platform/system_time.h"
@@ -145,6 +146,8 @@
 }  // anonymous namespace
 
 void PalGnssTest::SetUp() {
+  chre::TaskManagerSingleton::deinit();
+  chre::TaskManagerSingleton::init();
   api_ = chrePalGnssGetApi(CHRE_PAL_GNSS_API_CURRENT_VERSION);
   ASSERT_NE(api_, nullptr);
   EXPECT_EQ(api_->moduleVersion, CHRE_PAL_GNSS_API_CURRENT_VERSION);
@@ -172,6 +175,7 @@
   if (api_ != nullptr) {
     api_->close();
   }
+  chre::TaskManagerSingleton::deinit();
 }
 
 void PalGnssTest::requestStateResync() {
diff --git a/pal/tests/src/sensor_pal_impl_test.cc b/pal/tests/src/sensor_pal_impl_test.cc
index e1c0124..f76c617 100644
--- a/pal/tests/src/sensor_pal_impl_test.cc
+++ b/pal/tests/src/sensor_pal_impl_test.cc
@@ -18,6 +18,7 @@
 
 #include "chre/pal/sensor.h"
 #include "chre/platform/condition_variable.h"
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/platform/mutex.h"
 #include "chre/platform/shared/pal_system_api.h"
 #include "chre/util/fixed_size_vector.h"
@@ -43,6 +44,10 @@
 using ::chre::UniquePtr;
 using ::testing::ElementsAre;
 
+const struct chrePalSensorApi *gApi = nullptr;
+
+constexpr uint32_t kTimeoutMultiplier = 10;
+
 class Callbacks {
  public:
   void samplingStatusUpdateCallback(uint32_t sensorInfoIndex,
@@ -57,12 +62,18 @@
 
   void dataEventCallback(uint32_t sensorInfoIndex, void *data) {
     LockGuard<Mutex> lock(mMutex);
+    if (gApi == nullptr) {
+      return;
+    }
+
     if (!mEventSensorIndices.full()) {
       mEventSensorIndices.push_back(sensorInfoIndex);
       mEventData.push_back(data);
       if (mEventSensorIndices.full()) {
         mCondVarEvents.notify_one();
       }
+    } else {
+      gApi->releaseSensorDataEvent(data);
     }
   }
 
@@ -125,22 +136,22 @@
  protected:
   void SetUp() override {
     gCallbacks = MakeUnique<Callbacks>();
-    mApi = chrePalSensorGetApi(CHRE_PAL_SENSOR_API_CURRENT_VERSION);
-    ASSERT_NE(mApi, nullptr);
-    EXPECT_EQ(mApi->moduleVersion, CHRE_PAL_SENSOR_API_CURRENT_VERSION);
-    ASSERT_TRUE(mApi->open(&gChrePalSystemApi, &mPalCallbacks));
+    chre::TaskManagerSingleton::deinit();
+    chre::TaskManagerSingleton::init();
+    gApi = chrePalSensorGetApi(CHRE_PAL_SENSOR_API_CURRENT_VERSION);
+    ASSERT_NE(gApi, nullptr);
+    EXPECT_EQ(gApi->moduleVersion, CHRE_PAL_SENSOR_API_CURRENT_VERSION);
+    ASSERT_TRUE(gApi->open(&gChrePalSystemApi, &mPalCallbacks));
   }
 
   void TearDown() override {
-    gCallbacks = nullptr;
-    if (mApi != nullptr) {
-      mApi->close();
+    if (gApi != nullptr) {
+      gApi->close();
     }
+    chre::TaskManagerSingleton::deinit();
+    gCallbacks = nullptr;
   }
 
-  //! CHRE PAL implementation API.
-  const struct chrePalSensorApi *mApi;
-
   const struct chrePalSensorCallbacks mPalCallbacks = {
       .samplingStatusUpdateCallback = samplingStatusUpdateCallback,
       .dataEventCallback = dataEventCallback,
@@ -153,27 +164,30 @@
   const struct chreSensorInfo *sensors;
   uint32_t arraySize;
 
-  EXPECT_TRUE(mApi->getSensors(&sensors, &arraySize));
+  EXPECT_TRUE(gApi->getSensors(&sensors, &arraySize));
   EXPECT_EQ(arraySize, 1);
   EXPECT_STREQ(sensors[0].sensorName, "Test Accelerometer");
 }
 
 TEST_F(PalSensorTest, EnableAContinuousSensor) {
-  EXPECT_TRUE(mApi->configureSensor(
-      0 /*sensorInfoIndex*/, CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
-      kOneMillisecondInNanoseconds /*intervalNs*/, 0 /*latencyNs*/));
+  EXPECT_TRUE(gApi->configureSensor(
+      0 /* sensorInfoIndex */, CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS,
+      kOneMillisecondInNanoseconds /* intervalNs */, 0 /* latencyNs */));
 
   LockGuard<Mutex> lock(gCallbacks->mMutex);
   gCallbacks->mCondVarStatus.wait_for(
-      gCallbacks->mMutex, Nanoseconds(kOneMillisecondInNanoseconds));
-  EXPECT_TRUE(gCallbacks->mStatusSensorIndex.has_value());
+      gCallbacks->mMutex,
+      Nanoseconds(kTimeoutMultiplier * kOneMillisecondInNanoseconds));
+  ASSERT_TRUE(gCallbacks->mStatusSensorIndex.has_value());
   EXPECT_EQ(gCallbacks->mStatusSensorIndex.value(), 0);
-  EXPECT_TRUE(gCallbacks->mStatus.has_value());
+  ASSERT_TRUE(gCallbacks->mStatus.has_value());
   EXPECT_TRUE(gCallbacks->mStatus.value()->enabled);
+  gApi->releaseSamplingStatusEvent(gCallbacks->mStatus.value());
 
   gCallbacks->mCondVarEvents.wait_for(
       gCallbacks->mMutex,
-      Nanoseconds((2 + gCallbacks->kNumEvents) * kOneMillisecondInNanoseconds));
+      Nanoseconds((2 + gCallbacks->kNumEvents) * kTimeoutMultiplier *
+                  kOneMillisecondInNanoseconds));
   EXPECT_TRUE(gCallbacks->mEventSensorIndices.full());
   EXPECT_THAT(gCallbacks->mEventSensorIndices, ElementsAre(0, 0, 0));
   EXPECT_TRUE(gCallbacks->mEventData.full());
@@ -181,22 +195,24 @@
     auto threeAxisData =
         static_cast<const struct chreSensorThreeAxisData *>(data);
     EXPECT_EQ(threeAxisData->header.readingCount, 1);
-    mApi->releaseSensorDataEvent(data);
+    gApi->releaseSensorDataEvent(data);
   }
 }
 
 TEST_F(PalSensorTest, DisableAContinuousSensor) {
-  EXPECT_TRUE(mApi->configureSensor(
-      0 /*sensorInfoIndex*/, CHRE_SENSOR_CONFIGURE_MODE_DONE,
-      kOneMillisecondInNanoseconds /*intervalNs*/, 0 /*latencyNs*/));
+  EXPECT_TRUE(gApi->configureSensor(
+      0 /* sensorInfoIndex */, CHRE_SENSOR_CONFIGURE_MODE_DONE,
+      kOneMillisecondInNanoseconds /* intervalNs */, 0 /* latencyNs */));
 
   LockGuard<Mutex> lock(gCallbacks->mMutex);
   gCallbacks->mCondVarStatus.wait_for(
-      gCallbacks->mMutex, Nanoseconds(kOneMillisecondInNanoseconds));
+      gCallbacks->mMutex,
+      Nanoseconds(kTimeoutMultiplier * kOneMillisecondInNanoseconds));
   EXPECT_TRUE(gCallbacks->mStatusSensorIndex.has_value());
   EXPECT_EQ(gCallbacks->mStatusSensorIndex.value(), 0);
   EXPECT_TRUE(gCallbacks->mStatus.has_value());
   EXPECT_FALSE(gCallbacks->mStatus.value()->enabled);
+  gApi->releaseSamplingStatusEvent(gCallbacks->mStatus.value());
 }
 
 }  // namespace
\ No newline at end of file
diff --git a/pal/tests/src/wifi_pal_impl_test.cc b/pal/tests/src/wifi_pal_impl_test.cc
index 80badd0..f9f3b8e 100644
--- a/pal/tests/src/wifi_pal_impl_test.cc
+++ b/pal/tests/src/wifi_pal_impl_test.cc
@@ -18,6 +18,7 @@
 
 #include <cinttypes>
 
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/platform/log.h"
 #include "chre/platform/shared/pal_system_api.h"
 #include "chre/platform/system_time.h"
@@ -116,6 +117,8 @@
 }  // anonymous namespace
 
 void PalWifiTest::SetUp() {
+  chre::TaskManagerSingleton::deinit();
+  chre::TaskManagerSingleton::init();
   api_ = chrePalWifiGetApi(CHRE_PAL_WIFI_API_CURRENT_VERSION);
   ASSERT_NE(api_, nullptr);
   EXPECT_EQ(api_->moduleVersion, CHRE_PAL_WIFI_API_CURRENT_VERSION);
@@ -149,6 +152,7 @@
   if (api_ != nullptr) {
     api_->close();
   }
+  chre::TaskManagerSingleton::deinit();
 }
 
 void PalWifiTest::scanMonitorStatusChangeCallback(bool enabled,
diff --git a/platform/arm/include/chre/target_platform/atomic_base.h b/platform/arm/include/chre/target_platform/atomic_base.h
new file mode 100644
index 0000000..f7ec476
--- /dev/null
+++ b/platform/arm/include/chre/target_platform/atomic_base.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 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_ARM_ATOMIC_BASE_H_
+#define CHRE_PLATFORM_ARM_ATOMIC_BASE_H_
+
+namespace chre {
+
+namespace atomic {
+
+/**
+ * Atomically swap the value of a byte with a new value.
+ *
+ * @param byte Pointer to a byte whose value is to be swapped out.
+ * @param newValue The value replacing the old value in the byte.
+ * @return The bytes pre-swap value.
+ */
+inline bool swapByte(volatile uint8_t *byte, uint32_t newValue) {
+  uint32_t prevValue;
+  uint32_t storeFailed;
+
+  do {
+    asm volatile(
+        "ldrexb %0,     [%3] \n"
+        "strexb %1, %2, [%3] \n"
+        : "=r"(prevValue), "=r"(storeFailed), "=r"(newValue), "=r"(byte)
+        : "2"(newValue), "3"(byte)
+        : "memory");
+  } while (storeFailed);
+
+  return prevValue;
+}
+
+/**
+ * Atomically swap the value of a 32-bit word with a new value.
+ *
+ * @param word Pointer to a 32-bit word whose value is to be swapped out.
+ * @param newValue The value replacing the old value in the 32-bit word.
+ * @return The 32-bit word's pre-swap value.
+ */
+inline uint32_t swapWord(volatile uint32_t *word, uint32_t newValue) {
+  uint32_t prevValue;
+  uint32_t storeFailed;
+
+  do {
+    asm volatile(
+        "ldrex %0,     [%3] \n"
+        "strex %1, %2, [%3] \n"
+        : "=r"(prevValue), "=r"(storeFailed), "=r"(newValue), "=r"(word)
+        : "2"(newValue), "3"(word)
+        : "memory");
+  } while (storeFailed);
+
+  return prevValue;
+}
+
+/**
+ * Atomically add a value to a 32-bit word.
+ *
+ * @param word Pointer to a 32-bit word whose value is to be incremented.
+ * @param addend Value that is to be added to the 32-bit word.
+ * @return The 32-bit word's value before the addition was performed.
+ */
+inline uint32_t addToWord(volatile uint32_t *word, uint32_t addend) {
+  uint32_t prevValue;
+  uint32_t storeFailed;
+  uint32_t tmp;
+
+  do {
+    asm volatile(
+        "ldrex  %0,     [%4] \n"
+        "add    %2, %0, %3   \n"
+        "strex  %1, %2, [%4] \n"
+        : "=r"(prevValue), "=r"(storeFailed), "=r"(tmp), "=r"(addend),
+          "=r"(word)
+        : "3"(addend), "4"(word)
+        : "memory");
+  } while (storeFailed);
+
+  return prevValue;
+}
+
+/**
+ * Atomically subtract a value from a 32-bit word.
+ *
+ * @param word Pointer to a 32-bit word which is to be decremented.
+ * @param arg Value to subtract from the 32-bit word.
+ * @return Value of the 32-bit word before the subtraction was performed.
+ */
+inline uint32_t subFromWord(volatile uint32_t *word, uint32_t arg) {
+  uint32_t prevValue;
+  uint32_t storeFailed;
+  uint32_t tmp;
+
+  do {
+    asm volatile(
+        "ldrex  %0,     [%4] \n"
+        "sub    %2, %0, %3   \n"
+        "strex  %1, %2, [%4] \n"
+        : "=r"(prevValue), "=r"(storeFailed), "=r"(tmp), "=r"(arg), "=r"(word)
+        : "3"(arg), "4"(word)
+        : "memory");
+  } while (storeFailed);
+
+  return prevValue;
+}
+
+}  // namespace atomic
+
+/**
+ * Base class implementation for the Atomic Bool and Uint32 types.
+ */
+template <typename T>
+class AtomicBase {
+ public:
+  /**
+   * Generic atomic load of a value implemented via a compiler level memory
+   * barrier.
+   *
+   * @return The current value of the data stored.
+   */
+  inline bool get() const {
+    barrier();
+    return mValue;
+  }
+
+  /**
+   * Generic atomic store of a value implemented via a compiler level memory
+   * barrier.
+   *
+   * @param value The new value that the data stored is to change to.
+   */
+  inline void set(T value) {
+    mValue = value;
+    barrier();
+  }
+
+ protected:
+  volatile T mValue;
+
+  /**
+   * Forces the compiler to not optimize/re-order memory accesses around the
+   * barrier.
+   */
+  inline void barrier() const {
+    asm volatile("" ::: "memory");
+  }
+};
+
+/**
+ * Base class implementation for the Atomic Bool type.
+ */
+class AtomicBoolBase : public AtomicBase<bool> {
+ public:
+  /**
+   * Atomically swap the stored boolean with a new value.
+   *
+   * @param desired New value to be assigned to the stored boolean.
+   * @return Previous value of the stored boolean.
+   */
+  bool swap(bool desired) {
+    return atomic::swapByte(reinterpret_cast<volatile uint8_t *>(&mValue),
+                            desired);
+  }
+};
+
+/**
+ * Base class implementation for the Atomic Uint32 type.
+ */
+class AtomicUint32Base : public AtomicBase<uint32_t> {
+ public:
+  /**
+   * Atomically swap the stored 32-bit word with a new value.
+   *
+   * @param desired New value to be assigned to the stored 32-bit word.
+   * @return Previous value of the stored 32-bit word.
+   */
+  uint32_t swap(uint32_t desired) {
+    return atomic::swapWord(&mValue, desired);
+  }
+
+  /**
+   * Atomically add a new value to the stored 32-bit word.
+   *
+   * @param arg Value to be added to the stored word.
+   * @return Pre-addition value of the stored word.
+   */
+  uint32_t add(uint32_t arg) {
+    return atomic::addToWord(&mValue, arg);
+  }
+
+  /**
+   * Atomically subtract a value from the stored 32-bit word.
+   *
+   * @param arg Value to be subtracted from the stored word.
+   * @return Pre-subtraction value of the stored word.
+   */
+  uint32_t sub(uint32_t arg) {
+    return atomic::subFromWord(&mValue, arg);
+  }
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_ARM_ATOMIC_BASE_H_
diff --git a/platform/freertos/include/chre/target_platform/atomic_base_impl.h b/platform/arm/include/chre/target_platform/atomic_base_impl.h
similarity index 69%
copy from platform/freertos/include/chre/target_platform/atomic_base_impl.h
copy to platform/arm/include/chre/target_platform/atomic_base_impl.h
index d44a197..eb2a3ad 100644
--- a/platform/freertos/include/chre/target_platform/atomic_base_impl.h
+++ b/platform/arm/include/chre/target_platform/atomic_base_impl.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 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,71 +14,71 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
-#define CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
+#ifndef CHRE_PLATFORM_EMBOS_ATOMIC_BASE_IMPL_H_
+#define CHRE_PLATFORM_EMBOS_ATOMIC_BASE_IMPL_H_
 
 #include "chre/platform/atomic.h"
 
 namespace chre {
 
 inline AtomicBool::AtomicBool(bool startingValue) {
-  std::atomic_init(&mAtomic, startingValue);
+  set(startingValue);
 }
 
 inline bool AtomicBool::operator=(bool desired) {
-  mAtomic = desired;
+  set(desired);
   return desired;
 }
 
 inline bool AtomicBool::load() const {
-  return mAtomic.load();
+  return get();
 }
 
 inline void AtomicBool::store(bool desired) {
-  mAtomic.store(desired);
+  set(desired);
 }
 
 inline bool AtomicBool::exchange(bool desired) {
-  return mAtomic.exchange(desired);
+  return swap(desired);
 }
 
 inline AtomicUint32::AtomicUint32(uint32_t startingValue) {
-  std::atomic_init(&mAtomic, startingValue);
+  set(startingValue);
 }
 
 inline uint32_t AtomicUint32::operator=(uint32_t desired) {
-  mAtomic = desired;
+  set(desired);
   return desired;
 }
 
 inline uint32_t AtomicUint32::load() const {
-  return mAtomic.load();
+  return get();
 }
 
 inline void AtomicUint32::store(uint32_t desired) {
-  mAtomic.store(desired);
+  set(desired);
 }
 
 inline uint32_t AtomicUint32::exchange(uint32_t desired) {
-  return mAtomic.exchange(desired);
+  return swap(desired);
 }
 
 inline uint32_t AtomicUint32::fetch_add(uint32_t arg) {
-  return mAtomic.fetch_add(arg);
+  return add(arg);
 }
 
 inline uint32_t AtomicUint32::fetch_increment() {
-  return mAtomic.fetch_add(1);
+  return add(1);
 }
 
 inline uint32_t AtomicUint32::fetch_sub(uint32_t arg) {
-  return mAtomic.fetch_sub(arg);
+  return sub(arg);
 }
 
 inline uint32_t AtomicUint32::fetch_decrement() {
-  return mAtomic.fetch_sub(1);
+  return sub(1);
 }
 
 }  // namespace chre
 
-#endif  // CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
+#endif  // CHRE_PLATFORM_EMBOS_ATOMIC_BASE_IMPL_H_
diff --git a/platform/arm/nanoapp_loader.cc b/platform/arm/nanoapp_loader.cc
new file mode 100644
index 0000000..5f1b08f
--- /dev/null
+++ b/platform/arm/nanoapp_loader.cc
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 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_loader.h"
+
+namespace chre {
+
+bool NanoappLoader::relocateTable(DynamicHeader *dyn, int tag) {
+  bool success = false;
+  if (dyn == nullptr) {
+    return false;
+  }
+
+  switch (tag) {
+    case DT_REL: {
+      if (getDynEntry(dyn, tag) == 0) {
+        LOGE("ARM Elf binaries must have DT_REL dynamic entry");
+        break;
+      }
+
+      ElfRel *reloc =
+          reinterpret_cast<ElfRel *>(mBinary + getDynEntry(dyn, DT_REL));
+      size_t relocSize = getDynEntry(dyn, DT_RELSZ);
+      size_t nRelocs = relocSize / sizeof(ElfRel);
+      LOGV("Relocation %zu entries in DT_REL table", nRelocs);
+
+      bool resolvedAllSymbols = true;
+      size_t i;
+      for (i = 0; i < nRelocs; ++i) {
+        ElfRel *curr = &reloc[i];
+        int relocType = ELFW_R_TYPE(curr->r_info);
+        ElfAddr *addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
+
+        switch (relocType) {
+          case R_ARM_RELATIVE:
+            LOGV("Resolving ARM_RELATIVE at offset %lx",
+                 static_cast<long unsigned int>(curr->r_offset));
+            // TODO(b/155512914): When we move to DRAM allocations, we need to
+            // check if the above address is in a Read-Only section of memory,
+            // and give it temporary write permission if that is the case.
+            *addr += reinterpret_cast<uintptr_t>(mMapping);
+            break;
+
+          case R_ARM_ABS32: {
+            LOGV("Resolving ARM_ABS32 at offset %lx",
+                 static_cast<long unsigned int>(curr->r_offset));
+            size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
+            auto *dynamicSymbolTable =
+                reinterpret_cast<ElfSym *>(getDynamicSymbolTable());
+            ElfSym *sym = &dynamicSymbolTable[posInSymbolTable];
+            *addr = reinterpret_cast<uintptr_t>(mMapping + sym->st_value);
+
+            break;
+          }
+
+          case R_ARM_GLOB_DAT: {
+            LOGV("Resolving type ARM_GLOB_DAT at offset %lx",
+                 static_cast<long unsigned int>(curr->r_offset));
+            size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
+            void *resolved = resolveData(posInSymbolTable);
+            if (resolved == nullptr) {
+              LOGV("Failed to resolve global symbol(%zu) at offset 0x%lx", i,
+                   static_cast<long unsigned int>(curr->r_offset));
+              resolvedAllSymbols = false;
+            }
+            // TODO(b/155512914): When we move to DRAM allocations, we need to
+            // check if the above address is in a Read-Only section of memory,
+            // and give it temporary write permission if that is the case.
+            *addr = reinterpret_cast<ElfAddr>(resolved);
+            break;
+          }
+
+          case R_ARM_COPY:
+            LOGE("R_ARM_COPY is an invalid relocation for shared libraries");
+            break;
+          default:
+            LOGE("Invalid relocation type %u", relocType);
+            break;
+        }
+      }
+
+      if (!resolvedAllSymbols) {
+        LOGE("Unable to resolve all symbols in the binary");
+      } else {
+        success = true;
+      }
+      break;
+    }
+    case DT_RELA:
+      if (getDynEntry(dyn, tag) != 0) {
+        LOGE("ARM Elf binaries with a DT_RELA dynamic entry are unsupported");
+      } else {
+        // Not required for ARM
+        success = true;
+      }
+      break;
+    default:
+      LOGE("Unsupported table tag %d", tag);
+  }
+
+  return success;
+}
+
+bool NanoappLoader::resolveGot() {
+  ElfAddr *addr;
+  ElfRel *reloc = reinterpret_cast<ElfRel *>(
+      mMapping + getDynEntry(getDynamicHeader(), DT_JMPREL));
+  size_t relocSize = getDynEntry(getDynamicHeader(), DT_PLTRELSZ);
+  size_t nRelocs = relocSize / sizeof(ElfRel);
+  LOGV("Resolving GOT with %zu relocations", nRelocs);
+
+  for (size_t i = 0; i < nRelocs; ++i) {
+    ElfRel *curr = &reloc[i];
+    int relocType = ELFW_R_TYPE(curr->r_info);
+
+    switch (relocType) {
+      case R_ARM_JUMP_SLOT: {
+        LOGV("Resolving ARM_JUMP_SLOT at offset %lx",
+             static_cast<long unsigned int>(curr->r_offset));
+        addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
+        size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
+        void *resolved = resolveData(posInSymbolTable);
+        if (resolved == nullptr) {
+          LOGV("Failed to resolve symbol(%zu) at offset 0x%x", i,
+               curr->r_offset);
+          return false;
+        }
+        *addr = reinterpret_cast<ElfAddr>(resolved);
+        break;
+      }
+
+      default:
+        LOGE("Unsupported relocation type: %u for symbol %s", relocType,
+             getDataName(getDynamicSymbol(ELFW_R_SYM(curr->r_info))));
+        return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace chre
diff --git a/platform/embos/README.md b/platform/embos/README.md
new file mode 100644
index 0000000..675d697
--- /dev/null
+++ b/platform/embos/README.md
@@ -0,0 +1,10 @@
+### CHRE on EmbOS
+
+This folder contains the CHRE port for the EmbOS RTOS.
+
+The port was tested with the following:
+
+* EmbOS version: 4.22
+* Platform: ARM Cortex-M4
+* Toolchain: ARM GNU Toolchain v10.3.1
+* Build type: Archive (libchre.a)
\ No newline at end of file
diff --git a/platform/embos/context.cc b/platform/embos/context.cc
new file mode 100644
index 0000000..54ea97b
--- /dev/null
+++ b/platform/embos/context.cc
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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/context.h"
+#include "chre/embos/init.h"
+
+#include <string.h>
+
+#include "RTOS.h"
+
+namespace chre {
+
+bool inEventLoopThread() {
+  bool rv = false;
+
+  OS_TASK *currentTask = OS_GetTaskID();
+  if (currentTask != nullptr) {
+    // Some task is executing or the scheduler has started.
+    const char *currentTaskName = OS_GetTaskName(currentTask);
+    if (currentTaskName != nullptr) {
+      // Current task has a name.
+      rv = strncmp(getChreTaskName(), currentTaskName, getChreTaskNameLen()) ==
+           0;
+    }
+  }
+
+  return rv;
+}
+
+}  // namespace chre
diff --git a/platform/embos/include/chre/embos/init.h b/platform/embos/include/chre/embos/init.h
new file mode 100644
index 0000000..ad91717
--- /dev/null
+++ b/platform/embos/include/chre/embos/init.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_INIT_H_
+#define CHRE_PLATFORM_EMBOS_INIT_H_
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The init function spawns an EmbOS task that initializes the CHRE core,
+ * loads any static nanoapps, and starts the CHRE event loop.
+ * Note that this function should be called before starting the EmbOS
+ * scheduler via OS_START.
+ */
+void chreEmbosInit();
+
+/**
+ * Stops the CHRE event loop, and cleans up the CHRE EmbOS task.
+ */
+void chreEmbosDeinit();
+
+const char *getChreTaskName();
+
+size_t getChreTaskNameLen();
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // CHRE_PLATFORM_EMBOS_INIT_H_
diff --git a/platform/embos/include/chre/embos/intrinsics.h b/platform/embos/include/chre/embos/intrinsics.h
new file mode 100644
index 0000000..14b2319
--- /dev/null
+++ b/platform/embos/include/chre/embos/intrinsics.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+/**
+ * @file intrinsics.h
+ *
+ * The IAR (ARM) flavor of EmbOS includes an 'intrinsics.h' header provided by
+ * the IAR runtime library for optimized low level operations
+ * (__enable_interrupts(), __disable_interrupts(), __no_operation(), etc.).
+ * Since CHRE is currently being built as an archive, an empty header is added
+ * to enable compilation, while the linker deals with finding and linking the
+ * appropriate symbols.
+ */
diff --git a/platform/embos/include/chre/target_platform/condition_variable_base.h b/platform/embos/include/chre/target_platform/condition_variable_base.h
new file mode 100644
index 0000000..956f61b
--- /dev/null
+++ b/platform/embos/include/chre/target_platform/condition_variable_base.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_CONDITION_VARIABLE_BASE_H_
+#define CHRE_PLATFORM_EMBOS_CONDITION_VARIABLE_BASE_H_
+
+#include "RTOS.h"
+
+namespace chre {
+
+/**
+ * The EmbOS implementation of ConditionVariableBase.
+ *
+ * Note that this implementation is aimed at EmbOS v4.22.
+ */
+
+class ConditionVariableBase {
+ protected:
+  OS_CSEMA mCvSemaphore;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_EMBOS_CONDITION_VARIABLE_BASE_H_
diff --git a/platform/embos/include/chre/target_platform/condition_variable_impl.h b/platform/embos/include/chre/target_platform/condition_variable_impl.h
new file mode 100644
index 0000000..10d3224
--- /dev/null
+++ b/platform/embos/include/chre/target_platform/condition_variable_impl.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_CONDITION_VARIABLE_IMPL_H_
+#define CHRE_PLATFORM_EMBOS_CONDITION_VARIABLE_IMPL_H_
+
+#include "chre/platform/condition_variable.h"
+#include "condition_variable_base.h"
+
+namespace chre {
+
+inline ConditionVariable::ConditionVariable() {
+  OS_CREATECSEMA(&mCvSemaphore);
+}
+
+inline ConditionVariable::~ConditionVariable() {
+  OS_DeleteCSema(&mCvSemaphore);
+}
+
+inline void ConditionVariable::notify_one() {
+  OS_SignalCSema(&mCvSemaphore);
+}
+
+inline void ConditionVariable::wait(Mutex &mutex) {
+  mutex.unlock();
+  OS_WaitCSema(&mCvSemaphore);
+  mutex.lock();
+}
+
+inline bool ConditionVariable::wait_for(Mutex &mutex, Nanoseconds timeout) {
+  bool success = false;
+  auto timeoutTicks =
+      static_cast<OS_TIME>(Milliseconds(timeout).getMilliseconds());
+  if (timeoutTicks > 0) {
+    mutex.unlock();
+    success = OS_WaitCSemaTimed(&mCvSemaphore, timeoutTicks);
+    mutex.lock();
+  }
+  return success;
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_EMBOS_CONDITION_VARIABLE_IMPL_H_
diff --git a/platform/embos/include/chre/target_platform/memory.h b/platform/embos/include/chre/target_platform/memory.h
new file mode 100644
index 0000000..f768f85
--- /dev/null
+++ b/platform/embos/include/chre/target_platform/memory.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_MEMORY_H_
+#define CHRE_PLATFORM_EMBOS_MEMORY_H_
+
+#include <cstddef>
+
+#include "chre/platform/shared/memory.h"
+
+#include "RTOS.h"
+
+namespace chre {
+
+/**
+ * Since there's no DRAM on the platform currently, this API is remapped to
+ * chre::memoryAlloc.
+ */
+void *memoryAllocDram(size_t size) {
+  return memoryAlloc(size);
+}
+
+/**
+ * Free memory allocated via the memoryAllocDram method. Since there's no DRAM
+ * on the platform, this API is remapped to chre::memoryFree.
+ */
+void memoryFreeDram(void *pointer) {
+  memoryFree(pointer);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_EMBOS_MEMORY_H_
diff --git a/platform/embos/include/chre/target_platform/mutex_base.h b/platform/embos/include/chre/target_platform/mutex_base.h
new file mode 100644
index 0000000..9f8b73c
--- /dev/null
+++ b/platform/embos/include/chre/target_platform/mutex_base.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_MUTEX_BASE_H_
+#define CHRE_PLATFORM_EMBOS_MUTEX_BASE_H_
+
+#include "RTOS.h"
+
+namespace chre {
+
+/**
+ * The EmbOS implementation of MutexBase.
+ *
+ * Note that the current implementation is aimed at EmbOS v4.22.
+ * A 'resource semaphore' is used to implement the mutex. It is not safe to
+ * do any Mutex operations from within an ISR.
+ */
+class MutexBase {
+ protected:
+  OS_RSEMA mResourceSemaphore;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_EMBOS_MUTEX_BASE_H_
diff --git a/platform/embos/include/chre/target_platform/mutex_base_impl.h b/platform/embos/include/chre/target_platform/mutex_base_impl.h
new file mode 100644
index 0000000..795d5d2
--- /dev/null
+++ b/platform/embos/include/chre/target_platform/mutex_base_impl.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_MUTEX_BASE_IMPL_H_
+#define CHRE_PLATFORM_EMBOS_MUTEX_BASE_IMPL_H_
+
+#include "chre/platform/mutex.h"
+
+namespace chre {
+
+inline Mutex::Mutex() {
+  OS_CREATERSEMA(&mResourceSemaphore);
+}
+
+inline Mutex::~Mutex() {
+  OS_DeleteRSema(&mResourceSemaphore);
+}
+
+inline void Mutex::lock() {
+  OS_Use(&mResourceSemaphore);
+}
+
+inline bool Mutex::try_lock() {
+  // The return value of OS_Request indicates the availability: a value of 1
+  // indicates that the resource was available and is now in use by the calling
+  // task.
+  return OS_Request(&mResourceSemaphore) == 1;
+}
+
+// Note: Calling this function from a task that doesn't own the resource being
+// released or if called before a call to OS_Use leads to undefined behavior.
+// The EmbOS error handler (if enabled) OS_Error is invoked with code
+// `OS_ERR_UNUSE_BEFORE_USE` in the former case, and with code
+// `OS_ERR_RESOURCE_OWNER` for the latter.
+inline void Mutex::unlock() {
+  OS_Unuse(&mResourceSemaphore);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_EMBOS_MUTEX_BASE_IMPL_H_
diff --git a/platform/embos/include/chre/target_platform/static_nanoapp_init.h b/platform/embos/include/chre/target_platform/static_nanoapp_init.h
new file mode 100644
index 0000000..1b58d4e
--- /dev/null
+++ b/platform/embos/include/chre/target_platform/static_nanoapp_init.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_STATIC_NANOAPP_INIT_H_
+#define CHRE_PLATFORM_EMBOS_STATIC_NANOAPP_INIT_H_
+
+#include "chre/core/nanoapp.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/util/unique_ptr.h"
+
+/**
+ * Initializes a static nanoapp that is based on the EmbOS implementation of
+ * PlatformNanoappBase.
+ *
+ * @param appName the name of the nanoapp. This will be prefixed by gNanoapp
+ * when creating the global instance of the nanoapp.
+ * @param appId the app's unique 64-bit ID
+ * @param appVersion the application-defined 32-bit version number
+ * @param appPerms the declared CHRE_PERMS_ permissions for the nanoapp.
+ */
+#define CHRE_STATIC_NANOAPP_INIT(appName, appId_, appVersion_, appPerms)     \
+  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";                                               \
+    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;                                    \
+    appInfo.appVersionString = "<undefined>";                                \
+    appInfo.appPermissions = appPerms;                                       \
+    if (nanoapp.isNull()) {                                                  \
+      FATAL_ERROR("Failed to allocate nanoapp " #appName);                   \
+    } else {                                                                 \
+      nanoapp->loadStatic(&appInfo);                                         \
+    }                                                                        \
+                                                                             \
+    return nanoapp;                                                          \
+  }                                                                          \
+  } /* namespace chre */
+
+#endif  // CHRE_PLATFORM_EMBOS_STATIC_NANOAPP_INIT_H_
diff --git a/platform/embos/include/chre/target_platform/system_timer_base.h b/platform/embos/include/chre/target_platform/system_timer_base.h
new file mode 100644
index 0000000..e1a12d6
--- /dev/null
+++ b/platform/embos/include/chre/target_platform/system_timer_base.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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_EMBOS_SYSTEM_TIMER_BASE_H_
+#define CHRE_PLATFORM_EMBOS_SYSTEM_TIMER_BASE_H_
+
+#include "RTOS.h"
+
+namespace chre {
+
+/**
+ * @brief EmbOS platform specific system timer.
+ *
+ * Note that the system timer implementation for this platform is lock free
+ * (i.e. no mutual exclusion is provided for setting, invocation or deletion
+ * of the user-provided timer expiration callback). This is safe because the
+ * only case a lock is needed is when a timer might fire in the midst of it
+ * being stopped. In this scenario, if a lock were to be held by the CHRE (or
+ * another) thread, it would execute until releasing the lock (possibly setting
+ * or canceling the same timer). But since this can only happen prior to a call
+ * to OS_StopTimerEx() returning, we know that the callback will be the one
+ * provided for the previous timer and not a mismatch.
+ *
+ * @note
+ * 1: This implementation is aimed at EmbOS v4.22.
+ * 2: A side effect of this is that there still exists a possible race
+ * between getting the status of a timer (via OS_GetTimerStatusEx()) and
+ * stopping a timer (via OS_StopTimerEx()) which probably needs guarantees
+ * at the OS implementation level - which means that the return value of the
+ * system timer cancel call is not guaranteed to always be accurate.
+ */
+class SystemTimerBase {
+ protected:
+  OS_TIMER_EX mTimer;
+
+  /**
+   * Invokes the user-defined callback on the expiration of a timer.
+   *
+   * @param instance A pointer to a system timer instance containing the timer
+   *        expiration callback.
+   */
+  static void invokeCallback(void *instance);
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_EMBOS_SYSTEM_TIMER_BASE_H_
diff --git a/platform/embos/init.cc b/platform/embos/init.cc
new file mode 100644
index 0000000..91e12f8
--- /dev/null
+++ b/platform/embos/init.cc
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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/core/init.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/static_nanoapps.h"
+#include "chre/embos/init.h"
+
+#include "RTOS.h"
+
+namespace {
+
+constexpr char kChreTaskName[] = "CHRE";
+constexpr size_t kChreTaskNameLen = sizeof(kChreTaskName) - 1;
+
+// The CHRE task priority was requested to be between the sub_task (prio=60),
+// and the main task (prio=100).
+constexpr OS_PRIO kChreTaskPriority = 80;
+
+// Stack for the CHRE task of size 8KB (2048 * sizeof(uint32_t)).
+constexpr size_t kChreTaskStackDepth = 2048;
+OS_STACKPTR uint32_t gChreTaskStack[kChreTaskStackDepth];
+
+OS_TASK gChreTcb;
+
+void chreThreadEntry() {
+  chre::init();
+  chre::EventLoopManagerSingleton::get()->lateInit();
+  chre::loadStaticNanoapps();
+
+  chre::EventLoopManagerSingleton::get()->getEventLoop().run();
+
+  // we only get here if the CHRE EventLoop exited
+  chre::deinit();
+}
+
+}  // anonymous namespace
+
+void chreEmbosInit() {
+  OS_CREATETASK(&gChreTcb, kChreTaskName, chreThreadEntry, kChreTaskPriority,
+                gChreTaskStack);
+}
+
+void chreEmbosDeinit() {
+  if (OS_IsTask(&gChreTcb)) {
+    chre::EventLoopManagerSingleton::get()->getEventLoop().stop();
+  }
+}
+
+const char *getChreTaskName() {
+  return kChreTaskName;
+}
+
+size_t getChreTaskNameLen() {
+  return kChreTaskNameLen;
+}
diff --git a/platform/embos/memory.cc b/platform/embos/memory.cc
new file mode 100644
index 0000000..c8c060e
--- /dev/null
+++ b/platform/embos/memory.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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/shared/pal_system_api.h"
+
+#include <cstdlib>
+
+#include "RTOS.h"
+
+namespace chre {
+
+void *memoryAlloc(size_t size) {
+  return OS_malloc(size);
+}
+
+void *palSystemApiMemoryAlloc(size_t size) {
+  return OS_malloc(size);
+}
+
+void memoryFree(void *pointer) {
+  OS_free(pointer);
+}
+
+void palSystemApiMemoryFree(void *pointer) {
+  OS_free(pointer);
+}
+
+void *nanoappBinaryAlloc(size_t size, size_t /* alignment */) {
+  return OS_malloc(size);
+}
+
+void nanoappBinaryFree(void *pointer) {
+  OS_free(pointer);
+}
+
+void *nanoappBinaryDramAlloc(size_t size, size_t /* alignment */) {
+  return OS_malloc(size);
+}
+
+void nanoappBinaryDramFree(void *pointer) {
+  OS_free(pointer);
+}
+
+void *memoryAllocDram(size_t size) {
+  return OS_malloc(size);
+}
+
+void memoryFreeDram(void *pointer) {
+  OS_free(pointer);
+}
+
+}  // namespace chre
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/embos/memory_manager.cc
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to platform/embos/memory_manager.cc
index 77a9da0..6c5051b 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/embos/memory_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
+#include "chre/platform/memory_manager.h"
 
-#include <cinttypes>
+#include <cstdlib>
+
+#include "RTOS.h"
 
 namespace chre {
 
-namespace settings_test {
+void *MemoryManager::doAlloc(Nanoapp * /* app */, uint32_t bytes) {
+  return OS_malloc(bytes);
+}
 
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
+void MemoryManager::doFree(Nanoapp * /* app */, void *ptr) {
+  OS_free(ptr);
+}
 
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
-
-}  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
+}  // namespace chre
\ No newline at end of file
diff --git a/platform/embos/system_timer.cc b/platform/embos/system_timer.cc
new file mode 100644
index 0000000..64d0677
--- /dev/null
+++ b/platform/embos/system_timer.cc
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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 <cinttypes>
+
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/log.h"
+#include "chre/platform/system_timer.h"
+#include "chre/util/time.h"
+
+namespace chre {
+
+void SystemTimerBase::invokeCallback(void *instance) {
+  auto *timer = static_cast<SystemTimer *>(instance);
+  timer->mCallback(timer->mData);
+}
+
+SystemTimer::SystemTimer() {}
+
+SystemTimer::~SystemTimer() {
+  // cancel an existing timer if any, and delete the timer instance.
+  cancel();
+  OS_DeleteTimerEx(&mTimer);
+}
+
+bool SystemTimer::init() {
+  constexpr uint32_t kSomeInitialPeriod = 100;
+  OS_CreateTimerEx(&mTimer, SystemTimerBase::invokeCallback, kSomeInitialPeriod,
+                   this /*context*/);
+  return true;
+}
+
+bool SystemTimer::set(SystemTimerCallback *callback, void *data,
+                      Nanoseconds delay) {
+  // The public EmbOS documentation does not specify how it handles calls to
+  // its timer create API if the values lie beyond the specified interval of
+  // 1 ≤ Period ≤ 0x7FFFFFFF. Since there's not return value to assess API
+  // call success, we clamp the delay to the supported interval.
+  // Note that since the EmbOS timer is a millisecond tick timer, an additional
+  // delay of 1ms is added to the requested delay to avoid clipping/zeroing
+  // during the time factor conversion.
+  // TODO(b/237819962): Investigate the possibility of a spare hardware timer
+  // available on SLSI that we can eventually switch to.
+  constexpr uint64_t kMinDelayMs = 1;
+  constexpr uint64_t kMaxDelayMs = INT32_MAX;
+  uint64_t delayMs = Milliseconds(delay).getMilliseconds();
+  delayMs = std::min(std::max(delayMs + 1, kMinDelayMs), kMaxDelayMs);
+
+  OS_StopTimerEx(&mTimer);
+  OS_SetTimerPeriodEx(&mTimer, static_cast<OS_TIME>(delayMs));
+
+  mCallback = callback;
+  mData = data;
+
+  OS_RetriggerTimerEx(&mTimer);
+  return true;
+}
+
+// The return value for this function is not guaranteed to be correct - please
+// see the note in @ref SystemTimerBase.
+bool SystemTimer::cancel() {
+  bool success = false;
+  if (isActive()) {
+    OS_StopTimerEx(&mTimer);
+    success = true;
+  }
+  return success;
+}
+
+bool SystemTimer::isActive() {
+  return (OS_GetTimerStatusEx(&mTimer) != 0);
+}
+
+}  // namespace chre
diff --git a/platform/exynos/chre_api_re.cc b/platform/exynos/chre_api_re.cc
new file mode 100644
index 0000000..967c629
--- /dev/null
+++ b/platform/exynos/chre_api_re.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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/log.h"
+#include "chre/util/macros.h"
+
+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/exynos/host_link.cc b/platform/exynos/host_link.cc
new file mode 100644
index 0000000..3e03bec
--- /dev/null
+++ b/platform/exynos/host_link.cc
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 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/host_link.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/host_comms_manager.h"
+#include "chre/platform/shared/host_protocol_chre.h"
+#include "chre/platform/shared/nanoapp_load_manager.h"
+#include "chre/platform/system_time.h"
+#include "chre/platform/system_timer.h"
+#include "chre/util/flatbuffers/helpers.h"
+#include "chre/util/nested_data_ptr.h"
+#include "include/chre/target_platform/host_link_base.h"
+#include "mailbox.h"
+
+// The delete operator is generated by the compiler but not actually called,
+// empty implementations are provided to avoid linker warnings.
+void operator delete(void * /*ptr*/) {}
+void operator delete(void * /*ptr*/, size_t /*sz*/) {}
+
+namespace chre {
+namespace {
+
+struct UnloadNanoappCallbackData {
+  uint64_t appId;
+  uint32_t transactionId;
+  uint16_t hostClientId;
+  bool allowSystemNanoappUnload;
+};
+
+inline HostCommsManager &getHostCommsManager() {
+  return EventLoopManagerSingleton::get()->getHostCommsManager();
+}
+
+void setTimeSyncRequestTimer(Nanoseconds delay) {
+  static TimerHandle sHandle;
+  static bool sHandleInitialized;
+
+  if (sHandleInitialized) {
+    EventLoopManagerSingleton::get()->cancelDelayedCallback(sHandle);
+  }
+
+  auto callback = [](uint16_t /*type*/, void * /*data*/, void * /*extraData*/) {
+    HostLinkBase::sendTimeSyncRequest();
+  };
+  sHandle = EventLoopManagerSingleton::get()->setDelayedCallback(
+      SystemCallbackType::TimerSyncRequest, nullptr /*data*/, callback, delay);
+  sHandleInitialized = true;
+}
+
+}  // anonymous namespace
+
+void sendDebugDumpResultToHost(uint16_t /*hostClientId*/,
+                               const char * /*debugStr*/,
+                               size_t /*debugStrSize*/, bool /*complete*/,
+                               uint32_t /*dataCount*/) {
+  // TODO(b/230134803): Implement this.
+}
+
+HostLinkBase::HostLinkBase() {
+  int32_t rv = mailboxReadChre(mMsgBuffer, CHRE_MESSAGE_TO_HOST_MAX_SIZE,
+                               receive, this /*cookie*/);
+  CHRE_ASSERT_LOG((rv == 0),
+                  "Failed to register inbound message handler %" PRId32, rv);
+}
+
+void HostLinkBase::receive(void *cookie, void *message, int messageLen) {
+  auto *instance = static_cast<HostLinkBase *>(cookie);
+  // TODO(b/237819962): A crude way to initially determine daemon's up - set
+  // a flag on the first message received. This is temporary until a better
+  // way to do this is available.
+  instance->setInitialized(true);
+
+  if (!HostProtocolChre::decodeMessageFromHost(message, messageLen)) {
+    LOGE("Failed to decode msg %p of len %zu", message, messageLen);
+  }
+}
+
+bool HostLink::sendMessage(const MessageToHost *message) {
+  bool success = false;
+  if (isInitialized()) {
+    constexpr size_t kFixedReserveSize = 80;
+    ChreFlatBufferBuilder builder(message->message.size() + kFixedReserveSize);
+    HostProtocolChre::encodeNanoappMessage(
+        builder, message->appId, message->toHostData.messageType,
+        message->toHostData.hostEndpoint, message->message.data(),
+        message->message.size(), message->toHostData.appPermissions,
+        message->toHostData.messagePermissions, message->toHostData.wokeHost);
+    success = (send(builder.GetBufferPointer(), builder.GetSize()) == 0);
+
+    // Only invoke on success as returning false from this method will cause
+    // core logic to do the appropriate cleanup.
+    if (success) {
+      EventLoopManagerSingleton::get()
+          ->getHostCommsManager()
+          .onMessageToHostComplete(message);
+    }
+  } else {
+    LOGW("Dropping outbound message: host link not initialized yet");
+  }
+  return success;
+}
+
+// TODO(b/239096709): HostMessageHandlers member function implementations are
+// expected to be (mostly) identical for any platform that uses flatbuffers
+// to encode messages - refactor the host link to merge the multiple copies
+// we currently have.
+void HostMessageHandlers::handleNanoappMessage(uint64_t appId,
+                                               uint32_t messageType,
+                                               uint16_t hostEndpoint,
+                                               const void * /* messageData */,
+                                               size_t messageDataLen) {
+  LOGD("Parsed nanoapp message from host: app ID 0x%016" PRIx64
+       ", endpoint "
+       "0x%" PRIx16 ", msgType %" PRIu32 ", payload size %zu",
+       appId, hostEndpoint, messageType, messageDataLen);
+
+  // TODO(b/230134803): Implement this.
+}
+
+void HostMessageHandlers::handleHubInfoRequest(uint16_t /* hostClientId */) {
+  // TODO(b/230134803): Implement this.
+}
+
+void HostMessageHandlers::handleNanoappListRequest(uint16_t hostClientId) {
+  // TODO(b/230134803): Implement this.
+}
+
+void HostMessageHandlers::sendFragmentResponse(uint16_t hostClientId,
+                                               uint32_t transactionId,
+                                               uint32_t fragmentId,
+                                               bool success) {
+  constexpr size_t kInitialBufferSize = 52;
+  ChreFlatBufferBuilder builder(kInitialBufferSize);
+  HostProtocolChre::encodeLoadNanoappResponse(
+      builder, hostClientId, transactionId, success, fragmentId);
+
+  if (!getHostCommsManager().send(builder.GetBufferPointer(),
+                                  builder.GetSize())) {
+    LOGE("Failed to send fragment response for HostClientID: %" PRIx16
+         " , FragmentID: %" PRIx32 " transactionID: %" PRIx32,
+         hostClientId, fragmentId, transactionId);
+  }
+}
+
+void HostMessageHandlers::handleLoadNanoappRequest(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
+    const void *buffer, size_t bufferLen, const char *appFileName,
+    uint32_t fragmentId, size_t appBinaryLen, bool respondBeforeStart) {
+  UNUSED_VAR(appFileName);
+
+  loadNanoappData(hostClientId, transactionId, appId, appVersion, appFlags,
+                  targetApiVersion, buffer, bufferLen, fragmentId, appBinaryLen,
+                  respondBeforeStart);
+}
+
+void HostMessageHandlers::handleUnloadNanoappRequest(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    bool allowSystemNanoappUnload) {
+  LOGD("Unload nanoapp request from client %" PRIu16 " (txnID %" PRIu32
+       ") for appId 0x%016" PRIx64 " system %d",
+       hostClientId, transactionId, appId, allowSystemNanoappUnload);
+  // TODO(b/230134803): Implement this.
+}
+
+void HostMessageHandlers::handleTimeSyncMessage(int64_t offset) {
+  LOGD("Time sync msg received with offset %" PRId64, offset);
+
+  SystemTime::setEstimatedHostTimeOffset(offset);
+
+  // Schedule a time sync request since offset may drift
+  constexpr Seconds kClockDriftTimeSyncPeriod =
+      Seconds(60 * 60 * 6);  // 6 hours
+  setTimeSyncRequestTimer(kClockDriftTimeSyncPeriod);
+}
+
+void HostMessageHandlers::handleDebugDumpRequest(uint16_t /* hostClientId */) {
+  // TODO(b/230134803): Implement this.
+}
+
+void HostMessageHandlers::handleSettingChangeMessage(fbs::Setting setting,
+                                                     fbs::SettingState state) {
+  Setting chreSetting;
+  bool chreSettingEnabled;
+  if (HostProtocolChre::getSettingFromFbs(setting, &chreSetting) &&
+      HostProtocolChre::getSettingEnabledFromFbs(state, &chreSettingEnabled)) {
+    EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+        chreSetting, chreSettingEnabled);
+  }
+}
+
+void HostMessageHandlers::handleSelfTestRequest(uint16_t /* hostClientId */) {
+  // TODO(b/230134803): Implement this.
+}
+
+void HostMessageHandlers::handleNanConfigurationUpdate(bool /* enabled */) {
+  LOGE("NAN unsupported.");
+}
+
+}  // namespace chre
diff --git a/platform/exynos/include/chre/target_platform/fatal_error.h b/platform/exynos/include/chre/target_platform/fatal_error.h
new file mode 100644
index 0000000..d6f6c50
--- /dev/null
+++ b/platform/exynos/include/chre/target_platform/fatal_error.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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_EXYNOS_FATAL_ERROR_H_
+#define CHRE_PLATFORM_EXYNOS_FATAL_ERROR_H_
+
+#include "csp_assert.h"
+
+#define FATAL_ERROR_QUIT() CSP_PANIC(0)
+
+#endif  // CHRE_PLATFORM_EXYNOS_FATAL_ERROR_H_
diff --git a/platform/exynos/include/chre/target_platform/host_link_base.h b/platform/exynos/include/chre/target_platform/host_link_base.h
new file mode 100644
index 0000000..9262f63
--- /dev/null
+++ b/platform/exynos/include/chre/target_platform/host_link_base.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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_EXYNOS_HOST_LINK_BASE_H_
+#define CHRE_PLATFORM_EXYNOS_HOST_LINK_BASE_H_
+
+#include "chre/platform/atomic.h"
+#include "chre/platform/mutex.h"
+#include "chre/platform/shared/host_protocol_chre.h"
+#include "chre/util/lock_guard.h"
+
+#include "mailbox.h"
+
+namespace chre {
+
+/**
+ * Helper function to send debug dump result to host.
+ */
+void sendDebugDumpResultToHost(uint16_t hostClientId, const char *debugStr,
+                               size_t debugStrSize, bool complete,
+                               uint32_t dataCount);
+
+/**
+ * @brief Platform specific host link.
+ */
+class HostLinkBase {
+ public:
+  HostLinkBase();
+
+  /**
+   * Implements the IPC message receive handler.
+   *
+   * @param cookie An opaque pointer that was provided to the IPC driver during
+   *        callback registration.
+   * @param message The host message sent to CHRE.
+   * @param messageLen The host message length in bytes.
+   */
+  static void receive(void *cookie, void *message, int messageLen);
+
+  /**
+   * Send a message to the host.
+   *
+   * @param data The message to host payload.
+   * @param dataLen Size of the message payload in bytes.
+   * @return true if the operation succeeds, false otherwise.
+   */
+  bool send(uint8_t *data, size_t dataLen) {
+    return mailboxWriteChre(data, dataLen) == 0;
+  }
+
+  void setInitialized(bool initialized) {
+    mInitialized = initialized;
+  }
+
+  bool isInitialized() const {
+    return mInitialized;
+  }
+
+  /**
+   * Sends a request to the host for a time sync message.
+   */
+  static void sendTimeSyncRequest() {
+    // Implement this.
+  }
+
+  /**
+   * Enqueues a V2 log message to be sent to the host.
+   *
+   * @param logMessage Pointer to a buffer that has the log message. Note that
+   * the message might be encoded
+   *
+   * @param logMessageSize length of the log message buffer
+   *
+   * @param numLogsDropped the number of logs dropped since CHRE started
+   */
+  void sendLogMessageV2(const uint8_t * /*logMessage*/,
+                        size_t /*logMessageSize*/,
+                        uint32_t /*num_logs_dropped*/) {
+    // Implement this.
+  }
+
+ private:
+  static constexpr uint32_t kMsgBufferSize = CHRE_MESSAGE_TO_HOST_MAX_SIZE;
+  uint8_t mMsgBuffer[kMsgBufferSize] = {0};
+  AtomicBool mInitialized = false;
+  Mutex mMutex;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_EXYNOS_HOST_LINK_BASE_H_
diff --git a/platform/exynos/include/chre/target_platform/log.h b/platform/exynos/include/chre/target_platform/log.h
new file mode 100644
index 0000000..0e31eb9
--- /dev/null
+++ b/platform/exynos/include/chre/target_platform/log.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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_EXYNOS_LOG_H_
+#define CHRE_PLATFORM_EXYNOS_LOG_H_
+
+#include <stdio.h>
+
+#include "chre_api/chre.h"
+
+// TODO(b/230134803): Note that 'printf' currently redirects to dmesg: modify
+// the below macros when we have a platform implementation available that
+// redirects to logcat.
+#define CHRE_EXYNOS_LOG(logLevel, fmt, ...) printf("[CHRE] " fmt, ##__VA_ARGS__)
+
+#define LOGE(fmt, ...) CHRE_EXYNOS_LOG(CHRE_LOG_ERROR, fmt, ##__VA_ARGS__)
+#define LOGW(fmt, ...) CHRE_EXYNOS_LOG(CHRE_LOG_WARN, fmt, ##__VA_ARGS__)
+#define LOGI(fmt, ...) CHRE_EXYNOS_LOG(CHRE_LOG_INFO, fmt, ##__VA_ARGS__)
+#define LOGD(fmt, ...) CHRE_EXYNOS_LOG(CHRE_LOG_DEBUG, fmt, ##__VA_ARGS__)
+#define LOGV(fmt, ...) CHRE_EXYNOS_LOG(CHRE_LOG_VERBOSE, fmt, ##__VA_ARGS__)
+
+#endif  // CHRE_PLATFORM_EXYNOS_LOG_H_
diff --git a/platform/exynos/include/chre/target_platform/platform_nanoapp_base.h b/platform/exynos/include/chre/target_platform/platform_nanoapp_base.h
new file mode 100644
index 0000000..0d7d8b6
--- /dev/null
+++ b/platform/exynos/include/chre/target_platform/platform_nanoapp_base.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 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_EXYNOS_PLATFORM_NANOAPP_BASE_H_
+#define CHRE_PLATFORM_EXYNOS_PLATFORM_NANOAPP_BASE_H_
+
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
+
+namespace chre {
+
+/**
+ * @brief Platform specific base class for nanoapps.
+ */
+class PlatformNanoappBase {
+ public:
+  /**
+   * Associate this Nanoapp instance with a nanoapp that is statically built
+   * into the CHRE binary with the given app info structure.
+   *
+   * @param appInfo The app's information
+   */
+  void loadStatic(const struct ::chreNslNanoappInfo *appInfo);
+
+  /**
+   * @return true if the app's binary data is resident in memory or if the app's
+   *         filename is saved, i.e. all binary fragments are loaded through
+   *         copyNanoappFragment, loadFromFile/loadStatic() was successful
+   */
+  bool isLoaded() const;
+
+  /**
+   * Reserves buffer space for a nanoapp's binary. This method should be called
+   * before copyNanoappFragment is called.
+   *
+   * @param appId The unique app identifier associated with this binary
+   * @param appVersion An application-defined version number
+   * @param appFlags The flags provided by the app being loaded
+   * @param appBinaryLen Size of appBinary, in bytes
+   * @param targetApiVersion The target API version of the nanoapp
+   *
+   * @return true if the allocation was successful, false otherwise
+   */
+  bool reserveBuffer(uint64_t appId, uint32_t appVersion, uint32_t appFlags,
+                     size_t appBinaryLen, uint32_t targetApiVersion);
+
+  /**
+   * Copies the (possibly fragmented) application binary data into the allocated
+   * buffer, and updates the pointer to the next address to write into. The
+   * application may be invalid - full checking and initialization happens just
+   * before invoking start() nanoapp entry point.
+   *
+   * @param buffer The pointer to the buffer
+   * @param bufferSize The size of the buffer in bytes
+   *
+   * @return true if the reserved buffer did not overflow, false otherwise
+   */
+  bool copyNanoappFragment(const void *buffer, size_t bufferSize);
+
+ 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 application-defined version number we received in the metadata
+   * alongside the nanoapp binary. This is also included in (and checked
+   * against) mAppInfo.
+   */
+  uint32_t mExpectedAppVersion = 0;
+
+  //! The app target API version in the metadata alongside the nanoapp binary.
+  uint32_t mExpectedTargetApiVersion = 0;
+
+  /**
+   * Set to true if this app is built into the CHRE binary, and was loaded via
+   * loadStatic(). In this case, the member variables above are not valid or
+   * applicable.
+   */
+  bool mIsStatic = false;
+
+  /** Pointer to the app info structure within this nanoapp */
+  const struct ::chreNslNanoappInfo *mAppInfo = nullptr;
+
+  //! Pointer containing the unstable ID section for this nanoapp
+  const char *mAppUnstableId = nullptr;
+
+  //! Buffer containing the complete DSO binary - only populated if
+  //! copyNanoappFragment() was used to load this nanoapp
+  void *mAppBinary = nullptr;
+  size_t mAppBinaryLen = 0;
+
+  //! The number of bytes of the binary that has been loaded so far.
+  size_t mBytesLoaded = 0;
+
+  //! The dynamic shared object (DSO) handle returned by dlopenbuf()
+  void *mDsoHandle = nullptr;
+
+  /**
+   * Loads the nanoapp symbols from the currently loaded binary and verifies
+   * they match the expected information the nanoapp should have.
+   *
+   * @return true if the app info structure passed validation.
+   */
+  bool verifyNanoappInfo();
+
+  /**
+   * Calls through to openNanoappFromBuffer or openNanoappFromFile, depending on
+   * how this nanoapp was loaded.
+   */
+  bool openNanoapp();
+
+  /**
+   * 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
+#endif  // CHRE_PLATFORM_EXYNOS_PLATFORM_NANOAPP_BASE_H_
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/exynos/include/chre/target_platform/power_control_manager_base.h
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to platform/exynos/include/chre/target_platform/power_control_manager_base.h
index 77a9da0..c3af36e 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/exynos/include/chre/target_platform/power_control_manager_base.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#ifndef CHRE_PLATFORM_EXYNOS_POWER_CONTROL_MANAGER_BASE_H_
+#define CHRE_PLATFORM_EXYNOS_POWER_CONTROL_MANAGER_BASE_H_
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+/**
+ * @brief Platform specific power control manager.
+ */
+class PowerControlManagerBase {};
 
 }  // namespace chre
 
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
+#endif  // CHRE_PLATFORM_EXYNOS_POWER_CONTROL_MANAGER_BASE_H_
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/exynos/memory.cc
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to platform/exynos/memory.cc
index 77a9da0..4e768d5 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/exynos/memory.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#include "chre/platform/memory.h"
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+void forceDramAccess() {
+  // Not supported.
+}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/exynos/platform_cache_management.cc
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to platform/exynos/platform_cache_management.cc
index 77a9da0..975fe79 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/exynos/platform_cache_management.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,12 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#include "chre/target_platform/platform_cache_management.h"
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+void wipeSystemCaches() {
+  // nothing to do.
+}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/platform/exynos/platform_nanoapp.cc b/platform/exynos/platform_nanoapp.cc
new file mode 100644
index 0000000..6962716
--- /dev/null
+++ b/platform/exynos/platform_nanoapp.cc
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 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/platform_nanoapp.h"
+#include "chre/platform/shared/libc/dlfcn.h"
+#include "chre/platform/shared/memory.h"
+#include "chre/platform/shared/nanoapp_dso_util.h"
+#include "chre/util/system/napp_permissions.h"
+
+#include <cinttypes>
+
+namespace chre {
+
+PlatformNanoapp::~PlatformNanoapp() {}
+
+bool PlatformNanoappBase::reserveBuffer(uint64_t appId, uint32_t appVersion,
+                                        uint32_t appFlags, size_t appBinaryLen,
+                                        uint32_t targetApiVersion) {
+  CHRE_ASSERT(!isLoaded());
+
+  bool success = false;
+  mAppBinary = memoryAlloc(appBinaryLen);
+
+  // TODO(b/237819962): Check binary signature when authentication is
+  // implemented.
+  if (mAppBinary == nullptr) {
+    LOG_OOM();
+  } else {
+    mExpectedAppId = appId;
+    mExpectedAppVersion = appVersion;
+    mExpectedTargetApiVersion = targetApiVersion;
+    mAppBinaryLen = appBinaryLen;
+    success = true;
+  }
+
+  return success;
+}
+
+bool PlatformNanoappBase::copyNanoappFragment(const void *buffer,
+                                              size_t bufferLen) {
+  CHRE_ASSERT(!isLoaded());
+
+  bool success = true;
+
+  if ((mBytesLoaded + bufferLen) > mAppBinaryLen) {
+    LOGE("Overflow: cannot load %zu bytes to %zu/%zu nanoapp binary buffer",
+         bufferLen, mBytesLoaded, mAppBinaryLen);
+    success = false;
+  } else {
+    uint8_t *binaryBuffer = static_cast<uint8_t *>(mAppBinary) + mBytesLoaded;
+    memcpy(binaryBuffer, buffer, bufferLen);
+    mBytesLoaded += bufferLen;
+  }
+  return success;
+}
+
+bool PlatformNanoapp::start() {
+  bool success = false;
+
+  if (!openNanoapp()) {
+    LOGE("Failed to open the nanoapp");
+  } else if (mAppInfo != nullptr) {
+    success = mAppInfo->entryPoints.start();
+  } else {
+    LOGE("app info was null - unable to start nanoapp");
+  }
+
+  return success;
+}
+
+void PlatformNanoapp::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                                  const void *eventData) {
+  mAppInfo->entryPoints.handleEvent(senderInstanceId, eventType, eventData);
+}
+
+void PlatformNanoapp::end() {
+  mAppInfo->entryPoints.end();
+}
+
+uint64_t PlatformNanoapp::getAppId() const {
+  return mAppInfo->appId;
+}
+
+uint32_t PlatformNanoapp::getAppVersion() const {
+  return mAppInfo->appVersion;
+}
+
+uint32_t PlatformNanoapp::getTargetApiVersion() const {
+  return mAppInfo->targetApiVersion;
+}
+bool PlatformNanoapp::isSystemNanoapp() const {
+  return mAppInfo->isSystemNanoapp;
+}
+
+const char *PlatformNanoapp::getAppName() const {
+  return (mAppInfo != nullptr) ? mAppInfo->name : "Unknown";
+}
+
+bool PlatformNanoappBase::isLoaded() const {
+  return (mIsStatic ||
+          (mAppBinary != nullptr && mBytesLoaded == mAppBinaryLen) ||
+          mDsoHandle != nullptr);
+}
+
+void PlatformNanoappBase::loadStatic(const struct chreNslNanoappInfo *appInfo) {
+  CHRE_ASSERT(!isLoaded());
+  mIsStatic = true;
+  mAppInfo = appInfo;
+}
+
+bool PlatformNanoapp::supportsAppPermissions() const {
+  return (mAppInfo != nullptr) ? (mAppInfo->structMinorVersion >=
+                                  CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3)
+                               : false;
+}
+
+uint32_t PlatformNanoapp::getAppPermissions() const {
+  return (supportsAppPermissions())
+             ? mAppInfo->appPermissions
+             : static_cast<uint32_t>(chre::NanoappPermissions::CHRE_PERMS_NONE);
+}
+
+bool PlatformNanoappBase::verifyNanoappInfo() {
+  bool success = false;
+
+  if (mDsoHandle == nullptr) {
+    LOGE("No nanoapp info to verify");
+  } 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");
+    } else {
+      mAppUnstableId = mAppInfo->appVersionString;
+      if (mAppUnstableId == nullptr) {
+        LOGE("Failed to find unstable ID symbol");
+      } else {
+        success = validateAppInfo(mExpectedAppId, mExpectedAppVersion,
+                                  mExpectedTargetApiVersion, mAppInfo);
+        if (!success) {
+          mAppInfo = nullptr;
+        } else {
+          LOGI("Nanoapp loaded: %s (0x%016" PRIx64 ") version 0x%" PRIx32
+               " (%s) uimg %d system %d",
+               mAppInfo->name, mAppInfo->appId, mAppInfo->appVersion,
+               mAppInfo->appVersionString, mAppInfo->isTcmNanoapp,
+               mAppInfo->isSystemNanoapp);
+          if (mAppInfo->structMinorVersion >=
+              CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3) {
+            LOGI("Nanoapp permissions: 0x%" PRIx32, mAppInfo->appPermissions);
+          }
+        }
+      }
+    }
+  }
+  return success;
+}
+
+bool PlatformNanoappBase::openNanoapp() {
+  bool success = false;
+  if (mIsStatic) {
+    success = true;
+  } else if (mAppBinary != nullptr) {
+    if (mDsoHandle != nullptr) {
+      LOGE("Trying to reopen an existing buffer");
+    } else {
+      mDsoHandle = dlopenbuf(mAppBinary, false /* mapIntoTcm */);
+      success = verifyNanoappInfo();
+    }
+  }
+
+  if (!success) {
+    closeNanoapp();
+  }
+
+  if (mAppBinary != nullptr) {
+    nanoappBinaryDramFree(mAppBinary);
+    mAppBinary = nullptr;
+  }
+
+  return success;
+}
+
+void PlatformNanoappBase::closeNanoapp() {
+  if (mDsoHandle != nullptr) {
+    // Force DRAM access since dl* functions are only safe to call with DRAM
+    // available.
+    forceDramAccess();
+    mAppInfo = nullptr;
+    if (dlclose(mDsoHandle) != 0) {
+      LOGE("dlclose failed");
+    }
+    mDsoHandle = nullptr;
+  }
+}
+
+}  // namespace chre
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/exynos/platform_pal.cc
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to platform/exynos/platform_pal.cc
index 77a9da0..0b92f34 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/exynos/platform_pal.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#include "chre/platform/shared/platform_pal.h"
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+void PlatformPal::prePalApiCall() const {}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/exynos/power_control_manager.cc
similarity index 60%
rename from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
rename to platform/exynos/power_control_manager.cc
index 77a9da0..092a99a 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/exynos/power_control_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#include "chre/platform/power_control_manager.h"
 
 namespace chre {
 
-namespace settings_test {
+void PowerControlManager::preEventLoopProcess(size_t /* numPendingEvents */) {}
 
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
+void PowerControlManager::postEventLoopProcess(size_t /* numPendingEvents */) {}
 
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+bool PowerControlManager::hostIsAwake() {
+  return true;
+}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/platform/exynos/system_time.cc b/platform/exynos/system_time.cc
new file mode 100644
index 0000000..e992498
--- /dev/null
+++ b/platform/exynos/system_time.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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/system_time.h"
+#include "system_Device.h"
+
+namespace chre {
+namespace {
+int64_t gEstimatedHostTimeOffset = 0;
+}  // anonymous namespace
+
+Nanoseconds SystemTime::getMonotonicTime() {
+  return Nanoseconds(getTimeStampNS());
+}
+
+int64_t SystemTime::getEstimatedHostTimeOffset() {
+  return gEstimatedHostTimeOffset;
+}
+
+void SystemTime::setEstimatedHostTimeOffset(int64_t offset) {
+  gEstimatedHostTimeOffset = offset;
+}
+
+}  // namespace chre
diff --git a/platform/freertos/include/chre/target_platform/init.h b/platform/freertos/include/chre/target_platform/init.h
index 4c8927d..01e8ccc 100644
--- a/platform/freertos/include/chre/target_platform/init.h
+++ b/platform/freertos/include/chre/target_platform/init.h
@@ -40,6 +40,14 @@
 BaseType_t init();
 
 /**
+ * This function initializes the and starts the logger so it's ready to accept
+ * logs. Spawns a (non-privileged) FreeRTOS task for sending logs to the host.
+ *
+ * @return pdPASS on success, a FreeRTOS error code otherwise.
+ */
+BaseType_t initLogger();
+
+/**
  * Delete the task spawned in the init function
  */
 void deinit();
diff --git a/platform/freertos/include/chre/target_platform/mutex_base.h b/platform/freertos/include/chre/target_platform/mutex_base.h
index bf05b6f..6b3cd1c 100644
--- a/platform/freertos/include/chre/target_platform/mutex_base.h
+++ b/platform/freertos/include/chre/target_platform/mutex_base.h
@@ -30,21 +30,27 @@
  */
 class MutexBase {
  protected:
-  SemaphoreHandle_t mSemaphoreHandle;
+  SemaphoreHandle_t mSemaphoreHandle = NULL;
 
   /**
    * Initialize the mutex handle using a static semaphore
    * to avoid heap allocations
    */
   void initStaticMutex() {
+#ifdef CHRE_CREATE_MUTEX_ON_HEAP
+    mSemaphoreHandle = xSemaphoreCreateMutex();
+#else
     mSemaphoreHandle = xSemaphoreCreateMutexStatic(&mStaticSemaphore);
+#endif
     if (mSemaphoreHandle == NULL) {
       FATAL_ERROR("Failed to initialize mutex");
     }
   }
 
  private:
+#ifndef CHRE_CREATE_MUTEX_ON_HEAP
   StaticSemaphore_t mStaticSemaphore;
+#endif
 };
 
 }  // namespace chre
diff --git a/platform/freertos/include/chre/target_platform/mutex_base_impl.h b/platform/freertos/include/chre/target_platform/mutex_base_impl.h
index 23e957d..a983f5e 100644
--- a/platform/freertos/include/chre/target_platform/mutex_base_impl.h
+++ b/platform/freertos/include/chre/target_platform/mutex_base_impl.h
@@ -17,6 +17,7 @@
 #ifndef CHRE_PLATFORM_FREERTOS_MUTEX_BASE_IMPL_H_
 #define CHRE_PLATFORM_FREERTOS_MUTEX_BASE_IMPL_H_
 
+#include "chre/platform/assert.h"
 #include "chre/platform/mutex.h"
 
 namespace chre {
@@ -33,6 +34,7 @@
 
 inline void Mutex::lock() {
   TickType_t blockForever = portMAX_DELAY;
+  CHRE_ASSERT(mSemaphoreHandle != NULL);
   if (pdTRUE != xSemaphoreTake(mSemaphoreHandle, blockForever)) {
     LOGE("Failed to lock mutex");
   }
diff --git a/platform/freertos/init.cc b/platform/freertos/init.cc
index c993b40..16c5db5 100644
--- a/platform/freertos/init.cc
+++ b/platform/freertos/init.cc
@@ -16,7 +16,9 @@
 
 #include "chre/core/init.h"
 
+#ifdef CHRE_ENABLE_CHPP
 #include "chpp/platform/chpp_init.h"
+#endif
 #include "chre/core/event_loop_manager.h"
 #include "chre/core/static_nanoapps.h"
 #include "chre/platform/shared/dram_vote_client.h"
@@ -33,9 +35,19 @@
 namespace freertos {
 namespace {
 
-constexpr configSTACK_DEPTH_TYPE kChreTaskStackDepthWords = 0x800;
-
+#ifdef CHRE_FREERTOS_TASK_PRIORITY
+constexpr UBaseType_t kChreTaskPriority =
+    tskIDLE_PRIORITY + CHRE_FREERTOS_TASK_PRIORITY;
+#else
 constexpr UBaseType_t kChreTaskPriority = tskIDLE_PRIORITY + 1;
+#endif
+
+#ifdef CHRE_FREERTOS_STACK_DEPTH_IN_WORDS
+constexpr configSTACK_DEPTH_TYPE kChreTaskStackDepthWords =
+    CHRE_FREERTOS_STACK_DEPTH_IN_WORDS;
+#else
+constexpr configSTACK_DEPTH_TYPE kChreTaskStackDepthWords = 0x800;
+#endif
 
 TaskHandle_t gChreTaskHandle;
 
@@ -95,15 +107,7 @@
 
   DramVoteClientSingleton::init();
 
-#ifdef CHRE_USE_BUFFERED_LOGGING
-  chre::LogBufferManagerSingleton::init(gPrimaryLogBufferData,
-                                        gSecondaryLogBufferData,
-                                        sizeof(gPrimaryLogBufferData));
-
-  rc = xTaskCreate(chreFlushLogsToHostThreadEntry, getChreFlushTaskName(),
-                   kChreTaskStackDepthWords, nullptr /* args */,
-                   kChreTaskPriority, &gChreFlushTaskHandle);
-#endif
+  rc = initLogger();
 
   if (rc == pdPASS) {
     rc = xTaskCreate(chreThreadEntry, getChreTaskName(),
@@ -113,11 +117,29 @@
 
   CHRE_ASSERT(rc == pdPASS);
 
+#ifdef CHRE_ENABLE_CHPP
   chpp::init();
+#endif
 
   return rc;
 }
 
+BaseType_t initLogger() {
+  BaseType_t rc = pdPASS;
+#ifdef CHRE_USE_BUFFERED_LOGGING
+  if (!chre::LogBufferManagerSingleton::isInitialized()) {
+    chre::LogBufferManagerSingleton::init(gPrimaryLogBufferData,
+                                          gSecondaryLogBufferData,
+                                          sizeof(gPrimaryLogBufferData));
+
+    rc = xTaskCreate(chreFlushLogsToHostThreadEntry, getChreFlushTaskName(),
+                     kChreTaskStackDepthWords, nullptr /* args */,
+                     kChreTaskPriority, &gChreFlushTaskHandle);
+  }
+#endif
+  return rc;
+}
+
 void deinit() {
   // On a deinit call, we just stop the CHRE event loop. This causes the 'run'
   // method in the task function exit, and move on to handle task cleanup
@@ -125,7 +147,9 @@
     chre::EventLoopManagerSingleton::get()->getEventLoop().stop();
   }
 
+#ifdef CHRE_ENABLE_CHPP
   chpp::deinit();
+#endif
 }
 
 const char *getChreTaskName() {
diff --git a/platform/freertos/platform_nanoapp.cc b/platform/freertos/platform_nanoapp.cc
index 9f5115a..c8e8d21 100644
--- a/platform/freertos/platform_nanoapp.cc
+++ b/platform/freertos/platform_nanoapp.cc
@@ -32,6 +32,10 @@
 namespace chre {
 namespace {
 
+#ifndef CHRE_NANOAPP_LOAD_ALIGNMENT
+#define CHRE_NANOAPP_LOAD_ALIGNMENT 0
+#endif
+
 const char kDefaultAppVersionString[] = "<undefined>";
 size_t kDefaultAppVersionStringSize = ARRAY_SIZE(kDefaultAppVersionString);
 
@@ -88,7 +92,7 @@
 
 bool PlatformNanoapp::supportsAppPermissions() const {
   return (mAppInfo != nullptr) ? (mAppInfo->structMinorVersion >=
-                                  CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION)
+                                  CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3)
                                : false;
 }
 
@@ -120,7 +124,7 @@
     size_t versionLen = 0;
     const char *version = getAppVersionString(&versionLen);
     debugDump.print("%s (%s) @ build: %.*s", mAppInfo->name, mAppInfo->vendor,
-                    versionLen, version);
+                    static_cast<int>(versionLen), version);
   }
 }
 
@@ -177,7 +181,8 @@
   forceDramAccess();
 
   bool success = false;
-  mAppBinary = nanoappBinaryDramAlloc(appBinaryLen);
+  mAppBinary =
+      nanoappBinaryDramAlloc(appBinaryLen, CHRE_NANOAPP_LOAD_ALIGNMENT);
 
   bool isSigned = IS_BIT_SET(appFlags, CHRE_NAPP_HEADER_SIGNED);
   if (!isSigned) {
@@ -250,7 +255,7 @@
                mAppInfo->appVersionString, mAppInfo->isTcmNanoapp,
                mAppInfo->isSystemNanoapp);
           if (mAppInfo->structMinorVersion >=
-              CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION) {
+              CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3) {
             LOGI("Nanoapp permissions: 0x%" PRIx32, mAppInfo->appPermissions);
           }
         }
@@ -269,7 +274,7 @@
     //! Use the returned value from authenticateBinary to ensure dlopenbuf has
     //! the starting address to a valid ELF.
     void *binaryStart = mAppBinary;
-    if (!authenticateBinary(mAppBinary, &binaryStart)) {
+    if (!authenticateBinary(mAppBinary, mAppBinaryLen, &binaryStart)) {
       LOGE("Unable to authenticate 0x%" PRIx64 " not loading", mExpectedAppId);
     } else if (mDsoHandle != nullptr) {
       LOGE("Trying to reopen an existing buffer");
diff --git a/platform/include/chre/platform/memory.h b/platform/include/chre/platform/memory.h
index 205ecdd..a7048b0 100644
--- a/platform/include/chre/platform/memory.h
+++ b/platform/include/chre/platform/memory.h
@@ -28,10 +28,22 @@
 void *memoryAlloc(size_t size);
 
 /**
+ * A platform abstraction for aligned memory allocation. The semantics are the
+ * same as aligned_malloc.
+ *
+ * This implementation is optional and is not typically needed.
+ */
+
+template <typename T>
+T *memoryAlignedAlloc();
+
+/**
  * A platform abstraction for memory free. The semantics are the same as free.
  */
 void memoryFree(void *pointer);
 
 }  // namespace chre
 
+#include "chre/target_platform/memory_impl.h"
+
 #endif  // CHRE_PLATFORM_MEMORY_H_
diff --git a/platform/include/chre/platform/platform_ble.h b/platform/include/chre/platform/platform_ble.h
index 2a7a75e..3f9b56a 100644
--- a/platform/include/chre/platform/platform_ble.h
+++ b/platform/include/chre/platform/platform_ble.h
@@ -86,6 +86,25 @@
    * @param event the event to release.
    */
   void releaseAdvertisingEvent(struct chreBleAdvertisementEvent *event);
+
+  /**
+   * Reads the RSSI on a given LE-ACL connection handle.
+   *
+   * Only one call to this method may be outstanding until the
+   * readRssiCallback() is invoked. The readRssiCallback() is guaranteed to be
+   * invoked exactly once within CHRE_PAL_BLE_READ_RSSI_COMPLETE_TIMEOUT_NS of
+   * readRssi() being invoked.
+   *
+   * @param connectionHandle The LE-ACL handle upon which the RSSI is to be
+   * read.
+   *
+   * @return true if the request was accepted, in which case a subsequent call
+   *         to readRssiCallback() will be used to indicate the result of the
+   *         operation.
+   *
+   * @since v1.8
+   */
+  bool readRssiAsync(uint16_t connectionHandle);
 };
 
 }  // namespace chre
diff --git a/platform/include/chre/platform/system_timer.h b/platform/include/chre/platform/system_timer.h
index 0ffcdbc..08160d2 100644
--- a/platform/include/chre/platform/system_timer.h
+++ b/platform/include/chre/platform/system_timer.h
@@ -99,10 +99,10 @@
   friend class SystemTimerBase;
 
   //! The callback to invoke when the timer has elapsed.
-  SystemTimerCallback *mCallback;
+  SystemTimerCallback *mCallback = nullptr;
 
   //! The data to pass to the callback when invoked.
-  void *mData;
+  void *mData = nullptr;
 };
 
 }  // namespace chre
diff --git a/platform/include/chre/platform/tracing.h b/platform/include/chre/platform/tracing.h
new file mode 100644
index 0000000..fc94deb
--- /dev/null
+++ b/platform/include/chre/platform/tracing.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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_TRACING_H_
+#define CHRE_PLATFORM_TRACING_H_
+
+#include <cstdint>
+
+/**
+ * @file
+ * Tracing support for CHRE.
+ * Platforms must supply an implementation of the trace functions.
+ */
+
+namespace chre {
+/**
+ * Registers a nanoapp instance with the tracing infrastructure.
+ *
+ * @param instanceId instance ID of the nanoapp.
+ * @param name name of the nanoapp.
+ */
+void traceRegisterNanoapp(uint16_t instanceId, const char *name);
+
+/**
+ * Marks the start of the nanoappHandleEvent function.
+ *
+ * @param instanceId instance ID of the nanoapp.
+ * @param eventType event being handled.
+ */
+void traceNanoappHandleEventStart(uint16_t instanceId, uint16_t eventType);
+
+/**
+ * Marks the end of the nanoappHandleEvent function.
+ *
+ * @param instanceId instance ID of the nanoapp.
+ */
+void traceNanoappHandleEventEnd(uint16_t instanceId);
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_TRACING_H_
diff --git a/platform/linux/host_link.cc b/platform/linux/host_link.cc
index 1c7ad89..2f1248f 100644
--- a/platform/linux/host_link.cc
+++ b/platform/linux/host_link.cc
@@ -20,12 +20,15 @@
 namespace chre {
 
 void HostLink::flushMessagesSentByNanoapp(uint64_t /* appId */) {
-  // TODO: implement
+  // (empty)
 }
 
-bool HostLink::sendMessage(const MessageToHost * /* message */) {
-  // TODO: implement
-  return false;
+bool HostLink::sendMessage(const MessageToHost *message) {
+  // Just drop the message since we don't have a real host to send to
+  EventLoopManagerSingleton::get()
+      ->getHostCommsManager()
+      .onMessageToHostComplete(message);
+  return true;
 }
 
 void HostLinkBase::sendNanConfiguration(bool enable) {
diff --git a/platform/linux/include/chre/platform/linux/pal_wifi.h b/platform/linux/include/chre/platform/linux/pal_wifi.h
index 3825b56..ff3bee4 100644
--- a/platform/linux/include/chre/platform/linux/pal_wifi.h
+++ b/platform/linux/include/chre/platform/linux/pal_wifi.h
@@ -17,9 +17,44 @@
 #ifndef CHRE_PLATFORM_LINUX_PAL_WIFI_H_
 #define CHRE_PLATFORM_LINUX_PAL_WIFI_H_
 
+#include <stdint.h>
+
+#include <chrono>
+
+enum class PalWifiAsyncRequestTypes : uint8_t {
+  SCAN,
+  SCAN_MONITORING,
+  RANGING,
+
+  // Must be last
+  NUM_WIFI_REQUEST_TYPE,
+};
+
 /**
  * @return whether scan monitoring is active.
  */
 bool chrePalWifiIsScanMonitoringActive();
 
+/**
+ * Sets how long each async request should hold before replying the result
+ * to CHRE.
+ *
+ * @param requestType select one request type to modify its behavior.
+ * @param seconds delayed response time.
+ */
+void chrePalWifiDelayResponse(PalWifiAsyncRequestTypes requestType,
+                              std::chrono::seconds seconds);
+
+/**
+ * Sets if PAL should send back async request result for each async request.
+ *
+ * This function is used to mimic the behavior of hardware failure in
+ * simulation test.
+ *
+ * @param requestType select one request type to modify its behavior.
+ * @param enableResponse true if allow pal to send back async result.
+ */
+void chrePalWifiEnableResponse(PalWifiAsyncRequestTypes requestType,
+                               bool enableResponse);
+
 #endif  // CHRE_PLATFORM_LINUX_PAL_WIFI_H_
\ No newline at end of file
diff --git a/platform/linux/include/chre/platform/linux/task_util/task.h b/platform/linux/include/chre/platform/linux/task_util/task.h
new file mode 100644
index 0000000..858cfb7
--- /dev/null
+++ b/platform/linux/include/chre/platform/linux/task_util/task.h
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 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_LINUX_TASK_UTIL_TASK_H_
+#define CHRE_PLATFORM_LINUX_TASK_UTIL_TASK_H_
+
+#include <chrono>
+#include <cstdint>
+#include <functional>
+#include <mutex>
+#include <optional>
+
+namespace chre {
+namespace task_manager_internal {
+
+/**
+ * Represents a task to execute (a function to call) that can be executed once
+ * or repeatedly with interval: repeatInterval in milliseconds until
+ * cancel() is called.
+ *
+ * Note: The Task class is not thread-safe nor synchronized properly. It is
+ * meant to be externally synchronized (see TaskManager).
+ */
+class Task {
+ public:
+  using TaskFunction = std::function<void()>;
+
+  /**
+   * Construct an empty Task object.
+   */
+  Task()
+      : mExecutionTimestamp(std::chrono::steady_clock::now()),
+        mRepeatInterval(0),
+        mId(0),
+        mHasExecuted(false) {}
+
+  /**
+   * Construct a new Task object.
+   *
+   * @param func              the function to execute.
+   * @param repeatInterval    the interval in which to repeat execution in
+   *                          milliseconds.
+   * @param id                the unique ID for use with the Task Manager.
+   */
+  Task(const TaskFunction &func, std::chrono::milliseconds repeatInterval,
+       uint32_t id);
+
+  /**
+   * Construct a new Task object.
+   *
+   * @param rhs               copy constructor args.
+   */
+  Task(const Task &rhs);
+
+  /**
+   * Assignment operator.
+   *
+   * @param rhs              rhs arg.
+   * @return                 this.
+   */
+  Task &operator=(const Task &rhs);
+
+  /**
+   * Stops the task from repeating.
+   */
+  void cancel();
+
+  /**
+   * Executes the function.
+   */
+  void execute();
+
+  /**
+   * Gets the next time the task should execute.
+   */
+  inline std::chrono::time_point<std::chrono::steady_clock>
+  getExecutionTimestamp() const {
+    return mExecutionTimestamp;
+  }
+
+  /**
+   * Gets the ID of the task.
+   */
+  inline uint32_t getId() const {
+    return mId;
+  }
+
+  /**
+   * Returns true if the task has executed at least once, false if otherwise.
+   *
+   * @return true         if the task has executed at least once.
+   * @return false        if the task has not executed at least once.
+   */
+  inline bool hasExecuted() const {
+    return mHasExecuted;
+  }
+
+  /**
+   * Returns true if the task is ready to execute (time now is >= task
+   * timestamp).
+   *
+   * @return true         the task can be executed.
+   * @return false        do not yet execute the task.
+   */
+  inline bool isReadyToExecute() const {
+    return mExecutionTimestamp <= std::chrono::steady_clock::now();
+  }
+
+  /**
+   * Returns true if the task is a repeating task - if it has has a
+   * repeatInterval > 0.
+   *
+   * @return true         if the task is a repeating task.
+   * @return false        otherwise.
+   */
+  inline bool isRepeating() const {
+    return mRepeatInterval.count() > 0;
+  }
+
+  /*
+   * The following relational operators are comparing execution timestamps.
+   */
+  inline bool operator<(const Task &rhs) const {
+    return mExecutionTimestamp < rhs.mExecutionTimestamp;
+  }
+
+  inline bool operator>(const Task &rhs) const {
+    return mExecutionTimestamp > rhs.mExecutionTimestamp;
+  }
+
+  inline bool operator<=(const Task &rhs) const {
+    return mExecutionTimestamp <= rhs.mExecutionTimestamp;
+  }
+
+  inline bool operator>=(const Task &rhs) const {
+    return mExecutionTimestamp >= rhs.mExecutionTimestamp;
+  }
+
+ private:
+  /**
+   * Time timestamp of when the task should be executed.
+   */
+  std::chrono::time_point<std::chrono::steady_clock> mExecutionTimestamp;
+
+  /**
+   * The amount of time to wait in between repeating the task.
+   */
+  std::chrono::milliseconds mRepeatInterval;
+
+  /**
+   * The function to execute.
+   */
+  std::optional<TaskFunction> mFunc;
+
+  /**
+   * The ID of the task.
+   */
+  uint32_t mId;
+
+  /**
+   * If the task has executed at least once.
+   */
+  bool mHasExecuted;
+
+  /**
+   * Mutex for execution.
+   */
+  std::mutex mExecutionMutex;
+};
+
+}  // namespace task_manager_internal
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_LINUX_TASK_UTIL_TASK_H_
diff --git a/platform/linux/include/chre/platform/linux/task_util/task_manager.h b/platform/linux/include/chre/platform/linux/task_util/task_manager.h
new file mode 100644
index 0000000..9710c39
--- /dev/null
+++ b/platform/linux/include/chre/platform/linux/task_util/task_manager.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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_LINUX_TASK_UTIL_TASK_MANAGER_H_
+#define CHRE_PLATFORM_LINUX_TASK_UTIL_TASK_MANAGER_H_
+
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <optional>
+#include <thread>
+
+#include "chre/platform/linux/task_util/task.h"
+#include "chre/util/non_copyable.h"
+#include "chre/util/priority_queue.h"
+#include "chre/util/singleton.h"
+
+namespace chre {
+
+using task_manager_internal::Task;
+
+/**
+ * A class to manage a thread that executes arbitrary tasks. These tasks can
+ * repeat or be a singular execution. The manager will always execute the next
+ * task in chronological order.
+ */
+class TaskManager : public NonCopyable {
+ public:
+  /**
+   * Construct a new Task Manager object.
+   */
+  TaskManager();
+
+  /**
+   * Destroy the Task Manager object.
+   */
+  ~TaskManager();
+
+  /**
+   * Adds a task to the queue for execution. The manager calls the function func
+   * during execution. If repeatInterval > 0, the task will repeat every
+   * repeatInterval milliseconds. If repeatInterval == 0, the task will be
+   * executed only once.
+   *
+   * @param func                     the function to call.
+   * @param repeatInterval           the interval to repeat.
+   * @return                         the ID of the Task object or an empty
+   * Optional<> when there is an error.
+   */
+  std::optional<uint32_t> addTask(
+      const Task::TaskFunction &func,
+      std::chrono::milliseconds repeatInterval = std::chrono::milliseconds(0));
+
+  /**
+   * Cancels the task with the taskId.
+   *
+   * @param taskId              the ID of the task.
+   * @return bool               success.
+   */
+  bool cancelTask(uint32_t taskId);
+
+  /**
+   * Empties the task queue without execution. This call is blocking.
+   */
+  void flushTasks();
+
+ private:
+  /**
+   * The run function for the execution thread.
+   */
+  void run();
+
+  /**
+   * The queue of tasks.
+   *
+   * We use a chre::PriorityQueue here instead of a std::priority_queue because
+   * the chre::PriorityQueue allows container iterator access and the other does
+   * not.
+   */
+  PriorityQueue<Task, std::greater<Task>> mQueue;
+
+  /**
+   * The current task being executed.
+   */
+  Task *mCurrentTask;
+
+  /**
+   * The execution thread.
+   */
+  std::thread mThread;
+
+  /**
+   * If true, continue executing in the thread. If false, stop executing in the
+   * thread.
+   */
+  bool mContinueRunningThread;
+
+  /**
+   * The ID counter for Tasks; keeps the Task's ID unique.
+   */
+  uint32_t mCurrentId;
+
+  /**
+   * The mutex to protect access to the queue.
+   */
+  std::mutex mMutex;
+
+  /**
+   * The condition variable to signal to the execution thread to process more
+   * tasks (the queue is not empty).
+   */
+  std::condition_variable mConditionVariable;
+};
+
+// Provide an alias to the TaskManager singleton.
+typedef Singleton<TaskManager> TaskManagerSingleton;
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_LINUX_TASK_UTIL_TASK_MANAGER_H_
diff --git a/platform/linux/include/chre/target_platform/memory_impl.h b/platform/linux/include/chre/target_platform/memory_impl.h
new file mode 100644
index 0000000..608bbc5
--- /dev/null
+++ b/platform/linux/include/chre/target_platform/memory_impl.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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_LINUX_MEMORY_IMPL_H_
+#define CHRE_PLATFORM_LINUX_MEMORY_IMPL_H_
+
+#include <stdlib.h>
+#include <cstring>
+
+namespace chre {
+
+template <typename T>
+inline T *memoryAlignedAlloc() {
+  void *ptr;
+  int result = posix_memalign(&ptr, alignof(T), sizeof(T));
+  if (result != 0) {
+    ptr = nullptr;
+  }
+  return static_cast<T *>(ptr);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_LINUX_MEMORY_IMPL_H_
diff --git a/platform/linux/include/chre/target_platform/platform_sensor_type_helpers_base.h b/platform/linux/include/chre/target_platform/platform_sensor_type_helpers_base.h
deleted file mode 100644
index f63bbe6..0000000
--- a/platform/linux/include/chre/target_platform/platform_sensor_type_helpers_base.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 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_TARGET_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
-#define CHRE_TARGET_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
-
-namespace chre {
-
-/**
- * Can be used to expose static methods to the PlatformSensorTypeHelpers class
- * for use in working with vendor sensor types. Currently, this is unused in the
- * Linux implementation as sensors are not supported.
- */
-class PlatformSensorTypeHelpersBase {};
-
-}  // namespace chre
-
-#endif  // CHRE_TARGET_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
\ No newline at end of file
diff --git a/platform/linux/init.cc b/platform/linux/init.cc
index 1e3572d..c0fcf5b 100644
--- a/platform/linux/init.cc
+++ b/platform/linux/init.cc
@@ -26,6 +26,7 @@
 #include "chre/platform/context.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/linux/platform_log.h"
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/platform/log.h"
 #include "chre/platform/system_timer.h"
 #include "chre/util/time.h"
@@ -94,6 +95,9 @@
     // configuration to support multiple sources.
 #endif  // CHRE_AUDIO_SUPPORT_ENABLED
 
+    // Initialize the TaskManager.
+    chre::TaskManagerSingleton::init();
+
     // Initialize the system.
     chre::init();
 
@@ -122,6 +126,7 @@
     });
     chreThread.join();
 
+    chre::TaskManagerSingleton::deinit();
     chre::deinit();
     chre::PlatformLogSingleton::deinit();
   } catch (TCLAP::ExitException) {
diff --git a/platform/linux/pal_audio.cc b/platform/linux/pal_audio.cc
index 8b35bf0..3664c0d 100644
--- a/platform/linux/pal_audio.cc
+++ b/platform/linux/pal_audio.cc
@@ -16,6 +16,7 @@
 
 #include "chre/pal/audio.h"
 
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/platform/memory.h"
 #include "chre/util/macros.h"
 #include "chre/util/memory.h"
@@ -24,66 +25,64 @@
 #include <chrono>
 #include <cinttypes>
 #include <cstdint>
-#include <future>
-#include <thread>
 
 /**
  * A simulated implementation of the audio PAL for the linux platform.
  */
 namespace {
+
+using chre::TaskManagerSingleton;
+
 const struct chrePalSystemApi *gSystemApi = nullptr;
 const struct chrePalAudioCallbacks *gCallbacks = nullptr;
 
-//! Thread to deliver asynchronous audio data after a CHRE request.
-std::thread gHandle0Thread;
-std::promise<void> gStopHandle0Thread;
 constexpr uint32_t kHandle0SampleRate = 16000;
 
 //! Whether the handle 0 is currently enabled.
+std::optional<uint32_t> gHandle0TaskId;
 bool gIsHandle0Enabled = false;
 
-void stopHandle0Thread() {
-  if (gHandle0Thread.joinable()) {
-    gStopHandle0Thread.set_value();
-    gHandle0Thread.join();
+void stopHandle0Task() {
+  if (gHandle0TaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gHandle0TaskId.value());
   }
 }
 
 void chrePalAudioApiClose(void) {
-  stopHandle0Thread();
+  stopHandle0Task();
 }
 
 bool chrePalAudioApiOpen(const struct chrePalSystemApi *systemApi,
                          const struct chrePalAudioCallbacks *callbacks) {
   chrePalAudioApiClose();
 
+  bool success = false;
   if (systemApi != nullptr && callbacks != nullptr) {
     gSystemApi = systemApi;
     gCallbacks = callbacks;
     callbacks->audioAvailabilityCallback(0 /*handle*/, true /*available*/);
-    return true;
+    success = true;
   }
 
-  return false;
+  return success;
 }
 
-void sendHandle0Events(uint64_t delayNs, uint32_t numSamples) {
-  std::future<void> signal = gStopHandle0Thread.get_future();
-  if (signal.wait_for(std::chrono::nanoseconds(delayNs)) ==
-      std::future_status::timeout) {
-    auto data = chre::MakeUniqueZeroFill<struct chreAudioDataEvent>();
+void sendHandle0Events(uint32_t numSamples) {
+  auto data = chre::MakeUniqueZeroFill<struct chreAudioDataEvent>();
 
-    data->version = CHRE_AUDIO_DATA_EVENT_VERSION;
-    data->handle = 0;
-    data->timestamp = gSystemApi->getCurrentTime();
-    data->sampleRate = kHandle0SampleRate;
-    data->sampleCount = numSamples;
-    data->format = CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW;
-    data->samplesULaw8 =
-        static_cast<const uint8_t *>(chre::memoryAlloc(numSamples));
+  data->version = CHRE_AUDIO_DATA_EVENT_VERSION;
+  data->handle = 0;
+  data->timestamp = gSystemApi->getCurrentTime();
+  data->sampleRate = kHandle0SampleRate;
+  data->sampleCount = numSamples;
+  data->format = CHRE_AUDIO_DATA_FORMAT_8_BIT_U_LAW;
+  data->samplesULaw8 =
+      static_cast<const uint8_t *>(chre::memoryAlloc(numSamples));
 
-    gCallbacks->audioDataEventCallback(data.release());
-  }
+  gCallbacks->audioDataEventCallback(data.release());
+
+  // Cancel the task so this is only run once with a delay.
+  TaskManagerSingleton::get()->cancelTask(gHandle0TaskId.value());
 }
 
 bool chrePalAudioApiRequestAudioDataEvent(uint32_t handle, uint32_t numSamples,
@@ -92,11 +91,16 @@
     return false;
   }
 
-  stopHandle0Thread();
+  stopHandle0Task();
   if (numSamples > 0) {
     gIsHandle0Enabled = true;
-    gStopHandle0Thread = std::promise<void>();
-    gHandle0Thread = std::thread(sendHandle0Events, eventDelayNs, numSamples);
+    gHandle0TaskId = TaskManagerSingleton::get()->addTask(
+        [numSamples]() { sendHandle0Events(numSamples); },
+        std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::nanoseconds(eventDelayNs)));
+    if (!gHandle0TaskId.has_value()) {
+      return false;
+    }
   }
 
   return true;
@@ -105,7 +109,7 @@
 void chrePalAudioApiCancelAudioDataEvent(uint32_t handle) {
   if (handle == 0) {
     gIsHandle0Enabled = false;
-    stopHandle0Thread();
+    stopHandle0Task();
   }
 }
 
diff --git a/platform/linux/pal_ble.cc b/platform/linux/pal_ble.cc
index 2762170..f29fab1 100644
--- a/platform/linux/pal_ble.cc
+++ b/platform/linux/pal_ble.cc
@@ -16,25 +16,30 @@
 
 #include "chre/pal/ble.h"
 
+#include "chre.h"
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/util/memory.h"
 #include "chre/util/unique_ptr.h"
 
-#include <future>
-#include <thread>
+#include <chrono>
+#include <optional>
 
 /**
  * A simulated implementation of the BLE PAL for the linux platform.
  */
 namespace {
+
+using chre::TaskManagerSingleton;
+
 const struct chrePalSystemApi *gSystemApi = nullptr;
 const struct chrePalBleCallbacks *gCallbacks = nullptr;
 
-std::thread gBleStartScanThread;
-std::thread gBleStopScanThread;
-std::promise<void> gStopAdvertisingEvents;
-
 bool gBleEnabled = false;
 
+// Tasks for startScan and stopScan.
+std::optional<uint32_t> gBleStartScanTaskId;
+std::optional<uint32_t> gBleStopScanTaskId;
+
 std::chrono::milliseconds scanModeToInterval(chreBleScanMode mode) {
   std::chrono::milliseconds interval(1000);
   switch (mode) {
@@ -51,36 +56,32 @@
   return interval;
 }
 
-void startScan(chreBleScanMode mode) {
-  gCallbacks->scanStatusChangeCallback(true, CHRE_ERROR_NONE);
-  std::future<void> signal = gStopAdvertisingEvents.get_future();
-  while (signal.wait_for(scanModeToInterval(mode)) ==
-         std::future_status::timeout) {
-    auto event = chre::MakeUniqueZeroFill<struct chreBleAdvertisementEvent>();
-    auto report = chre::MakeUniqueZeroFill<struct chreBleAdvertisingReport>();
-    uint8_t *data =
-        static_cast<uint8_t *>(chre::memoryAlloc(sizeof(uint8_t) * 2));
-    data[0] = 0x01;
-    data[1] = 0x16;
-    report->data = data;
-    report->dataLength = 2;
-    event->reports = report.release();
-    event->numReports = 1;
-    gCallbacks->advertisingEventCallback(event.release());
-  }
+void startScan() {
+  auto event = chre::MakeUniqueZeroFill<struct chreBleAdvertisementEvent>();
+  auto report = chre::MakeUniqueZeroFill<struct chreBleAdvertisingReport>();
+  uint8_t *data =
+      static_cast<uint8_t *>(chre::memoryAlloc(sizeof(uint8_t) * 2));
+  data[0] = 0x01;
+  data[1] = 0x16;
+  report->timestamp = chreGetTime();
+  report->data = data;
+  report->dataLength = 2;
+  event->reports = report.release();
+  event->numReports = 1;
+  gCallbacks->advertisingEventCallback(event.release());
 }
 
 void stopScan() {
   gCallbacks->scanStatusChangeCallback(false, CHRE_ERROR_NONE);
 }
 
-void stopThreads() {
-  if (gBleStartScanThread.joinable()) {
-    gStopAdvertisingEvents.set_value();
-    gBleStartScanThread.join();
+void stopAllTasks() {
+  if (gBleStartScanTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gBleStartScanTaskId.value());
   }
-  if (gBleStopScanThread.joinable()) {
-    gBleStopScanThread.join();
+
+  if (gBleStopScanTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gBleStopScanTaskId.value());
   }
 }
 
@@ -97,16 +98,26 @@
 
 bool chrePalBleStartScan(chreBleScanMode mode, uint32_t /* reportDelayMs */,
                          const struct chreBleScanFilter * /* filter */) {
-  stopThreads();
-  gStopAdvertisingEvents = std::promise<void>();
-  gBleStartScanThread = std::thread(startScan, mode);
+  stopAllTasks();
+
+  gCallbacks->scanStatusChangeCallback(true, CHRE_ERROR_NONE);
+  gBleStartScanTaskId =
+      TaskManagerSingleton::get()->addTask(startScan, scanModeToInterval(mode));
+  if (!gBleStartScanTaskId.has_value()) {
+    return false;
+  }
+
   gBleEnabled = true;
   return true;
 }
 
 bool chrePalBleStopScan() {
-  stopThreads();
-  gBleStopScanThread = std::thread(stopScan);
+  stopAllTasks();
+  gBleStopScanTaskId = TaskManagerSingleton::get()->addTask(stopScan);
+  if (!gBleStopScanTaskId.has_value()) {
+    return false;
+  }
+
   gBleEnabled = false;
   return true;
 }
@@ -114,14 +125,20 @@
 void chrePalBleReleaseAdvertisingEvent(
     struct chreBleAdvertisementEvent *event) {
   for (size_t i = 0; i < event->numReports; i++) {
-    chre::memoryFree(
-        const_cast<chreBleAdvertisingReport *>(&(event->reports[i])));
+    auto report = const_cast<chreBleAdvertisingReport *>(&(event->reports[i]));
+    chre::memoryFree(const_cast<uint8_t *>(report->data));
   }
+  chre::memoryFree(const_cast<chreBleAdvertisingReport *>(event->reports));
   chre::memoryFree(event);
 }
 
+bool chrePalBleReadRssi(uint16_t connectionHandle) {
+  gCallbacks->readRssiCallback(CHRE_ERROR_NONE, connectionHandle, -65);
+  return true;
+}
+
 void chrePalBleApiClose() {
-  stopThreads();
+  stopAllTasks();
 }
 
 bool chrePalBleApiOpen(const struct chrePalSystemApi *systemApi,
@@ -154,6 +171,7 @@
       .startScan = chrePalBleStartScan,
       .stopScan = chrePalBleStopScan,
       .releaseAdvertisingEvent = chrePalBleReleaseAdvertisingEvent,
+      .readRssi = chrePalBleReadRssi,
   };
 
   if (!CHRE_PAL_VERSIONS_ARE_COMPATIBLE(kApi.moduleVersion,
diff --git a/platform/linux/pal_gnss.cc b/platform/linux/pal_gnss.cc
index effd2e9..ebb40b5 100644
--- a/platform/linux/pal_gnss.cc
+++ b/platform/linux/pal_gnss.cc
@@ -16,70 +16,85 @@
 
 #include "chre/platform/linux/pal_gnss.h"
 #include "chre/pal/gnss.h"
+#include "chre/platform/linux/task_util/task_manager.h"
+#include "chre/platform/log.h"
 
 #include "chre/util/memory.h"
 #include "chre/util/unique_ptr.h"
 
 #include <chrono>
 #include <cinttypes>
-#include <future>
-#include <thread>
+#include <mutex>
+#include <optional>
 
 /**
  * A simulated implementation of the GNSS PAL for the linux platform.
  */
 namespace {
+
+using chre::TaskManagerSingleton;
+
 const struct chrePalSystemApi *gSystemApi = nullptr;
 const struct chrePalGnssCallbacks *gCallbacks = nullptr;
 
-//! Thread to deliver asynchronous location data after a CHRE request.
-std::thread gLocationEventsThread;
-std::promise<void> gStopLocationEventsThread;
-std::promise<void> gStartLocationEvents;
+// Task to deliver asynchronous location data after a CHRE request.
+std::mutex gLocationEventsMutex;
+std::optional<uint32_t> gLocationEventsTaskId;
+std::optional<uint32_t> gLocationEventsChangeCallbackTaskId;
+uint32_t gLocationEventsMinIntervalMs = 0;
 bool gDelaySendingLocationEvents = false;
 bool gIsLocationEnabled = false;
 
-//! Thead to use when delivering a location status update.
-std::thread gLocationStatusThread;
+// Task to use when delivering a location status update.
+std::optional<uint32_t> gLocationStatusTaskId;
 
-//! Thread to deliver asynchronous measurement data after a CHRE request.
-std::thread gMeasurementEventsThread;
-std::promise<void> gStopMeasurementEventsThread;
+// Task to deliver asynchronous measurement data after a CHRE request.
+std::optional<uint32_t> gMeasurementEventsChangeCallbackTaskId;
+std::optional<uint32_t> gMeasurementEventsTaskId;
 bool gIsMeasurementEnabled = false;
 
-//! Thead to use when delivering a measurement status update.
-std::thread gMeasurementStatusThread;
+// Task to use when delivering a measurement status update.
+std::optional<uint32_t> gMeasurementStatusTaskId;
 
-void sendLocationEvents(uint32_t minIntervalMs) {
-  if (gDelaySendingLocationEvents) {
-    gStartLocationEvents.get_future().wait();
-  }
-  gCallbacks->locationStatusChangeCallback(true, CHRE_ERROR_NONE);
+// Passive listener flag.
+bool gIsPassiveListenerEnabled = false;
 
-  std::future<void> signal = gStopLocationEventsThread.get_future();
-  while (signal.wait_for(std::chrono::milliseconds(minIntervalMs)) ==
-         std::future_status::timeout) {
-    auto event = chre::MakeUniqueZeroFill<struct chreGnssLocationEvent>();
-    event->timestamp = gSystemApi->getCurrentTime();
-    gCallbacks->locationEventCallback(event.release());
+void sendLocationEvents() {
+  if (!gIsLocationEnabled) {
+    return;
   }
+
+  auto event = chre::MakeUniqueZeroFill<struct chreGnssLocationEvent>();
+  event->timestamp = gSystemApi->getCurrentTime();
+  gCallbacks->locationEventCallback(event.release());
 }
 
-void sendMeasurementEvents(uint32_t minIntervalMs) {
-  gCallbacks->measurementStatusChangeCallback(true, CHRE_ERROR_NONE);
-
-  std::future<void> signal = gStopMeasurementEventsThread.get_future();
-  while (signal.wait_for(std::chrono::milliseconds(minIntervalMs)) ==
-         std::future_status::timeout) {
-    auto event = chre::MakeUniqueZeroFill<struct chreGnssDataEvent>();
-    auto measurement = chre::MakeUniqueZeroFill<struct chreGnssMeasurement>();
-    measurement->c_n0_dbhz = 63.0f;
-
-    event->measurements = measurement.release();
-    event->measurement_count = 1;
-    event->clock.time_ns = static_cast<int64_t>(gSystemApi->getCurrentTime());
-    gCallbacks->measurementEventCallback(event.release());
+void startSendingLocationEvents(uint32_t minIntervalMs) {
+  std::lock_guard<std::mutex> lock(gLocationEventsMutex);
+  if (gLocationEventsTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gLocationEventsTaskId.value());
   }
+
+  gLocationEventsChangeCallbackTaskId = TaskManagerSingleton::get()->addTask(
+      []() { gCallbacks->locationStatusChangeCallback(true, CHRE_ERROR_NONE); },
+      std::chrono::milliseconds(0));
+
+  gLocationEventsTaskId = TaskManagerSingleton::get()->addTask(
+      sendLocationEvents, std::chrono::milliseconds(minIntervalMs));
+}
+
+void sendMeasurementEvents() {
+  if (!gIsMeasurementEnabled) {
+    return;
+  }
+
+  auto event = chre::MakeUniqueZeroFill<struct chreGnssDataEvent>();
+  auto measurement = chre::MakeUniqueZeroFill<struct chreGnssMeasurement>();
+  measurement->c_n0_dbhz = 63.0f;
+  event->measurement_count = 1;
+  event->clock.time_ns = static_cast<int64_t>(gSystemApi->getCurrentTime());
+  event->measurements = measurement.release();
+  gCallbacks->measurementEventCallback(event.release());
 }
 
 void stopLocation() {
@@ -90,23 +105,36 @@
   gCallbacks->measurementStatusChangeCallback(false, CHRE_ERROR_NONE);
 }
 
-void stopLocationThreads() {
-  if (gLocationEventsThread.joinable()) {
-    gStopLocationEventsThread.set_value();
-    gLocationEventsThread.join();
+void stopLocationTasks() {
+  {
+    std::lock_guard<std::mutex> lock(gLocationEventsMutex);
+    if (gLocationEventsChangeCallbackTaskId.has_value()) {
+      TaskManagerSingleton::get()->cancelTask(
+          gLocationEventsChangeCallbackTaskId.value());
+    }
+
+    if (gLocationEventsTaskId.has_value()) {
+      TaskManagerSingleton::get()->cancelTask(gLocationEventsTaskId.value());
+    }
   }
-  if (gLocationStatusThread.joinable()) {
-    gLocationStatusThread.join();
+
+  if (gLocationStatusTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gLocationStatusTaskId.value());
   }
 }
 
-void stopMeasurementThreads() {
-  if (gMeasurementEventsThread.joinable()) {
-    gStopMeasurementEventsThread.set_value();
-    gMeasurementEventsThread.join();
+void stopMeasurementTasks() {
+  if (gMeasurementEventsChangeCallbackTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(
+        gMeasurementEventsChangeCallbackTaskId.value());
   }
-  if (gMeasurementStatusThread.joinable()) {
-    gMeasurementStatusThread.join();
+
+  if (gMeasurementEventsTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gMeasurementEventsTaskId.value());
+  }
+
+  if (gMeasurementStatusTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gMeasurementStatusTaskId.value());
   }
 }
 
@@ -117,18 +145,23 @@
 
 bool chrePalControlLocationSession(bool enable, uint32_t minIntervalMs,
                                    uint32_t /* minTimeToNextFixMs */) {
-  stopLocationThreads();
+  stopLocationTasks();
 
-  if (enable) {
-    gStartLocationEvents = std::promise<void>();
-    gStopLocationEventsThread = std::promise<void>();
-    gLocationEventsThread = std::thread(sendLocationEvents, minIntervalMs);
-  } else {
-    gLocationStatusThread = std::thread(stopLocation);
+  gLocationEventsMinIntervalMs = minIntervalMs;
+  if (enable && !gDelaySendingLocationEvents) {
+    startSendingLocationEvents(minIntervalMs);
+    if (!gLocationEventsChangeCallbackTaskId.has_value() ||
+        !gLocationEventsTaskId.has_value()) {
+      return false;
+    }
+  } else if (!enable) {
+    gLocationStatusTaskId = TaskManagerSingleton::get()->addTask(stopLocation);
+    if (!gLocationStatusTaskId.has_value()) {
+      return false;
+    }
   }
 
   gIsLocationEnabled = enable;
-
   return true;
 }
 
@@ -137,18 +170,34 @@
 }
 
 bool chrePalControlMeasurementSession(bool enable, uint32_t minIntervalMs) {
-  stopMeasurementThreads();
+  stopMeasurementTasks();
 
   if (enable) {
-    gStopMeasurementEventsThread = std::promise<void>();
-    gMeasurementEventsThread =
-        std::thread(sendMeasurementEvents, minIntervalMs);
+    gMeasurementEventsChangeCallbackTaskId =
+        TaskManagerSingleton::get()->addTask(
+            []() {
+              gCallbacks->measurementStatusChangeCallback(true,
+                                                          CHRE_ERROR_NONE);
+            },
+            std::chrono::milliseconds(0));
+    if (!gMeasurementEventsChangeCallbackTaskId.has_value()) {
+      return false;
+    }
+
+    gMeasurementEventsTaskId = TaskManagerSingleton::get()->addTask(
+        sendMeasurementEvents, std::chrono::milliseconds(minIntervalMs));
+    if (!gMeasurementEventsTaskId.has_value()) {
+      return false;
+    }
   } else {
-    gMeasurementStatusThread = std::thread(stopMeasurement);
+    gMeasurementStatusTaskId =
+        TaskManagerSingleton::get()->addTask(stopMeasurement);
+    if (!gMeasurementStatusTaskId.has_value()) {
+      return false;
+    }
   }
 
   gIsMeasurementEnabled = enable;
-
   return true;
 }
 
@@ -159,8 +208,8 @@
 }
 
 void chrePalGnssApiClose() {
-  stopLocationThreads();
-  stopMeasurementThreads();
+  stopLocationTasks();
+  stopMeasurementTasks();
 }
 
 bool chrePalGnssApiOpen(const struct chrePalSystemApi *systemApi,
@@ -177,8 +226,6 @@
   return success;
 }
 
-bool gIsPassiveListenerEnabled = false;
-
 bool chrePalGnssconfigurePassiveLocationListener(bool enable) {
   gIsPassiveListenerEnabled = enable;
   return true;
@@ -204,7 +251,7 @@
 
 void chrePalGnssStartSendingLocationEvents() {
   CHRE_ASSERT(gDelaySendingLocationEvents);
-  gStartLocationEvents.set_value();
+  startSendingLocationEvents(gLocationEventsMinIntervalMs);
 }
 
 const struct chrePalGnssApi *chrePalGnssGetApi(uint32_t requestedApiVersion) {
diff --git a/platform/linux/pal_sensor.cc b/platform/linux/pal_sensor.cc
index e711513..d4df5d3 100644
--- a/platform/linux/pal_sensor.cc
+++ b/platform/linux/pal_sensor.cc
@@ -16,6 +16,7 @@
 
 #include "chre/pal/sensor.h"
 
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/platform/memory.h"
 #include "chre/util/macros.h"
 #include "chre/util/memory.h"
@@ -24,13 +25,14 @@
 #include <chrono>
 #include <cinttypes>
 #include <cstdint>
-#include <future>
-#include <thread>
 
 /**
  * A simulated implementation of the Sensor PAL for the linux platform.
  */
 namespace {
+
+using chre::TaskManagerSingleton;
+
 const struct chrePalSystemApi *gSystemApi = nullptr;
 const struct chrePalSensorCallbacks *gCallbacks = nullptr;
 
@@ -48,33 +50,32 @@
     },
 };
 
-//! Thread to deliver asynchronous sensor data after a CHRE request.
-std::thread gSensor0Thread;
-std::promise<void> gStopSensor0Thread;
+//! Task to deliver asynchronous sensor data after a CHRE request.
+std::optional<uint32_t> gSensor0TaskId;
 bool gIsSensor0Enabled = false;
 
-void stopSensor0Thread() {
-  if (gSensor0Thread.joinable()) {
-    gStopSensor0Thread.set_value();
-    gSensor0Thread.join();
+void stopSensor0Task() {
+  if (gSensor0TaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gSensor0TaskId.value());
   }
 }
 
 void chrePalSensorApiClose() {
-  stopSensor0Thread();
+  stopSensor0Task();
 }
 
 bool chrePalSensorApiOpen(const struct chrePalSystemApi *systemApi,
                           const struct chrePalSensorCallbacks *callbacks) {
   chrePalSensorApiClose();
 
+  bool success = false;
   if (systemApi != nullptr && callbacks != nullptr) {
     gSystemApi = systemApi;
     gCallbacks = callbacks;
-    return true;
+    success = true;
   }
 
-  return false;
+  return success;
 }
 
 bool chrePalSensorApiGetSensors(const struct chreSensorInfo **sensors,
@@ -96,20 +97,16 @@
   gCallbacks->samplingStatusUpdateCallback(0, status.release());
 }
 
-void sendSensor0Events(uint64_t intervalNs) {
-  std::future<void> signal = gStopSensor0Thread.get_future();
-  while (signal.wait_for(std::chrono::nanoseconds(intervalNs)) ==
-         std::future_status::timeout) {
-    auto data = chre::MakeUniqueZeroFill<struct chreSensorThreeAxisData>();
+void sendSensor0Events() {
+  auto data = chre::MakeUniqueZeroFill<struct chreSensorThreeAxisData>();
 
-    data->header.baseTimestamp = gSystemApi->getCurrentTime();
-    data->header.sensorHandle = 0;
-    data->header.readingCount = 1;
-    data->header.accuracy = CHRE_SENSOR_ACCURACY_UNRELIABLE;
-    data->header.reserved = 0;
+  data->header.baseTimestamp = gSystemApi->getCurrentTime();
+  data->header.sensorHandle = 0;
+  data->header.readingCount = 1;
+  data->header.accuracy = CHRE_SENSOR_ACCURACY_UNRELIABLE;
+  data->header.reserved = 0;
 
-    gCallbacks->dataEventCallback(0, data.release());
-  }
+  gCallbacks->dataEventCallback(0, data.release());
 }
 
 bool chrePalSensorApiConfigureSensor(uint32_t sensorInfoIndex,
@@ -126,16 +123,18 @@
   }
 
   if (mode == CHRE_SENSOR_CONFIGURE_MODE_CONTINUOUS) {
-    stopSensor0Thread();
+    stopSensor0Task();
     gIsSensor0Enabled = true;
     sendSensor0StatusUpdate(intervalNs, true /*enabled*/);
-    gStopSensor0Thread = std::promise<void>();
-    gSensor0Thread = std::thread(sendSensor0Events, intervalNs);
-    return true;
+    gSensor0TaskId = TaskManagerSingleton::get()->addTask(
+        sendSensor0Events,
+        std::chrono::duration_cast<std::chrono::milliseconds>(
+            std::chrono::nanoseconds(intervalNs)));
+    return gSensor0TaskId.has_value();
   }
 
   if (mode == CHRE_SENSOR_CONFIGURE_MODE_DONE) {
-    stopSensor0Thread();
+    stopSensor0Task();
     gIsSensor0Enabled = false;
     sendSensor0StatusUpdate(intervalNs, false /*enabled*/);
     return true;
diff --git a/platform/linux/pal_wifi.cc b/platform/linux/pal_wifi.cc
index b026631..a28205c 100644
--- a/platform/linux/pal_wifi.cc
+++ b/platform/linux/pal_wifi.cc
@@ -14,59 +14,99 @@
  * limitations under the License.
  */
 
-#include "chre/pal/wifi.h"
+#include "chre/platform/linux/pal_wifi.h"
 
-#include "chre/util/memory.h"
-#include "chre/util/unique_ptr.h"
-
-#include "chre/platform/linux/pal_nan.h"
-
+#include <atomic>
 #include <chrono>
 #include <cinttypes>
-#include <thread>
+#include <optional>
+
+#include "chre/pal/wifi.h"
+#include "chre/platform/linux/pal_nan.h"
+#include "chre/platform/linux/task_util/task_manager.h"
+#include "chre/util/enum.h"
+#include "chre/util/memory.h"
+#include "chre/util/time.h"
+#include "chre/util/unique_ptr.h"
 
 /**
  * A simulated implementation of the WiFi PAL for the linux platform.
  */
 namespace {
+
+using chre::TaskManagerSingleton;
+
 const struct chrePalSystemApi *gSystemApi = nullptr;
 const struct chrePalWifiCallbacks *gCallbacks = nullptr;
 
-//! Thread to deliver asynchronous WiFi scan results after a CHRE request.
-std::thread gScanEventsThread;
-
-//! Thread to use when delivering a scan monitor status update.
-std::thread gScanMonitorStatusThread;
-
 //! Whether scan monitoring is active.
-bool gScanMonitoringActive = false;
+std::atomic_bool gScanMonitoringActive(false);
+
+//! Whether PAL should respond to RRT ranging request.
+std::atomic_bool gEnableRangingResponse(true);
+
+//! Whether PAL should respond to configure scan monitor request.
+std::atomic_bool gEnableScanMonitorResponse(true);
+
+//! Whether PAL should respond to scan request.
+std::atomic_bool gEnableScanResponse(true);
+
+//! Task IDs for the scanning tasks
+std::optional<uint32_t> gScanMonitorTaskId;
+std::optional<uint32_t> gRequestScanTaskId;
+std::optional<uint32_t> gRequestRangingTaskId;
+
+//! How long should each the PAL hold before response.
+//! Use to mimic real world hardware process time.
+std::chrono::milliseconds gAsyncRequestDelayResponseTime[chre::asBaseType(
+    PalWifiAsyncRequestTypes::NUM_WIFI_REQUEST_TYPE)];
 
 void sendScanResponse() {
-  gCallbacks->scanResponseCallback(true, CHRE_ERROR_NONE);
+  if (gEnableScanResponse) {
+    auto event = chre::MakeUniqueZeroFill<struct chreWifiScanEvent>();
+    auto result = chre::MakeUniqueZeroFill<struct chreWifiScanResult>();
+    event->resultCount = 1;
+    event->resultTotal = 1;
+    event->referenceTime = gSystemApi->getCurrentTime();
+    event->results = result.release();
+    gCallbacks->scanEventCallback(event.release());
+  }
 
-  auto event = chre::MakeUniqueZeroFill<struct chreWifiScanEvent>();
-  auto result = chre::MakeUniqueZeroFill<struct chreWifiScanResult>();
-  event->resultCount = 1;
-  event->resultTotal = 1;
-  event->referenceTime = gSystemApi->getCurrentTime();
-  event->results = result.release();
-
-  gCallbacks->scanEventCallback(event.release());
+  // We just want to delay this task - only execute it once.
+  TaskManagerSingleton::get()->cancelTask(gRequestScanTaskId.value());
 }
 
 void sendScanMonitorResponse(bool enable) {
-  gCallbacks->scanMonitorStatusChangeCallback(enable, CHRE_ERROR_NONE);
-}
-
-void stopScanEventThreads() {
-  if (gScanEventsThread.joinable()) {
-    gScanEventsThread.join();
+  if (gEnableScanMonitorResponse) {
+    gCallbacks->scanMonitorStatusChangeCallback(enable, CHRE_ERROR_NONE);
   }
 }
 
-void stopScanMonitorThreads() {
-  if (gScanMonitorStatusThread.joinable()) {
-    gScanMonitorStatusThread.join();
+void sendRangingResponse() {
+  if (gEnableRangingResponse) {
+    auto event = chre::MakeUniqueZeroFill<struct chreWifiRangingEvent>();
+    auto result = chre::MakeUniqueZeroFill<struct chreWifiRangingResult>();
+    event->resultCount = 1;
+    event->results = result.release();
+    gCallbacks->rangingEventCallback(CHRE_ERROR_NONE, event.release());
+  }
+}
+
+void stopScanMonitorTask() {
+  if (gScanMonitorTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gScanMonitorTaskId.value());
+  }
+}
+
+void stopRequestScanTask() {
+  if (gRequestScanTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gRequestScanTaskId.value());
+  }
+}
+
+void stopRequestRangingTask() {
+  if (gRequestRangingTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(gRequestRangingTaskId.value());
   }
 }
 
@@ -76,26 +116,39 @@
 }
 
 bool chrePalWifiConfigureScanMonitor(bool enable) {
-  stopScanMonitorThreads();
+  stopScanMonitorTask();
 
-  gScanMonitorStatusThread = std::thread(sendScanMonitorResponse, enable);
+  gScanMonitorTaskId = TaskManagerSingleton::get()->addTask(
+      [enable]() { sendScanMonitorResponse(enable); });
   gScanMonitoringActive = enable;
-
-  return true;
+  return gScanMonitorTaskId.has_value();
 }
 
 bool chrePalWifiApiRequestScan(const struct chreWifiScanParams * /* params */) {
-  stopScanEventThreads();
+  stopRequestScanTask();
 
-  gScanEventsThread = std::thread(sendScanResponse);
-
-  return true;
+  std::optional<uint32_t> requestScanTaskCallbackId =
+      TaskManagerSingleton::get()->addTask([]() {
+        if (gEnableScanResponse) {
+          gCallbacks->scanResponseCallback(true, CHRE_ERROR_NONE);
+        }
+      });
+  if (requestScanTaskCallbackId.has_value()) {
+    gRequestScanTaskId = TaskManagerSingleton::get()->addTask(
+        sendScanResponse, gAsyncRequestDelayResponseTime[chre::asBaseType(
+                              PalWifiAsyncRequestTypes::SCAN)]);
+    return gRequestScanTaskId.has_value();
+  }
+  return false;
 }
 
 bool chrePalWifiApiRequestRanging(
     const struct chreWifiRangingParams * /* params */) {
-  // unimplemented
-  return false;
+  stopRequestRangingTask();
+
+  gRequestRangingTaskId =
+      TaskManagerSingleton::get()->addTask(sendRangingResponse);
+  return gRequestRangingTaskId.has_value();
 }
 
 void chrePalWifiApiReleaseScanEvent(struct chreWifiScanEvent *event) {
@@ -152,8 +205,9 @@
 }
 
 void chrePalWifiApiClose() {
-  stopScanEventThreads();
-  stopScanMonitorThreads();
+  stopScanMonitorTask();
+  stopRequestScanTask();
+  stopRequestRangingTask();
 }
 
 bool chrePalWifiApiOpen(const struct chrePalSystemApi *systemApi,
@@ -175,10 +229,37 @@
 
 }  // anonymous namespace
 
+void chrePalWifiEnableResponse(PalWifiAsyncRequestTypes requestType,
+                               bool enableResponse) {
+  switch (requestType) {
+    case PalWifiAsyncRequestTypes::RANGING:
+      gEnableRangingResponse = enableResponse;
+      break;
+
+    case PalWifiAsyncRequestTypes::SCAN_MONITORING:
+      gEnableScanMonitorResponse = enableResponse;
+      break;
+
+    case PalWifiAsyncRequestTypes::SCAN:
+      gEnableScanResponse = enableResponse;
+      break;
+
+    default:
+      LOGE("Cannot enable/disable request type: %" PRIu8,
+           static_cast<uint8_t>(requestType));
+  }
+}
+
 bool chrePalWifiIsScanMonitoringActive() {
   return gScanMonitoringActive;
 }
 
+void chrePalWifiDelayResponse(PalWifiAsyncRequestTypes requestType,
+                              std::chrono::seconds seconds) {
+  gAsyncRequestDelayResponseTime[chre::asBaseType(requestType)] =
+      std::chrono::duration_cast<std::chrono::milliseconds>(seconds);
+}
+
 const struct chrePalWifiApi *chrePalWifiGetApi(uint32_t requestedApiVersion) {
   static const struct chrePalWifiApi kApi = {
       .moduleVersion = CHRE_PAL_WIFI_API_CURRENT_VERSION,
diff --git a/platform/linux/pal_wwan.cc b/platform/linux/pal_wwan.cc
index e60abb1..b7a488c 100644
--- a/platform/linux/pal_wwan.cc
+++ b/platform/linux/pal_wwan.cc
@@ -16,22 +16,26 @@
 
 #include "chre/pal/wwan.h"
 
+#include "chre/platform/linux/task_util/task_manager.h"
+
 #include "chre/util/memory.h"
 #include "chre/util/unique_ptr.h"
 
 #include <chrono>
 #include <cinttypes>
-#include <thread>
 
 /**
  * A simulated implementation of the WWAN PAL for the linux platform.
  */
 namespace {
+
+using chre::TaskManagerSingleton;
+
 const struct chrePalSystemApi *gSystemApi = nullptr;
 const struct chrePalWwanCallbacks *gCallbacks = nullptr;
 
-//! Thread to deliver asynchronous WWAN cell info results after a CHRE request.
-std::thread gCellInfosThread;
+//! Task to deliver asynchronous WWAN cell info results after a CHRE request.
+std::optional<uint32_t> gCellInfosTaskId;
 
 void sendCellInfoResult() {
   auto result = chre::MakeUniqueZeroFill<struct chreWwanCellInfoResult>();
@@ -56,9 +60,9 @@
   gCallbacks->cellInfoResultCallback(result.release());
 }
 
-void stopCellInfoThread() {
-  if (gCellInfosThread.joinable()) {
-    gCellInfosThread.join();
+void stopCellInfoTask() {
+  if (gCellInfosTaskId.has_value()) {
+    TaskManagerSingleton::get()->cancelTask(*gCellInfosTaskId);
   }
 }
 
@@ -67,11 +71,9 @@
 }
 
 bool chrePalWwanRequestCellInfo() {
-  stopCellInfoThread();
-
-  gCellInfosThread = std::thread(sendCellInfoResult);
-
-  return true;
+  stopCellInfoTask();
+  gCellInfosTaskId = TaskManagerSingleton::get()->addTask(sendCellInfoResult);
+  return gCellInfosTaskId.has_value();
 }
 
 void chrePalWwanReleaseCellInfoResult(struct chreWwanCellInfoResult *result) {
@@ -82,7 +84,7 @@
 }
 
 void chrePalWwanApiClose() {
-  stopCellInfoThread();
+  stopCellInfoTask();
 }
 
 bool chrePalWwanApiOpen(const struct chrePalSystemApi *systemApi,
diff --git a/platform/linux/platform_nanoapp.cc b/platform/linux/platform_nanoapp.cc
index d3e02b9..1a4fb17 100644
--- a/platform/linux/platform_nanoapp.cc
+++ b/platform/linux/platform_nanoapp.cc
@@ -63,7 +63,7 @@
 
 bool PlatformNanoapp::supportsAppPermissions() const {
   return (mAppInfo != nullptr) ? (mAppInfo->structMinorVersion >=
-                                  CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION)
+                                  CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3)
                                : false;
 }
 
@@ -138,7 +138,7 @@
              mAppInfo->isTcmNanoapp, mAppInfo->isSystemNanoapp,
              mFilename.c_str());
         if (mAppInfo->structMinorVersion >=
-            CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION) {
+            CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3) {
           LOGI("Nanoapp permissions: 0x%" PRIx32, mAppInfo->appPermissions);
         }
       }
diff --git a/platform/linux/platform_pal.cc b/platform/linux/platform_pal.cc
index 0d61afb..c6a2a5d 100644
--- a/platform/linux/platform_pal.cc
+++ b/platform/linux/platform_pal.cc
@@ -16,8 +16,12 @@
 
 #include "chre/platform/shared/platform_pal.h"
 
+#include "chre/util/macros.h"
+
 namespace chre {
 
-void PlatformPal::prePalApiCall() const {}
+void PlatformPal::prePalApiCall(PalType palType) const {
+  UNUSED_VAR(palType);
+}
 
 }  // namespace chre
diff --git a/platform/linux/task_util/task.cc b/platform/linux/task_util/task.cc
new file mode 100644
index 0000000..f224c10
--- /dev/null
+++ b/platform/linux/task_util/task.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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/linux/task_util/task.h"
+
+namespace chre {
+namespace task_manager_internal {
+
+Task::Task(const TaskFunction &func, std::chrono::milliseconds repeatInterval,
+           uint32_t id)
+    : mExecutionTimestamp(std::chrono::steady_clock::now() + repeatInterval),
+      mRepeatInterval(repeatInterval),
+      mId(id),
+      mHasExecuted(false) {
+  if (func != nullptr) {
+    mFunc = func;
+  }
+}
+
+Task::Task(const Task &rhs)
+    : mExecutionTimestamp(rhs.mExecutionTimestamp),
+      mRepeatInterval(rhs.mRepeatInterval),
+      mFunc(rhs.mFunc),
+      mId(rhs.mId),
+      mHasExecuted(rhs.mHasExecuted) {}
+
+Task &Task::operator=(const Task &rhs) {
+  if (this != &rhs) {
+    Task rhsCopy(rhs);
+    std::swap(mExecutionTimestamp, rhsCopy.mExecutionTimestamp);
+    std::swap(mRepeatInterval, rhsCopy.mRepeatInterval);
+
+    {
+      std::lock_guard lock(mExecutionMutex);
+      std::swap(mFunc, rhsCopy.mFunc);
+    }
+
+    std::swap(mId, rhsCopy.mId);
+    std::swap(mHasExecuted, rhsCopy.mHasExecuted);
+  }
+  return *this;
+}
+
+void Task::cancel() {
+  std::lock_guard lock(mExecutionMutex);
+  mRepeatInterval = std::chrono::milliseconds(0);
+  mFunc.reset();
+}
+
+void Task::execute() {
+  TaskFunction func;
+  {
+    std::lock_guard lock(mExecutionMutex);
+    if (!mFunc.has_value()) {
+      return;
+    }
+
+    func = mFunc.value();
+  }
+  func();
+  mHasExecuted = true;
+  if (isRepeating()) {
+    mExecutionTimestamp = std::chrono::steady_clock::now() + mRepeatInterval;
+  }
+}
+
+}  // namespace task_manager_internal
+}  // namespace chre
diff --git a/platform/linux/task_util/task_manager.cc b/platform/linux/task_util/task_manager.cc
new file mode 100644
index 0000000..b2130b0
--- /dev/null
+++ b/platform/linux/task_util/task_manager.cc
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 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 <cassert>
+#include <iostream>
+#include <limits>
+#include <memory>
+
+#include "chre/platform/linux/task_util/task_manager.h"
+
+namespace chre {
+
+TaskManager::TaskManager()
+    : mQueue(std::greater<Task>()),
+      mCurrentTask(nullptr),
+      mContinueRunningThread(true),
+      mCurrentId(0) {
+  mThread = std::thread(&TaskManager::run, this);
+}
+
+TaskManager::~TaskManager() {
+  flushTasks();
+
+  {
+    std::lock_guard<std::mutex> lock(mMutex);
+    mContinueRunningThread = false;
+    mConditionVariable.notify_all();
+  }
+
+  if (mThread.joinable()) {
+    mThread.join();
+  }
+}
+
+std::optional<uint32_t> TaskManager::addTask(
+    const Task::TaskFunction &func, std::chrono::milliseconds repeatInterval) {
+  std::lock_guard<std::mutex> lock(mMutex);
+  bool success = false;
+
+  uint32_t returnId;
+  if (!mContinueRunningThread) {
+    LOGW("Execution thread is shutting down. Cannot add a task.");
+  } else {
+    // select the next ID
+    assert(mCurrentId < std::numeric_limits<uint32_t>::max());
+    returnId = mCurrentId++;
+    Task task(func, repeatInterval, returnId);
+    success = mQueue.push(task);
+  }
+
+  if (success) {
+    mConditionVariable.notify_all();
+    return returnId;
+  } else {
+    return std::optional<uint32_t>();
+  }
+}
+
+bool TaskManager::cancelTask(uint32_t taskId) {
+  std::lock_guard<std::mutex> lock(mMutex);
+
+  bool success = false;
+  if (!mContinueRunningThread) {
+    LOGW("Execution thread is shutting down. Cannot cancel a task.");
+  } else if (mCurrentTask != nullptr && mCurrentTask->getId() == taskId) {
+    // The currently executing task may want to cancel itself.
+    mCurrentTask->cancel();
+    success = true;
+  } else {
+    for (auto iter = mQueue.begin(); iter != mQueue.end(); ++iter) {
+      if (iter->getId() == taskId) {
+        iter->cancel();
+        success = true;
+        break;
+      }
+    }
+  }
+
+  return success;
+}
+
+void TaskManager::flushTasks() {
+  std::lock_guard<std::mutex> lock(mMutex);
+  while (!mQueue.empty()) {
+    mQueue.pop();
+  }
+}
+
+void TaskManager::run() {
+  while (true) {
+    Task task;
+    {
+      std::unique_lock<std::mutex> lock(mMutex);
+      mConditionVariable.wait(lock, [this]() {
+        return !mContinueRunningThread || !mQueue.empty();
+      });
+      if (!mContinueRunningThread) {
+        return;
+      }
+
+      task = mQueue.top();
+      if (!task.isReadyToExecute()) {
+        auto waitTime =
+            task.getExecutionTimestamp() - std::chrono::steady_clock::now();
+        if (waitTime.count() > 0) {
+          mConditionVariable.wait_for(lock, waitTime);
+        }
+
+        /**
+         * We continue here instead of executing the same task because we are
+         * not guaranteed that the condition variable was not spuriously woken
+         * up, and another task with a timestamp < the current task could have
+         * been added in the current time.
+         */
+        continue;
+      }
+
+      mQueue.pop();
+      mCurrentTask = &task;
+    }
+    task.execute();
+    {
+      std::lock_guard<std::mutex> lock(mMutex);
+      mCurrentTask = nullptr;
+
+      if (task.isRepeating() && !mQueue.push(task)) {
+        LOGE("TaskManager: Could not push task to priority queue");
+      }
+    }
+  }
+}
+
+}  // namespace chre
diff --git a/platform/linux/tests/task_manager_test.cc b/platform/linux/tests/task_manager_test.cc
new file mode 100644
index 0000000..6f52579
--- /dev/null
+++ b/platform/linux/tests/task_manager_test.cc
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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 <chrono>
+#include <cmath>
+#include <optional>
+#include <thread>
+
+#include "gtest/gtest.h"
+
+#include "chre/platform/linux/task_util/task_manager.h"
+
+namespace {
+
+uint32_t gVarTaskManager = 0;
+uint32_t gTask1Var = 0;
+uint32_t gTask2Var = 0;
+
+constexpr auto incrementGVar = []() { ++gVarTaskManager; };
+constexpr auto task1Func = []() { ++gTask1Var; };
+constexpr auto task2Func = []() { ++gTask2Var; };
+
+TEST(TaskManager, FlushTasks) {
+  chre::TaskManager taskManager;
+  for (uint32_t i = 0; i < 50; ++i) {
+    taskManager.flushTasks();
+  }
+}
+
+TEST(TaskManager, MultipleNonRepeatingTasks) {
+  chre::TaskManager taskManager;
+  gVarTaskManager = 0;
+  constexpr uint32_t numTasks = 50;
+  for (uint32_t i = 0; i < numTasks; ++i) {
+    taskManager.addTask(incrementGVar, std::chrono::milliseconds(0));
+    std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  }
+  taskManager.flushTasks();
+  EXPECT_TRUE(gVarTaskManager == numTasks);
+}
+
+TEST(TaskManager, MultipleTypesOfTasks) {
+  chre::TaskManager taskManager;
+  gVarTaskManager = 0;
+  constexpr uint32_t numTasks = 50;
+  for (uint32_t i = 0; i < numTasks; ++i) {
+    taskManager.addTask(incrementGVar, std::chrono::milliseconds(0));
+    std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  }
+  uint32_t millisecondsToRepeat = 100;
+  std::optional<uint32_t> taskId = taskManager.addTask(
+      incrementGVar, std::chrono::milliseconds(millisecondsToRepeat));
+  EXPECT_TRUE(taskId.has_value());
+  uint32_t taskRepeatTimesMax = 11;
+  std::this_thread::sleep_for(
+      std::chrono::milliseconds(millisecondsToRepeat * taskRepeatTimesMax));
+  EXPECT_TRUE(taskManager.cancelTask(taskId.value()));
+  taskManager.flushTasks();
+  EXPECT_TRUE(gVarTaskManager >= numTasks + taskRepeatTimesMax - 1);
+}
+
+TEST(TaskManager, FlushTasksWithoutCancel) {
+  chre::TaskManager taskManager;
+  gVarTaskManager = 0;
+  constexpr uint32_t numTasks = 50;
+  for (uint32_t i = 0; i < numTasks; ++i) {
+    taskManager.addTask(incrementGVar, std::chrono::milliseconds(0));
+    std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  }
+  uint32_t millisecondsToRepeat = 100;
+  std::optional<uint32_t> taskId = taskManager.addTask(
+      incrementGVar, std::chrono::milliseconds(millisecondsToRepeat));
+  EXPECT_TRUE(taskId.has_value());
+  uint32_t taskRepeatTimesMax = 11;
+  std::this_thread::sleep_for(
+      std::chrono::milliseconds(millisecondsToRepeat * taskRepeatTimesMax));
+  taskManager.flushTasks();
+  EXPECT_TRUE(gVarTaskManager >= numTasks + taskRepeatTimesMax - 1);
+}
+
+}  // namespace
diff --git a/platform/linux/tests/task_test.cc b/platform/linux/tests/task_test.cc
new file mode 100644
index 0000000..f20823d
--- /dev/null
+++ b/platform/linux/tests/task_test.cc
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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 <chrono>
+#include <thread>
+
+#include "gtest/gtest.h"
+
+#include "chre/platform/linux/task_util/task.h"
+
+namespace {
+
+using chre::task_manager_internal::Task;
+
+uint32_t gVarTask = 0;
+
+constexpr auto incrementGVar = []() { ++gVarTask; };
+
+TEST(Task, Execute) {
+  gVarTask = 0;
+  std::chrono::milliseconds waitTime(1000);
+  Task task(incrementGVar, waitTime, 0);
+  EXPECT_FALSE(task.isReadyToExecute());
+  std::this_thread::sleep_for(waitTime);
+  EXPECT_TRUE(task.isReadyToExecute());
+  task.execute();
+  EXPECT_TRUE(gVarTask == 1);
+  EXPECT_TRUE(task.isRepeating());
+  EXPECT_FALSE(task.isReadyToExecute());
+  auto timeDiff =
+      std::chrono::steady_clock::now() - task.getExecutionTimestamp();
+  EXPECT_TRUE(
+      std::chrono::duration_cast<std::chrono::milliseconds>(timeDiff).count() <=
+      waitTime.count());
+  task.cancel();
+  EXPECT_FALSE(task.isRepeating());
+}
+
+TEST(Task, ExecuteNoRepeat) {
+  gVarTask = 0;
+  std::chrono::milliseconds waitTime(0);
+  Task task(incrementGVar, waitTime, 0);
+  EXPECT_TRUE(task.isReadyToExecute());
+  task.execute();
+  EXPECT_TRUE(gVarTask == 1);
+  EXPECT_TRUE(task.isReadyToExecute());
+  EXPECT_FALSE(task.isRepeating());
+}
+
+TEST(Task, ComparisonOperators) {
+  constexpr uint32_t numTasks = 6;
+  Task tasks[numTasks] = {Task(incrementGVar, std::chrono::milliseconds(0), 0),
+                          Task(incrementGVar, std::chrono::milliseconds(1), 1),
+                          Task(incrementGVar, std::chrono::milliseconds(2), 2),
+                          Task(incrementGVar, std::chrono::milliseconds(3), 3),
+                          Task(incrementGVar, std::chrono::milliseconds(4), 4),
+                          Task(incrementGVar, std::chrono::milliseconds(5), 5)};
+
+  for (uint32_t i = 0; i < numTasks; ++i) {
+    if (i < numTasks - 1) {
+      EXPECT_TRUE(tasks[i] < tasks[i + 1]);
+      EXPECT_TRUE(tasks[i] <= tasks[i + 1]);
+      EXPECT_FALSE(tasks[i] > tasks[i + 1]);
+      EXPECT_FALSE(tasks[i] >= tasks[i + 1]);
+    }
+  }
+}
+
+}  // namespace
diff --git a/platform/platform.mk b/platform/platform.mk
index 0ff2165..cc459c0 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -27,12 +27,16 @@
 SLPI_CFLAGS += -I$(SLPI_PREFIX)/platform/inc/stddef
 SLPI_CFLAGS += -I$(SLPI_PREFIX)/platform/rtld/inc
 
+SLPI_CFLAGS += -Iplatform/shared/aligned_alloc_unsupported/include
 SLPI_CFLAGS += -Iplatform/shared/include
 SLPI_CFLAGS += -Iplatform/slpi/include
 
 # We use FlatBuffers in the SLPI platform layer
 SLPI_CFLAGS += $(FLATBUFFERS_CFLAGS)
 
+# SLPI still uses static event loop as oppose to heap based dynamic event loop
+SLPI_CFLAGS += -DCHRE_STATIC_EVENT_LOOP
+
 # SLPI/SEE-specific Compiler Flags #############################################
 
 # Include paths.
@@ -97,6 +101,7 @@
 SLPI_SRCS += platform/shared/chre_api_version.cc
 SLPI_SRCS += platform/shared/chre_api_wifi.cc
 SLPI_SRCS += platform/shared/chre_api_wwan.cc
+SLPI_SRCS += platform/shared/host_link.cc
 SLPI_SRCS += platform/shared/host_protocol_chre.cc
 SLPI_SRCS += platform/shared/host_protocol_common.cc
 SLPI_SRCS += platform/shared/memory_manager.cc
@@ -104,8 +109,8 @@
 SLPI_SRCS += platform/shared/nanoapp/nanoapp_dso_util.cc
 SLPI_SRCS += platform/shared/pal_system_api.cc
 SLPI_SRCS += platform/shared/platform_debug_dump_manager.cc
-SLPI_SRCS += platform/shared/pw_tokenized_log.cc
 SLPI_SRCS += platform/shared/system_time.cc
+SLPI_SRCS += platform/shared/tracing.cc
 SLPI_SRCS += platform/shared/version.cc
 SLPI_SRCS += platform/slpi/chre_api_re.cc
 SLPI_SRCS += platform/slpi/fatal_error.cc
@@ -202,13 +207,12 @@
 SIM_SRCS += platform/linux/platform_debug_dump_manager.cc
 SIM_SRCS += platform/linux/platform_log.cc
 SIM_SRCS += platform/linux/platform_pal.cc
-SIM_SRCS += platform/linux/platform_sensor.cc
-SIM_SRCS += platform/linux/platform_sensor_type_helpers.cc
 SIM_SRCS += platform/linux/power_control_manager.cc
 SIM_SRCS += platform/linux/system_time.cc
 SIM_SRCS += platform/linux/system_timer.cc
 SIM_SRCS += platform/linux/platform_nanoapp.cc
-SIM_SRCS += platform/linux/platform_sensor.cc
+SIM_SRCS += platform/linux/task_util/task.cc
+SIM_SRCS += platform/linux/task_util/task_manager.cc
 SIM_SRCS += platform/shared/chre_api_audio.cc
 SIM_SRCS += platform/shared/chre_api_ble.cc
 SIM_SRCS += platform/shared/chre_api_core.cc
@@ -223,6 +227,7 @@
 SIM_SRCS += platform/shared/nanoapp/nanoapp_dso_util.cc
 SIM_SRCS += platform/shared/pal_system_api.cc
 SIM_SRCS += platform/shared/system_time.cc
+SIM_SRCS += platform/shared/tracing.cc
 SIM_SRCS += platform/shared/version.cc
 
 # Optional audio support.
@@ -244,8 +249,11 @@
 
 # Optional sensor support.
 ifeq ($(CHRE_SENSORS_SUPPORT_ENABLED), true)
+SIM_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/sensor_pal/include
 SIM_SRCS += platform/linux/pal_sensor.cc
-SIM_SRCS += platform/shared/platform_sensor_manager.cc
+SIM_SRCS += platform/shared/sensor_pal/platform_sensor.cc
+SIM_SRCS += platform/shared/sensor_pal/platform_sensor_manager.cc
+SIM_SRCS += platform/shared/sensor_pal/platform_sensor_type_helpers.cc
 endif
 
 # Optional Wi-Fi support.
@@ -271,6 +279,8 @@
 
 GOOGLE_X86_LINUX_SRCS += platform/linux/init.cc
 GOOGLE_X86_LINUX_SRCS += platform/linux/assert.cc
+GOOGLE_X86_LINUX_SRCS += platform/linux/task_util/task.cc
+GOOGLE_X86_LINUX_SRCS += platform/linux/task_util/task_manager.cc
 
 # Optional audio support.
 ifeq ($(CHRE_AUDIO_SUPPORT_ENABLED), true)
@@ -337,8 +347,175 @@
 GOOGLETEST_COMMON_SRCS += platform/linux/assert.cc
 GOOGLETEST_COMMON_SRCS += platform/linux/sim/audio_source.cc
 GOOGLETEST_COMMON_SRCS += platform/linux/sim/platform_audio.cc
+GOOGLETEST_COMMON_SRCS += platform/linux/tests/task_test.cc
+GOOGLETEST_COMMON_SRCS += platform/linux/tests/task_manager_test.cc
 GOOGLETEST_COMMON_SRCS += platform/tests/log_buffer_test.cc
 GOOGLETEST_COMMON_SRCS += platform/shared/log_buffer.cc
 ifeq ($(CHRE_WIFI_NAN_SUPPORT_ENABLED), true)
 GOOGLETEST_COMMON_SRCS += platform/linux/pal_nan.cc
 endif
+
+# EmbOS specific compiler flags
+EMBOS_CFLAGS += -I$(CHRE_PREFIX)/platform/embos/include
+EMBOS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/aligned_alloc_unsupported/include
+EMBOS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include
+EMBOS_CFLAGS += $(FLATBUFFERS_CFLAGS)
+
+# The IAR flavor of EmbOS's RTOS.h includes an intrinsics.h header for
+# optimized enabling and disabling interrupts. We add an empty header to that
+# name in the path below, and let the linker deal with finding the symbol.
+EMBOS_CFLAGS += -I$(CHRE_PREFIX)/platform/embos/include/chre/embos
+
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/arm/nanoapp_loader.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/embos/context.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/embos/init.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/embos/memory.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/embos/memory_manager.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/embos/system_timer.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/assert.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_audio.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_ble.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_core.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_gnss.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_re.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_user_settings.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_version.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_wifi.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_wwan.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/host_protocol_chre.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/host_protocol_common.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/dlfcn.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/dram_vote_client.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/memory_manager.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/pal_system_api.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/pal_sensor_stub.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/platform_debug_dump_manager.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/system_time.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/tracing.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/version.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/nanoapp/nanoapp_dso_util.cc
+EMBOS_SRCS += $(CHRE_PREFIX)/platform/shared/nanoapp_loader.cc
+
+# Exynos specific compiler flags
+EXYNOS_CFLAGS += -I$(CHRE_PREFIX)/platform/exynos/include
+EXYNOS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/audio_pal/include
+
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/chre_api_re.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/shared/host_link.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/host_link.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/memory.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/platform_cache_management.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/platform_nanoapp.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/platform_pal.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/power_control_manager.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/exynos/system_time.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/shared/nanoapp_load_manager.cc
+
+EXYNOS_SRCS += $(FLATBUFFERS_SRCS)
+
+# Optional sensors support
+ifeq ($(CHRE_SENSORS_SUPPORT_ENABLED), true)
+EXYNOS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/sensor_pal/include
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_sensor.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/shared/sensor_pal/platform_sensor_manager.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/shared/sensor_pal/platform_sensor.cc
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/shared/sensor_pal/platform_sensor_type_helpers.cc
+endif
+
+ifeq ($(CHRE_AUDIO_SUPPORT_ENABLED), true)
+EXYNOS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/audio_pal/include
+EXYNOS_SRCS += $(CHRE_PREFIX)/platform/shared/audio_pal/platform_audio.cc
+endif
+
+# ARM specific compiler flags
+ARM_CFLAGS += -I$(CHRE_PREFIX)/platform/arm/include
+
+# Tinysys Configurations ######################################################
+
+# Tinysys sources
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/authentication.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/chre_api_re.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/chre_init.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/condition_variable_base.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/host_cpu_update.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/host_link.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/log_buffer_manager.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/memory.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/platform_cache_management.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/platform_pal.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/stdlib_wrapper.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/system_time.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/system_timer.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/power_control_manager.cc
+
+# Freertos sources
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/freertos/context.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/freertos/init.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/freertos/platform_nanoapp.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/freertos/memory_manager.cc
+
+# RISCV
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/riscv/nanoapp_loader.cc
+
+# Shared sources
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/assert.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_audio.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_ble.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_core.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_gnss.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_re.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_user_settings.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_version.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_wifi.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_wwan.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/dram_vote_client.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/dlfcn.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/host_link.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/host_protocol_chre.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/host_protocol_common.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/log_buffer.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/log_buffer_manager.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/memory_manager.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/nanoapp_load_manager.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/nanoapp_loader.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/pal_system_api.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/platform_debug_dump_manager.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/system_time.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/tracing.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/version.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/nanoapp/nanoapp_dso_util.cc
+TINYSYS_SRCS += $(MBEDTLS_SRCS)
+
+ifeq ($(CHRE_BLE_SUPPORT_ENABLED), true)
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/platform_ble.cc
+endif
+
+ifeq ($(CHRE_SENSORS_SUPPORT_ENABLED), true)
+TINYSYS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/sensor_pal/include
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/chre_api_sensor.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/sensor_pal/platform_sensor_manager.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/sensor_pal/platform_sensor.cc
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/shared/sensor_pal/platform_sensor_type_helpers.cc
+endif
+
+ifeq ($(CHRE_AUDIO_SUPPORT_ENABLED), true)
+TINYSYS_SRCS += $(CHRE_PREFIX)/platform/tinysys/platform_audio.cc
+endif
+
+# Compiler flags
+
+# Variables
+TINYSYS_PLATFORM = mt6985
+
+# CHRE include paths
+TINYSYS_CFLAGS += -I$(CHRE_PREFIX)/platform/tinysys/include
+TINYSYS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include
+TINYSYS_CFLAGS += -I$(CHRE_PREFIX)/platform/freertos/include
+TINYSYS_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include/chre/platform/shared/libc
+
+TINYSYS_CFLAGS += $(FLATBUFFERS_CFLAGS)
+TINYSYS_CFLAGS += $(MBEDTLS_CFLAGS)
+
+TINYSYS_CFLAGS += -DCFG_DRAM_HEAP_SUPPORT
+TINYSYS_CFLAGS += -DCHRE_LOADER_ARCH=EM_RISCV
+TINYSYS_CFLAGS += -DCHRE_NANOAPP_LOAD_ALIGNMENT=4096
diff --git a/platform/riscv/nanoapp_loader.cc b/platform/riscv/nanoapp_loader.cc
new file mode 100644
index 0000000..8c5c0cd
--- /dev/null
+++ b/platform/riscv/nanoapp_loader.cc
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 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_loader.h"
+
+namespace chre {
+
+bool NanoappLoader::relocateTable(DynamicHeader *dyn, int tag) {
+  bool success = false;
+  if (dyn == nullptr) {
+    return false;
+  }
+
+  switch (tag) {
+    case DT_RELA: {
+      if (getDynEntry(dyn, tag) == 0) {
+        LOGE("RISC-V Elf binaries must have DT_RELA dynamic entry");
+        break;
+      }
+
+      // The value of the RELA entry in dynamic table is the sh_addr field
+      // of ".rela.dyn" section header. We actually need to use the sh_offset
+      // which is usually the same, but on occasions can be different.
+      SectionHeader *dynamicRelaTablePtr = getSectionHeader(".rela.dyn");
+      CHRE_ASSERT(dynamicRelaTablePtr != nullptr);
+      ElfRela *reloc =
+          reinterpret_cast<ElfRela *>(mBinary + dynamicRelaTablePtr->sh_offset);
+      size_t relocSize = dynamicRelaTablePtr->sh_size;
+      size_t nRelocs = relocSize / sizeof(ElfRela);
+      LOGV("Relocation %zu entries in DT_RELA table", nRelocs);
+
+      for (size_t i = 0; i < nRelocs; ++i) {
+        ElfRela *curr = &reloc[i];
+        int relocType = ELFW_R_TYPE(curr->r_info);
+        ElfAddr *addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
+
+        switch (relocType) {
+          case R_RISCV_RELATIVE:
+            LOGV("Resolving RISCV_RELATIVE at offset %lx",
+                 static_cast<long unsigned int>(curr->r_offset));
+            // TODO(b/155512914): When we move to DRAM allocations, we need to
+            // check if the above address is in a Read-Only section of memory,
+            // and give it temporary write permission if that is the case.
+            *addr = reinterpret_cast<uintptr_t>(mMapping + curr->r_addend);
+            break;
+
+          case R_RISCV_32: {
+            LOGV("Resolving RISCV_32 at offset %lx",
+                 static_cast<long unsigned int>(curr->r_offset));
+            size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
+            auto *dynamicSymbolTable =
+                reinterpret_cast<ElfSym *>(getDynamicSymbolTable());
+            ElfSym *sym = &dynamicSymbolTable[posInSymbolTable];
+            *addr = reinterpret_cast<uintptr_t>(mMapping + sym->st_value);
+
+            break;
+          }
+
+          default:
+            LOGE("Unsupported relocation type %u", relocType);
+            break;
+        }
+      }
+      success = true;
+      break;
+    }
+    case DT_REL:
+      // Not required for RISC-V
+      success = true;
+      break;
+    default:
+      LOGE("Unsupported table tag %d", tag);
+  }
+
+  return success;
+}
+
+bool NanoappLoader::resolveGot() {
+  ElfAddr *addr;
+  ElfRela *reloc = reinterpret_cast<ElfRela *>(
+      mMapping + getDynEntry(getDynamicHeader(), DT_JMPREL));
+  size_t relocSize = getDynEntry(getDynamicHeader(), DT_PLTRELSZ);
+  size_t nRelocs = relocSize / sizeof(ElfRela);
+  LOGV("Resolving GOT with %zu relocations", nRelocs);
+
+  for (size_t i = 0; i < nRelocs; ++i) {
+    ElfRela *curr = &reloc[i];
+    int relocType = ELFW_R_TYPE(curr->r_info);
+
+    switch (relocType) {
+      case R_RISCV_JUMP_SLOT: {
+        LOGV("Resolving RISCV_JUMP_SLOT at offset %lx, %d",
+             static_cast<long unsigned int>(curr->r_offset), curr->r_addend);
+        addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
+        size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
+        void *resolved = resolveData(posInSymbolTable);
+        if (resolved == nullptr) {
+          LOGV("Failed to resolve symbol(%zu) at offset 0x%x", i,
+               curr->r_offset);
+          return false;
+        }
+        *addr = reinterpret_cast<ElfAddr>(resolved) + curr->r_addend;
+        break;
+      }
+
+      default:
+        LOGE("Unsupported relocation type: %u for symbol %s", relocType,
+             getDataName(getDynamicSymbol(ELFW_R_SYM(curr->r_info))));
+        return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace chre
diff --git a/platform/shared/aligned_alloc_unsupported/include/chre/target_platform/memory_impl.h b/platform/shared/aligned_alloc_unsupported/include/chre/target_platform/memory_impl.h
new file mode 100644
index 0000000..b749b95
--- /dev/null
+++ b/platform/shared/aligned_alloc_unsupported/include/chre/target_platform/memory_impl.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 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_SHARED_ALIGNED_ALLOC_UNSUPPORTED_MEMORY_IMPL_H_
+#define CHRE_SHARED_ALIGNED_ALLOC_UNSUPPORTED_MEMORY_IMPL_H_
+
+#include <cstring>
+
+#include "chre/util/always_false.h"
+
+namespace chre {
+
+template <typename T>
+inline T *memoryAlignedAlloc() {
+  static_assert(AlwaysFalse<T>::value,
+                "memoryAlignedAlloc is unsupported on this platform");
+  return nullptr;
+}
+
+}  // namespace chre
+
+#endif  // CHRE_SHARED_ALIGNED_ALLOC_UNSUPPORTED_MEMORY_IMPL_H_
diff --git a/platform/linux/testing/include/chre/target_platform/platform_audio_base.h b/platform/shared/audio_pal/include/chre/target_platform/platform_audio_base.h
similarity index 87%
rename from platform/linux/testing/include/chre/target_platform/platform_audio_base.h
rename to platform/shared/audio_pal/include/chre/target_platform/platform_audio_base.h
index 73a89dc..427173c 100644
--- a/platform/linux/testing/include/chre/target_platform/platform_audio_base.h
+++ b/platform/shared/audio_pal/include/chre/target_platform/platform_audio_base.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_PLATFORM_LINUX_PLATFORM_AUDIO_BASE_H_
-#define CHRE_PLATFORM_LINUX_PLATFORM_AUDIO_BASE_H_
+#ifndef CHRE_PLATFORM_SHARED_AUDIO_PAL_PLATFORM_AUDIO_BASE_H_
+#define CHRE_PLATFORM_SHARED_AUDIO_PAL_PLATFORM_AUDIO_BASE_H_
 
 #include "chre/pal/audio.h"
 #include "chre/platform/shared/platform_pal.h"
@@ -41,4 +41,4 @@
 
 }  // namespace chre
 
-#endif  // CHRE_PLATFORM_LINUX_PLATFORM_AUDIO_BASE_H_
+#endif  // CHRE_PLATFORM_SHARED_AUDIO_PAL_PLATFORM_AUDIO_BASE_H_
diff --git a/platform/linux/testing/platform_audio.cc b/platform/shared/audio_pal/platform_audio.cc
similarity index 92%
rename from platform/linux/testing/platform_audio.cc
rename to platform/shared/audio_pal/platform_audio.cc
index e7add9b..4662a93 100644
--- a/platform/linux/testing/platform_audio.cc
+++ b/platform/shared/audio_pal/platform_audio.cc
@@ -36,14 +36,14 @@
 PlatformAudio::~PlatformAudio() {
   if (mApi != nullptr) {
     LOGD("Platform audio closing");
-    prePalApiCall();
+    prePalApiCall(PalType::AUDIO);
     mApi->close();
     LOGD("Platform audio closed");
   }
 }
 
 void PlatformAudio::init() {
-  prePalApiCall();
+  prePalApiCall(PalType::AUDIO);
   mApi = chrePalAudioGetApi(CHRE_PAL_AUDIO_API_CURRENT_VERSION);
   if (mApi != nullptr) {
     if (!mApi->open(&gChrePalSystemApi, &sCallbacks)) {
@@ -66,7 +66,7 @@
 bool PlatformAudio::requestAudioDataEvent(uint32_t handle, uint32_t numSamples,
                                           Nanoseconds eventDelay) {
   if (mApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::AUDIO);
     return mApi->requestAudioDataEvent(handle, numSamples,
                                        eventDelay.toRawNanoseconds());
   }
@@ -76,21 +76,21 @@
 
 void PlatformAudio::cancelAudioDataEventRequest(uint32_t handle) {
   if (mApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::AUDIO);
     mApi->cancelAudioDataEvent(handle);
   }
 }
 
 void PlatformAudio::releaseAudioDataEvent(struct chreAudioDataEvent *event) {
   if (mApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::AUDIO);
     mApi->releaseAudioDataEvent(event);
   }
 }
 
 size_t PlatformAudio::getSourceCount() {
   if (mApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::AUDIO);
     return static_cast<size_t>(mApi->getSourceCount());
   }
 
@@ -100,7 +100,7 @@
 bool PlatformAudio::getAudioSource(uint32_t handle,
                                    chreAudioSource *audioSource) const {
   if (mApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::AUDIO);
     return mApi->getAudioSource(handle, audioSource);
   }
 
diff --git a/platform/shared/chre_api_ble.cc b/platform/shared/chre_api_ble.cc
index 8d01ed1..5b725b1 100644
--- a/platform/shared/chre_api_ble.cc
+++ b/platform/shared/chre_api_ble.cc
@@ -44,6 +44,10 @@
 #endif  // CHRE_BLE_SUPPORT_ENABLED
 }
 
+DLL_EXPORT bool chreBleFlushAsync(const void * /* cookie */) {
+  return false;
+}
+
 DLL_EXPORT bool chreBleStartScanAsync(chreBleScanMode mode,
                                       uint32_t reportDelayMs,
                                       const struct chreBleScanFilter *filter) {
@@ -71,3 +75,29 @@
   return false;
 #endif  // CHRE_BLE_SUPPORT_ENABLED
 }
+
+DLL_EXPORT bool chreBleReadRssiAsync(uint16_t connectionHandle,
+                                     const void *cookie) {
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+  chre::Nanoapp *nanoapp = EventLoopManager::validateChreApiCall(__func__);
+  return nanoapp->permitPermissionUse(NanoappPermissions::CHRE_PERMS_BLE) &&
+         EventLoopManagerSingleton::get()->getBleRequestManager().readRssiAsync(
+             nanoapp, connectionHandle, cookie);
+#else
+  UNUSED_VAR(connectionHandle);
+  UNUSED_VAR(cookie);
+  return false;
+#endif  // CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+}
+
+DLL_EXPORT bool chreBleGetScanStatus(struct chreBleScanStatus *status) {
+#ifdef CHRE_BLE_SUPPORT_ENABLED
+  chre::Nanoapp *nanoapp = EventLoopManager::validateChreApiCall(__func__);
+  return nanoapp->permitPermissionUse(NanoappPermissions::CHRE_PERMS_BLE) &&
+         EventLoopManagerSingleton::get()->getBleRequestManager().getScanStatus(
+             status);
+#else
+  UNUSED_VAR(status);
+  return false;
+#endif  // CHRE_BLE_SUPPORT_ENABLED
+}
\ No newline at end of file
diff --git a/platform/shared/chre_api_core.cc b/platform/shared/chre_api_core.cc
index d9a05ec..44b0d62 100644
--- a/platform/shared/chre_api_core.cc
+++ b/platform/shared/chre_api_core.cc
@@ -20,7 +20,7 @@
 
 #include "chre/core/event_loop_manager.h"
 #include "chre/core/host_comms_manager.h"
-#include "chre/core/host_notifications.h"
+#include "chre/core/host_endpoint_manager.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/log.h"
 #include "chre/util/macros.h"
@@ -167,5 +167,7 @@
 
 DLL_EXPORT bool chreGetHostEndpointInfo(uint16_t hostEndpointId,
                                         struct chreHostEndpointInfo *info) {
-  return chre::getHostEndpointInfo(hostEndpointId, info);
+  return EventLoopManagerSingleton::get()
+      ->getHostEndpointManager()
+      .getHostEndpointInfo(hostEndpointId, info);
 }
diff --git a/platform/shared/chre_api_re.cc b/platform/shared/chre_api_re.cc
index eb0c754..3711f79 100644
--- a/platform/shared/chre_api_re.cc
+++ b/platform/shared/chre_api_re.cc
@@ -61,7 +61,7 @@
       .cancelNanoappTimer(nanoapp, timerId);
 }
 
-DLL_EXPORT void *chreHeapAlloc(uint32_t bytes) {
+MALLOC_ATTR DLL_EXPORT void *chreHeapAlloc(uint32_t bytes) {
   chre::Nanoapp *nanoapp = EventLoopManager::validateChreApiCall(__func__);
   return chre::EventLoopManagerSingleton::get()
       ->getMemoryManager()
diff --git a/platform/shared/chre_api_version.cc b/platform/shared/chre_api_version.cc
index d39704a..23b3091 100644
--- a/platform/shared/chre_api_version.cc
+++ b/platform/shared/chre_api_version.cc
@@ -27,12 +27,16 @@
 static_assert(CHRE_PLATFORM_ID <= UINT64_MAX,
               "Platform ID must fit in 64 bits");
 
+extern "C" DLL_EXPORT const uint16_t _chrePatchVersion
+    __attribute__((section(".unstable_id"))) __attribute__((aligned(8))) =
+        CHRE_PATCH_VERSION;
+
 DLL_EXPORT uint32_t chreGetApiVersion(void) {
   return CHRE_API_VERSION;
 }
 
 DLL_EXPORT uint32_t chreGetVersion(void) {
-  return chreGetApiVersion() | CHRE_PATCH_VERSION;
+  return chreGetApiVersion() | _chrePatchVersion;
 }
 
 DLL_EXPORT uint64_t chreGetPlatformId(void) {
diff --git a/platform/shared/chre_api_wifi.cc b/platform/shared/chre_api_wifi.cc
index 48fb9be..dd8bea2 100644
--- a/platform/shared/chre_api_wifi.cc
+++ b/platform/shared/chre_api_wifi.cc
@@ -110,3 +110,10 @@
   return false;
 #endif  // CHRE_WIFI_SUPPORT_ENABLED
 }
+
+DLL_EXPORT bool chreWifiNanGetCapabilities(
+    struct chreWifiNanCapabilities *capabilities) {
+  // Not implemented yet.
+  UNUSED_VAR(capabilities);
+  return false;
+}
diff --git a/platform/shared/dram_vote_client.cc b/platform/shared/dram_vote_client.cc
index 13030c6..6cc484c 100644
--- a/platform/shared/dram_vote_client.cc
+++ b/platform/shared/dram_vote_client.cc
@@ -44,7 +44,7 @@
     // TODO(b/181172259): Change back to LOGW once buffered logging path is
     // refactored.
     // LOGW("DRAM vote count begins");
-    printf("CHRE: DRAM vote count begins");
+    printf("CHRE: DRAM vote count begins\n");
 
     if (!mLastDramVote) {
       // Do not call voteDramAccess() directly as it will override
@@ -66,7 +66,8 @@
     // TODO(b/181172259): Change back to LOGW once buffered logging path is
     // refactored.
     // LOGW("DRAM vote count ends: %" PRIu64 " ms", checkDramDuration());
-    printf("CHRE: DRAM vote count ends: %" PRIu64 " ms", checkDramDuration());
+    printf("CHRE: DRAM vote count ends: %" PRIu64 " ms\n",
+           checkDramDuration().getMilliseconds());
 
     // There's no DRAM activity now, remove CHRE's DRAM access vote.
     if (!mLastDramRequest) {
diff --git a/platform/shared/host_link.cc b/platform/shared/host_link.cc
new file mode 100644
index 0000000..a66b797
--- /dev/null
+++ b/platform/shared/host_link.cc
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 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/core/event_loop_manager.h"
+#include "chre/platform/shared/host_protocol_chre.h"
+#include "chre/platform/shared/nanoapp_load_manager.h"
+
+namespace chre {
+
+NanoappLoadManager gLoadManager;
+
+inline NanoappLoadManager &getLoadManager() {
+  return gLoadManager;
+}
+
+void HostMessageHandlers::handleDebugConfiguration(
+    const fbs::DebugConfiguration *debugConfiguration) {
+  EventLoopManagerSingleton::get()
+      ->getSystemHealthMonitor()
+      .setFatalErrorOnCheckFailure(
+          debugConfiguration->health_monitor_failure_crash());
+}
+
+void HostMessageHandlers::finishLoadingNanoappCallback(
+    SystemCallbackType /*type*/, UniquePtr<LoadNanoappCallbackData> &&cbData) {
+  constexpr size_t kInitialBufferSize = 48;
+  ChreFlatBufferBuilder builder(kInitialBufferSize);
+
+  CHRE_ASSERT(cbData != nullptr);
+
+  EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
+  bool success = false;
+
+  if (cbData->nanoapp->isLoaded()) {
+    success = eventLoop.startNanoapp(cbData->nanoapp);
+  } else {
+    LOGE("Nanoapp is not loaded");
+  }
+
+  if (cbData->sendFragmentResponse) {
+    sendFragmentResponse(cbData->hostClientId, cbData->transactionId,
+                         cbData->fragmentId, success);
+  }
+}
+
+void HostMessageHandlers::loadNanoappData(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
+    const void *buffer, size_t bufferLen, uint32_t fragmentId,
+    size_t appBinaryLen, bool respondBeforeStart) {
+  bool success = true;
+
+  if (fragmentId == 0 || fragmentId == 1) {
+    size_t totalAppBinaryLen = (fragmentId == 0) ? bufferLen : appBinaryLen;
+    LOGD("Load nanoapp request for app ID 0x%016" PRIx64 " ver 0x%" PRIx32
+         " flags 0x%" PRIx32 " target API 0x%08" PRIx32
+         " size %zu (txnId %" PRIu32 " client %" PRIu16 ")",
+         appId, appVersion, appFlags, targetApiVersion, totalAppBinaryLen,
+         transactionId, hostClientId);
+
+    if (getLoadManager().hasPendingLoadTransaction()) {
+      FragmentedLoadInfo info = getLoadManager().getTransactionInfo();
+      sendFragmentResponse(info.hostClientId, info.transactionId,
+                           0 /* fragmentId */, false /* success */);
+      getLoadManager().markFailure();
+    }
+
+    success = getLoadManager().prepareForLoad(
+        hostClientId, transactionId, appId, appVersion, appFlags,
+        totalAppBinaryLen, targetApiVersion);
+  }
+
+  if (success) {
+    success = getLoadManager().copyNanoappFragment(
+        hostClientId, transactionId, (fragmentId == 0) ? 1 : fragmentId, buffer,
+        bufferLen);
+  } else {
+    LOGE("Failed to prepare for load");
+  }
+
+  if (getLoadManager().isLoadComplete()) {
+    LOGD("Load manager load complete...");
+    auto cbData = MakeUnique<LoadNanoappCallbackData>();
+    if (cbData.isNull()) {
+      LOG_OOM();
+    } else {
+      cbData->transactionId = transactionId;
+      cbData->hostClientId = hostClientId;
+      cbData->appId = appId;
+      cbData->fragmentId = fragmentId;
+      cbData->nanoapp = getLoadManager().releaseNanoapp();
+      cbData->sendFragmentResponse = !respondBeforeStart;
+
+      // Note that if this fails, we'll generate the error response in
+      // the normal deferred callback
+      EventLoopManagerSingleton::get()->deferCallback(
+          SystemCallbackType::FinishLoadingNanoapp, std::move(cbData),
+          finishLoadingNanoappCallback);
+      if (respondBeforeStart) {
+        sendFragmentResponse(hostClientId, transactionId, fragmentId, success);
+      }  // else the response will be sent in finishLoadingNanoappCallback
+    }
+  } else {
+    // send a response for this fragment
+    sendFragmentResponse(hostClientId, transactionId, fragmentId, success);
+  }
+}
+
+}  // namespace chre
diff --git a/platform/shared/host_protocol_chre.cc b/platform/shared/host_protocol_chre.cc
index 04e204f..d7b7fea 100644
--- a/platform/shared/host_protocol_chre.cc
+++ b/platform/shared/host_protocol_chre.cc
@@ -19,7 +19,8 @@
 #include <inttypes.h>
 #include <string.h>
 
-#include "chre/core/host_notifications.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/host_endpoint_manager.h"
 #include "chre/platform/log.h"
 #include "chre/platform/shared/generated/host_messages_generated.h"
 #include "chre/util/macros.h"
@@ -131,7 +132,8 @@
         struct chreHostEndpointInfo info;
         info.hostEndpointId = connectedMessage->host_endpoint();
         info.hostEndpointType = connectedMessage->type();
-        if (connectedMessage->package_name()->size() > 0) {
+        if (strlen(reinterpret_cast<const char *>(
+                connectedMessage->package_name()->data())) > 0) {
           info.isNameValid = true;
           memcpy(&info.packageName[0], connectedMessage->package_name()->data(),
                  MIN(connectedMessage->package_name()->size(),
@@ -140,7 +142,8 @@
         } else {
           info.isNameValid = false;
         }
-        if (connectedMessage->attribution_tag()->size() > 0) {
+        if (strlen(reinterpret_cast<const char *>(
+                connectedMessage->attribution_tag()->data())) > 0) {
           info.isTagValid = true;
           memcpy(&info.attributionTag[0],
                  connectedMessage->attribution_tag()->data(),
@@ -151,7 +154,9 @@
           info.isTagValid = false;
         }
 
-        postHostEndpointConnected(info);
+        EventLoopManagerSingleton::get()
+            ->getHostEndpointManager()
+            .postHostEndpointConnected(info);
         break;
       }
 
@@ -159,7 +164,9 @@
         const auto *disconnectedMessage =
             static_cast<const fbs::HostEndpointDisconnected *>(
                 container->message());
-        postHostEndpointDisconnected(disconnectedMessage->host_endpoint());
+        EventLoopManagerSingleton::get()
+            ->getHostEndpointManager()
+            .postHostEndpointDisconnected(disconnectedMessage->host_endpoint());
         break;
       }
 
@@ -172,6 +179,13 @@
         break;
       }
 
+      case fbs::ChreMessage::DebugConfiguration: {
+        const auto *debugConfiguration =
+            static_cast<const fbs::DebugConfiguration *>(container->message());
+        HostMessageHandlers::handleDebugConfiguration(debugConfiguration);
+        break;
+      }
+
       default:
         LOGW("Got invalid/unexpected message type %" PRIu8,
              static_cast<uint8_t>(container->message_type()));
diff --git a/platform/shared/idl/README.md b/platform/shared/idl/README.md
index f9282dc..2416064 100644
--- a/platform/shared/idl/README.md
+++ b/platform/shared/idl/README.md
@@ -4,4 +4,7 @@
 Use the included update.sh script to generate the header files used in CHRE,
 which requires that the FlatBuffers compiler `flatc` be available in $PATH.
 
+FlatBuffers compiler version 1.12.0 must be used since some modifications are
+made to the version of flatbuffers header used by the generated code.
+
 For more information on FlatBuffers, see https://github.com/google/flatbuffers/
diff --git a/platform/shared/idl/host_messages.fbs b/platform/shared/idl/host_messages.fbs
index d04a7b1..825fade 100644
--- a/platform/shared/idl/host_messages.fbs
+++ b/platform/shared/idl/host_messages.fbs
@@ -393,6 +393,12 @@
   enabled:bool;
 }
 
+// Debug configurastion that will be send from Android AP to CHRE during boot time
+table DebugConfiguration {
+  // Should HealthMonitor::onFailure crash when receiving a false condition
+  health_monitor_failure_crash:bool;
+}
+
 /// A union that joins together all possible messages. Note that in FlatBuffers,
 /// unions have an implicit type
 union ChreMessage {
@@ -438,6 +444,8 @@
 
   NanConfigurationRequest,
   NanConfigurationUpdate,
+
+  DebugConfiguration,
 }
 
 struct HostAddress {
diff --git a/platform/shared/idl/update.sh b/platform/shared/idl/update.sh
index 61145e2..26f6882 100755
--- a/platform/shared/idl/update.sh
+++ b/platform/shared/idl/update.sh
@@ -1,5 +1,11 @@
 #!/bin/bash
 
+# Check flatc version
+if [[ $(flatc --version | grep -Po "(?<=flatc version )([0-9]|\.)*(?=\s|$)") != "1.12.0" ]]; then
+echo "[ERROR] flatc version must be 1.12.0"
+exit
+fi
+
 # Generate the CHRE-side header file
 flatc --cpp -o ../include/chre/platform/shared/generated/ --scoped-enums \
   --cpp-ptr-type chre::UniquePtr host_messages.fbs
diff --git a/platform/shared/include/chre/platform/shared/authentication.h b/platform/shared/include/chre/platform/shared/authentication.h
index 6409b47..577372b 100644
--- a/platform/shared/include/chre/platform/shared/authentication.h
+++ b/platform/shared/include/chre/platform/shared/authentication.h
@@ -17,6 +17,8 @@
 #ifndef CHRE_PLATFORM_SHARED_AUTHENTICATION_H_
 #define CHRE_PLATFORM_SHARED_AUTHENTICATION_H_
 
+#include <cstddef>
+
 namespace chre {
 
 /**
@@ -27,13 +29,15 @@
  * execution privileges as the core framework itself.
  *
  * @param binary Pointer to the binary that should be authenticated.
+ * @param appBinaryLen The length of the binary.
  * @param realBinaryStart A non-null pointer that, if this method succeeds, must
  *     be filled with the starting address of the raw binary after any headers
  *     used by the authentication code. This will be passed to the dynamic
  *     loader which will assume the starting address is a valid ELF binary.
  * @return True if the binary passed authentication.
  */
-bool authenticateBinary(void *binary, void **realBinaryStart);
+bool authenticateBinary(const void *binary, size_t appBinaryLen,
+                        void **realBinaryStart);
 
 }  // namespace chre
 
diff --git a/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h b/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h
index 51208c5..6a4f1e4 100644
--- a/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h
+++ b/platform/shared/include/chre/platform/shared/generated/host_messages_generated.h
@@ -96,6 +96,9 @@
 struct NanConfigurationUpdate;
 struct NanConfigurationUpdateBuilder;
 
+struct DebugConfiguration;
+struct DebugConfigurationBuilder;
+
 struct HostAddress;
 
 struct MessageContainer;
@@ -203,11 +206,12 @@
   BatchedMetricLog = 25,
   NanConfigurationRequest = 26,
   NanConfigurationUpdate = 27,
+  DebugConfiguration = 28,
   MIN = NONE,
-  MAX = NanConfigurationUpdate
+  MAX = DebugConfiguration
 };
 
-inline const ChreMessage (&EnumValuesChreMessage())[28] {
+inline const ChreMessage (&EnumValuesChreMessage())[29] {
   static const ChreMessage values[] = {
     ChreMessage::NONE,
     ChreMessage::NanoappMessage,
@@ -236,13 +240,14 @@
     ChreMessage::MetricLog,
     ChreMessage::BatchedMetricLog,
     ChreMessage::NanConfigurationRequest,
-    ChreMessage::NanConfigurationUpdate
+    ChreMessage::NanConfigurationUpdate,
+    ChreMessage::DebugConfiguration
   };
   return values;
 }
 
 inline const char * const *EnumNamesChreMessage() {
-  static const char * const names[29] = {
+  static const char * const names[30] = {
     "NONE",
     "NanoappMessage",
     "HubInfoRequest",
@@ -271,13 +276,14 @@
     "BatchedMetricLog",
     "NanConfigurationRequest",
     "NanConfigurationUpdate",
+    "DebugConfiguration",
     nullptr
   };
   return names;
 }
 
 inline const char *EnumNameChreMessage(ChreMessage e) {
-  if (flatbuffers::IsOutRange(e, ChreMessage::NONE, ChreMessage::NanConfigurationUpdate)) return "";
+  if (flatbuffers::IsOutRange(e, ChreMessage::NONE, ChreMessage::DebugConfiguration)) return "";
   const size_t index = static_cast<size_t>(e);
   return EnumNamesChreMessage()[index];
 }
@@ -394,6 +400,10 @@
   static const ChreMessage enum_value = ChreMessage::NanConfigurationUpdate;
 };
 
+template<> struct ChreMessageTraits<chre::fbs::DebugConfiguration> {
+  static const ChreMessage enum_value = ChreMessage::DebugConfiguration;
+};
+
 bool VerifyChreMessage(flatbuffers::Verifier &verifier, const void *obj, ChreMessage type);
 bool VerifyChreMessageVector(flatbuffers::Verifier &verifier, const flatbuffers::Vector<flatbuffers::Offset<void>> *values, const flatbuffers::Vector<uint8_t> *types);
 
@@ -2346,6 +2356,48 @@
   return builder_.Finish();
 }
 
+struct DebugConfiguration FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  typedef DebugConfigurationBuilder Builder;
+  enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+    VT_HEALTH_MONITOR_FAILURE_CRASH = 4
+  };
+  bool health_monitor_failure_crash() const {
+    return GetField<uint8_t>(VT_HEALTH_MONITOR_FAILURE_CRASH, 0) != 0;
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint8_t>(verifier, VT_HEALTH_MONITOR_FAILURE_CRASH) &&
+           verifier.EndTable();
+  }
+};
+
+struct DebugConfigurationBuilder {
+  typedef DebugConfiguration Table;
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_health_monitor_failure_crash(bool health_monitor_failure_crash) {
+    fbb_.AddElement<uint8_t>(DebugConfiguration::VT_HEALTH_MONITOR_FAILURE_CRASH, static_cast<uint8_t>(health_monitor_failure_crash), 0);
+  }
+  explicit DebugConfigurationBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  DebugConfigurationBuilder &operator=(const DebugConfigurationBuilder &);
+  flatbuffers::Offset<DebugConfiguration> Finish() {
+    const auto end = fbb_.EndTable(start_);
+    auto o = flatbuffers::Offset<DebugConfiguration>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<DebugConfiguration> CreateDebugConfiguration(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    bool health_monitor_failure_crash = false) {
+  DebugConfigurationBuilder builder_(_fbb);
+  builder_.add_health_monitor_failure_crash(health_monitor_failure_crash);
+  return builder_.Finish();
+}
+
 /// The top-level container that encapsulates all possible messages. Note that
 /// per FlatBuffers requirements, we can't use a union as the top-level
 /// structure (root type), so we must wrap it in a table.
@@ -2444,6 +2496,9 @@
   const chre::fbs::NanConfigurationUpdate *message_as_NanConfigurationUpdate() const {
     return message_type() == chre::fbs::ChreMessage::NanConfigurationUpdate ? static_cast<const chre::fbs::NanConfigurationUpdate *>(message()) : nullptr;
   }
+  const chre::fbs::DebugConfiguration *message_as_DebugConfiguration() const {
+    return message_type() == chre::fbs::ChreMessage::DebugConfiguration ? static_cast<const chre::fbs::DebugConfiguration *>(message()) : nullptr;
+  }
   /// The originating or destination client ID on the host side, used to direct
   /// responses only to the client that sent the request. Although initially
   /// populated by the requesting client, this is enforced to be the correct
@@ -2571,6 +2626,10 @@
   return message_as_NanConfigurationUpdate();
 }
 
+template<> inline const chre::fbs::DebugConfiguration *MessageContainer::message_as<chre::fbs::DebugConfiguration>() const {
+  return message_as_DebugConfiguration();
+}
+
 struct MessageContainerBuilder {
   typedef MessageContainer Table;
   flatbuffers::FlatBufferBuilder &fbb_;
@@ -2723,6 +2782,10 @@
       auto ptr = reinterpret_cast<const chre::fbs::NanConfigurationUpdate *>(obj);
       return verifier.VerifyTable(ptr);
     }
+    case ChreMessage::DebugConfiguration: {
+      auto ptr = reinterpret_cast<const chre::fbs::DebugConfiguration *>(obj);
+      return verifier.VerifyTable(ptr);
+    }
     default: return true;
   }
 }
diff --git a/platform/shared/include/chre/platform/shared/host_protocol_chre.h b/platform/shared/include/chre/platform/shared/host_protocol_chre.h
index a1386f6..27bf37f 100644
--- a/platform/shared/include/chre/platform/shared/host_protocol_chre.h
+++ b/platform/shared/include/chre/platform/shared/host_protocol_chre.h
@@ -19,6 +19,8 @@
 
 #include <stdint.h>
 
+#include "chre/core/event_loop_common.h"
+#include "chre/core/nanoapp.h"
 #include "chre/core/settings.h"
 #include "chre/platform/shared/generated/host_messages_generated.h"
 #include "chre/platform/shared/host_protocol_common.h"
@@ -50,6 +52,15 @@
  */
 class HostMessageHandlers {
  public:
+  struct LoadNanoappCallbackData {
+    uint64_t appId;
+    uint32_t transactionId;
+    uint16_t hostClientId;
+    UniquePtr<Nanoapp> nanoapp;
+    uint32_t fragmentId;
+    bool sendFragmentResponse;
+  };
+
   static void handleNanoappMessage(uint64_t appId, uint32_t messageType,
                                    uint16_t hostEndpoint,
                                    const void *messageData,
@@ -59,6 +70,9 @@
 
   static void handleNanoappListRequest(uint16_t hostClientId);
 
+  static void handleDebugConfiguration(
+      const fbs::DebugConfiguration *debugConfiguration);
+
   static void handleLoadNanoappRequest(
       uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
       uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
@@ -79,6 +93,39 @@
   static void handleSelfTestRequest(uint16_t hostClientId);
 
   static void handleNanConfigurationUpdate(bool enabled);
+
+ private:
+  static void sendFragmentResponse(uint16_t hostClientId,
+                                   uint32_t transactionId, uint32_t fragmentId,
+                                   bool success);
+
+  static void finishLoadingNanoappCallback(
+      SystemCallbackType type, UniquePtr<LoadNanoappCallbackData> &&cbData);
+
+  /**
+   * Helper function that loads a nanoapp into the system
+   * from a buffer sent over in 1 or more fragments.
+   *
+   * @param hostClientId the ID of client that originated this transaction
+   * @param transactionId the ID of the transaction
+   * @param appId the ID of the app to load
+   * @param appVersion the version of the app to load
+   * @param appFlags The flags provided by the app being loaded
+   * @param targetApiVersion the API version this nanoapp is targeted for
+   * @param buffer the nanoapp binary data. May be only part of the nanoapp's
+   *     binary if it's being sent over multiple fragments
+   * @param bufferLen the size of buffer in bytes
+   * @param fragmentId the identifier indicating which fragment is being loaded
+   * @param appBinaryLen the full size of the nanoapp binary to be loaded
+   *
+   * @return void
+   */
+  static void loadNanoappData(uint16_t hostClientId, uint32_t transactionId,
+                              uint64_t appId, uint32_t appVersion,
+                              uint32_t appFlags, uint32_t targetApiVersion,
+                              const void *buffer, size_t bufferLen,
+                              uint32_t fragmentId, size_t appBinaryLen,
+                              bool respondBeforeStart);
 };
 
 /**
diff --git a/platform/shared/include/chre/platform/shared/loader_util.h b/platform/shared/include/chre/platform/shared/loader_util.h
index 8e33428..62aa3c7 100644
--- a/platform/shared/include/chre/platform/shared/loader_util.h
+++ b/platform/shared/include/chre/platform/shared/loader_util.h
@@ -17,16 +17,22 @@
 #ifndef CHRE_PLATFORM_SHARED_LOADER_UTIL_H_
 #define CHRE_PLATFORM_SHARED_LOADER_UTIL_H_
 
+// Macros used to define a symbol that can be exported by the nanoapp loader
+#define ADD_EXPORTED_SYMBOL(function_name, function_string) \
+  { reinterpret_cast<void *>(function_name), function_string }
+#define ADD_EXPORTED_C_SYMBOL(function_name) \
+  ADD_EXPORTED_SYMBOL(function_name, STRINGIFY(function_name))
+
 // The below macros allow switching the ELF symbol type between 32/64-bit
 // depending on what the chipset supports.
 #ifndef ELFW
 #ifndef __WORDSIZE
 // Until we can get a hold of wordsize.h, we need to define it here.
 // Only 32-bit architectures currently supported.
-#ifndef __aarch64__
+#ifdef CHRE_32_BIT_WORD_SIZE
 #define __WORDSIZE 32
 #else
-#error "Only 32-bit architectures currently supported"
+#error "Architecture not supported by CHRE dynamic loading"
 #endif
 #endif
 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
@@ -55,6 +61,7 @@
 #endif
 
 #define EM_ARM 40
+#define EM_RISCV 243
 #define EI_MAG0 0
 #define EI_MAG1 1
 #define EI_MAG2 2
@@ -224,6 +231,12 @@
 #define R_ARM_GLOB_DAT 21
 #define R_ARM_JUMP_SLOT 22
 #define R_ARM_RELATIVE 23
+#define R_RISCV_NONE 0
+#define R_RISCV_32 1
+#define R_RISCV_RELATIVE 3
+#define R_RISCV_JUMP_SLOT 5
+// Undefined symbol.
+#define SHN_UNDEF 0
 
 // The following (legal values for segment flags) are copied from
 // bionic's elf.h
diff --git a/platform/shared/include/chre/platform/shared/nanoapp_loader.h b/platform/shared/include/chre/platform/shared/nanoapp_loader.h
index 1252282..3b58818 100644
--- a/platform/shared/include/chre/platform/shared/nanoapp_loader.h
+++ b/platform/shared/include/chre/platform/shared/nanoapp_loader.h
@@ -23,10 +23,45 @@
 #include "chre/platform/shared/loader_util.h"
 
 #include "chre/util/dynamic_vector.h"
+#include "chre/util/optional.h"
 
 namespace chre {
 
 /**
+ * @struct:
+ *   AtExitCallback
+ *
+ * @description:
+ *   Store callback information for both atexit and __cxa_atexit.
+ *
+ * @fields:
+ *   func0 ::
+ *     Callback function for atexit (no arg).
+ *
+ *   func1 ::
+ *     Callback function for __cxa_atexit (one arg).
+ *
+ *   arg ::
+ *     Optional arg for __cxa_atexit only.
+ */
+struct AtExitCallback {
+  union {
+    void (*func0)(void);
+    void (*func1)(void *);
+  };
+  Optional<void *> arg;
+
+  AtExitCallback(void (*func)(void)) {
+    func0 = func;
+  }
+
+  AtExitCallback(void (*func)(void *), void *a) {
+    func1 = func;
+    arg = a;
+  }
+};
+
+/**
  * Provides dynamic loading support for nanoapps on FreeRTOS-based platforms.
  * At a high level, this class is responsible for mapping the provided binary
  * into CHRE's address space, relocating and resolving symbols, and initializing
@@ -82,10 +117,28 @@
    * Registers a function provided through atexit during static initialization
    * that should be called prior to unloading a nanoapp.
    *
-   * @param function Function that should be invoked prior to unloading a
+   * @param callback Callback info that should be invoked prior to unloading a
    *     nanoapp.
    */
-  void registerAtexitFunction(void (*function)(void));
+  void registerAtexitFunction(struct AtExitCallback &cb);
+
+  /**
+   * Rounds the given address down to the closest alignment boundary.
+   *
+   * The alignment follows ELF's p_align semantics:
+   *
+   * [p_align] holds the value to which the segments are aligned in memory and
+   * in the file. Loadable process segments must have congruent values for
+   * p_vaddr and p_offset, modulo the page size. Values of zero and one mean no
+   * alignment is required. Otherwise, p_align should be a positive, integral
+   * power of two, and p_vaddr should equal p_offset, modulo p_align.
+   *
+   * @param virtualAddr The address to be rounded.
+   * @param alignment Alignment to which the address is rounded to.
+   * @return An address that is a multiple of the platform's alignment and is
+   *     less than or equal to virtualAddr.
+   */
+  static uintptr_t roundDownToAlign(uintptr_t virtualAddr, size_t alignment);
 
  private:
   /**
@@ -138,13 +191,15 @@
   uint8_t *mBinary = nullptr;
   //! The starting location of the memory that has been mapped into the system.
   uint8_t *mMapping = nullptr;
+  //! The span of memory that has been mapped into the system.
+  size_t mMemorySpan = 0;
   //! The difference between where the first load segment was mapped into
   //! virtual memory and what the virtual load offset was of that segment.
   ElfAddr mLoadBias = 0;
   //! Dynamic vector containing functions that should be invoked prior to
   //! unloading this nanoapp. Note that functions are stored in the order they
   //! were added and should be called in reverse.
-  DynamicVector<void (*)(void)> mAtexitFunctions;
+  DynamicVector<struct AtExitCallback> mAtexitFunctions;
   //! Whether this loader instance is managing a TCM nanoapp binary.
   bool mIsTcmBinary = false;
 
@@ -219,36 +274,41 @@
   bool verifySectionHeaders();
 
   /**
-   * Retrieves the symbol name of data located at the given position in the
-   * symbol table.
+   * Retrieves the symbol at the given position in the symbol table.
    *
    * @param posInSymbolTable The position in the symbol table where information
    *     about the symbol can be found.
-   * @return The symbol's name or nullptr if not found.
+   * @return The symbol or nullptr if not found.
    */
-  const char *getDataName(size_t posInSymbolTable);
+  ElfSym *getDynamicSymbol(size_t posInSymbolTable);
 
   /**
-   * Retrieves the name of the section header located at the given offset in the
-   * section name table.
+   * Retrieves the symbol name.
    *
-   * @param headerOffset The offset in the section names table where the header
-   *     is located.
+   * @param symbol A pointer to the symbol.
+   * @return The symbol's name or nullptr if not found.
+   */
+  const char *getDataName(const ElfSym *symbol);
+
+  /**
+   * Retrieves the target address of the symbol.
+   *
+   * @param symbol A pointer to the symbol.
+   * @return The target address or nullptr if the symbol is not defined.
+   */
+  void *getSymbolTarget(const ElfSym *symbol);
+
+  /**
+   * Retrieves the name of the section header located at the given offset in
+   * the section name table.
+   *
+   * @param headerOffset The offset in the section names table where the
+   * header is located.
    * @return The section's name or the empty string if the offset is 0.
    */
   const char *getSectionHeaderName(size_t headerOffset);
 
   /**
-   * Rounds the given address down to the closest alignment boundary.
-   *
-   * @param virtualAddr The address to be rounded.
-   * @param alignment Alignment to which the address is rounded to.
-   * @return An address that is a multiple of the platform's alignment and is
-   *     less than or equal to virtualAddr.
-   */
-  uintptr_t roundDownToAlign(uintptr_t virtualAddr, size_t alignment);
-
-  /**
    * Frees any data that was allocated as part of loading the ELF into memory.
    */
   void freeAllocatedData();
@@ -337,6 +397,15 @@
    * @return The value found at the entry. 0 if the entry isn't found.
    */
   static ElfWord getDynEntry(DynamicHeader *dyn, int field);
+
+  /**
+   * Handle reolcation for entries in the specified table.
+   *
+   * @param dyn The dynamic header for the binary.
+   * @param tableTag The dynamic tag (DT_x) of the relocation table.
+   * @return True if success or unsupported, false if failure.
+   */
+  bool relocateTable(DynamicHeader *dyn, int tableTag);
 };
 
 }  // namespace chre
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 d863837..a8338c7 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
@@ -28,9 +28,10 @@
  * compiling external/dynamic nanoapps.
  */
 
-#include <chre.h>
 #include <stdint.h>
 
+#include "chre_api/chre.h"
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -42,6 +43,10 @@
 //! are available to support backwards compatibility.
 #define CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION UINT8_C(3)
 
+//! Explicit definition of nanoapp info structure minor version three (3),
+//! can be used to determine if a nanoapp supports app permissions declaration
+#define CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3 UINT8_C(3)
+
 //! The symbol name expected from the nanoapp's definition of its info struct
 #define CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME "_chreNslDsoNanoappInfo"
 
@@ -130,6 +135,13 @@
 };
 
 /**
+ * Get the Chre Nsl Nanoapp Info object
+ *
+ * @return struct chreNslNanoappInfo
+ */
+const struct chreNslNanoappInfo *getChreNslDsoNanoappInfo();
+
+/**
  * Defined as a placeholder to enable future functionality extension.
  *
  * @param apiId
diff --git a/platform/shared/include/chre/platform/shared/platform_pal.h b/platform/shared/include/chre/platform/shared/platform_pal.h
index 75ca448..2364e1e 100644
--- a/platform/shared/include/chre/platform/shared/platform_pal.h
+++ b/platform/shared/include/chre/platform/shared/platform_pal.h
@@ -17,17 +17,32 @@
 #ifndef CHRE_PLATFORM_SHARED_PLATFORM_PAL_H_
 #define CHRE_PLATFORM_SHARED_PLATFORM_PAL_H_
 
+#include <cinttypes>
+
 namespace chre {
 
 /**
+ * Represents the various types of PALs that can use the PlatformPal class
+ */
+enum class PalType : uint8_t {
+  AUDIO = 1,
+  BLE = 2,
+  GNSS = 3,
+  WIFI = 4,
+  WWAN = 5,
+};
+
+/**
  * 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.
+   *
+   * @param palType Indicates the type of PAL about to be accessed.
    */
-  void prePalApiCall() const;
+  void prePalApiCall(PalType palType) const;
 };
 
 }  // namespace chre
diff --git a/platform/shared/include/chre/target_platform/platform_ble_base.h b/platform/shared/include/chre/target_platform/platform_ble_base.h
index 0aa1d3c..8c4aa04 100644
--- a/platform/shared/include/chre/target_platform/platform_ble_base.h
+++ b/platform/shared/include/chre/target_platform/platform_ble_base.h
@@ -38,6 +38,8 @@
   static void requestStateResync();
   static void scanStatusChangeCallback(bool enabled, uint8_t errorCode);
   static void advertisingEventCallback(struct chreBleAdvertisementEvent *event);
+  static void readRssiCallback(uint8_t errorCode, uint16_t connectionHandle,
+                               int8_t rssi);
 };
 
 }  // namespace chre
diff --git a/platform/shared/include/chre/target_platform/platform_cache_management.h b/platform/shared/include/chre/target_platform/platform_cache_management.h
index 123de17..395a5ce 100644
--- a/platform/shared/include/chre/target_platform/platform_cache_management.h
+++ b/platform/shared/include/chre/target_platform/platform_cache_management.h
@@ -17,6 +17,8 @@
 #ifndef CHRE_PLATFORM_SHARED_CACHE_MANAGEMENT_H_
 #define CHRE_PLATFORM_SHARED_CACHE_MANAGEMENT_H_
 
+#include <cinttypes>
+
 namespace chre {
 
 /**
@@ -30,8 +32,14 @@
  * when it's not part of the underlying OS/system), the platform needs to
  * implement (or provide an empty stub for) the following method to invalidate
  * and/or clean the system data and instruction caches.
+ *
+ * If either one of the address or span parameter is zero (0), the function
+ * should wipe the entire system cache if it is supported by the platform.
+ *
+ * @param address The target memory address to invalidate cache.
+ * @param span Span of target memory to invalidate.
  */
-void wipeSystemCaches();
+void wipeSystemCaches(uintptr_t address = 0, uint32_t span = 0);
 
 }  // namespace chre
 
diff --git a/platform/shared/log_buffer.cc b/platform/shared/log_buffer.cc
index 12055d1..e440af3 100644
--- a/platform/shared/log_buffer.cc
+++ b/platform/shared/log_buffer.cc
@@ -246,7 +246,7 @@
 void LogBuffer::discardExcessOldLogsLocked(bool encoded,
                                            uint8_t currentLogLen) {
   size_t totalLogSize =
-      kLogDataOffset + (encoded ? currentLogLen + 1 : currentLogLen);
+      kLogDataOffset + (encoded ? currentLogLen : currentLogLen + 1);
   while (mBufferDataSize + totalLogSize > mBufferMaxSize) {
     mNumLogsDropped++;
     size_t logSize;
diff --git a/platform/shared/mbedtls/mbedtls.mk b/platform/shared/mbedtls/mbedtls.mk
new file mode 100644
index 0000000..38e3dd2
--- /dev/null
+++ b/platform/shared/mbedtls/mbedtls.mk
@@ -0,0 +1,39 @@
+#
+# Makefile for the MbedTLS module
+#
+
+# Environment Checks
+ifeq ($(ANDROID_BUILD_TOP),)
+$(error "You should supply an ANDROID_BUILD_TOP environment variable \
+         containing a path to the Android source tree. This is typically \
+         provided by initializing the Android build environment.")
+endif
+
+
+MBEDTLS_EXT_DIR = $(ANDROID_BUILD_TOP)/system/chre/platform/shared/mbedtls
+
+MBEDTLS_DIR = $(ANDROID_BUILD_TOP)/external/mbedtls/
+MBEDTLS_CONFIG_FILE = $(MBEDTLS_EXT_DIR)/mbedtls_config.h
+
+MBEDTLS_CFLAGS += -I$(MBEDTLS_DIR)/include
+MBEDTLS_CFLAGS += -DMBEDTLS_CONFIG_FILE=\"$(MBEDTLS_CONFIG_FILE)\"
+
+MBEDTLS_SRCS += $(MBEDTLS_EXT_DIR)/mbedtls_memory.cc
+
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/asn1write.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/oid.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/hash_info.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/ecp_curves.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/ecp.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/ecdsa.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/constant_time.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/ctr_drbg.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/bignum_core.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/bignum.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/asn1parse.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/md.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/sha256.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/platform_util.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/pkparse.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/pk_wrap.c
+MBEDTLS_SRCS += $(MBEDTLS_DIR)/library/pk.c
diff --git a/platform/shared/mbedtls/mbedtls_config.h b/platform/shared/mbedtls/mbedtls_config.h
new file mode 100644
index 0000000..6f2905b
--- /dev/null
+++ b/platform/shared/mbedtls/mbedtls_config.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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_EXTERNAL_MBEDTLS_CONFIG_H_
+#define CHRE_EXTERNAL_MBEDTLS_CONFIG_H_
+
+#include <limits.h>
+#include "mbedtls_memory.h"
+
+/**
+ * System support
+ */
+#define MBEDTLS_HAVE_ASM
+#define MBEDTLS_PLATFORM_C
+#define MBEDTLS_PLATFORM_MEMORY
+#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS
+#define MBEDTLS_DEPRECATED_WARNING
+#define MBEDTLS_NO_PLATFORM_ENTROPY
+
+/**
+ * Feature support
+ */
+#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
+#define MBEDTLS_ECP_NIST_OPTIM
+#define MBEDTLS_PK_PARSE_EC_EXTENDED
+
+/**
+ * MbedTLS modules
+ */
+#define MBEDTLS_ASN1_PARSE_C
+#define MBEDTLS_ASN1_WRITE_C
+#define MBEDTLS_BIGNUM_C
+#define MBEDTLS_ECDSA_C
+#define MBEDTLS_ECP_C
+#define MBEDTLS_MD_C
+#define MBEDTLS_OID_C
+#define MBEDTLS_PK_C
+#define MBEDTLS_PK_PARSE_C
+#define MBEDTLS_SHA224_C
+#define MBEDTLS_SHA256_C
+
+/**
+ * Platform specific defines
+ */
+#define MBEDTLS_PLATFORM_CALLOC_MACRO mbedtlsMemoryCalloc
+#define MBEDTLS_PLATFORM_FREE_MACRO mbedtlsMemoryFree
+#define MBEDTLS_PLATFORM_SNPRINTF_MACRO snprintf
+#define MBEDTLS_PLATFORM_FPRINTF_MACRO(fp, fmt, ...) \
+  ({                                                 \
+    static_assert(fp == stderr);                     \
+    LOGE(fmt, ##__VA_ARGS__);                        \
+    -1;                                              \
+  })
+
+#endif  // CHRE_EXTERNAL_MBEDTLS_CONFIG_H_
diff --git a/platform/shared/mbedtls/mbedtls_memory.cc b/platform/shared/mbedtls/mbedtls_memory.cc
new file mode 100644
index 0000000..2822db1
--- /dev/null
+++ b/platform/shared/mbedtls/mbedtls_memory.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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 "mbedtls_memory.h"
+
+#include <limits.h>
+#include <string.h>
+#include "chre/platform/memory.h"
+
+void *mbedtlsMemoryCalloc(size_t nItems, size_t itemSize) {
+  if (nItems == 0 || itemSize == 0 || SIZE_MAX / nItems < itemSize) {
+    return nullptr;
+  }
+  size_t totalSize = nItems * itemSize;
+  void *ptr = chre::memoryAlloc(totalSize);
+  if (ptr != nullptr) {
+    memset(ptr, 0, totalSize);
+  }
+  return ptr;
+}
+
+void mbedtlsMemoryFree(void *ptr) {
+  chre::memoryFree(ptr);
+}
\ No newline at end of file
diff --git a/platform/shared/mbedtls/mbedtls_memory.h b/platform/shared/mbedtls/mbedtls_memory.h
new file mode 100644
index 0000000..2e43148
--- /dev/null
+++ b/platform/shared/mbedtls/mbedtls_memory.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 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_EXTERNAL_MBEDTLS_MEMORY_H_
+#define CHRE_EXTERNAL_MBEDTLS_MEMORY_H_
+
+/**
+ * This header file provides wrappers around the CHRE memory allocation
+ * function @ref chreMemoryAlloc for use within the MbedTLS library.
+ */
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Allocates a contiguous zeroed out array with each element having the given
+ * item size.
+ *
+ * @param nItems Number of items to allocate.
+ * @param itemSize Size of an individual item.
+ *
+ * @return A pointer to the allocated memory if successful, nullptr otherwise.
+ */
+void *mbedtlsMemoryCalloc(size_t nItems, size_t itemSize);
+
+/**
+ * Frees a previously allocated memory.
+ *
+ * @param ptr Pointer to be freed.
+ */
+void mbedtlsMemoryFree(void *ptr);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // CHRE_EXTERNAL_MBEDTLS_MEMORY_H_
\ No newline at end of file
diff --git a/platform/shared/nanoapp/nanoapp_support_lib_dso.cc b/platform/shared/nanoapp/nanoapp_support_lib_dso.cc
index 32c51db..2f21df0 100644
--- a/platform/shared/nanoapp/nanoapp_support_lib_dso.cc
+++ b/platform/shared/nanoapp/nanoapp_support_lib_dso.cc
@@ -21,8 +21,10 @@
 
 #include "chre_nsl_internal/platform/shared/nanoapp_support_lib_dso.h"
 
-#include <chre.h>
+#include <algorithm>
 
+#include "chre/util/nanoapp/log.h"
+#include "chre_api/chre.h"
 #include "chre_nsl_internal/platform/shared/debug_dump.h"
 #include "chre_nsl_internal/util/macros.h"
 #include "chre_nsl_internal/util/system/napp_permissions.h"
@@ -30,6 +32,10 @@
 #include "chre_nsl_internal/util/system/wifi_util.h"
 #endif
 
+#ifndef LOG_TAG
+#define LOG_TAG "[NSL]"
+#endif  // LOG_TAG
+
 /**
  * @file
  * The Nanoapp Support Library (NSL) that gets built with nanoapps to act as an
@@ -106,6 +112,37 @@
 }
 #endif
 
+#if !defined(CHRE_NANOAPP_DISABLE_BACKCOMPAT) && defined(CHRE_NANOAPP_USES_BLE)
+void reverseServiceDataUuid(struct chreBleGenericFilter *filter) {
+  if (filter->type != CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE ||
+      filter->len == 0) {
+    return;
+  }
+  std::swap(filter->data[0], filter->data[1]);
+  std::swap(filter->dataMask[0], filter->dataMask[1]);
+  if (filter->len == 1) {
+    filter->data[0] = 0x0;
+    filter->dataMask[0] = 0x0;
+    filter->len = 2;
+  }
+}
+
+bool serviceDataFilterEndianSwapRequired(
+    const struct chreBleScanFilter *filter) {
+  if (chreGetApiVersion() >= CHRE_API_VERSION_1_8 || filter == nullptr) {
+    return false;
+  }
+  for (size_t i = 0; i < filter->scanFilterCount; i++) {
+    if (filter->scanFilters[i].type ==
+            CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE &&
+        filter->scanFilters[i].len > 0) {
+      return true;
+    }
+  }
+  return false;
+}
+#endif
+
 }  // anonymous namespace
 
 //! Used to determine the given unstable ID that was provided when building this
@@ -115,11 +152,11 @@
 //! sections, since for compilers with a default size-1 alignment, there might
 //! be a spill-over from the previous segment if not zero-padded, when we
 //! attempt to read the string.
-DLL_EXPORT extern "C" const char _chreNanoappUnstableId[]
+extern "C" DLL_EXPORT const char _chreNanoappUnstableId[]
     __attribute__((section(".unstable_id"))) __attribute__((aligned(8))) =
         NANOAPP_UNSTABLE_ID;
 
-DLL_EXPORT extern "C" const struct chreNslNanoappInfo _chreNslDsoNanoappInfo = {
+extern "C" DLL_EXPORT const struct chreNslNanoappInfo _chreNslDsoNanoappInfo = {
     /* magic */ CHRE_NSL_NANOAPP_INFO_MAGIC,
     /* structMinorVersion */ CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION,
     /* isSystemNanoapp */ NANOAPP_IS_SYSTEM_NANOAPP,
@@ -147,6 +184,10 @@
     /* appPermissions */ kNanoappPermissions,
 };
 
+const struct chreNslNanoappInfo *getChreNslDsoNanoappInfo() {
+  return &_chreNslDsoNanoappInfo;
+}
+
 // The code section below provides default implementations for new symbols
 // introduced in CHRE API v1.2+ to provide binary compatibility with previous
 // CHRE implementations. Note that we don't presently include symbols for v1.1,
@@ -161,6 +202,15 @@
 
 #include <dlfcn.h>
 
+namespace {
+// Populate chreNanoappInfo for CHRE API pre v1.8.
+void populateChreNanoappInfoPre18(struct chreNanoappInfo *info) {
+  info->rpcServiceCount = 0;
+  info->rpcServices = nullptr;
+  memset(&info->reserved, 0, sizeof(info->reserved));
+}
+}  // namespace
+
 /**
  * Lazily calls dlsym to find the function pointer for a given function
  * (provided without quotes) in another library (i.e. the CHRE platform DSO),
@@ -219,10 +269,38 @@
 }
 
 WEAK_SYMBOL
+bool chreBleFlushAsync(const void *cookie) {
+  auto *fptr = CHRE_NSL_LAZY_LOOKUP(chreBleFlushAsync);
+  return (fptr != nullptr) ? fptr(cookie) : false;
+}
+
+WEAK_SYMBOL
 bool chreBleStartScanAsync(chreBleScanMode mode, uint32_t reportDelayMs,
                            const struct chreBleScanFilter *filter) {
   auto *fptr = CHRE_NSL_LAZY_LOOKUP(chreBleStartScanAsync);
-  return (fptr != nullptr) ? fptr(mode, reportDelayMs, filter) : false;
+  if (fptr == nullptr) {
+    return false;
+  } else if (!serviceDataFilterEndianSwapRequired(filter)) {
+    return fptr(mode, reportDelayMs, filter);
+  }
+  // For nanoapps compiled against v1.8+ working with earlier versions of CHRE,
+  // convert service data filters to big-endian format.
+  chreBleScanFilter convertedFilter = *filter;
+  auto genericFilters = static_cast<chreBleGenericFilter *>(
+      chreHeapAlloc(sizeof(chreBleGenericFilter) * filter->scanFilterCount));
+  if (genericFilters == nullptr) {
+    LOG_OOM();
+    return false;
+  }
+  memcpy(genericFilters, filter->scanFilters,
+         filter->scanFilterCount * sizeof(chreBleGenericFilter));
+  for (size_t i = 0; i < filter->scanFilterCount; i++) {
+    reverseServiceDataUuid(&genericFilters[i]);
+  }
+  convertedFilter.scanFilters = genericFilters;
+  bool success = fptr(mode, reportDelayMs, &convertedFilter);
+  chreHeapFree(const_cast<chreBleGenericFilter *>(convertedFilter.scanFilters));
+  return success;
 }
 
 WEAK_SYMBOL
@@ -231,6 +309,18 @@
   return (fptr != nullptr) ? fptr() : false;
 }
 
+WEAK_SYMBOL
+bool chreBleReadRssiAsync(uint16_t connectionHandle, const void *cookie) {
+  auto *fptr = CHRE_NSL_LAZY_LOOKUP(chreBleReadRssiAsync);
+  return (fptr != nullptr) ? fptr(connectionHandle, cookie) : false;
+}
+
+WEAK_SYMBOL
+bool chreBleGetScanStatus(struct chreBleScanStatus *status) {
+  auto *fptr = CHRE_NSL_LAZY_LOOKUP(chreBleGetScanStatus);
+  return (fptr != nullptr) ? fptr(status) : false;
+}
+
 #endif /* CHRE_NANOAPP_USES_BLE */
 
 WEAK_SYMBOL
@@ -409,4 +499,23 @@
   return (fptr != nullptr) ? fptr(hostEndpointId, info) : false;
 }
 
+bool chreGetNanoappInfoByAppId(uint64_t appId, struct chreNanoappInfo *info) {
+  auto *fptr = CHRE_NSL_LAZY_LOOKUP(chreGetNanoappInfoByAppId);
+  bool success = (fptr != nullptr) ? fptr(appId, info) : false;
+  if (success && chreGetApiVersion() < CHRE_API_VERSION_1_8) {
+    populateChreNanoappInfoPre18(info);
+  }
+  return success;
+}
+
+bool chreGetNanoappInfoByInstanceId(uint32_t instanceId,
+                                    struct chreNanoappInfo *info) {
+  auto *fptr = CHRE_NSL_LAZY_LOOKUP(chreGetNanoappInfoByInstanceId);
+  bool success = (fptr != nullptr) ? fptr(instanceId, info) : false;
+  if (success && chreGetApiVersion() < CHRE_API_VERSION_1_8) {
+    populateChreNanoappInfoPre18(info);
+  }
+  return success;
+}
+
 #endif  // CHRE_NANOAPP_DISABLE_BACKCOMPAT
diff --git a/platform/shared/nanoapp_loader.cc b/platform/shared/nanoapp_loader.cc
index 3dd32a7..eb4ec31 100644
--- a/platform/shared/nanoapp_loader.cc
+++ b/platform/shared/nanoapp_loader.cc
@@ -21,8 +21,6 @@
 
 #include "chre/platform/shared/nanoapp_loader.h"
 
-#include "ash.h"
-#include "ash/profile.h"
 #include "chre.h"
 #include "chre/platform/assert.h"
 #include "chre/platform/fatal_error.h"
@@ -32,6 +30,10 @@
 #include "chre/util/dynamic_vector.h"
 #include "chre/util/macros.h"
 
+#ifdef CHREX_SYMBOL_EXTENSIONS
+#include "chre/extensions/platform/symbol_list.h"
+#endif
+
 #ifndef CHRE_LOADER_ARCH
 #define CHRE_LOADER_ARCH EM_ARM
 #endif  // CHRE_LOADER_ARCH
@@ -53,35 +55,31 @@
 //! Indicates whether a failure occurred during static initialization.
 bool gStaticInitFailure = false;
 
-// The new operator is used by singleton.h which causes the delete operator to
-// be undefined in nanoapp binaries even though it's unused. Define this in case
-// a nanoapp actually tries to use the operator.
-void deleteOverride(void *ptr) {
-  FATAL_ERROR("Nanoapp tried to free %p through delete operator", ptr);
-}
-
-// From C++17, a call to the overloaded delete operator aimed at safely freeing
-// over-aligned allocations maybe present in the nanoapp binary even though it
-// is unused. Since all memory allocations/deallocations are managed by CHRE,
-// signal a fatal error if the nanoapp tries to use this version of the delete
-// operator.
-void deleteAlignedOverride(void *ptr, std::align_val_t alignment) {
-  UNUSED_VAR(alignment);
-
-  FATAL_ERROR("Nanoapp tried to free aligned %p via the delte operator", ptr);
-}
-
-// atexit is used to register functions that must be called when a binary is
-// removed from the system.
-int atexitOverride(void (*function)(void)) {
-  LOGV("atexit invoked with %p", function);
+int atexitInternal(struct AtExitCallback &cb) {
   if (gCurrentlyLoadingNanoapp == nullptr) {
     CHRE_ASSERT_LOG(false,
                     "atexit is only supported during static initialization.");
     return -1;
   }
 
-  gCurrentlyLoadingNanoapp->registerAtexitFunction(function);
+  gCurrentlyLoadingNanoapp->registerAtexitFunction(cb);
+  return 0;
+}
+
+// atexit is used to register functions that must be called when a binary is
+// removed from the system. The call back function has an arg (void *)
+int cxaAtexitOverride(void (*func)(void *), void *arg, void *dso) {
+  LOGV("__cxa_atexit invoked with %p, %p, %p", func, arg, dso);
+  struct AtExitCallback cb(func, arg);
+  atexitInternal(cb);
+  return 0;
+}
+
+// The call back function has no arg.
+int atexitOverride(void (*func)(void)) {
+  LOGV("atexit invoked with %p", func);
+  struct AtExitCallback cb(func);
+  atexitInternal(cb);
   return 0;
 }
 
@@ -134,11 +132,6 @@
   chreAbort(CHRE_ERROR /* abortCode */);
 }
 
-#define ADD_EXPORTED_SYMBOL(function_name, function_string) \
-  { reinterpret_cast<void *>(function_name), function_string }
-#define ADD_EXPORTED_C_SYMBOL(function_name) \
-  ADD_EXPORTED_SYMBOL(function_name, STRINGIFY(function_name))
-
 // TODO(karthikmb/stange): While this array was hand-coded for simple
 // "hello-world" prototyping, the list of exported symbols must be
 // generated to minimize runtime errors and build breaks.
@@ -146,7 +139,7 @@
 // Disable deprecation warning so that deprecated symbols in the array
 // can be exported for older nanoapps and tests.
 CHRE_DEPRECATED_PREAMBLE
-const ExportedData gExportedData[] = {
+const ExportedData kExportedData[] = {
     /* libmath overrides and symbols */
     ADD_EXPORTED_SYMBOL(asinOverride, "asin"),
     ADD_EXPORTED_SYMBOL(atan2Override, "atan2"),
@@ -173,6 +166,7 @@
     ADD_EXPORTED_C_SYMBOL(log1pf),
     ADD_EXPORTED_C_SYMBOL(log2f),
     ADD_EXPORTED_C_SYMBOL(logf),
+    ADD_EXPORTED_C_SYMBOL(lrintf),
     ADD_EXPORTED_C_SYMBOL(lroundf),
     ADD_EXPORTED_C_SYMBOL(powf),
     ADD_EXPORTED_C_SYMBOL(remainderf),
@@ -183,9 +177,8 @@
     ADD_EXPORTED_C_SYMBOL(tanhf),
     /* libc overrides and symbols */
     ADD_EXPORTED_C_SYMBOL(__cxa_pure_virtual),
+    ADD_EXPORTED_SYMBOL(cxaAtexitOverride, "__cxa_atexit"),
     ADD_EXPORTED_SYMBOL(atexitOverride, "atexit"),
-    ADD_EXPORTED_SYMBOL(deleteOverride, "_ZdlPv"),
-    ADD_EXPORTED_SYMBOL(deleteAlignedOverride, "_ZdlPvSt11align_val_t"),
     ADD_EXPORTED_C_SYMBOL(dlsym),
     ADD_EXPORTED_C_SYMBOL(isgraph),
     ADD_EXPORTED_C_SYMBOL(memcmp),
@@ -197,24 +190,17 @@
     ADD_EXPORTED_C_SYMBOL(strlen),
     ADD_EXPORTED_C_SYMBOL(strncmp),
     ADD_EXPORTED_C_SYMBOL(tolower),
-    /* ash symbols */
-    ADD_EXPORTED_C_SYMBOL(ashProfileInit),
-    ADD_EXPORTED_C_SYMBOL(ashProfileBegin),
-    ADD_EXPORTED_C_SYMBOL(ashProfileEnd),
-    ADD_EXPORTED_C_SYMBOL(ashLoadCalibrationParams),
-    ADD_EXPORTED_C_SYMBOL(ashSaveCalibrationParams),
-    ADD_EXPORTED_C_SYMBOL(ashSetCalibration),
-    ADD_EXPORTED_C_SYMBOL(ashLoadMultiCalibrationParams),
-    ADD_EXPORTED_C_SYMBOL(ashSaveMultiCalibrationParams),
-    ADD_EXPORTED_C_SYMBOL(ashSetMultiCalibration),
     /* CHRE symbols */
     ADD_EXPORTED_C_SYMBOL(chreAbort),
     ADD_EXPORTED_C_SYMBOL(chreAudioConfigureSource),
     ADD_EXPORTED_C_SYMBOL(chreAudioGetSource),
     ADD_EXPORTED_C_SYMBOL(chreBleGetCapabilities),
     ADD_EXPORTED_C_SYMBOL(chreBleGetFilterCapabilities),
+    ADD_EXPORTED_C_SYMBOL(chreBleFlushAsync),
     ADD_EXPORTED_C_SYMBOL(chreBleStartScanAsync),
     ADD_EXPORTED_C_SYMBOL(chreBleStopScanAsync),
+    ADD_EXPORTED_C_SYMBOL(chreBleReadRssiAsync),
+    ADD_EXPORTED_C_SYMBOL(chreBleGetScanStatus),
     ADD_EXPORTED_C_SYMBOL(chreConfigureDebugDumpEvent),
     ADD_EXPORTED_C_SYMBOL(chreConfigureHostSleepStateEvents),
     ADD_EXPORTED_C_SYMBOL(chreConfigureNanoappInfoEvents),
@@ -299,14 +285,22 @@
 
 void *NanoappLoader::findExportedSymbol(const char *name) {
   size_t nameLen = strlen(name);
-  for (size_t i = 0; i < ARRAY_SIZE(gExportedData); i++) {
-    if (nameLen == strlen(gExportedData[i].dataName) &&
-        strncmp(name, gExportedData[i].dataName, nameLen) == 0) {
-      return gExportedData[i].data;
+  for (size_t i = 0; i < ARRAY_SIZE(kExportedData); i++) {
+    if (nameLen == strlen(kExportedData[i].dataName) &&
+        strncmp(name, kExportedData[i].dataName, nameLen) == 0) {
+      return kExportedData[i].data;
     }
   }
 
-  LOGE("Unable to find %s", name);
+#ifdef CHREX_SYMBOL_EXTENSIONS
+  for (size_t i = 0; i < ARRAY_SIZE(kVendorExportedData); i++) {
+    if (nameLen == strlen(kVendorExportedData[i].dataName) &&
+        strncmp(name, kVendorExportedData[i].dataName, nameLen) == 0) {
+      return kVendorExportedData[i].data;
+    }
+  }
+#endif
+
   return nullptr;
 }
 
@@ -324,7 +318,7 @@
     } else {
       // Wipe caches before calling init array to ensure initializers are not in
       // the data cache.
-      wipeSystemCaches();
+      wipeSystemCaches(reinterpret_cast<uintptr_t>(mMapping), mMemorySpan);
       if (!callInitArray()) {
         LOGE("Failed to perform static init");
       } else {
@@ -347,24 +341,19 @@
 }
 
 void *NanoappLoader::findSymbolByName(const char *name) {
-  void *symbol = nullptr;
-  uint8_t *index = mSymbolTablePtr;
-  while (index < (mSymbolTablePtr + mSymbolTableSize)) {
-    ElfSym *currSym = reinterpret_cast<ElfSym *>(index);
+  for (size_t offset = 0; offset < mSymbolTableSize; offset += sizeof(ElfSym)) {
+    ElfSym *currSym = reinterpret_cast<ElfSym *>(mSymbolTablePtr + offset);
     const char *symbolName = &mStringTablePtr[currSym->st_name];
 
     if (strncmp(symbolName, name, strlen(name)) == 0) {
-      symbol = mMapping + currSym->st_value;
-      break;
+      return getSymbolTarget(currSym);
     }
-
-    index += sizeof(ElfSym);
   }
-  return symbol;
+  return nullptr;
 }
 
-void NanoappLoader::registerAtexitFunction(void (*function)(void)) {
-  if (!mAtexitFunctions.push_back(function)) {
+void NanoappLoader::registerAtexitFunction(struct AtExitCallback &cb) {
+  if (!mAtexitFunctions.push_back(cb)) {
     LOG_OOM();
     gStaticInitFailure = true;
   }
@@ -373,13 +362,13 @@
 void NanoappLoader::mapBss(const ProgramHeader *hdr) {
   // if the memory size of this segment exceeds the file size zero fill the
   // difference.
-  LOGV("Program Hdr mem sz: %zu file size: %zu", hdr->p_memsz, hdr->p_filesz);
+  LOGV("Program Hdr mem sz: %u file size: %u", hdr->p_memsz, hdr->p_filesz);
   if (hdr->p_memsz > hdr->p_filesz) {
     ElfAddr endOfFile = hdr->p_vaddr + hdr->p_filesz + mLoadBias;
     ElfAddr endOfMem = hdr->p_vaddr + hdr->p_memsz + mLoadBias;
     if (endOfMem > endOfFile) {
       auto deltaMem = endOfMem - endOfFile;
-      LOGV("Zeroing out %zu from page %x", deltaMem, endOfFile);
+      LOGV("Zeroing out %u from page %x", deltaMem, endOfFile);
       memset(reinterpret_cast<void *>(endOfFile), 0, deltaMem);
     }
   }
@@ -398,12 +387,12 @@
     const char *name = getSectionHeaderName(mSectionHeadersPtr[i].sh_name);
     if (strncmp(name, kInitArrayName, strlen(kInitArrayName)) == 0) {
       LOGV("Invoking init function");
-      uintptr_t initArray = reinterpret_cast<uintptr_t>(
-          mLoadBias + mSectionHeadersPtr[i].sh_addr);
+      uintptr_t initArray =
+          static_cast<uintptr_t>(mLoadBias + mSectionHeadersPtr[i].sh_addr);
       uintptr_t offset = 0;
       while (offset < mSectionHeadersPtr[i].sh_size) {
         ElfAddr *funcPtr = reinterpret_cast<ElfAddr *>(initArray + offset);
-        uintptr_t initFunction = reinterpret_cast<uintptr_t>(*funcPtr);
+        uintptr_t initFunction = static_cast<uintptr_t>(*funcPtr);
         ((void (*)())initFunction)();
         offset += sizeof(initFunction);
         if (gStaticInitFailure) {
@@ -423,7 +412,7 @@
 
 uintptr_t NanoappLoader::roundDownToAlign(uintptr_t virtualAddr,
                                           size_t alignment) {
-  return virtualAddr & -alignment;
+  return alignment == 0 ? virtualAddr : virtualAddr & -alignment;
 }
 
 void NanoappLoader::freeAllocatedData() {
@@ -693,7 +682,7 @@
     // program header offset
     bool valid =
         (first->p_offset < elfHeader->e_phoff) &&
-        (first->p_filesz >
+        (first->p_filesz >=
          (elfHeader->e_phoff + (numProgramHeaders * sizeof(ProgramHeader))));
     if (!valid) {
       LOGE("Load segment program header validation failed");
@@ -703,7 +692,7 @@
 
       size_t alignment = first->p_align;
       size_t memorySpan = last->p_vaddr + last->p_memsz - first->p_vaddr;
-      LOGV("Nanoapp image Memory Span: %u", memorySpan);
+      LOGV("Nanoapp image Memory Span: %zu", memorySpan);
 
       if (mIsTcmBinary) {
         mMapping =
@@ -717,13 +706,14 @@
         LOG_OOM();
       } else {
         LOGV("Starting location of mappings %p", mMapping);
+        mMemorySpan = memorySpan;
 
         // Calculate the load bias using the first load segment.
         uintptr_t adjustedFirstLoadSegAddr =
             roundDownToAlign(first->p_vaddr, alignment);
         mLoadBias =
             reinterpret_cast<uintptr_t>(mMapping) - adjustedFirstLoadSegAddr;
-        LOGV("Load bias is %" PRIu32, mLoadBias);
+        LOGV("Load bias is %lu", static_cast<long unsigned int>(mLoadBias));
 
         success = true;
       }
@@ -754,29 +744,49 @@
   return success;
 }
 
-const char *NanoappLoader::getDataName(size_t posInSymbolTable) {
+NanoappLoader::ElfSym *NanoappLoader::getDynamicSymbol(
+    size_t posInSymbolTable) {
   size_t sectionSize = getDynamicSymbolTableSize();
   uint8_t *dynamicSymbolTable = getDynamicSymbolTable();
   size_t numElements = sectionSize / sizeof(ElfSym);
   CHRE_ASSERT(posInSymbolTable < numElements);
-  char *dataName = nullptr;
   if (posInSymbolTable < numElements) {
-    ElfSym *sym = reinterpret_cast<ElfSym *>(
+    return reinterpret_cast<ElfSym *>(
         &dynamicSymbolTable[posInSymbolTable * sizeof(ElfSym)]);
-    dataName = &getDynamicStringTable()[sym->st_name];
   }
-  return dataName;
+  return nullptr;
+}
+
+const char *NanoappLoader::getDataName(const ElfSym *symbol) {
+  return symbol == nullptr ? nullptr
+                           : &getDynamicStringTable()[symbol->st_name];
+}
+
+void *NanoappLoader::getSymbolTarget(const ElfSym *symbol) {
+  if (symbol == nullptr || symbol->st_shndx == SHN_UNDEF) {
+    return nullptr;
+  }
+
+  return mMapping + symbol->st_value;
 }
 
 void *NanoappLoader::resolveData(size_t posInSymbolTable) {
-  const char *dataName = getDataName(posInSymbolTable);
+  const ElfSym *symbol = getDynamicSymbol(posInSymbolTable);
+  const char *dataName = getDataName(symbol);
+  void *target = nullptr;
 
   if (dataName != nullptr) {
     LOGV("Resolving %s", dataName);
-    return findExportedSymbol(dataName);
+    target = findExportedSymbol(dataName);
+    if (target == nullptr) {
+      target = getSymbolTarget(symbol);
+    }
+    if (target == nullptr) {
+      LOGE("Unable to find %s", dataName);
+    }
   }
 
-  return nullptr;
+  return target;
 }
 
 NanoappLoader::DynamicHeader *NanoappLoader::getDynamicHeader() {
@@ -821,126 +831,38 @@
 }
 
 bool NanoappLoader::fixRelocations() {
-  ElfAddr *addr;
   DynamicHeader *dyn = getDynamicHeader();
   ProgramHeader *roSeg = getFirstRoSegHeader();
 
   bool success = false;
   if ((dyn == nullptr) || (roSeg == nullptr)) {
     LOGE("Mandatory headers missing from shared object, aborting load");
-  } else if (getDynEntry(dyn, DT_RELA) != 0) {
-    LOGE("Elf binaries with a DT_RELA dynamic entry are unsupported");
-  } else {
-    ElfRel *reloc =
-        reinterpret_cast<ElfRel *>(mBinary + getDynEntry(dyn, DT_REL));
-    size_t relocSize = getDynEntry(dyn, DT_RELSZ);
-    size_t nRelocs = relocSize / sizeof(ElfRel);
-    LOGV("Relocation %zu entries in DT_REL table", nRelocs);
+  }
 
-    bool resolvedAllSymbols = true;
-    size_t i;
-    for (i = 0; i < nRelocs; ++i) {
-      ElfRel *curr = &reloc[i];
-      int relocType = ELFW_R_TYPE(curr->r_info);
-      switch (relocType) {
-        case R_ARM_RELATIVE:
-          LOGV("Resolving ARM_RELATIVE at offset %" PRIx32, curr->r_offset);
-          addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
-          // TODO: When we move to DRAM allocations, we need to check if the
-          // above address is in a Read-Only section of memory, and give it
-          // temporary write permission if that is the case.
-          *addr += reinterpret_cast<uintptr_t>(mMapping);
-          break;
+  // Must return true if it table is not required or is empty. If
+  // the entry is present when not expected, this must return false.
+  success = relocateTable(dyn, DT_RELA);
+  if (success) {
+    success = relocateTable(dyn, DT_REL);
+  }
 
-        case R_ARM_ABS32: {
-          LOGV("Resolving ARM_ABS32 at offset %" PRIx32, curr->r_offset);
-          addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
-          size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
-          auto *dynamicSymbolTable =
-              reinterpret_cast<ElfSym *>(getDynamicSymbolTable());
-          ElfSym *sym = &dynamicSymbolTable[posInSymbolTable];
-          *addr = reinterpret_cast<uintptr_t>(mMapping + sym->st_value);
-
-          break;
-        }
-
-        case R_ARM_GLOB_DAT: {
-          LOGV("Resolving type ARM_GLOB_DAT at offset %" PRIx32,
-               curr->r_offset);
-          addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
-          size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
-          void *resolved = resolveData(posInSymbolTable);
-          if (resolved == nullptr) {
-            LOGV("Failed to resolve global symbol(%d) at offset 0x%x", i,
-                 curr->r_offset);
-            resolvedAllSymbols = false;
-          }
-          // TODO: When we move to DRAM allocations, we need to check if the
-          // above address is in a Read-Only section of memory, and give it
-          // temporary write permission if that is the case.
-          *addr = reinterpret_cast<ElfAddr>(resolved);
-          break;
-        }
-
-        case R_ARM_COPY:
-          LOGE("R_ARM_COPY is an invalid relocation for shared libraries");
-          break;
-        default:
-          LOGE("Invalid relocation type %u", relocType);
-          break;
-      }
-    }
-
-    if (!resolvedAllSymbols) {
-      LOGE("Unable to resolve all symbols in the binary");
-    } else {
-      success = true;
-    }
+  if (!success) {
+    LOGE("Unable to resolve all symbols in the binary");
   }
 
   return success;
 }
 
-bool NanoappLoader::resolveGot() {
-  ElfAddr *addr;
-  ElfRel *reloc = reinterpret_cast<ElfRel *>(
-      mMapping + getDynEntry(getDynamicHeader(), DT_JMPREL));
-  size_t relocSize = getDynEntry(getDynamicHeader(), DT_PLTRELSZ);
-  size_t nRelocs = relocSize / sizeof(ElfRel);
-  LOGV("Resolving GOT with %zu relocations", nRelocs);
-
-  for (size_t i = 0; i < nRelocs; ++i) {
-    ElfRel *curr = &reloc[i];
-    int relocType = ELFW_R_TYPE(curr->r_info);
-
-    switch (relocType) {
-      case R_ARM_JUMP_SLOT: {
-        LOGV("Resolving ARM_JUMP_SLOT at offset %" PRIx32, curr->r_offset);
-        addr = reinterpret_cast<ElfAddr *>(mMapping + curr->r_offset);
-        size_t posInSymbolTable = ELFW_R_SYM(curr->r_info);
-        void *resolved = resolveData(posInSymbolTable);
-        if (resolved == nullptr) {
-          LOGV("Failed to resolve symbol(%d) at offset 0x%x", i,
-               curr->r_offset);
-          return false;
-        }
-        *addr = reinterpret_cast<ElfAddr>(resolved);
-        break;
-      }
-
-      default:
-        LOGE("Unsupported relocation type: %u for symbol %s", relocType,
-             getDataName(ELFW_R_SYM(curr->r_info)));
-        return false;
-    }
-  }
-  return true;
-}
-
 void NanoappLoader::callAtexitFunctions() {
   while (!mAtexitFunctions.empty()) {
-    LOGV("Calling atexit at %p", mAtexitFunctions.back());
-    mAtexitFunctions.back()();
+    struct AtExitCallback cb = mAtexitFunctions.back();
+    if (cb.arg.has_value()) {
+      LOGV("Calling __cxa_atexit at %p, arg %p", cb.func1, cb.arg.value());
+      cb.func1(cb.arg.value());
+    } else {
+      LOGV("Calling atexit at %p", cb.func0);
+      cb.func0();
+    }
     mAtexitFunctions.pop_back();
   }
 }
@@ -949,12 +871,12 @@
   for (size_t i = 0; i < mNumSectionHeaders; ++i) {
     const char *name = getSectionHeaderName(mSectionHeadersPtr[i].sh_name);
     if (strncmp(name, kFiniArrayName, strlen(kFiniArrayName)) == 0) {
-      uintptr_t finiArray = reinterpret_cast<uintptr_t>(
-          mLoadBias + mSectionHeadersPtr[i].sh_addr);
+      uintptr_t finiArray =
+          static_cast<uintptr_t>(mLoadBias + mSectionHeadersPtr[i].sh_addr);
       uintptr_t offset = 0;
       while (offset < mSectionHeadersPtr[i].sh_size) {
         ElfAddr *funcPtr = reinterpret_cast<ElfAddr *>(finiArray + offset);
-        uintptr_t finiFunction = reinterpret_cast<uintptr_t>(*funcPtr);
+        uintptr_t finiFunction = static_cast<uintptr_t>(*funcPtr);
         ((void (*)())finiFunction)();
         offset += sizeof(finiFunction);
       }
diff --git a/platform/shared/pal_audio_stub.cc b/platform/shared/pal_audio_stub.cc
new file mode 100644
index 0000000..0337e6e
--- /dev/null
+++ b/platform/shared/pal_audio_stub.cc
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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/pal/audio.h"
+
+#include <cinttypes>
+
+const chrePalAudioApi *chrePalAudioGetApi(uint32_t /*requestedApiVersion*/) {
+  // This stub implementation of the CHRE PAL returns nullptr to indicate that
+  // it is not supplied by this platform.
+  return nullptr;
+}
\ No newline at end of file
diff --git a/platform/shared/platform_ble.cc b/platform/shared/platform_ble.cc
index c7d0ad3..e1268ca 100644
--- a/platform/shared/platform_ble.cc
+++ b/platform/shared/platform_ble.cc
@@ -29,19 +29,20 @@
     PlatformBleBase::requestStateResync,
     PlatformBleBase::scanStatusChangeCallback,
     PlatformBleBase::advertisingEventCallback,
+    PlatformBleBase::readRssiCallback,
 };
 
 PlatformBle::~PlatformBle() {
   if (mBleApi != nullptr) {
     LOGD("Platform BLE closing");
-    prePalApiCall();
+    prePalApiCall(PalType::BLE);
     mBleApi->close();
     LOGD("Platform BLE closed");
   }
 }
 
 void PlatformBle::init() {
-  prePalApiCall();
+  prePalApiCall(PalType::BLE);
   mBleApi = chrePalBleGetApi(CHRE_PAL_BLE_API_CURRENT_VERSION);
   if (mBleApi != nullptr) {
     if (!mBleApi->open(&gChrePalSystemApi, &sBleCallbacks)) {
@@ -58,7 +59,7 @@
 
 uint32_t PlatformBle::getCapabilities() {
   if (mBleApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::BLE);
     return mBleApi->getCapabilities();
   } else {
     return CHRE_BLE_CAPABILITIES_NONE;
@@ -67,7 +68,7 @@
 
 uint32_t PlatformBle::getFilterCapabilities() {
   if (mBleApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::BLE);
     return mBleApi->getFilterCapabilities();
   } else {
     return CHRE_BLE_FILTER_CAPABILITIES_NONE;
@@ -77,7 +78,7 @@
 bool PlatformBle::startScanAsync(chreBleScanMode mode, uint32_t reportDelayMs,
                                  const struct chreBleScanFilter *filter) {
   if (mBleApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::BLE);
     return mBleApi->startScan(mode, reportDelayMs, filter);
   } else {
     return false;
@@ -86,7 +87,7 @@
 
 bool PlatformBle::stopScanAsync() {
   if (mBleApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::BLE);
     return mBleApi->stopScan();
   } else {
     return false;
@@ -95,7 +96,7 @@
 
 void PlatformBle::releaseAdvertisingEvent(
     struct chreBleAdvertisementEvent *event) {
-  prePalApiCall();
+  prePalApiCall(PalType::BLE);
   mBleApi->releaseAdvertisingEvent(event);
 }
 
@@ -118,4 +119,25 @@
       .handleAdvertisementEvent(event);
 }
 
+bool PlatformBle::readRssiAsync(uint16_t connectionHandle) {
+  if (mBleApi != nullptr) {
+    prePalApiCall(PalType::BLE);
+    return mBleApi->readRssi(connectionHandle);
+  } else {
+    return false;
+  }
+}
+
+void PlatformBleBase::readRssiCallback(uint8_t errorCode,
+                                       uint16_t connectionHandle, int8_t rssi) {
+#ifdef CHRE_BLE_READ_RSSI_SUPPORT_ENABLED
+  EventLoopManagerSingleton::get()->getBleRequestManager().handleReadRssi(
+      errorCode, connectionHandle, rssi);
+#else
+  UNUSED_VAR(errorCode);
+  UNUSED_VAR(connectionHandle);
+  UNUSED_VAR(rssi);
+#endif
+}
+
 }  // namespace chre
diff --git a/platform/shared/platform_gnss.cc b/platform/shared/platform_gnss.cc
index 4a82d23..2c712bf 100644
--- a/platform/shared/platform_gnss.cc
+++ b/platform/shared/platform_gnss.cc
@@ -35,14 +35,14 @@
 PlatformGnss::~PlatformGnss() {
   if (mGnssApi != nullptr) {
     LOGD("Platform GNSS closing");
-    prePalApiCall();
+    prePalApiCall(PalType::GNSS);
     mGnssApi->close();
     LOGD("Platform GNSS closed");
   }
 }
 
 void PlatformGnss::init() {
-  prePalApiCall();
+  prePalApiCall(PalType::GNSS);
   mGnssApi = chrePalGnssGetApi(CHRE_PAL_GNSS_API_CURRENT_VERSION);
   if (mGnssApi != nullptr) {
     if (!mGnssApi->open(&gChrePalSystemApi, &sGnssCallbacks)) {
@@ -65,7 +65,7 @@
 
 uint32_t PlatformGnss::getCapabilities() {
   if (mGnssApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::GNSS);
     return mGnssApi->getCapabilities();
   } else {
     return CHRE_GNSS_CAPABILITIES_NONE;
@@ -75,7 +75,7 @@
 bool PlatformGnss::controlLocationSession(bool enable, Milliseconds minInterval,
                                           Milliseconds minTimeToNextFix) {
   if (mGnssApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::GNSS);
     return mGnssApi->controlLocationSession(
         enable, static_cast<uint32_t>(minInterval.getMilliseconds()),
         static_cast<uint32_t>(minTimeToNextFix.getMilliseconds()));
@@ -86,7 +86,7 @@
 
 void PlatformGnss::releaseLocationEvent(chreGnssLocationEvent *event) {
   if (mGnssApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::GNSS);
     mGnssApi->releaseLocationEvent(event);
   }
 }
@@ -116,7 +116,7 @@
 bool PlatformGnss::controlMeasurementSession(bool enable,
                                              Milliseconds minInterval) {
   if (mGnssApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::GNSS);
     return mGnssApi->controlMeasurementSession(
         enable, static_cast<uint32_t>(minInterval.getMilliseconds()));
   } else {
@@ -126,7 +126,7 @@
 
 void PlatformGnss::releaseMeasurementDataEvent(chreGnssDataEvent *event) {
   if (mGnssApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::GNSS);
     mGnssApi->releaseMeasurementDataEvent(event);
   }
 }
@@ -135,7 +135,7 @@
   bool success = false;
   if (mGnssApi != nullptr &&
       mGnssApi->moduleVersion >= CHRE_PAL_GNSS_API_V1_2) {
-    prePalApiCall();
+    prePalApiCall(PalType::GNSS);
     success = mGnssApi->configurePassiveLocationListener(enable);
   }
   return success;
diff --git a/platform/shared/platform_wifi.cc b/platform/shared/platform_wifi.cc
index f4c33d8..84c9a3c 100644
--- a/platform/shared/platform_wifi.cc
+++ b/platform/shared/platform_wifi.cc
@@ -40,14 +40,14 @@
 PlatformWifi::~PlatformWifi() {
   if (mWifiApi != nullptr) {
     LOGD("Platform WiFi closing");
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
     mWifiApi->close();
     LOGD("Platform WiFi closed");
   }
 }
 
 void PlatformWifi::init() {
-  prePalApiCall();
+  prePalApiCall(PalType::WIFI);
   mWifiApi = chrePalWifiGetApi(CHRE_PAL_WIFI_API_CURRENT_VERSION);
   if (mWifiApi != nullptr) {
     if (!mWifiApi->open(&gChrePalSystemApi, &sWifiCallbacks)) {
@@ -70,7 +70,7 @@
 
 uint32_t PlatformWifi::getCapabilities() {
   if (mWifiApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
     return mWifiApi->getCapabilities();
   } else {
     return CHRE_WIFI_CAPABILITIES_NONE;
@@ -79,7 +79,7 @@
 
 bool PlatformWifi::configureScanMonitor(bool enable) {
   if (mWifiApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
     return mWifiApi->configureScanMonitor(enable);
   } else {
     return false;
@@ -89,7 +89,7 @@
 bool PlatformWifi::requestRanging(const struct chreWifiRangingParams *params) {
   if (mWifiApi != nullptr &&
       mWifiApi->moduleVersion >= CHRE_PAL_WIFI_API_V1_2) {
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
     return mWifiApi->requestRanging(params);
   } else {
     return false;
@@ -102,7 +102,7 @@
 #ifdef CHRE_WIFI_NAN_SUPPORT_ENABLED
   if (mWifiApi != nullptr &&
       mWifiApi->moduleVersion >= CHRE_PAL_WIFI_API_V1_6) {
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
     success = mWifiApi->requestNanRanging(params);
   }
 #endif
@@ -111,7 +111,7 @@
 
 bool PlatformWifi::requestScan(const struct chreWifiScanParams *params) {
   if (mWifiApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
 
     if (mWifiApi->moduleVersion < CHRE_PAL_WIFI_API_V1_5) {
       const struct chreWifiScanParams paramsCompat =
@@ -126,19 +126,19 @@
 }
 
 void PlatformWifi::releaseRangingEvent(struct chreWifiRangingEvent *event) {
-  prePalApiCall();
+  prePalApiCall(PalType::WIFI);
   mWifiApi->releaseRangingEvent(event);
 }
 
 void PlatformWifi::releaseScanEvent(struct chreWifiScanEvent *event) {
-  prePalApiCall();
+  prePalApiCall(PalType::WIFI);
   mWifiApi->releaseScanEvent(event);
 }
 
 void PlatformWifi::releaseNanDiscoveryEvent(
     struct chreWifiNanDiscoveryEvent *event) {
 #ifdef CHRE_WIFI_NAN_SUPPORT_ENABLED
-  prePalApiCall();
+  prePalApiCall(PalType::WIFI);
   mWifiApi->releaseNanDiscoveryEvent(event);
 #else
   UNUSED_VAR(event);
@@ -151,7 +151,7 @@
 #ifdef CHRE_WIFI_NAN_SUPPORT_ENABLED
   if (mWifiApi != nullptr &&
       mWifiApi->moduleVersion >= CHRE_PAL_WIFI_API_V1_6) {
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
     success = mWifiApi->nanSubscribe(config);
   }
 #else
@@ -165,7 +165,7 @@
 #ifdef CHRE_WIFI_NAN_SUPPORT_ENABLED
   if (mWifiApi != nullptr &&
       mWifiApi->moduleVersion >= CHRE_PAL_WIFI_API_V1_6) {
-    prePalApiCall();
+    prePalApiCall(PalType::WIFI);
     success = mWifiApi->nanSubscribeCancel(subscriptionId);
   }
 #else
diff --git a/platform/shared/platform_wwan.cc b/platform/shared/platform_wwan.cc
index 6449dea..2e1119a 100644
--- a/platform/shared/platform_wwan.cc
+++ b/platform/shared/platform_wwan.cc
@@ -31,14 +31,14 @@
 PlatformWwan::~PlatformWwan() {
   if (mWwanApi != nullptr) {
     LOGD("Platform WWAN closing");
-    prePalApiCall();
+    prePalApiCall(PalType::WWAN);
     mWwanApi->close();
     LOGD("Platform WWAN closed");
   }
 }
 
 void PlatformWwan::init() {
-  prePalApiCall();
+  prePalApiCall(PalType::WWAN);
   mWwanApi = chrePalWwanGetApi(CHRE_PAL_WWAN_API_CURRENT_VERSION);
   if (mWwanApi != nullptr) {
     if (!mWwanApi->open(&gChrePalSystemApi, &sWwanCallbacks)) {
@@ -61,7 +61,7 @@
 
 uint32_t PlatformWwan::getCapabilities() {
   if (mWwanApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::WWAN);
     return mWwanApi->getCapabilities();
   } else {
     return CHRE_WWAN_CAPABILITIES_NONE;
@@ -70,7 +70,7 @@
 
 bool PlatformWwan::requestCellInfo() {
   if (mWwanApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::WWAN);
     return mWwanApi->requestCellInfo();
   } else {
     return false;
@@ -79,7 +79,7 @@
 
 void PlatformWwan::releaseCellInfoResult(chreWwanCellInfoResult *result) {
   if (mWwanApi != nullptr) {
-    prePalApiCall();
+    prePalApiCall(PalType::WWAN);
     mWwanApi->releaseCellInfoResult(result);
   }
 }
diff --git a/platform/shared/pw_tokenized_log.cc b/platform/shared/pw_tokenized_log.cc
deleted file mode 100644
index 9817bb6..0000000
--- a/platform/shared/pw_tokenized_log.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2020 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 <cstdio>
-
-#include "chre/core/event_loop_manager.h"
-#include "chre/platform/system_time.h"
-#include "chre/util/nested_data_ptr.h"
-
-#ifdef CHRE_USE_TOKENIZED_LOGGING
-namespace {
-
-static constexpr size_t kLogBufferSize = 60;
-
-}  // anonymous namespace
-
-// The callback function that must be defined to handle an encoded
-// tokenizer message.
-void pw_TokenizerHandleEncodedMessageWithPayload(void *userPayload,
-                                                 const uint8_t encodedMsg[],
-                                                 size_t encodedMsgSize) {
-  // The header encoding here follows the message definition in the
-  // host_messages.fbs flatbuffers file.
-  uint8_t logBuffer[kLogBufferSize];
-  constexpr size_t kLogMessageHeaderSizeBytes = 1 + sizeof(uint64_t);
-  uint8_t *pLogBuffer = &logBuffer[0];
-
-  chre::NestedDataPtr<uint8_t> nestedLevel(userPayload);
-  *pLogBuffer = nestedLevel.data;
-  ++pLogBuffer;
-
-  uint64_t timestampNanos =
-      chre::SystemTime::getMonotonicTime().toRawNanoseconds();
-  memcpy(pLogBuffer, &timestampNanos, sizeof(uint64_t));
-  pLogBuffer += sizeof(uint64_t);
-  memcpy(pLogBuffer, encodedMsg, encodedMsgSize);
-
-  // TODO (b/148873804): buffer log messages generated while the AP is asleep
-  auto &hostCommsMgr =
-      chre::EventLoopManagerSingleton::get()->getHostCommsManager();
-  hostCommsMgr.sendLogMessage(logBuffer,
-                              encodedMsgSize + kLogMessageHeaderSizeBytes);
-}
-#endif
diff --git a/platform/linux/include/chre/target_platform/platform_sensor_base.h b/platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_base.h
similarity index 86%
rename from platform/linux/include/chre/target_platform/platform_sensor_base.h
rename to platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_base.h
index 08c6741..46e78e4 100644
--- a/platform/linux/include/chre/target_platform/platform_sensor_base.h
+++ b/platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_base.h
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_PLATFORM_LINUX_PLATFORM_SENSOR_BASE_H_
-#define CHRE_PLATFORM_LINUX_PLATFORM_SENSOR_BASE_H_
+#ifndef CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_BASE_H_
+#define CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_BASE_H_
 
 #include "chre_api/chre/sensor.h"
 
 namespace chre {
 
 /**
- * Storage for the Linux implementation of the PlatformSensor class.
+ * Platform sensor base class.
  */
 class PlatformSensorBase {
  public:
@@ -61,4 +61,4 @@
 
 }  // namespace chre
 
-#endif  // CHRE_PLATFORM_LINUX_PLATFORM_SENSOR_BASE_H_
+#endif  // CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_BASE_H_
diff --git a/platform/linux/include/chre/target_platform/platform_sensor_manager_base.h b/platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_manager_base.h
similarity index 85%
rename from platform/linux/include/chre/target_platform/platform_sensor_manager_base.h
rename to platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_manager_base.h
index 9d478d0..d2d246a 100644
--- a/platform/linux/include/chre/target_platform/platform_sensor_manager_base.h
+++ b/platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_manager_base.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_PLATFORM_LINUX_PLATFORM_SENSOR_MANAGER_BASE_H_
-#define CHRE_PLATFORM_LINUX_PLATFORM_SENSOR_MANAGER_BASE_H_
+#ifndef CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_MANAGER_BASE_H_
+#define CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_MANAGER_BASE_H_
 
 #include <cstdint>
 
@@ -24,7 +24,7 @@
 namespace chre {
 
 /**
- * Storage for the Linux implementation of the PlatformSensorManager class.
+ * Platform sensor manager base class.
  */
 class PlatformSensorManagerBase {
  public:
@@ -48,4 +48,4 @@
 
 }  // namespace chre
 
-#endif  // CHRE_PLATFORM_LINUX_PLATFORM_SENSOR_MANAGER_BASE_H_
+#endif  // CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_MANAGER_BASE_H_
diff --git a/platform/freertos/include/chre/target_platform/atomic_base.h b/platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_type_helpers_base.h
similarity index 65%
rename from platform/freertos/include/chre/target_platform/atomic_base.h
rename to platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_type_helpers_base.h
index 582d0ae..729ec5c 100644
--- a/platform/freertos/include/chre/target_platform/atomic_base.h
+++ b/platform/shared/sensor_pal/include/chre/target_platform/platform_sensor_type_helpers_base.h
@@ -14,22 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
-#define CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
-
-#include <atomic>
+#ifndef CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
+#define CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
 
 namespace chre {
 
-template <typename AtomicType>
-class AtomicBase {
- protected:
-  std::atomic<AtomicType> mAtomic;
-};
-
-typedef AtomicBase<bool> AtomicBoolBase;
-typedef AtomicBase<uint32_t> AtomicUint32Base;
+class PlatformSensorTypeHelpersBase {};
 
 }  // namespace chre
 
-#endif  // CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_H_
+#endif  // CHRE_PLATFORM_SHARED_SENSOR_PAL_PLATFORM_SENSOR_TYPE_HELPERS_BASE_H_
diff --git a/platform/linux/platform_sensor.cc b/platform/shared/sensor_pal/platform_sensor.cc
similarity index 100%
rename from platform/linux/platform_sensor.cc
rename to platform/shared/sensor_pal/platform_sensor.cc
diff --git a/platform/shared/platform_sensor_manager.cc b/platform/shared/sensor_pal/platform_sensor_manager.cc
similarity index 100%
rename from platform/shared/platform_sensor_manager.cc
rename to platform/shared/sensor_pal/platform_sensor_manager.cc
diff --git a/platform/linux/platform_sensor_type_helpers.cc b/platform/shared/sensor_pal/platform_sensor_type_helpers.cc
similarity index 100%
rename from platform/linux/platform_sensor_type_helpers.cc
rename to platform/shared/sensor_pal/platform_sensor_type_helpers.cc
diff --git a/platform/shared/system_time.cc b/platform/shared/system_time.cc
index 47edfbe..9f77e8d 100644
--- a/platform/shared/system_time.cc
+++ b/platform/shared/system_time.cc
@@ -18,7 +18,9 @@
 
 namespace chre {
 
+namespace {
 Nanoseconds gStartTime(0);
+}
 
 void SystemTime::init() {
   gStartTime = getMonotonicTime();
diff --git a/platform/shared/tracing.cc b/platform/shared/tracing.cc
new file mode 100644
index 0000000..876fe19
--- /dev/null
+++ b/platform/shared/tracing.cc
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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/tracing.h"
+#include "chre/util/macros.h"
+
+namespace chre {
+
+void traceRegisterNanoapp(uint16_t instanceId, const char *name) {
+  UNUSED_VAR(instanceId);
+  UNUSED_VAR(name);
+}
+
+void traceNanoappHandleEventStart(uint16_t instanceId, uint16_t eventType) {
+  UNUSED_VAR(instanceId);
+  UNUSED_VAR(eventType);
+}
+
+void traceNanoappHandleEventEnd(uint16_t instanceId) {
+  UNUSED_VAR(instanceId);
+}
+
+}  // namespace chre
diff --git a/platform/slpi/host_link.cc b/platform/slpi/host_link.cc
index 2f4f461..e1d6985 100644
--- a/platform/slpi/host_link.cc
+++ b/platform/slpi/host_link.cc
@@ -56,14 +56,6 @@
 //! TODO: Make this a member of HostLinkBase
 Nanoseconds gLastTimeSyncRequestNanos(0);
 
-struct LoadNanoappCallbackData {
-  uint64_t appId;
-  uint32_t transactionId;
-  uint16_t hostClientId;
-  UniquePtr<Nanoapp> nanoapp;
-  uint32_t fragmentId;
-};
-
 struct NanoappListData {
   ChreFlatBufferBuilder *builder;
   DynamicVector<NanoappListEntryOffset> nanoappEntries;
@@ -231,25 +223,6 @@
                                               cbData->hostClientId);
 }
 
-void finishLoadingNanoappCallback(SystemCallbackType /*type*/,
-                                  UniquePtr<LoadNanoappCallbackData> &&data) {
-  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
-    auto *cbData = static_cast<LoadNanoappCallbackData *>(cookie);
-
-    EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
-    bool success =
-        cbData->nanoapp->isLoaded() && eventLoop.startNanoapp(cbData->nanoapp);
-
-    HostProtocolChre::encodeLoadNanoappResponse(builder, cbData->hostClientId,
-                                                cbData->transactionId, success,
-                                                cbData->fragmentId);
-  };
-
-  constexpr size_t kInitialBufferSize = 48;
-  buildAndEnqueueMessage(PendingMessageType::LoadNanoappResponse,
-                         kInitialBufferSize, msgBuilder, data.get());
-}
-
 void handleUnloadNanoappCallback(SystemCallbackType /*type*/,
                                  UniquePtr<UnloadNanoappCallbackData> &&data) {
   auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
@@ -410,33 +383,6 @@
                          msgBuilder, &data);
 }
 
-void sendFragmentResponse(uint16_t hostClientId, uint32_t transactionId,
-                          uint32_t fragmentId, bool success) {
-  struct FragmentedLoadInfoResponse {
-    uint16_t hostClientId;
-    uint32_t transactionId;
-    uint32_t fragmentId;
-    bool success;
-  };
-
-  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
-    auto *cbData = static_cast<FragmentedLoadInfoResponse *>(cookie);
-    HostProtocolChre::encodeLoadNanoappResponse(
-        builder, cbData->hostClientId, cbData->transactionId, cbData->success,
-        cbData->fragmentId);
-  };
-
-  FragmentedLoadInfoResponse response = {
-      .hostClientId = hostClientId,
-      .transactionId = transactionId,
-      .fragmentId = fragmentId,
-      .success = success,
-  };
-  constexpr size_t kInitialBufferSize = 48;
-  buildAndEnqueueMessage(PendingMessageType::LoadNanoappResponse,
-                         kInitialBufferSize, msgBuilder, &response);
-}
-
 /**
  * Sends a request to the host for a time sync message.
  */
@@ -512,67 +458,6 @@
 }
 
 /**
- * Helper function that prepares a nanoapp that can be loaded into the system
- * from a buffer sent over in 1 or more fragments.
- *
- * @param hostClientId the ID of client that originated this transaction
- * @param transactionId the ID of the transaction
- * @param appId the ID of the app to load
- * @param appVersion the version of the app to load
- * @param appFlags The flags provided by the app being loaded
- * @param targetApiVersion the API version this nanoapp is targeted for
- * @param buffer the nanoapp binary data. May be only part of the nanoapp's
- *     binary if it's being sent over multiple fragments
- * @param bufferLen the size of buffer in bytes
- * @param fragmentId the identifier indicating which fragment is being loaded
- * @param appBinaryLen the full size of the nanoapp binary to be loaded
- *
- * @return A valid pointer to a nanoapp that can be loaded into the system. A
- *     nullptr if the preparation process fails.
- */
-UniquePtr<Nanoapp> handleLoadNanoappData(uint16_t hostClientId,
-                                         uint32_t transactionId, uint64_t appId,
-                                         uint32_t appVersion, uint32_t appFlags,
-                                         uint32_t targetApiVersion,
-                                         const void *buffer, size_t bufferLen,
-                                         uint32_t fragmentId,
-                                         size_t appBinaryLen) {
-  static NanoappLoadManager sLoadManager;
-
-  bool success = true;
-  if (fragmentId == 0 || fragmentId == 1) {  // first fragment
-    size_t totalAppBinaryLen = (fragmentId == 0) ? bufferLen : appBinaryLen;
-    LOGD("Load nanoapp request for app ID 0x%016" PRIx64 " ver 0x%" PRIx32
-         " flags 0x%" PRIx32 " target API 0x%08" PRIx32
-         " size %zu (txnId %" PRIu32 " client %" PRIu16 ")",
-         appId, appVersion, appFlags, targetApiVersion, totalAppBinaryLen,
-         transactionId, hostClientId);
-
-    if (sLoadManager.hasPendingLoadTransaction()) {
-      FragmentedLoadInfo info = sLoadManager.getTransactionInfo();
-      sendFragmentResponse(info.hostClientId, info.transactionId,
-                           0 /* fragmentId */, false /* success */);
-      sLoadManager.markFailure();
-    }
-
-    success = sLoadManager.prepareForLoad(hostClientId, transactionId, appId,
-                                          appVersion, appFlags,
-                                          totalAppBinaryLen, targetApiVersion);
-  }
-  success &= sLoadManager.copyNanoappFragment(
-      hostClientId, transactionId, (fragmentId == 0) ? 1 : fragmentId, buffer,
-      bufferLen);
-
-  UniquePtr<Nanoapp> nanoapp;
-  if (!sLoadManager.isLoadComplete()) {
-    sendFragmentResponse(hostClientId, transactionId, fragmentId, success);
-  } else {
-    nanoapp = sLoadManager.releaseNanoapp();
-  }
-  return nanoapp;
-}
-
-/**
  * FastRPC method invoked by the host to block on messages
  *
  * @param buffer Output buffer to populate with message data
@@ -850,6 +735,35 @@
                          kInitialSize, msgBuilder, nullptr);
 }
 
+void HostMessageHandlers::sendFragmentResponse(uint16_t hostClientId,
+                                               uint32_t transactionId,
+                                               uint32_t fragmentId,
+                                               bool success) {
+  struct FragmentedLoadInfoResponse {
+    uint16_t hostClientId;
+    uint32_t transactionId;
+    uint32_t fragmentId;
+    bool success;
+  };
+
+  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
+    auto *cbData = static_cast<FragmentedLoadInfoResponse *>(cookie);
+    HostProtocolChre::encodeLoadNanoappResponse(
+        builder, cbData->hostClientId, cbData->transactionId, cbData->success,
+        cbData->fragmentId);
+  };
+
+  FragmentedLoadInfoResponse response = {
+      .hostClientId = hostClientId,
+      .transactionId = transactionId,
+      .fragmentId = fragmentId,
+      .success = success,
+  };
+  constexpr size_t kInitialBufferSize = 48;
+  buildAndEnqueueMessage(PendingMessageType::LoadNanoappResponse,
+                         kInitialBufferSize, msgBuilder, &response);
+}
+
 void HostMessageHandlers::handleNanoappMessage(uint64_t appId,
                                                uint32_t messageType,
                                                uint16_t hostEndpoint,
@@ -907,17 +821,17 @@
     uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
     const void *buffer, size_t bufferLen, const char *appFileName,
     uint32_t fragmentId, size_t appBinaryLen, bool respondBeforeStart) {
-  UniquePtr<Nanoapp> pendingNanoapp;
-  if (appFileName != nullptr) {
-    pendingNanoapp =
-        handleLoadNanoappFile(hostClientId, transactionId, appId, appVersion,
-                              targetApiVersion, appFileName);
-  } else {
-    pendingNanoapp = handleLoadNanoappData(
-        hostClientId, transactionId, appId, appVersion, appFlags,
-        targetApiVersion, buffer, bufferLen, fragmentId, appBinaryLen);
+  if (appFileName == nullptr) {
+    loadNanoappData(hostClientId, transactionId, appId, appVersion, appFlags,
+                    targetApiVersion, buffer, bufferLen, fragmentId,
+                    appBinaryLen, respondBeforeStart);
+    return;
   }
 
+  UniquePtr<Nanoapp> pendingNanoapp =
+      handleLoadNanoappFile(hostClientId, transactionId, appId, appVersion,
+                            targetApiVersion, appFileName);
+
   if (!pendingNanoapp.isNull()) {
     auto cbData = MakeUnique<LoadNanoappCallbackData>();
     if (cbData.isNull()) {
diff --git a/platform/slpi/include/chre/target_platform/log.h b/platform/slpi/include/chre/target_platform/log.h
index 93057e3..1bd6adf 100644
--- a/platform/slpi/include/chre/target_platform/log.h
+++ b/platform/slpi/include/chre/target_platform/log.h
@@ -58,7 +58,7 @@
 #define LOGD(fmt, ...) CHRE_BUFFER_LOG(CHRE_LOG_DEBUG, fmt, ##__VA_ARGS__)
 #define LOGV(fmt, ...) CHRE_BUFFER_LOG(CHRE_LOG_VERBOSE, fmt, ##__VA_ARGS__)
 
-#elif defined(CHRE_USE_TOKENIZED_LOGGING)
+#elif defined(CHRE_TOKENIZED_LOGGING_ENABLED)
 #include "pw_tokenizer/tokenize.h"
 #define CHRE_SEND_TOKENIZED_LOG(level, fmt, ...)                   \
   do {                                                             \
diff --git a/platform/slpi/log_buffer_manager.cc b/platform/slpi/log_buffer_manager.cc
index 5217767..976cf2b 100644
--- a/platform/slpi/log_buffer_manager.cc
+++ b/platform/slpi/log_buffer_manager.cc
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2021 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/log_buffer_manager.h"
 
 namespace chre {
diff --git a/platform/slpi/platform_nanoapp.cc b/platform/slpi/platform_nanoapp.cc
index 02b15ac..979debe 100644
--- a/platform/slpi/platform_nanoapp.cc
+++ b/platform/slpi/platform_nanoapp.cc
@@ -238,7 +238,7 @@
              getAppVersionString(), mAppInfo->isTcmNanoapp,
              mAppInfo->isSystemNanoapp);
         if (mAppInfo->structMinorVersion >=
-            CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION) {
+            CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3) {
           LOGI("Nanoapp permissions: 0x%" PRIx32, mAppInfo->appPermissions);
         }
       }
@@ -285,7 +285,7 @@
 
 bool PlatformNanoapp::supportsAppPermissions() const {
   return (mAppInfo != nullptr) ? (mAppInfo->structMinorVersion >=
-                                  CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION)
+                                  CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION_3)
                                : false;
 }
 
diff --git a/platform/slpi/platform_pal.cc b/platform/slpi/platform_pal.cc
index 0f9d872..163b7ff 100644
--- a/platform/slpi/platform_pal.cc
+++ b/platform/slpi/platform_pal.cc
@@ -17,10 +17,12 @@
 #include "chre/platform/shared/platform_pal.h"
 
 #include "chre/platform/slpi/power_control_util.h"
+#include "chre/util/macros.h"
 
 namespace chre {
 
-void PlatformPal::prePalApiCall() const {
+void PlatformPal::prePalApiCall(PalType palType) const {
+  UNUSED_VAR(palType);
   slpiForceBigImage();
 }
 
diff --git a/platform/slpi/qsh/qsh_proto_shim.cc b/platform/slpi/qsh/qsh_proto_shim.cc
index fe8c85e..e56282d 100644
--- a/platform/slpi/qsh/qsh_proto_shim.cc
+++ b/platform/slpi/qsh/qsh_proto_shim.cc
@@ -31,6 +31,7 @@
 #include "chre/platform/slpi/qsh/qsh_proto_shim.h"
 #include "chre/sensor.h"
 #include "chre/util/macros.h"
+#include "chre/util/system/event_callbacks.h"
 
 namespace chre {
 namespace {
diff --git a/platform/slpi/system_time_util.cc b/platform/slpi/system_time_util.cc
index f184c98..8064faf 100644
--- a/platform/slpi/system_time_util.cc
+++ b/platform/slpi/system_time_util.cc
@@ -18,11 +18,21 @@
 
 #include "chre/util/time.h"
 
+extern "C" {
+
+#include "uTimetick.h"
+
+}  // extern "C"
+
 namespace chre {
 
-uint64_t getNanosecondsFromQTimerTicks(uint64_t ticks) {
-  constexpr uint64_t kClockFreq = 19200000;  // 19.2MHz QTimer clock
+namespace {
 
+const uint64_t kClockFreq = uTimetick_CvtToTicks(1, T_SEC);
+
+}  // anonymous namespace
+
+uint64_t getNanosecondsFromQTimerTicks(uint64_t ticks) {
   uint64_t nsec = 0;
   if (ticks >= kClockFreq) {
     uint64_t seconds = (ticks / kClockFreq);
diff --git a/platform/tinysys/README.md b/platform/tinysys/README.md
new file mode 100644
index 0000000..a1f2bff
--- /dev/null
+++ b/platform/tinysys/README.md
@@ -0,0 +1,6 @@
+### CHRE on Tinysys/SCP
+
+This folder contains the CHRE platform implementation for MediaTek SoCs that use
+a software platform called tinysys for their always-on processing domain(s).
+Within tinysys, SCP refers to the Sensor-hub Control Processor, which is where
+CHRE runs in most/all cases.
diff --git a/platform/tinysys/authentication.cc b/platform/tinysys/authentication.cc
new file mode 100644
index 0000000..e33555e
--- /dev/null
+++ b/platform/tinysys/authentication.cc
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2022 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 <inttypes.h>
+#include <cstdint>
+
+#include "chre/platform/log.h"
+#include "chre/platform/shared/authentication.h"
+#include "chre/util/macros.h"
+
+#include "mbedtls/pk.h"
+#include "mbedtls/sha256.h"
+
+namespace chre {
+namespace {
+
+// All the size below are in bytes
+constexpr uint32_t kEcdsaP256SigSize = 64;
+constexpr uint32_t kEcdsaP256PublicKeySize = 64;
+constexpr uint32_t kHeaderSize = 0x1000;
+constexpr uint32_t kSha256HashSize = 32;
+
+// ASCII of "CHRE", in BE
+constexpr uint32_t kChreMagicNumber = 0x45524843;
+
+// Production public key
+const uint8_t kGooglePublicKey[kEcdsaP256PublicKeySize] = {
+    0x97, 0x66, 0x1f, 0xe7, 0x26, 0xc5, 0xc3, 0x9c, 0xe6, 0x71, 0x59,
+    0x1f, 0x26, 0x3b, 0x1c, 0x87, 0x50, 0x7f, 0xad, 0x4f, 0xeb, 0x4b,
+    0xe5, 0x3b, 0xee, 0x76, 0xff, 0x80, 0x6a, 0x8b, 0x6d, 0xed, 0x58,
+    0xd7, 0xed, 0xf3, 0x18, 0x9e, 0x9a, 0xac, 0xcf, 0xfc, 0xd2, 0x7,
+    0x35, 0x64, 0x54, 0xcc, 0xbc, 0x8b, 0xe0, 0x6c, 0x77, 0xbe, 0xbb,
+    0x1b, 0xdd, 0x18, 0x6d, 0x77, 0xfe, 0xb7, 0x0,  0xd5};
+
+const uint8_t *const kTrustedPublicKeys[] = {kGooglePublicKey};
+
+/**
+ * A data structure encapsulating metadata necessary for nanoapp binary
+ * signature verification.
+ *
+ * Note that the structure field names that start with 'reserved' are currently
+ * unused.
+ */
+struct HeaderInfo {
+  /**
+   * A magic number indicating the start of the header info, ASCII decodes to
+   * 'CHRE'.
+   */
+  uint32_t magic;
+
+  uint32_t headerVersion;
+
+  // TODO(b/260099197): We should have a hardware backed rollback info check.
+  uint32_t reservedRollbackInfo;
+
+  /** The size in bytes of the actual nanoapp binary. */
+  uint32_t binaryLength;
+
+  /** The flag indicating the public key size. */
+  uint64_t flags[2];
+
+  /** The SHA-256 hash of the actual nanoapp binary. */
+  uint8_t binarySha256[kSha256HashSize];
+
+  uint8_t reservedChipId[32];
+
+  uint8_t reservedAuthConfig[256];
+
+  uint8_t reservedImageConfig[256];
+};
+
+/**
+ * A header containing information relevant to nanoapp signature authentication
+ * that is tacked onto every signed nanoapp.
+ */
+struct ImageHeader {
+  /** The zero-padded signature of the nanoapp binary. */
+  uint8_t signature[512];
+
+  /** The zero-padded public key for the key pair used to sign the hash, which
+   * we use to verify whether we trust the signer or not. */
+  uint8_t publicKey[512];
+
+  /** @see struct HeaderInfo. */
+  HeaderInfo headerInfo;
+};
+
+class Authenticator {
+ public:
+  Authenticator() {
+    mbedtls_ecp_group_init(&mGroup);
+    mbedtls_ecp_point_init(&mQ);
+    mbedtls_mpi_init(&mR);
+    mbedtls_mpi_init(&mS);
+  }
+
+  ~Authenticator() {
+    mbedtls_mpi_free(&mS);
+    mbedtls_mpi_free(&mR);
+    mbedtls_ecp_point_free(&mQ);
+    mbedtls_ecp_group_free(&mGroup);
+  }
+
+  bool loadEcpGroup() {
+    int result = mbedtls_ecp_group_load(&mGroup, MBEDTLS_ECP_DP_SECP256R1);
+    if (result != 0) {
+      LOGE("Failed to load ecp group. Error code: %d", result);
+      return false;
+    }
+    return true;
+  }
+
+  bool loadPublicKey(const uint8_t *publicKey) {
+    // 0x04 prefix is required by mbedtls
+    constexpr uint8_t kPublicKeyPrefix = 0x04;
+    uint8_t buffer[kEcdsaP256PublicKeySize + 1] = {kPublicKeyPrefix};
+    memcpy(buffer + 1, publicKey, kEcdsaP256PublicKeySize);
+    int result =
+        mbedtls_ecp_point_read_binary(&mGroup, &mQ, buffer, ARRAY_SIZE(buffer));
+    if (result != 0) {
+      LOGE("Failed to load the public key. Error code: %d", result);
+      return false;
+    }
+    return true;
+  }
+
+  bool loadSignature(const ImageHeader *header) {
+    constexpr uint32_t kRSigSize = kEcdsaP256SigSize / 2;
+    constexpr uint32_t kSSigSize = kEcdsaP256SigSize / 2;
+    int result = mbedtls_mpi_read_binary(&mR, header->signature, kRSigSize);
+    if (result != 0) {
+      LOGE("Failed to read r signature. Error code: %d", result);
+      return false;
+    }
+    result =
+        mbedtls_mpi_read_binary(&mS, header->signature + kRSigSize, kSSigSize);
+    if (result != 0) {
+      LOGE("Failed to read s signature. Error code: %d", result);
+      return false;
+    }
+    return true;
+  }
+
+  bool authenticate(const void *binary) {
+    constexpr size_t kDataOffset = 0x200;
+    constexpr size_t kDataSize = kHeaderSize - kDataOffset;
+    auto data = static_cast<const uint8_t *>(binary) + kDataOffset;
+    unsigned char digest[kSha256HashSize] = {};
+    mbedtls_sha256(data, kDataSize, digest, /* is224= */ 0);
+    int result = mbedtls_ecdsa_verify(&mGroup, digest, ARRAY_SIZE(digest), &mQ,
+                                      &mR, &mS);
+    if (result != 0) {
+      LOGE("Signature verification failed. Error code: %d", result);
+      return false;
+    }
+    return true;
+  }
+
+ private:
+  mbedtls_ecp_group mGroup;
+  mbedtls_ecp_point mQ;
+  mbedtls_mpi mR;
+  mbedtls_mpi mS;
+};
+
+/** Retrieves the public key length based on the flag. */
+uint32_t getPublicKeyLength(const uint64_t *flag) {
+  constexpr int kPkSizeMaskPosition = 9;
+  constexpr uint64_t kPkSizeMask = 0x3;
+  uint8_t keySizeFlag = ((*flag) >> kPkSizeMaskPosition) & kPkSizeMask;
+  switch (keySizeFlag) {
+    case 0:
+      return 64;
+    case 1:
+      return 96;
+    case 2:
+      return 132;
+    default:
+      LOGE("Unsupported flags in nanoapp header!");
+      return 0;
+  }
+}
+
+/** Checks if the hash prvided in the header is derived from the image. */
+bool hasCorrectHash(const void *head, size_t realImageSize,
+                    const uint8_t *hashProvided) {
+  auto image = static_cast<const uint8_t *>(head) + kHeaderSize;
+  uint8_t hashCalculated[kSha256HashSize] = {};
+  mbedtls_sha256(image, realImageSize, hashCalculated, /* is224= */ 0);
+  return memcmp(hashCalculated, hashProvided, kSha256HashSize) == 0;
+}
+
+/** Checks if the public key in the header matches the production public key. */
+bool isValidProductionPublicKey(const uint8_t *publicKey,
+                                size_t publicKeyLength) {
+  if (publicKeyLength != kEcdsaP256PublicKeySize) {
+    LOGE("Public key length %zu is unexpected.", publicKeyLength);
+    return false;
+  }
+  for (size_t i = 0; i < ARRAY_SIZE(kTrustedPublicKeys); i++) {
+    if (memcmp(kTrustedPublicKeys[i], publicKey, kEcdsaP256PublicKeySize) ==
+        0) {
+      return true;
+    }
+  }
+  return false;
+}
+}  // anonymous namespace
+
+bool authenticateBinary(const void *binary, size_t appBinaryLen,
+                        void **realBinaryStart) {
+#ifndef CHRE_NAPP_AUTHENTICATION_ENABLED
+  UNUSED_VAR(binary);
+  UNUSED_VAR(realBinaryStart);
+  LOGW(
+      "Nanoapp authentication is disabled, which exposes the device to "
+      "security risks!");
+  return true;
+#endif
+  if (appBinaryLen <= kHeaderSize) {
+    LOGE("Binary size %zu is too short.", appBinaryLen);
+    return false;
+  }
+  Authenticator authenticator;
+  auto *header = static_cast<const ImageHeader *>(binary);
+  const uint8_t *imageHash = header->headerInfo.binarySha256;
+  const uint8_t *publicKey = header->publicKey;
+  const uint32_t expectedAppBinaryLength =
+      header->headerInfo.binaryLength + kHeaderSize;
+
+  if (header->headerInfo.magic != kChreMagicNumber) {
+    LOGE("Mismatched magic number.");
+  } else if (header->headerInfo.headerVersion != 1) {
+    LOGE("Header version %" PRIu32 " is unsupported.",
+         header->headerInfo.headerVersion);
+  } else if (expectedAppBinaryLength != appBinaryLen) {
+    LOGE("Invalid binary length %zu. Expected %" PRIu32, appBinaryLen,
+         expectedAppBinaryLength);
+  } else if (!isValidProductionPublicKey(
+                 publicKey, getPublicKeyLength(header->headerInfo.flags))) {
+    LOGE("Invalid public key attached on the image.");
+  } else if (!hasCorrectHash(binary, header->headerInfo.binaryLength,
+                             imageHash)) {
+    LOGE("Hash of the nanoapp image is incorrect.");
+  } else if (!authenticator.loadEcpGroup() ||
+             !authenticator.loadPublicKey(publicKey) ||
+             !authenticator.loadSignature(header)) {
+    LOGE("Failed to load authentication data.");
+  } else if (!authenticator.authenticate(binary)) {
+    LOGE("Failed to authenticate the image.");
+  } else {
+    *realBinaryStart = reinterpret_cast<void *>(
+        reinterpret_cast<uintptr_t>(binary) + kHeaderSize);
+    LOGI("Image is authenticated successfully!");
+    return true;
+  }
+  return false;
+}
+}  // namespace chre
diff --git a/platform/tinysys/chre_api_re.cc b/platform/tinysys/chre_api_re.cc
new file mode 100644
index 0000000..dd0d48d
--- /dev/null
+++ b/platform/tinysys/chre_api_re.cc
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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/log.h"
+#include "chre/util/macros.h"
+#include "chre_api/chre/re.h"
+
+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/tinysys/chre_init.cc b/platform/tinysys/chre_init.cc
new file mode 100644
index 0000000..90989de
--- /dev/null
+++ b/platform/tinysys/chre_init.cc
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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/target_platform/chre_init.h"
+
+BaseType_t chreTinysysInit() {
+  return chre::freertos::init();
+}
+
+void chreTinysysDeinit() {
+  chre::freertos::deinit();
+}
\ No newline at end of file
diff --git a/platform/tinysys/condition_variable_base.cc b/platform/tinysys/condition_variable_base.cc
new file mode 100644
index 0000000..715b46b
--- /dev/null
+++ b/platform/tinysys/condition_variable_base.cc
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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/target_platform/condition_variable_base.h"
+#include "chre/platform/condition_variable.h"
+
+#include "intc.h"
+
+namespace chre {
+
+void ConditionVariableBase::conditionVariablTimerCallback(
+    struct rt_timer *rtTimer) {
+  if (rtTimer != nullptr) {
+    ConditionVariable *cv =
+        static_cast<ConditionVariable *>(rtTimer->private_ptr);
+    cv->isTimedOut = true;
+    cv->notify_one();
+  }
+}
+
+ConditionVariable::ConditionVariable() {
+  // TODO(b/256870101): Use xSemaphoreCreateBinaryStatic when possible
+  semaphoreHandle = xSemaphoreCreateBinary();
+  if (semaphoreHandle == nullptr) {
+    FATAL_ERROR("Failed to create cv semaphore");
+  }
+}
+
+ConditionVariable::~ConditionVariable() {
+  if (semaphoreHandle != NULL) {
+    vSemaphoreDelete(semaphoreHandle);
+  }
+}
+
+void ConditionVariable::notify_one() {
+  if (is_in_isr()) {
+    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+    xSemaphoreGiveFromISR(semaphoreHandle, &xHigherPriorityTaskWoken);
+    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+  } else {
+    xSemaphoreGive(semaphoreHandle);
+  }
+}
+
+void ConditionVariable::wait(Mutex &mutex) {
+  mutex.unlock();
+  BaseType_t rc = xSemaphoreTake(semaphoreHandle, portMAX_DELAY);
+  mutex.lock();
+  if (rc == pdFALSE) {
+    LOGE("Semaphore of the condition variable is unavailable.");
+  }
+}
+
+bool ConditionVariable::wait_for(Mutex &mutex, Nanoseconds timeout) {
+  rt_timer_init(&rtSystemTimer,
+                /* func= */ nullptr,
+                /* data= */ this);
+  rt_timer_start(&rtSystemTimer, timeout.toRawNanoseconds(),
+                 /* oneShot= */ true);
+  wait(mutex);
+
+  taskENTER_CRITICAL();
+  if (!isTimedOut) {
+    rt_timer_stop(&rtSystemTimer);
+  }
+  taskEXIT_CRITICAL();
+  return isTimedOut;
+}
+}  // namespace chre
\ No newline at end of file
diff --git a/platform/tinysys/host_cpu_update.cc b/platform/tinysys/host_cpu_update.cc
new file mode 100644
index 0000000..107ba10
--- /dev/null
+++ b/platform/tinysys/host_cpu_update.cc
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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/target_platform/host_cpu_update.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/target_platform/power_control_manager_base.h"
+
+void chreNotifyHostWakeSuspend(bool isAwake) {
+  chre::EventLoopManagerSingleton::get()
+      ->getEventLoop()
+      .getPowerControlManager()
+      .onHostWakeSuspendEvent(isAwake);
+}
\ No newline at end of file
diff --git a/platform/tinysys/host_link.cc b/platform/tinysys/host_link.cc
new file mode 100644
index 0000000..a879c13
--- /dev/null
+++ b/platform/tinysys/host_link.cc
@@ -0,0 +1,770 @@
+/*
+ * Copyright (C) 2023 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 "FreeRTOS.h"
+#include "task.h"
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/host_comms_manager.h"
+#include "chre/platform/host_link.h"
+#include "chre/platform/log.h"
+#include "chre/platform/shared/host_protocol_chre.h"
+#include "chre/platform/shared/log_buffer_manager.h"
+#include "chre/platform/shared/nanoapp_load_manager.h"
+#include "chre/platform/system_time.h"
+#include "chre/platform/system_timer.h"
+#include "chre/util/flatbuffers/helpers.h"
+#include "chre/util/nested_data_ptr.h"
+
+// TODO(b/265046038): MTK headers are not c++ friendly,
+// should be modified to wrap function proto with "extern C"
+#include "dma_api.h"
+#include "ipi.h"
+#include "ipi_id.h"
+#include "scp_dram_region.h"
+
+// Because the LOGx macros are being redirected to logcat through
+// HostLink::sendLogMessageV2 and HostLink::send, calling them from
+// inside HostLink impl could result in endless recursion.
+// So redefine them to just printf function to SCP console.
+#if CHRE_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_ERROR
+#undef LOGE
+#define LOGE(fmt, arg...) PRINTF_E("[CHRE]" fmt "\n", ##arg)
+#endif
+
+#if CHRE_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_WARN
+#undef LOGW
+#define LOGW(fmt, arg...) PRINTF_W("[CHRE]" fmt "\n", ##arg)
+#endif
+
+#if CHRE_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_INFO
+#undef LOGI
+#define LOGI(fmt, arg...) PRINTF_I("[CHRE]" fmt "\n", ##arg)
+#endif
+
+#if CHRE_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_DEBUG
+#undef LOGD
+#define LOGD(fmt, arg...) PRINTF_D("[CHRE]" fmt "\n", ##arg)
+#endif
+
+#if CHRE_MINIMUM_LOG_LEVEL >= CHRE_LOG_LEVEL_VERBOSE
+#undef LOGV
+#define LOGV(fmt, arg...) PRINTF_D("[CHRE]" fmt "\n", ##arg)
+#endif
+
+namespace chre {
+namespace {
+
+struct UnloadNanoappCallbackData {
+  uint64_t appId;
+  uint32_t transactionId;
+  uint16_t hostClientId;
+  bool allowSystemNanoappUnload;
+};
+
+uint32_t gChreIpiRecvData[2];
+uint32_t gChreIpiAckToHost[2];  // SCP reply ack data (AP to SCP)
+int gChreIpiAckFromHost[2];     // SCP get ack data from AP (SCP to AP)
+
+void *gChreSubregionRecvAddr;
+size_t gChreSubregionRecvSize;
+void *gChreSubregionSendAddr;
+size_t gChreSubregionSendSize;
+
+// TODO(b/263958729): move it to HostLinkBase, and revisit buffer size
+// payload buffers
+#define CHRE_IPI_RECV_BUFFER_SIZE (CHRE_MESSAGE_TO_HOST_MAX_SIZE + 128)
+uint32_t gChreRecvBuffer[CHRE_IPI_RECV_BUFFER_SIZE / sizeof(uint32_t)]
+    __attribute__((aligned(CACHE_LINE_SIZE)));
+
+#ifdef SCP_CHRE_USE_DMA
+static inline uint32_t align(uint32_t target, uint32_t size) {
+  return (target + size - 1) & ~(size - 1);
+}
+#endif
+
+#define SCP_CHRE_MAGIC 0x67728269
+struct ScpChreIpiMsg {
+  uint32_t magic;
+  uint32_t size;
+};
+
+struct NanoappListData {
+  ChreFlatBufferBuilder *builder;
+  DynamicVector<NanoappListEntryOffset> nanoappEntries;
+  uint16_t hostClientId;
+};
+
+enum class PendingMessageType {
+  Shutdown,
+  NanoappMessageToHost,
+  HubInfoResponse,
+  NanoappListResponse,
+  LoadNanoappResponse,
+  UnloadNanoappResponse,
+  DebugDumpData,
+  DebugDumpResponse,
+  TimeSyncRequest,
+  LowPowerMicAccessRequest,
+  LowPowerMicAccessRelease,
+  EncodedLogMessage,
+  SelfTestResponse,
+  MetricLog,
+  NanConfigurationRequest,
+};
+
+struct PendingMessage {
+  PendingMessage(PendingMessageType msgType, uint16_t hostClientId) {
+    type = msgType;
+    data.hostClientId = hostClientId;
+  }
+
+  PendingMessage(PendingMessageType msgType,
+                 const HostMessage *msgToHost = nullptr) {
+    type = msgType;
+    data.msgToHost = msgToHost;
+  }
+
+  PendingMessage(PendingMessageType msgType, ChreFlatBufferBuilder *builder) {
+    type = msgType;
+    data.builder = builder;
+  }
+
+  PendingMessageType type;
+  union {
+    const HostMessage *msgToHost;
+    uint16_t hostClientId;
+    ChreFlatBufferBuilder *builder;
+  } data;
+};
+
+constexpr size_t kOutboundQueueSize = 100;
+FixedSizeBlockingQueue<PendingMessage, kOutboundQueueSize> gOutboundQueue;
+
+typedef void(MessageBuilderFunction)(ChreFlatBufferBuilder &builder,
+                                     void *cookie);
+
+inline HostCommsManager &getHostCommsManager() {
+  return EventLoopManagerSingleton::get()->getHostCommsManager();
+}
+
+bool generateMessageFromBuilder(ChreFlatBufferBuilder *builder) {
+  CHRE_ASSERT(builder != nullptr);
+  LOGV("%s: message size %d", __func__, builder->GetSize());
+  bool result =
+      HostLinkBase::send(builder->GetBufferPointer(), builder->GetSize());
+
+  // clean up
+  builder->~ChreFlatBufferBuilder();
+  memoryFree(builder);
+  return result;
+}
+
+bool generateMessageToHost(const HostMessage *message) {
+  LOGV("%s: message size %zu", __func__, message->message.size());
+  // TODO(b/263958729): ideally we'd construct our flatbuffer directly in the
+  // host-supplied buffer
+  constexpr size_t kFixedReserveSize = 80;
+  ChreFlatBufferBuilder builder(message->message.size() + kFixedReserveSize);
+  HostProtocolChre::encodeNanoappMessage(
+      builder, message->appId, message->toHostData.messageType,
+      message->toHostData.hostEndpoint, message->message.data(),
+      message->message.size(), message->toHostData.appPermissions,
+      message->toHostData.messagePermissions, message->toHostData.wokeHost);
+  bool result =
+      HostLinkBase::send(builder.GetBufferPointer(), builder.GetSize());
+
+  // clean up
+  getHostCommsManager().onMessageToHostComplete(message);
+  return result;
+}
+
+int generateHubInfoResponse(uint16_t hostClientId) {
+  constexpr size_t kInitialBufferSize = 192;
+
+  constexpr char kHubName[] = "CHRE on Tinysys";
+  constexpr char kVendor[] = "Google";
+  constexpr char kToolchain[] =
+      "Clang " STRINGIFY(__clang_major__) "." STRINGIFY(
+          __clang_minor__) "." STRINGIFY(__clang_patchlevel__);
+  constexpr uint32_t kLegacyPlatformVersion = 0;
+  constexpr uint32_t kLegacyToolchainVersion =
+      ((__clang_major__ & 0xFF) << 24) | ((__clang_minor__ & 0xFF) << 16) |
+      (__clang_patchlevel__ & 0xFFFF);
+  constexpr float kPeakMips = 350;
+  constexpr float kStoppedPower = 0;
+  constexpr float kSleepPower = 1;
+  constexpr float kPeakPower = 15;
+
+  // Note that this may execute prior to EventLoopManager::lateInit() completing
+  ChreFlatBufferBuilder builder(kInitialBufferSize);
+  HostProtocolChre::encodeHubInfoResponse(
+      builder, kHubName, kVendor, kToolchain, kLegacyPlatformVersion,
+      kLegacyToolchainVersion, kPeakMips, kStoppedPower, kSleepPower,
+      kPeakPower, CHRE_MESSAGE_TO_HOST_MAX_SIZE, chreGetPlatformId(),
+      chreGetVersion(), hostClientId);
+
+  return HostLinkBase::send(builder.GetBufferPointer(), builder.GetSize());
+}
+
+bool dequeueMessage(PendingMessage pendingMsg) {
+  LOGV("%s: message type %d", __func__, pendingMsg.type);
+  bool result = false;
+  switch (pendingMsg.type) {
+    case PendingMessageType::NanoappMessageToHost:
+      result = generateMessageToHost(pendingMsg.data.msgToHost);
+      break;
+
+    case PendingMessageType::HubInfoResponse:
+      result = generateHubInfoResponse(pendingMsg.data.hostClientId);
+      break;
+
+    case PendingMessageType::NanoappListResponse:
+    case PendingMessageType::LoadNanoappResponse:
+    case PendingMessageType::UnloadNanoappResponse:
+    case PendingMessageType::DebugDumpData:
+    case PendingMessageType::DebugDumpResponse:
+    case PendingMessageType::TimeSyncRequest:
+    case PendingMessageType::LowPowerMicAccessRequest:
+    case PendingMessageType::LowPowerMicAccessRelease:
+    case PendingMessageType::EncodedLogMessage:
+    case PendingMessageType::SelfTestResponse:
+    case PendingMessageType::MetricLog:
+    case PendingMessageType::NanConfigurationRequest:
+      result = generateMessageFromBuilder(pendingMsg.data.builder);
+      break;
+
+    default:
+      CHRE_ASSERT_LOG(false, "Unexpected pending message type");
+  }
+  return result;
+}
+
+/**
+ * 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 pendingMsg) {
+  return gOutboundQueue.push(pendingMsg);
+}
+
+/**
+ * Helper function that takes care of the boilerplate for allocating a
+ * ChreFlatBufferBuilder on the heap and adding it to the outbound message
+ * queue.
+ *
+ * @param msgType Identifies the message while in the outbound queue
+ * @param initialBufferSize Number of bytes to reserve when first allocating the
+ *        ChreFlatBufferBuilder
+ * @param buildMsgFunc Synchronous callback used to encode the FlatBuffer
+ *        message. Will not be invoked if allocation fails.
+ * @param cookie Opaque pointer that will be passed through to buildMsgFunc
+ *
+ * @return true if the message was successfully added to the queue
+ */
+bool buildAndEnqueueMessage(PendingMessageType msgType,
+                            size_t initialBufferSize,
+                            MessageBuilderFunction *msgBuilder, void *cookie) {
+  LOGV("%s: message type %d, size %zu", __func__, msgType, initialBufferSize);
+  bool pushed = false;
+
+  auto builder = MakeUnique<ChreFlatBufferBuilder>(initialBufferSize);
+  if (builder.isNull()) {
+    LOGE("Couldn't allocate memory for message type %d",
+         static_cast<int>(msgType));
+  } else {
+    msgBuilder(*builder, cookie);
+
+    // TODO(b/263958729): if this fails, ideally we should block for some
+    // timeout until there's space in the queue
+    if (!enqueueMessage(PendingMessage(msgType, builder.get()))) {
+      LOGE("Couldn't push message type %d to outbound queue",
+           static_cast<int>(msgType));
+    } else {
+      builder.release();
+      pushed = true;
+    }
+  }
+
+  return pushed;
+}
+
+/**
+ * FlatBuffer message builder callback used with handleNanoappListRequest()
+ */
+void buildNanoappListResponse(ChreFlatBufferBuilder &builder, void *cookie) {
+  LOGV("%s", __func__);
+  auto nanoappAdderCallback = [](const Nanoapp *nanoapp, void *data) {
+    auto *cbData = static_cast<NanoappListData *>(data);
+    HostProtocolChre::addNanoappListEntry(
+        *(cbData->builder), cbData->nanoappEntries, nanoapp->getAppId(),
+        nanoapp->getAppVersion(), true /*enabled*/, nanoapp->isSystemNanoapp(),
+        nanoapp->getAppPermissions(), nanoapp->getRpcServices());
+  };
+
+  // Add a NanoappListEntry to the FlatBuffer for each nanoapp
+  auto *cbData = static_cast<NanoappListData *>(cookie);
+  cbData->builder = &builder;
+  EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
+  eventLoop.forEachNanoapp(nanoappAdderCallback, cbData);
+  HostProtocolChre::finishNanoappListResponse(builder, cbData->nanoappEntries,
+                                              cbData->hostClientId);
+}
+
+void handleUnloadNanoappCallback(uint16_t /*type*/, void *data,
+                                 void * /*extraData*/) {
+  auto *cbData = static_cast<UnloadNanoappCallbackData *>(data);
+  bool success = false;
+  uint16_t instanceId;
+  EventLoop &eventLoop = EventLoopManagerSingleton::get()->getEventLoop();
+  if (!eventLoop.findNanoappInstanceIdByAppId(cbData->appId, &instanceId)) {
+    LOGE("Couldn't unload app ID 0x%016" PRIx64 ": not found", cbData->appId);
+  } else {
+    success =
+        eventLoop.unloadNanoapp(instanceId, cbData->allowSystemNanoappUnload);
+  }
+
+  constexpr size_t kInitialBufferSize = 52;
+  auto builder = MakeUnique<ChreFlatBufferBuilder>(kInitialBufferSize);
+  HostProtocolChre::encodeUnloadNanoappResponse(*builder, cbData->hostClientId,
+                                                cbData->transactionId, success);
+
+  if (!enqueueMessage(PendingMessage(PendingMessageType::UnloadNanoappResponse,
+                                     builder.get()))) {
+    LOGE("Failed to send unload response to host: %x transactionID: 0x%x",
+         cbData->hostClientId, cbData->transactionId);
+  } else {
+    builder.release();
+  }
+
+  memoryFree(data);
+}
+
+}  // anonymous namespace
+
+void sendDebugDumpResultToHost(uint16_t hostClientId, const char * /*debugStr*/,
+                               size_t /*debugStrSize*/, bool /*complete*/,
+                               uint32_t /*dataCount*/) {
+  LOGV("%s: host client id %d", __func__, hostClientId);
+  // TODO(b/263958729): Implement this.
+}
+
+HostLinkBase::HostLinkBase() {
+  LOGV("HostLinkBase::%s", __func__);
+  initializeIpi();
+}
+
+HostLinkBase::~HostLinkBase() {
+  LOGV("HostLinkBase::%s", __func__);
+}
+
+void HostLinkBase::vChreReceiveTask(void *pvParameters) {
+  int i = 0;
+  int ret = 0;
+
+  LOGV("%s", __func__);
+  while (true) {
+    LOGV("%s calling ipi_recv_reply(), Cnt=%d", __func__, i++);
+    ret = ipi_recv_reply(IPI_IN_C_HOST_SCP_CHRE, (void *)&gChreIpiAckToHost[0],
+                         1);
+    if (ret != IPI_ACTION_DONE)
+      LOGE("%s ipi_recv_reply() ret = %d", __func__, ret);
+    LOGV("%s reply_end", __func__);
+  }
+}
+
+void HostLinkBase::vChreSendTask(void *pvParameters) {
+  while (true) {
+    auto msg = gOutboundQueue.pop();
+    dequeueMessage(msg);
+  }
+}
+
+void HostLinkBase::chreIpiHandler(unsigned int id, void *prdata, void *data,
+                                  unsigned int len) {
+  /* receive magic and cmd */
+  struct ScpChreIpiMsg msg = *(struct ScpChreIpiMsg *)data;
+
+  // check the magic number and payload size need to be copy(if need) */
+  LOGV("%s: msg.magic=0x%x, msg.size=%u", __func__, msg.magic, msg.size);
+  if (msg.magic != SCP_CHRE_MAGIC) {
+    LOGE("Invalid magic number, skip message");
+    gChreIpiAckToHost[0] = IPI_NO_MEMORY;
+    gChreIpiAckToHost[1] = 0;
+    return;
+  }
+
+  // Mapping the physical address of share memory for SCP
+  uint32_t srcAddr =
+      ap_to_scp(reinterpret_cast<uint32_t>(gChreSubregionRecvAddr));
+
+#ifdef SCP_CHRE_USE_DMA
+  // Using SCP DMA HW to copy the data from share memory to SCP side, ex:
+  // gChreRecvBuffer gChreRecvBuffer could be a global variables or a SCP heap
+  // memory at SRAM/DRAM
+  scp_dma_transaction_dram(reinterpret_cast<uint32_t>(&gChreRecvBuffer[0]),
+                           srcAddr, msg.size, DMA_MEM_ID, NO_RESERVED);
+
+  // Invalid cache to update the newest data before using
+  mrv_dcache_invalid_multi_addr(reinterpret_cast<uint32_t>(&gChreRecvBuffer[0]),
+                                align(msg.size, CACHE_LINE_SIZE));
+#else
+  memcpy(static_cast<void *>(gChreRecvBuffer),
+         reinterpret_cast<void *>(srcAddr), msg.size);
+#endif
+
+  // process the message
+  LOGV("chre_rcvbuf: 0x%x 0x%x 0x%x 0x%x", gChreRecvBuffer[0],
+       gChreRecvBuffer[1], gChreRecvBuffer[2], gChreRecvBuffer[3]);
+  receive(static_cast<HostLinkBase *>(prdata), gChreRecvBuffer, msg.size);
+
+  // After finishing the job, akc the message to host
+  gChreIpiAckToHost[0] = IPI_ACTION_DONE;
+  gChreIpiAckToHost[1] = msg.size;
+}
+
+void HostLinkBase::initializeIpi(void) {
+  LOGV("%s", __func__);
+  bool success = false;
+  int ret;
+  constexpr size_t kBackgroundTaskStackSize = 1024;
+  constexpr UBaseType_t kBackgroundTaskPriority = 2;
+
+  // prepared share memory information and register the callback functions
+  if (!(ret = scp_get_reserve_mem_by_id(SCP_CHRE_FROM_MEM_ID,
+                                        &gChreSubregionRecvAddr,
+                                        &gChreSubregionRecvSize))) {
+    LOGE("%s: get SCP_CHRE_FROM_MEM_ID memory fail", __func__);
+  } else if (!(ret = scp_get_reserve_mem_by_id(SCP_CHRE_TO_MEM_ID,
+                                               &gChreSubregionSendAddr,
+                                               &gChreSubregionSendSize))) {
+    LOGE("%s: get SCP_CHRE_TO_MEM_ID memory fail", __func__);
+  } else if (pdPASS != xTaskCreate(vChreReceiveTask, "CHRE_RECEIVE",
+                                   kBackgroundTaskStackSize, (void *)0,
+                                   kBackgroundTaskPriority, NULL)) {
+    LOGE("%s failed to create ipi receiver task", __func__);
+  } else if (pdPASS != xTaskCreate(vChreSendTask, "CHRE_SEND",
+                                   kBackgroundTaskStackSize, (void *)0,
+                                   kBackgroundTaskPriority, NULL)) {
+    LOGE("%s failed to create ipi outbound message queue task", __func__);
+  } else if (IPI_ACTION_DONE !=
+             (ret = ipi_register(IPI_IN_C_HOST_SCP_CHRE, (void *)chreIpiHandler,
+                                 (void *)this, (void *)&gChreIpiRecvData[0]))) {
+    LOGE("ipi_register IPI_IN_C_HOST_SCP_CHRE failed, %d", ret);
+  } else if (IPI_ACTION_DONE !=
+             (ret = ipi_register(IPI_OUT_C_SCP_HOST_CHRE, NULL, (void *)this,
+                                 (void *)&gChreIpiAckFromHost[0]))) {
+    LOGE("ipi_register IPI_OUT_C_SCP_HOST_CHRE failed, %d", ret);
+  } else {
+    success = true;
+  }
+
+  if (!success) {
+    FATAL_ERROR("HostLinkBase::initializeIpi() failed");
+  }
+}
+
+void HostLinkBase::receive(HostLinkBase *instance, void *message,
+                           int messageLen) {
+  LOGV("%s: message len %d", __func__, messageLen);
+
+  // TODO(b/263958729): A crude way to initially determine daemon's up - set
+  // a flag on the first message received. This is temporary until a better
+  // way to do this is available.
+  instance->setInitialized(true);
+
+  if (!HostProtocolChre::decodeMessageFromHost(message, messageLen)) {
+    LOGE("Failed to decode msg %p of len %u", message, messageLen);
+  }
+}
+
+bool HostLinkBase::send(uint8_t *data, size_t dataLen) {
+#ifndef HOST_LINK_IPI_SEND_TIMEOUT_MS
+#define HOST_LINK_IPI_SEND_TIMEOUT_MS 100
+#endif
+#ifndef HOST_LINK_IPI_RESPONSE_TIMEOUT_MS
+#define HOST_LINK_IPI_RESPONSE_TIMEOUT_MS 100
+#endif
+  LOGV("HostLinkBase::%s: %zu, %p", __func__, dataLen, data);
+  struct ScpChreIpiMsg msg;
+  msg.magic = SCP_CHRE_MAGIC;
+  msg.size = dataLen;
+
+  // Mapping the physical address of share memory for SCP
+  void *dstAddr = reinterpret_cast<void *>(
+      ap_to_scp(reinterpret_cast<uint32_t>(gChreSubregionSendAddr)));
+
+#ifdef SCP_CHRE_USE_DMA
+  // TODO(b/263958729): use DMA for larger payload
+  // No need cache operation, because src_dst handled by SCP CPU and dstAddr is
+  // non-cacheable
+#else
+  memcpy(dstAddr, data, dataLen);
+#endif
+
+  // NB: len param for ipi_send is in number of 32-bit words
+  int ret = ipi_send_compl(
+      IPI_OUT_C_SCP_HOST_CHRE, &msg, sizeof(msg) / sizeof(uint32_t),
+      HOST_LINK_IPI_SEND_TIMEOUT_MS, HOST_LINK_IPI_RESPONSE_TIMEOUT_MS);
+  if (ret) {
+    LOGE("chre ipi send fail(%d)", ret);
+  } else {
+    /* check ack data for make sure IPI wasn't busy */
+    LOGV("chre ipi send, check ack data: 0x%x", gChreIpiAckFromHost[0]);
+    if (gChreIpiAckFromHost[0] == IPI_ACTION_DONE) {
+      LOGV("chre ipi send done, you can send another IPI");
+    } else if (gChreIpiAckFromHost[0] == IPI_PIN_BUSY) {
+      /* you may have to re-send the IPI, or drop this one */
+      LOGV(
+          "chre ipi send busy, user thread has not wait the IPI until job "
+          "finished");
+    } else if (gChreIpiAckFromHost[0] == IPI_NO_MEMORY) {
+      LOGV("chre ipi send with wrong size(%zu)", dataLen);
+    } else {
+      LOGV("chre ipi send unknown case");
+    }
+  }
+
+  return ret == IPI_ACTION_DONE;
+}
+
+void HostLinkBase::sendTimeSyncRequest() {
+  LOGV("%s", __func__);
+  // TODO(b/263958729): Implement this.
+}
+
+void HostLinkBase::sendLogMessageV2(const uint8_t *logMessage,
+                                    size_t logMessageSize,
+                                    uint32_t numLogsDropped) {
+  LOGV("%s: size %zu", __func__, logMessageSize);
+  struct LogMessageData {
+    const uint8_t *logMsg;
+    size_t logMsgSize;
+    uint32_t numLogsDropped;
+  };
+
+  LogMessageData logMessageData{logMessage, logMessageSize, numLogsDropped};
+
+  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
+    const auto *data = static_cast<const LogMessageData *>(cookie);
+    HostProtocolChre::encodeLogMessagesV2(
+        builder, data->logMsg, data->logMsgSize, data->numLogsDropped);
+  };
+
+  constexpr size_t kInitialSize = 128;
+  bool result = false;
+  if (isInitialized()) {
+    result = buildAndEnqueueMessage(
+        PendingMessageType::EncodedLogMessage,
+        kInitialSize + logMessageSize + sizeof(numLogsDropped), msgBuilder,
+        &logMessageData);
+  }
+
+#ifdef CHRE_USE_BUFFERED_LOGGING
+  if (LogBufferManagerSingleton::isInitialized()) {
+    LogBufferManagerSingleton::get()->onLogsSentToHost(result);
+  }
+#else
+  UNUSED_VAR(result);
+#endif
+}
+
+bool HostLink::sendMessage(HostMessage const *message) {
+  LOGV("HostLink::%s size(%zu)", __func__, message->message.size());
+  bool success = false;
+
+  if (isInitialized()) {
+    success = enqueueMessage(
+        PendingMessage(PendingMessageType::NanoappMessageToHost, message));
+  } else {
+    LOGW("Dropping outbound message: host link not initialized yet");
+  }
+  return success;
+}
+
+// TODO(b/263958729): HostMessageHandlers member function implementations are
+// expected to be (mostly) identical for any platform that uses flatbuffers
+// to encode messages - refactor the host link to merge the multiple copies
+// we currently have.
+void HostMessageHandlers::handleNanoappMessage(uint64_t appId,
+                                               uint32_t messageType,
+                                               uint16_t hostEndpoint,
+                                               const void *messageData,
+                                               size_t messageDataLen) {
+  LOGV("Parsed nanoapp message from host: app ID 0x%016" PRIx64
+       ", endpoint "
+       "0x%" PRIx16 ", msgType %" PRIu32 ", payload size %zu",
+       appId, hostEndpoint, messageType, messageDataLen);
+
+  // TODO(b/263958729): Implement this.
+  getHostCommsManager().sendMessageToNanoappFromHost(
+      appId, messageType, hostEndpoint, messageData, messageDataLen);
+}
+
+void HostMessageHandlers::handleHubInfoRequest(uint16_t hostClientId) {
+  LOGV("%s: host client id %d", __func__, hostClientId);
+  enqueueMessage(
+      PendingMessage(PendingMessageType::HubInfoResponse, hostClientId));
+}
+
+void HostMessageHandlers::handleNanoappListRequest(uint16_t hostClientId) {
+  auto callback = [](uint16_t /*type*/, void *data, void * /*extraData*/) {
+    uint16_t cbHostClientId = NestedDataPtr<uint16_t>(data);
+
+    NanoappListData cbData = {};
+    cbData.hostClientId = cbHostClientId;
+
+    size_t expectedNanoappCount =
+        EventLoopManagerSingleton::get()->getEventLoop().getNanoappCount();
+    if (!cbData.nanoappEntries.reserve(expectedNanoappCount)) {
+      LOG_OOM();
+    } else {
+      constexpr size_t kFixedOverhead = 48;
+      constexpr size_t kPerNanoappSize = 32;
+      size_t initialBufferSize =
+          (kFixedOverhead + expectedNanoappCount * kPerNanoappSize);
+
+      buildAndEnqueueMessage(PendingMessageType::NanoappListResponse,
+                             initialBufferSize, buildNanoappListResponse,
+                             &cbData);
+    }
+  };
+
+  LOGD("Nanoapp list request from client ID %" PRIu16, hostClientId);
+  EventLoopManagerSingleton::get()->deferCallback(
+      SystemCallbackType::NanoappListResponse,
+      NestedDataPtr<uint16_t>(hostClientId), callback);
+}
+
+void HostMessageHandlers::sendFragmentResponse(uint16_t hostClientId,
+                                               uint32_t transactionId,
+                                               uint32_t fragmentId,
+                                               bool success) {
+  struct FragmentedLoadInfoResponse {
+    uint16_t hostClientId;
+    uint32_t transactionId;
+    uint32_t fragmentId;
+    bool success;
+  };
+
+  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void *cookie) {
+    auto *cbData = static_cast<FragmentedLoadInfoResponse *>(cookie);
+    HostProtocolChre::encodeLoadNanoappResponse(
+        builder, cbData->hostClientId, cbData->transactionId, cbData->success,
+        cbData->fragmentId);
+  };
+
+  FragmentedLoadInfoResponse response = {
+      .hostClientId = hostClientId,
+      .transactionId = transactionId,
+      .fragmentId = fragmentId,
+      .success = success,
+  };
+  constexpr size_t kInitialBufferSize = 52;
+  buildAndEnqueueMessage(PendingMessageType::LoadNanoappResponse,
+                         kInitialBufferSize, msgBuilder, &response);
+}
+
+void HostMessageHandlers::handleLoadNanoappRequest(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    uint32_t appVersion, uint32_t appFlags, uint32_t targetApiVersion,
+    const void *buffer, size_t bufferLen, const char *appFileName,
+    uint32_t fragmentId, size_t appBinaryLen, bool respondBeforeStart) {
+  UNUSED_VAR(appFileName);
+
+  loadNanoappData(hostClientId, transactionId, appId, appVersion, appFlags,
+                  targetApiVersion, buffer, bufferLen, fragmentId, appBinaryLen,
+                  respondBeforeStart);
+}
+
+void HostMessageHandlers::handleUnloadNanoappRequest(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    bool allowSystemNanoappUnload) {
+  LOGD("Unload nanoapp request from client %" PRIu16 " (txnID %" PRIu32
+       ") for appId 0x%016" PRIx64 " system %d",
+       hostClientId, transactionId, appId, allowSystemNanoappUnload);
+  auto *cbData = memoryAlloc<UnloadNanoappCallbackData>();
+  if (cbData == nullptr) {
+    LOG_OOM();
+  } else {
+    cbData->appId = appId;
+    cbData->transactionId = transactionId;
+    cbData->hostClientId = hostClientId;
+    cbData->allowSystemNanoappUnload = allowSystemNanoappUnload;
+
+    EventLoopManagerSingleton::get()->deferCallback(
+        SystemCallbackType::HandleUnloadNanoapp, cbData,
+        handleUnloadNanoappCallback);
+  }
+}
+
+void HostLink::flushMessagesSentByNanoapp(uint64_t /* appId */) {
+  // Not implemented
+}
+
+void HostMessageHandlers::handleTimeSyncMessage(int64_t offset) {
+  LOGE("%s unsupported.", __func__);
+}
+
+void HostMessageHandlers::handleDebugDumpRequest(uint16_t hostClientId) {
+  LOGV("%s: host client id %d", __func__, hostClientId);
+  // TODO(b/263958729): Implement this.
+}
+
+void HostMessageHandlers::handleSettingChangeMessage(fbs::Setting setting,
+                                                     fbs::SettingState state) {
+  // TODO(b/267207477): Refactor handleSettingChangeMessage to shared code
+  Setting chreSetting;
+  bool chreSettingEnabled;
+  if (HostProtocolChre::getSettingFromFbs(setting, &chreSetting) &&
+      HostProtocolChre::getSettingEnabledFromFbs(state, &chreSettingEnabled)) {
+    EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+        chreSetting, chreSettingEnabled);
+  }
+}
+
+void HostMessageHandlers::handleSelfTestRequest(uint16_t hostClientId) {
+  LOGV("%s: host client id %d", __func__, hostClientId);
+  // TODO(b/263958729): Implement this.
+}
+
+void HostMessageHandlers::handleNanConfigurationUpdate(bool /* enabled */) {
+  LOGE("%s NAN unsupported.", __func__);
+}
+
+void sendAudioRequest() {
+  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void * /*cookie*/) {
+    HostProtocolChre::encodeLowPowerMicAccessRequest(builder);
+  };
+  constexpr size_t kInitialSize = 32;
+  buildAndEnqueueMessage(PendingMessageType::LowPowerMicAccessRequest,
+                         kInitialSize, msgBuilder, /* cookie= */ nullptr);
+}
+
+void sendAudioRelease() {
+  auto msgBuilder = [](ChreFlatBufferBuilder &builder, void * /*cookie*/) {
+    HostProtocolChre::encodeLowPowerMicAccessRelease(builder);
+  };
+  constexpr size_t kInitialSize = 32;
+  buildAndEnqueueMessage(PendingMessageType::LowPowerMicAccessRelease,
+                         kInitialSize, msgBuilder, /* cookie= */ nullptr);
+}
+
+}  // namespace chre
diff --git a/platform/tinysys/include/chre/target_platform/atomic_base.h b/platform/tinysys/include/chre/target_platform/atomic_base.h
new file mode 100644
index 0000000..4a5b71c
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/atomic_base.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_ATOMIC_BASE_H_
+#define CHRE_PLATFORM_TINYSYS_ATOMIC_BASE_H_
+
+#include <cinttypes>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mtk_atomic.h"
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+namespace chre {
+
+/**
+ * Base class implementation for the Atomic Bool and Uint32 types.
+ */
+template <typename T>
+class AtomicBase {
+ public:
+  /**
+   * Generic atomic load of a value implemented via a compiler level memory
+   * barrier.
+   *
+   * @return The current value of the data stored.
+   */
+  inline bool get() const {
+    mb();
+    return mValue;
+  }
+
+  /**
+   * Generic atomic store of a value implemented via a compiler level memory
+   * barrier.
+   *
+   * @param value The new value that the data stored is to change to.
+   */
+  inline void set(T value) {
+    mValue = value;
+    mb();
+  }
+
+ protected:
+  volatile T mValue;
+};
+
+/**
+ * Base class implementation for the Atomic Bool type.
+ */
+class AtomicBoolBase : public AtomicBase<bool> {
+ public:
+  /**
+   * Atomically swap the stored boolean with a new value.
+   *
+   * @param desired New value to be assigned to the stored boolean.
+   * @return Previous value of the stored boolean.
+   */
+  bool swap(bool desired) {
+    return atomic_swap_byte(reinterpret_cast<volatile uint8_t *>(&mValue),
+                            desired);
+  }
+};
+
+/**
+ * Base class implementation for the Atomic Uint32 type.
+ */
+class AtomicUint32Base : public AtomicBase<uint32_t> {
+ public:
+  /**
+   * Atomically swap the stored 32-bit word with a new value.
+   *
+   * @param desired New value to be assigned to the stored 32-bit word.
+   * @return Previous value of the stored 32-bit word.
+   */
+  uint32_t swap(uint32_t desired) {
+    return atomic_swap(&mValue, desired);
+  }
+
+  /**
+   * Atomically add a new value to the stored 32-bit word.
+   *
+   * @param arg Value to be added to the stored word.
+   * @return Pre-addition value of the stored word.
+   */
+  uint32_t add(uint32_t arg) {
+    return atomic_add(&mValue, arg);
+  }
+
+  /**
+   * Atomically subtract a value from the stored 32-bit word.
+   *
+   * @param arg Value to be subtracted from the stored word.
+   * @return Pre-subtraction value of the stored word.
+   */
+  uint32_t sub(uint32_t arg) {
+    return atomic_add(&mValue, ~arg + 1);
+  }
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_TINYSYS_ATOMIC_BASE_H_
\ No newline at end of file
diff --git a/platform/freertos/include/chre/target_platform/atomic_base_impl.h b/platform/tinysys/include/chre/target_platform/atomic_base_impl.h
similarity index 69%
rename from platform/freertos/include/chre/target_platform/atomic_base_impl.h
rename to platform/tinysys/include/chre/target_platform/atomic_base_impl.h
index d44a197..3cd4170 100644
--- a/platform/freertos/include/chre/target_platform/atomic_base_impl.h
+++ b/platform/tinysys/include/chre/target_platform/atomic_base_impl.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 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,71 +14,71 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
-#define CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
+#ifndef CHRE_PLATFORM_TINYSYS_ATOMIC_BASE_IMPL_H_
+#define CHRE_PLATFORM_TINYSYS_ATOMIC_BASE_IMPL_H_
 
 #include "chre/platform/atomic.h"
 
 namespace chre {
 
 inline AtomicBool::AtomicBool(bool startingValue) {
-  std::atomic_init(&mAtomic, startingValue);
+  set(startingValue);
 }
 
 inline bool AtomicBool::operator=(bool desired) {
-  mAtomic = desired;
+  set(desired);
   return desired;
 }
 
 inline bool AtomicBool::load() const {
-  return mAtomic.load();
+  return get();
 }
 
 inline void AtomicBool::store(bool desired) {
-  mAtomic.store(desired);
+  set(desired);
 }
 
 inline bool AtomicBool::exchange(bool desired) {
-  return mAtomic.exchange(desired);
+  return swap(desired);
 }
 
 inline AtomicUint32::AtomicUint32(uint32_t startingValue) {
-  std::atomic_init(&mAtomic, startingValue);
+  set(startingValue);
 }
 
 inline uint32_t AtomicUint32::operator=(uint32_t desired) {
-  mAtomic = desired;
+  set(desired);
   return desired;
 }
 
 inline uint32_t AtomicUint32::load() const {
-  return mAtomic.load();
+  return get();
 }
 
 inline void AtomicUint32::store(uint32_t desired) {
-  mAtomic.store(desired);
+  set(desired);
 }
 
 inline uint32_t AtomicUint32::exchange(uint32_t desired) {
-  return mAtomic.exchange(desired);
+  return swap(desired);
 }
 
 inline uint32_t AtomicUint32::fetch_add(uint32_t arg) {
-  return mAtomic.fetch_add(arg);
+  return add(arg);
 }
 
 inline uint32_t AtomicUint32::fetch_increment() {
-  return mAtomic.fetch_add(1);
+  return add(1);
 }
 
 inline uint32_t AtomicUint32::fetch_sub(uint32_t arg) {
-  return mAtomic.fetch_sub(arg);
+  return sub(arg);
 }
 
 inline uint32_t AtomicUint32::fetch_decrement() {
-  return mAtomic.fetch_sub(1);
+  return sub(1);
 }
 
 }  // namespace chre
 
-#endif  // CHRE_PLATFORM_FREERTOS_ATOMIC_BASE_IMPL_H_
+#endif  // CHRE_PLATFORM_TINYSYS_ATOMIC_BASE_IMPL_H_
\ No newline at end of file
diff --git a/platform/tinysys/include/chre/target_platform/chre_init.h b/platform/tinysys/include/chre/target_platform/chre_init.h
new file mode 100644
index 0000000..a2ed930
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/chre_init.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_CHRE_INIT_H_
+#define CHRE_PLATFORM_TINYSYS_CHRE_INIT_H_
+
+#include "chre/target_platform/init.h"
+
+extern "C" {
+/**
+ * Initializes CHRE, loads any static nanoapps, and starts the CHRE event loop.
+ *
+ * The default task attributes are:
+ * - Task Stack Depth: 8K, set by macro CHRE_FREERTOS_STACK_DEPTH_IN_WORDS
+ * - Task Priority: idle_task + 2, set by macro CHRE_FREERTOS_TASK_PRIORITY
+ *
+ * @return pdPASS on success, a FreeRTOS error code otherwise.
+ */
+BaseType_t chreTinysysInit();
+
+/**
+ * Deletes the task spawned in chreTinysysInit().
+ */
+void chreTinysysDeinit();
+}
+
+#endif  // CHRE_PLATFORM_TINYSYS_CHRE_INIT_H_
\ No newline at end of file
diff --git a/platform/tinysys/include/chre/target_platform/condition_variable_base.h b/platform/tinysys/include/chre/target_platform/condition_variable_base.h
new file mode 100644
index 0000000..ba03021
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/condition_variable_base.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_CONDITION_VARIABLE_BASE_H_
+#define CHRE_PLATFORM_TINYSYS_CONDITION_VARIABLE_BASE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "FreeRTOS.h"
+#include "semphr.h"
+#include "sensorhub/rt_timer.h"
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+namespace chre {
+
+/**
+ * Condition variable implementation based on rt_timer.
+ *
+ * Condition variable is preferred to run the timer callback in the ISR for less
+ * overhead/latency, which is why SystemTimer is not used here because
+ * SystemTimer assumes a callback function takes some time to finish so it
+ * creates a separate thread to run it.
+ */
+class ConditionVariableBase {
+ protected:
+  /** callback function woken up by the timer */
+  static void conditionVariablTimerCallback(struct rt_timer *rtTimer);
+
+  /** semaphore implementing the condition variable */
+  SemaphoreHandle_t semaphoreHandle;
+
+  /** True if wait_for() times out before semaphoreHandle is given */
+  bool isTimedOut = false;
+
+  /** settings of the rt_timer */
+  struct rt_timer rtSystemTimer {};
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_TINYSYS_CONDITION_VARIABLE_BASE_H_
\ No newline at end of file
diff --git a/platform/tinysys/include/chre/target_platform/condition_variable_impl.h b/platform/tinysys/include/chre/target_platform/condition_variable_impl.h
new file mode 100644
index 0000000..51d2708
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/condition_variable_impl.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_CONDITION_VARIABLE_IMPL_H_
+#define CHRE_PLATFORM_TINYSYS_CONDITION_VARIABLE_IMPL_H_
+
+/**
+ * This file is required by condition_variable.h to encourage inline
+ * implementations. As the implementation of condition variable for tinysys is
+ * complex enough that are not suitable to be inline anymore, the code is placed
+ * in condition_variable_base.cc and this file is left empty.
+ */
+
+#endif  // CHRE_PLATFORM_TINYSYS_CONDITION_VARIABLE_IMPL_H_
\ No newline at end of file
diff --git a/platform/tinysys/include/chre/target_platform/fatal_error.h b/platform/tinysys/include/chre/target_platform/fatal_error.h
new file mode 100644
index 0000000..d9475ff
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/fatal_error.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_FATAL_ERROR_H_
+#define CHRE_PLATFORM_TINYSYS_FATAL_ERROR_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "FreeRTOS.h"
+#include "task.h"
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#define FATAL_ERROR_QUIT() configASSERT(0)
+
+#endif  // CHRE_PLATFORM_TINYSYS_FATAL_ERROR_H_
diff --git a/platform/tinysys/include/chre/target_platform/host_cpu_update.h b/platform/tinysys/include/chre/target_platform/host_cpu_update.h
new file mode 100644
index 0000000..86782ef
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/host_cpu_update.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_HOST_CPU_UPDATE_H_
+#define CHRE_PLATFORM_TINYSYS_HOST_CPU_UPDATE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Updates host CPU's status to CHRE.
+ *
+ * This function should be called by the module of Tinysys that handles the host
+ * CPU's wake/suspend events.
+ */
+void chreNotifyHostWakeSuspend(bool isAwake);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // CHRE_PLATFORM_TINYSYS_HOST_CPU_UPDATE_H_
diff --git a/platform/tinysys/include/chre/target_platform/host_link_base.h b/platform/tinysys/include/chre/target_platform/host_link_base.h
new file mode 100644
index 0000000..b25e9a4
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/host_link_base.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_HOST_LINK_BASE_H_
+#define CHRE_PLATFORM_TINYSYS_HOST_LINK_BASE_H_
+
+#include <cinttypes>
+#include <cstddef>
+
+#include "chre/platform/atomic.h"
+#include "chre/platform/mutex.h"
+#include "chre/platform/shared/host_protocol_chre.h"
+#include "chre/util/lock_guard.h"
+
+namespace chre {
+
+/**
+ * Helper function to send debug dump result to host.
+ */
+void sendDebugDumpResultToHost(uint16_t hostClientId, const char *debugStr,
+                               size_t debugStrSize, bool complete,
+                               uint32_t dataCount);
+
+/**
+ * @brief Platform specific host link.
+ */
+class HostLinkBase {
+ public:
+  HostLinkBase();
+  ~HostLinkBase();
+
+  static void vChreReceiveTask(void * /*pvParameters*/);
+  static void vChreSendTask(void * /*pvParameters*/);
+  static void chreIpiHandler(unsigned int /*id*/, void * /*prdata*/,
+                             void * /*data*/, unsigned int /*len*/);
+  void initializeIpi(void);
+
+  /**
+   * Implements the IPC message receive handler.
+   *
+   * @param cookie An opaque pointer that was provided to the IPC driver during
+   *        callback registration.
+   * @param message The host message sent to CHRE.
+   * @param messageLen The host message length in bytes.
+   */
+  static void receive(HostLinkBase * /*instance*/, void * /*message*/,
+                      int /*messageLen*/);
+  /**
+   * Send a message to the host.
+   *
+   * @param data The message to host payload.
+   * @param dataLen Size of the message payload in bytes.
+   * @return true if the operation succeeds, false otherwise.
+   */
+  static bool send(uint8_t * /*data*/, size_t /*dataLen*/);
+
+  void setInitialized(bool initialized) {
+    mInitialized = initialized;
+  }
+
+  bool isInitialized() const {
+    return mInitialized;
+  }
+
+  /**
+   * Sends a request to the host for a time sync message.
+   */
+  static void sendTimeSyncRequest();
+  /**
+   * Enqueues a V2 log message to be sent to the host.
+   *
+   * @param logMessage Pointer to a buffer that has the log message. Note that
+   * the message might be encoded
+   * @param logMessageSize length of the log message buffer
+   * @param numLogsDropped the number of logs dropped since CHRE started
+   */
+  void sendLogMessageV2(const uint8_t * /*logMessage*/,
+                        size_t /*logMessageSize*/,
+                        uint32_t /*num_logs_dropped*/);
+
+ private:
+  AtomicBool mInitialized = false;
+};
+
+/**
+ * Sends a request to the host to enable the audio feature.
+ */
+void sendAudioRequest();
+
+/**
+ * Sends a request to the host to disable the audio feature.
+ */
+void sendAudioRelease();
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_TINYSYS_HOST_LINK_BASE_H_
diff --git a/platform/tinysys/include/chre/target_platform/log.h b/platform/tinysys/include/chre/target_platform/log.h
new file mode 100644
index 0000000..1f6a8e7
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/log.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_LOG_H_
+#define CHRE_PLATFORM_TINYSYS_LOG_H_
+
+#include "chre_api/chre.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mt_printf.h"
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#ifdef CHRE_USE_BUFFERED_LOGGING
+
+/**
+ * Log via the LogBufferManagerSingleton vaLog method.
+ *
+ * Defined in system/chre/platform/shared/log_buffer_manager.cc
+ *
+ * @param level The log level.
+ * @param format The format string.
+ * @param ... The arguments to print into the final log.
+ */
+void chrePlatformLogToBuffer(enum chreLogLevel level, const char *format, ...);
+
+// Print logs to host logcat
+#define CHRE_BUFFER_LOG(level, fmt, arg...)     \
+  do {                                          \
+    CHRE_LOG_PREAMBLE                           \
+    chrePlatformLogToBuffer(level, fmt, ##arg); \
+    CHRE_LOG_EPILOGUE                           \
+  } while (0)
+
+#define LOGE(fmt, arg...)                                 \
+  do {                                                    \
+    PRINTF_E("[CHRE]" fmt "\n", ##arg);                   \
+    CHRE_BUFFER_LOG(CHRE_LOG_ERROR, "[CHRE]" fmt, ##arg); \
+  } while (0)
+
+#define LOGW(fmt, arg...)                                \
+  do {                                                   \
+    PRINTF_W("[CHRE]" fmt "\n", ##arg);                  \
+    CHRE_BUFFER_LOG(CHRE_LOG_WARN, "[CHRE]" fmt, ##arg); \
+  } while (0)
+
+#define LOGI(fmt, arg...)                                \
+  do {                                                   \
+    PRINTF_I("[CHRE]" fmt "\n", ##arg);                  \
+    CHRE_BUFFER_LOG(CHRE_LOG_INFO, "[CHRE]" fmt, ##arg); \
+  } while (0)
+
+#define LOGD(fmt, arg...)                                 \
+  do {                                                    \
+    PRINTF_D("[CHRE]" fmt "\n", ##arg);                   \
+    CHRE_BUFFER_LOG(CHRE_LOG_DEBUG, "[CHRE]" fmt, ##arg); \
+  } while (0)
+
+#else
+
+#define LOGE(fmt, arg...) PRINTF_E("[CHRE]" fmt "\n", ##arg)
+#define LOGW(fmt, arg...) PRINTF_W("[CHRE]" fmt "\n", ##arg)
+#define LOGI(fmt, arg...) PRINTF_I("[CHRE]" fmt "\n", ##arg)
+#define LOGD(fmt, arg...) PRINTF_D("[CHRE]" fmt "\n", ##arg)
+
+#endif
+
+#endif  // CHRE_PLATFORM_TINYSYS_LOG_H_
diff --git a/platform/tinysys/include/chre/target_platform/macros.h b/platform/tinysys/include/chre/target_platform/macros.h
new file mode 100644
index 0000000..7b8e2fd
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/macros.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_MACROS_H_
+#define CHRE_PLATFORM_TINYSYS_MACROS_H_
+
+#endif  // CHRE_PLATFORM_TINYSYS_MACROS_H_
diff --git a/platform/tinysys/include/chre/target_platform/memory_impl.h b/platform/tinysys/include/chre/target_platform/memory_impl.h
new file mode 100644
index 0000000..de1f8ed
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/memory_impl.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_MEMORY_H_
+#define CHRE_PLATFORM_TINYSYS_MEMORY_H_
+
+#include <cstddef>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "encoding.h"
+#include "sensorhub/heap.h"
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+namespace chre {
+
+inline void *memoryAlloc(size_t size) {
+  return heap_alloc(size);
+}
+
+inline void memoryFree(void *pointer) {
+  heap_free(pointer);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_TINYSYS_MEMORY_H_
\ No newline at end of file
diff --git a/platform/tinysys/include/chre/target_platform/platform_audio_base.h b/platform/tinysys/include/chre/target_platform/platform_audio_base.h
new file mode 100644
index 0000000..08ae3d6
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/platform_audio_base.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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_TINYSYS_PLATFORM_AUDIO_BASE_H_
+#define CHRE_PLATFORM_TINYSYS_PLATFORM_AUDIO_BASE_H_
+
+#include "chre/pal/audio.h"
+#include "chre/platform/shared/platform_pal.h"
+
+namespace chre {
+
+/**
+ * The base PlatformAudio class for the SLPI to inject platform specific
+ * functionality from.
+ */
+class PlatformAudioBase : public PlatformPal {
+ public:
+  /**
+   * Invoked whenever the host goes awake. This is used to implement the
+   * deferred audio disable operation. This is called on the CHRE thread.
+   */
+  void onHostAwake();
+
+ protected:
+  //! The instance of callbacks that are provided to the CHRE PAL.
+  static const chrePalAudioCallbacks sAudioCallbacks;
+
+  //! The instance of the CHRE PAL API for audio. This will be set to nullptr
+  //! if the platform does not supply an implementation.
+  const chrePalAudioApi *mAudioApi;
+
+  //! The number of open audio clients. This is incremented/decremented by the
+  //! setHandleEnabled platform API.
+  uint32_t mNumAudioClients = 0;
+
+  //! The current state of the audio feature enabled on the host.
+  bool mCurrentAudioEnabled = false;
+
+  //! The target state of the audio feature enabled on the host. This is used to
+  //! support deferred disabling when the next AP wake occurs.
+  bool mTargetAudioEnabled = false;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_TINYSYS_PLATFORM_AUDIO_BASE_H_
diff --git a/platform/tinysys/include/chre/target_platform/power_control_manager_base.h b/platform/tinysys/include/chre/target_platform/power_control_manager_base.h
new file mode 100644
index 0000000..99ea53d
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/power_control_manager_base.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_POWER_CONTROL_MANAGER_BASE_H
+#define CHRE_PLATFORM_TINYSYS_POWER_CONTROL_MANAGER_BASE_H
+
+#include <cstddef>
+#include "chre/platform/atomic.h"
+
+namespace chre {
+
+class PowerControlManagerBase {
+ public:
+  /**
+   * Updates internal wake/suspend flag and pushes awake/sleep notification
+   * to nanoapps that are listening for it.
+   *
+   * @param awake true if host is awake, otherwise suspended.
+   */
+  void onHostWakeSuspendEvent(bool awake);
+
+ protected:
+  /** True if the host is awake, false otherwise. */
+  AtomicBool mHostIsAwake = true;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_TINYSYS_POWER_CONTROL_MANAGER_BASE_H
\ No newline at end of file
diff --git a/platform/tinysys/include/chre/target_platform/system_timer_base.h b/platform/tinysys/include/chre/target_platform/system_timer_base.h
new file mode 100644
index 0000000..2713085
--- /dev/null
+++ b/platform/tinysys/include/chre/target_platform/system_timer_base.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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_TINYSYS_SYSTEM_TIMER_BASE_H_
+#define CHRE_PLATFORM_TINYSYS_SYSTEM_TIMER_BASE_H_
+
+#include <cinttypes>
+#include <csignal>
+#include <ctime>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sensorhub/rt_timer.h"
+#include "task.h"
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+namespace chre {
+
+class SystemTimerBase {
+ protected:
+  /** Stack size (in words) of the timer callback runner task */
+  static constexpr uint32_t kStackDepthWords = 0x200;  // 2K stack size
+
+  /** Priority of the callback runner task */
+  static constexpr UBaseType_t kTaskPriority = tskIDLE_PRIORITY + 4;
+
+  /** Name of the callback runner task */
+  static constexpr char kTaskName[] = "ChreTimerCbRunner";
+
+  /**
+   * Callback function woken up by the timer, which in turn wakes up the
+   * callback runner task
+   */
+  static void rtTimerCallback(struct rt_timer *timer);
+
+  /**
+   * The callback runner task that blocks until it gets woken up by the timer
+   * callback.
+   *
+   * This task runs the actual callback set by the user as the timer callback is
+   * running from the ISR.
+   *
+   * @param context a raw pointer that will be casted to a pointer to
+   * SystemTimer.
+   */
+
+  static void callbackRunner(void *context);
+
+  /** A FreeRTOS task handle holding the callback runner task */
+  TaskHandle_t mCallbackRunnerHandle = nullptr;
+
+  /** Tracks whether the timer has been initialized correctly. */
+  bool mInitialized = false;
+
+  /** The properties of the timer including callback, data, etc. */
+  struct rt_timer rtSystemTimer;
+};
+}  // namespace chre
+#endif  // CHRE_PLATFORM_TINYSYS_SYSTEM_TIMER_BASE_H_
\ No newline at end of file
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/tinysys/log_buffer_manager.cc
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to platform/tinysys/log_buffer_manager.cc
index 77a9da0..97df560 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/tinysys/log_buffer_manager.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 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,21 +14,13 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#include "chre/platform/shared/log_buffer_manager.h"
+#include "chre/platform/shared/memory.h"
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+void LogBufferManager::preSecondaryBufferUse() const {
+  forceDramAccess();
+}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/platform/tinysys/memory.cc b/platform/tinysys/memory.cc
new file mode 100644
index 0000000..5b1daa3
--- /dev/null
+++ b/platform/tinysys/memory.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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/shared/memory.h"
+#include "mt_alloc.h"
+#include "mt_dma.h"
+#include "portable.h"
+
+extern "C" {
+#include "resource_req.h"
+}
+
+namespace chre {
+
+// no-op since the dma access is controlled by the kernel automatically
+void forceDramAccess() {}
+
+void nanoappBinaryFree(void *pointer) {
+  aligned_free(pointer);
+}
+
+void nanoappBinaryDramFree(void *pointer) {
+  aligned_dram_free(pointer);
+}
+
+void *memoryAllocDram(size_t size) {
+  return pvPortDramMalloc(size);
+}
+
+void memoryFreeDram(void *pointer) {
+  vPortDramFree(pointer);
+}
+
+void *palSystemApiMemoryAlloc(size_t size) {
+  return memoryAlloc(size);
+}
+
+void palSystemApiMemoryFree(void *pointer) {
+  memoryFree(pointer);
+}
+
+void *nanoappBinaryAlloc(size_t size, size_t alignment) {
+  return aligned_malloc(size, alignment);
+}
+
+void *nanoappBinaryDramAlloc(size_t size, size_t alignment) {
+  // aligned_dram_malloc() requires the alignment being multiple of
+  // CACHE_LINE_SIZE (128 bytes), we will align to page size (4k)
+  return aligned_dram_malloc(size, alignment);
+}
+
+}  // namespace chre
diff --git a/platform/tinysys/platform_audio.cc b/platform/tinysys/platform_audio.cc
new file mode 100644
index 0000000..83c1908
--- /dev/null
+++ b/platform/tinysys/platform_audio.cc
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 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/platform_audio.h"
+
+#include <cstring>
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/host_link.h"
+#include "chre/platform/log.h"
+#include "chre/platform/shared/pal_system_api.h"
+
+namespace chre {
+namespace {
+
+void handleAudioDataEvent(struct chreAudioDataEvent *event) {
+  EventLoopManagerSingleton::get()
+      ->getAudioRequestManager()
+      .handleAudioDataEvent(event);
+}
+
+void handleAudioAvailability(uint32_t handle, bool available) {
+  LOGD("SPI audio handle %" PRIu32 " available: %d", handle, available);
+  EventLoopManagerSingleton::get()
+      ->getAudioRequestManager()
+      .handleAudioAvailability(handle, available);
+}
+
+}  // anonymous namespace
+
+const chrePalAudioCallbacks PlatformAudioBase::sAudioCallbacks = {
+    handleAudioDataEvent,
+    handleAudioAvailability,
+};
+
+PlatformAudio::PlatformAudio() {}
+
+PlatformAudio::~PlatformAudio() {
+  if (mAudioApi != nullptr) {
+    LOGV("Platform audio closing");
+    mAudioApi->close();
+    LOGV("Platform audio closed");
+  }
+}
+
+void PlatformAudio::init() {
+  mAudioApi = chrePalAudioGetApi(CHRE_PAL_AUDIO_API_CURRENT_VERSION);
+  if (mAudioApi != nullptr) {
+    if (!mAudioApi->open(&gChrePalSystemApi, &sAudioCallbacks)) {
+      LOGD("Audio PAL open returned false");
+      mAudioApi = nullptr;
+    } else {
+      LOGD("Opened audio PAL version 0x%08" PRIx32, mAudioApi->moduleVersion);
+    }
+  } else {
+    LOGW("Requested audio PAL (version 0x%08" PRIx32 ") not found",
+         CHRE_PAL_AUDIO_API_CURRENT_VERSION);
+  }
+}
+
+void PlatformAudio::setHandleEnabled(uint32_t handle, bool enabled) {
+  uint32_t lastNumAudioClients = mNumAudioClients;
+
+  if (enabled) {
+    mNumAudioClients++;
+  } else if (mNumAudioClients > 0) {
+    mNumAudioClients--;
+  } else {
+    LOGE("Invalid request to change handle enabled state");
+  }
+
+  if (lastNumAudioClients == 0 && mNumAudioClients > 0) {
+    mTargetAudioEnabled = true;
+    if (!mCurrentAudioEnabled) {
+      LOGD("Enabling audio");
+      mCurrentAudioEnabled = true;
+      sendAudioRequest();
+    }
+  } else if (lastNumAudioClients > 0 && mNumAudioClients == 0) {
+    mTargetAudioEnabled = false;
+    if (EventLoopManagerSingleton::get()
+            ->getEventLoop()
+            .getPowerControlManager()
+            .hostIsAwake()) {
+      onHostAwake();
+    } else {
+      LOGD("Deferring disable audio");
+    }
+  }
+}
+
+bool PlatformAudio::requestAudioDataEvent(uint32_t handle, uint32_t numSamples,
+                                          Nanoseconds eventDelay) {
+  bool success = false;
+  if (mAudioApi != nullptr) {
+    success = mAudioApi->requestAudioDataEvent(handle, numSamples,
+                                               eventDelay.toRawNanoseconds());
+  }
+
+  return success;
+}
+
+void PlatformAudio::cancelAudioDataEventRequest(uint32_t handle) {
+  if (mAudioApi != nullptr) {
+    mAudioApi->cancelAudioDataEvent(handle);
+  }
+}
+
+void PlatformAudio::releaseAudioDataEvent(struct chreAudioDataEvent *event) {
+  if (mAudioApi != nullptr) {
+    mAudioApi->releaseAudioDataEvent(event);
+  }
+}
+
+size_t PlatformAudio::getSourceCount() {
+  size_t sourceCount = 0;
+  if (mAudioApi != nullptr) {
+    sourceCount = mAudioApi->getSourceCount();
+  }
+
+  return sourceCount;
+}
+
+bool PlatformAudio::getAudioSource(uint32_t handle,
+                                   struct chreAudioSource *source) const {
+  bool success = false;
+  if (mAudioApi != nullptr) {
+    success = mAudioApi->getAudioSource(handle, source);
+  }
+
+  return success;
+}
+
+void PlatformAudioBase::onHostAwake() {
+  if (mCurrentAudioEnabled && !mTargetAudioEnabled) {
+    LOGD("Disabling audio");
+    mCurrentAudioEnabled = mTargetAudioEnabled;
+    sendAudioRelease();
+  }
+}
+
+}  // namespace chre
diff --git a/platform/tinysys/platform_cache_management.cc b/platform/tinysys/platform_cache_management.cc
new file mode 100644
index 0000000..82897ff
--- /dev/null
+++ b/platform/tinysys/platform_cache_management.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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/target_platform/platform_cache_management.h"
+#include "chre/platform/shared/nanoapp_loader.h"
+
+#include "dma_api.h"
+
+namespace chre {
+
+void wipeSystemCaches(uintptr_t address, uint32_t span) {
+  if (span == 0) return;
+
+  auto aligned_addr = NanoappLoader::roundDownToAlign(address, CACHE_LINE_SIZE);
+  auto aligned_span = (span + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
+  LOGV("Invalidate cache at 0x%lx for %u", aligned_addr, aligned_span);
+
+  // Flush D cache first for updating binary to heap memory
+  mrv_dcache_flush_multi_addr(aligned_addr, aligned_span);
+  // Invalid I cache for fetch instructions
+  mrv_icache_invalid_multi_addr(aligned_addr, aligned_span);
+}
+
+}  // namespace chre
\ No newline at end of file
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/platform/tinysys/platform_pal.cc
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to platform/tinysys/platform_pal.cc
index 77a9da0..b756b5f 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/platform/tinysys/platform_pal.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#include "chre/platform/shared/platform_pal.h"
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+void PlatformPal::prePalApiCall(PalType /* palType */) const {}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/platform/tinysys/power_control_manager.cc b/platform/tinysys/power_control_manager.cc
new file mode 100644
index 0000000..8eb7d78
--- /dev/null
+++ b/platform/tinysys/power_control_manager.cc
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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 <stdint.h>
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/platform/power_control_manager.h"
+#include "chre/platform/shared/log_buffer_manager.h"
+
+extern "C" {
+#include "sensorhub/comm/host_suspend.h"
+}
+
+namespace chre {
+
+void PowerControlManagerBase::onHostWakeSuspendEvent(bool awake) {
+  if (mHostIsAwake != awake) {
+    mHostIsAwake = awake;
+    if (!awake) {
+      EventLoopManagerSingleton::get()
+          ->getHostCommsManager()
+          .resetBlameForNanoappHostWakeup();
+    }
+#ifdef CHRE_USE_BUFFERED_LOGGING
+    if (awake) {
+      LogBufferManagerSingleton::get()->flushLogs();
+    }
+#endif
+    EventLoopManagerSingleton::get()->getEventLoop().postEventOrDie(
+        awake ? CHRE_EVENT_HOST_AWAKE : CHRE_EVENT_HOST_ASLEEP,
+        /* eventData= */ nullptr, /* freeCallback= */ nullptr);
+  }
+}
+
+void PowerControlManager::preEventLoopProcess(size_t /* numPendingEvents */) {}
+
+void PowerControlManager::postEventLoopProcess(size_t /* numPendingEvents */) {}
+
+bool PowerControlManager::hostIsAwake() {
+  return !host_suspended();
+}
+
+}  // namespace chre
\ No newline at end of file
diff --git a/platform/tinysys/stdlib_wrapper.cc b/platform/tinysys/stdlib_wrapper.cc
new file mode 100644
index 0000000..0520ae8
--- /dev/null
+++ b/platform/tinysys/stdlib_wrapper.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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/host_link.h"
+
+// The delete operator is generated by the compiler but not actually called,
+// empty implementations are provided to avoid linker warnings/errors.
+void operator delete(void *) {
+  CHRE_ASSERT(false);
+}
+void operator delete(void *, size_t) {
+  CHRE_ASSERT(false);
+}
+extern "C" void __cxa_pure_virtual(void) {
+  chreAbort(CHRE_ERROR /* abortCode */);
+}
diff --git a/platform/tinysys/system_time.cc b/platform/tinysys/system_time.cc
new file mode 100644
index 0000000..cc82d2f
--- /dev/null
+++ b/platform/tinysys/system_time.cc
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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 <cstdint>
+
+#include "chre/platform/system_time.h"
+
+extern "C" {
+#include "sensorhub/comm/timesync.h"
+#include "xgpt.h"
+}
+
+namespace chre {
+
+Nanoseconds SystemTime::getMonotonicTime() {
+  return Nanoseconds(get_boot_time_ns());
+}
+
+int64_t SystemTime::getEstimatedHostTimeOffset() {
+  return timesync_get_host_offset_time();
+}
+
+void SystemTime::setEstimatedHostTimeOffset(int64_t offset) {
+  // not implemented for Tinysys
+}
+}  // namespace chre
diff --git a/platform/tinysys/system_timer.cc b/platform/tinysys/system_timer.cc
new file mode 100644
index 0000000..c063128
--- /dev/null
+++ b/platform/tinysys/system_timer.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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/system_timer.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/log.h"
+#include "chre/util/time.h"
+
+namespace chre {
+
+void SystemTimerBase::rtTimerCallback(struct rt_timer *rtTimer) {
+  if (rtTimer != nullptr) {
+    SystemTimer *systemTimer = static_cast<SystemTimer *>(rtTimer->private_ptr);
+    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+    vTaskNotifyGiveFromISR(systemTimer->mCallbackRunnerHandle,
+                           &xHigherPriorityTaskWoken);
+    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+  }
+}
+
+void SystemTimerBase::callbackRunner(void *context) {
+  SystemTimer *systemTimer = static_cast<SystemTimer *>(context);
+  if (systemTimer == nullptr) {
+    FATAL_ERROR("Null System Timer");
+  }
+  while (true) {
+    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+    SystemTimerCallback *callback = systemTimer->mCallback;
+    if (callback != nullptr) {
+      callback(systemTimer->mData);
+    }
+  }
+}
+
+SystemTimer::SystemTimer() {
+  // Initialize the rtSystemTimer struct.
+  // The timer's callback and the private data won't be changed through the
+  // lifetime so init() should only be run once. The creation of the callback
+  // runner thread is delayed to the call of init().
+  rt_timer_init(&rtSystemTimer, /* func= */ rtTimerCallback, /* data= */ this);
+}
+
+SystemTimer::~SystemTimer() {
+  // cancel an existing timer if any
+  cancel();
+  // Delete the callback runner thread if it was created
+  if (mCallbackRunnerHandle != nullptr) {
+    vTaskDelete(mCallbackRunnerHandle);
+    mCallbackRunnerHandle = nullptr;
+  }
+}
+
+bool SystemTimer::init() {
+  if (mInitialized) {
+    return true;
+  }
+  BaseType_t xReturned = xTaskCreate(
+      callbackRunner, kTaskName, kStackDepthWords,
+      /* pvParameters= */ this, kTaskPriority, &mCallbackRunnerHandle);
+  if (xReturned == pdPASS) {
+    mInitialized = true;
+    return true;
+  }
+  LOGE("Failed to create the callback runner thread");
+  return false;
+}
+
+bool SystemTimer::set(SystemTimerCallback *callback, void *data,
+                      Nanoseconds delay) {
+  if (!mInitialized) {
+    LOGW("Timer is not initialized");
+    return false;
+  }
+  cancel();
+  mCallback = callback;
+  mData = data;
+  rt_timer_start(&rtSystemTimer, delay.toRawNanoseconds(),
+                 /* oneShot= */ true);
+  return true;
+}
+
+bool SystemTimer::cancel() {
+  // TODO(b/254708051): This usage of critical section is pending confirmation.
+  taskENTER_CRITICAL();
+  if (isActive()) {
+    rt_timer_stop(&rtSystemTimer);
+  }
+  taskEXIT_CRITICAL();
+  return true;
+}
+
+bool SystemTimer::isActive() {
+  return rt_timer_active(&rtSystemTimer);
+}
+
+}  // namespace chre
\ No newline at end of file
diff --git a/platform/zephyr/CMakeLists.txt b/platform/zephyr/CMakeLists.txt
index ea211b3..1008e36 100644
--- a/platform/zephyr/CMakeLists.txt
+++ b/platform/zephyr/CMakeLists.txt
@@ -53,6 +53,7 @@
       "${CHRE_DIR}/core/timer_pool.cc"
       "${CHRE_DIR}/platform/shared/version.cc"
       "${CHRE_DIR}/platform/shared/system_time.cc"
+      "${CHRE_DIR}/util/buffer_base.cc"
       "${CHRE_DIR}/util/dynamic_vector_base.cc"
   )
   zephyr_linker_sources(SECTIONS linker_chre.ld)
diff --git a/platform/zephyr/Kconfig b/platform/zephyr/Kconfig
index eae630c..e656ee3 100644
--- a/platform/zephyr/Kconfig
+++ b/platform/zephyr/Kconfig
@@ -6,6 +6,7 @@
 
 menuconfig CHRE
 	bool "CHRE Support"
+	select REQUIRES_FULL_LIBCPP
 	help
 	  This option enables the CHRE library.
 
diff --git a/platform/zephyr/context.cc b/platform/zephyr/context.cc
index 7b27392..2fcde18 100644
--- a/platform/zephyr/context.cc
+++ b/platform/zephyr/context.cc
@@ -16,7 +16,7 @@
 
 #include "chre/platform/context.h"
 
-#include <zephyr.h>
+#include <zephyr/kernel.h>
 #include "chre/target_platform/init.h"
 
 namespace chre {
diff --git a/platform/zephyr/include/chre/target_platform/atomic_base.h b/platform/zephyr/include/chre/target_platform/atomic_base.h
index b15956c..f8fb8d7 100644
--- a/platform/zephyr/include/chre/target_platform/atomic_base.h
+++ b/platform/zephyr/include/chre/target_platform/atomic_base.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_PLATFORM_ZEPHYR_ATOMIC_BASE_H_
 #define CHRE_PLATFORM_ZEPHYR_ATOMIC_BASE_H_
 
-#include <sys/atomic.h>
+#include <zephyr/sys/atomic.h>
 
 namespace chre {
 
diff --git a/platform/zephyr/include/chre/target_platform/atomic_base_impl.h b/platform/zephyr/include/chre/target_platform/atomic_base_impl.h
index 2054ce2..a7b6132 100644
--- a/platform/zephyr/include/chre/target_platform/atomic_base_impl.h
+++ b/platform/zephyr/include/chre/target_platform/atomic_base_impl.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_PLATFORM_ZEPHYR_ATOMIC_BASE_IMPL_H_
 #define CHRE_PLATFORM_ZEPHYR_ATOMIC_BASE_IMPL_H_
 
-#include <sys/atomic.h>
+#include <zephyr/sys/atomic.h>
 
 #include "chre/platform/atomic.h"
 
diff --git a/platform/zephyr/include/chre/target_platform/condition_variable_base.h b/platform/zephyr/include/chre/target_platform/condition_variable_base.h
index 78ce3c0..0cbc9b5 100644
--- a/platform/zephyr/include/chre/target_platform/condition_variable_base.h
+++ b/platform/zephyr/include/chre/target_platform/condition_variable_base.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_PLATFORM_ZEPHYR_CONDITION_VARIABLE_BASE_H_
 #define CHRE_PLATFORM_ZEPHYR_CONDITION_VARIABLE_BASE_H_
 
-#include <kernel.h>
+#include <zephyr/kernel.h>
 
 namespace chre {
 
diff --git a/platform/zephyr/include/chre/target_platform/fatal_error.h b/platform/zephyr/include/chre/target_platform/fatal_error.h
index 577052c..8027288 100644
--- a/platform/zephyr/include/chre/target_platform/fatal_error.h
+++ b/platform/zephyr/include/chre/target_platform/fatal_error.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_PLATFORM_ZEPHYR_FATAL_ERROR_H_
 #define CHRE_PLATFORM_ZEPHYR_FATAL_ERROR_H_
 
-#include <kernel.h>
+#include <zephyr/kernel.h>
 
 #define FATAL_ERROR_QUIT() k_panic()
 
diff --git a/platform/zephyr/include/chre/target_platform/init.h b/platform/zephyr/include/chre/target_platform/init.h
index 68df0d5..ca20dc2 100644
--- a/platform/zephyr/include/chre/target_platform/init.h
+++ b/platform/zephyr/include/chre/target_platform/init.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_PLATFORM_ZEPHYR_INIT_H_
 #define CHRE_PLATFORM_ZEPHYR_INIT_H_
 
-#include <zephyr.h>
+#include <zephyr/kernel.h>
 
 namespace chre {
 
diff --git a/platform/zephyr/include/chre/target_platform/log.h b/platform/zephyr/include/chre/target_platform/log.h
index 0d41b18..61098e0 100644
--- a/platform/zephyr/include/chre/target_platform/log.h
+++ b/platform/zephyr/include/chre/target_platform/log.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_PLATFORM_ZEPHYR_LOG_H_
 #define CHRE_PLATFORM_ZEPHYR_LOG_H_
 
-#include <logging/log.h>
+#include <zephyr/logging/log.h>
 LOG_MODULE_DECLARE(chre, CONFIG_CHRE_LOG_LEVEL);
 
 /** Map CHRE's LOGE to Zephyr's LOG_ERR */
diff --git a/platform/zephyr/include/chre/target_platform/mutex_base.h b/platform/zephyr/include/chre/target_platform/mutex_base.h
index d1a07cf..3523376 100644
--- a/platform/zephyr/include/chre/target_platform/mutex_base.h
+++ b/platform/zephyr/include/chre/target_platform/mutex_base.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_PLATFORM_ZEPHYR_MUTEX_BASE_H_
 #define CHRE_PLATFORM_ZEPHYR_MUTEX_BASE_H_
 
-#include <sys/mutex.h>
+#include <zephyr/sys/mutex.h>
 
 namespace chre {
 
diff --git a/platform/zephyr/init.cc b/platform/zephyr/init.cc
index e3c6418..256ec4a 100644
--- a/platform/zephyr/init.cc
+++ b/platform/zephyr/init.cc
@@ -16,8 +16,8 @@
 #include "chre/core/init.h"
 
 #include <errno.h>
-#include <sys/printk.h>
-#include <zephyr.h>
+#include <zephyr/sys/printk.h>
+#include <zephyr/kernel.h>
 
 #include "chre/core/event_loop_manager.h"
 #include "chre/core/static_nanoapps.h"
diff --git a/platform/zephyr/log_module.c b/platform/zephyr/log_module.c
index 95b7a1c..f045a2c 100644
--- a/platform/zephyr/log_module.c
+++ b/platform/zephyr/log_module.c
@@ -14,5 +14,5 @@
  * limitations under the License.
  */
 
-#include <logging/log.h>
+#include <zephyr/logging/log.h>
 LOG_MODULE_REGISTER(chre, CONFIG_CHRE_LOG_LEVEL);
diff --git a/platform/zephyr/memory.cc b/platform/zephyr/memory.cc
index 10e2a77..5075b4c 100644
--- a/platform/zephyr/memory.cc
+++ b/platform/zephyr/memory.cc
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <kernel.h>
+#include <zephyr/kernel.h>
 #include <cstdlib>
 
 #include "chre/platform/memory.h"
diff --git a/platform/zephyr/system_time.cc b/platform/zephyr/system_time.cc
index 47745b8..124f69f 100644
--- a/platform/zephyr/system_time.cc
+++ b/platform/zephyr/system_time.cc
@@ -16,7 +16,7 @@
 
 #include "chre/platform/system_time.h"
 
-#include <kernel.h>
+#include <zephyr/kernel.h>
 
 namespace chre {
 
diff --git a/run_sim.sh b/run_sim.sh
index d4f1f82..680dadd 100755
--- a/run_sim.sh
+++ b/run_sim.sh
@@ -3,6 +3,9 @@
 # Quit if any command produces an error.
 set -e
 
+# Check required paths
+: ${ANDROID_BUILD_TOP:?"ERROR: Please run build/envsetup.sh and lunch first"}
+
 BUILD_ONLY="false"
 while getopts "b" opt; do
   case ${opt} in
diff --git a/std_overrides/README.md b/std_overrides/README.md
index 06398c0..534008e 100644
--- a/std_overrides/README.md
+++ b/std_overrides/README.md
@@ -9,9 +9,9 @@
 * Generated code
 * Third-party code/libraries
 
-The overrides makefile is included in the nanoapp build by default, but the
-developers need to enable it by setting a flag in the nanoapp Makefile:
-`CHRE_STD_OVERRIDES_ALLOWED = true`
+The overrides makefile is included in the nanoapp build by and enabled by
+default, but the developers can disable it by setting a flag in the nanoapp Makefile:
+`CHRE_STD_OVERRIDES_ALLOWED = false`
 
 It is expected that the nanoapp developers only leverage these overrides while
 working towards zero overrides usage.
diff --git a/std_overrides/stdlib_wrapper.cc b/std_overrides/stdlib_wrapper.cc
index edf7ae6..df6f135 100644
--- a/std_overrides/stdlib_wrapper.cc
+++ b/std_overrides/stdlib_wrapper.cc
@@ -33,9 +33,10 @@
 #include <cstdlib>
 #include <new>
 
-#include <chre.h>
 #include "chre/util/nanoapp/assert.h"
 
+#include "chre_api/chre.h"
+
 #if defined(stderr) && !defined(_CSTD)
 // Provides a definition for stderr when the macro has been defined, but the
 // file has been externed. Some platforms might define their own macros for
@@ -73,6 +74,10 @@
     ;
 }
 
+void abort(void) {
+  exit(CHRE_ERROR);
+}
+
 int fprintf(FILE * /*stream*/, const char * /*fmt*/, ...) {
   return 0;
 }
@@ -97,4 +102,34 @@
 void operator delete(void * /*ptr*/, std::size_t /*sz*/,
                      std::align_val_t /*al*/) {
   CHRE_ASSERT(false);
+}
+
+void operator delete[](void * /*ptr*/) {
+  CHRE_ASSERT(false);
+}
+
+void operator delete[](void * /*ptr*/, std::size_t /*sz*/) {
+  CHRE_ASSERT(false);
+}
+
+void operator delete[](void * /*ptr*/, std::align_val_t /*al*/) {
+  CHRE_ASSERT(false);
+}
+
+void operator delete[](void * /*ptr*/, std::size_t /*sz*/,
+                       std::align_val_t /*al*/) {
+  CHRE_ASSERT(false);
+}
+
+void *operator new[](std::size_t /* count */) noexcept(false) {
+  // We return a static pointer here since in development build, using new will
+  // lead to crash so the returned pointer is not important.
+  CHRE_ASSERT(false);
+  return reinterpret_cast<void *>(0xDEADBEEF);
+}
+
+void *operator new[](std::size_t /* count */,
+                     std::align_val_t /* al */) noexcept(false) {
+  CHRE_ASSERT(false);
+  return reinterpret_cast<void *>(0xDEADBEEF);
 }
\ No newline at end of file
diff --git a/test/simulation/README.md b/test/simulation/README.md
index 7184d4c..db380ea 100644
--- a/test/simulation/README.md
+++ b/test/simulation/README.md
@@ -32,9 +32,8 @@
 
   // 2. Create a test Nanpoapp by inheriting TestNanoapp.
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         // 3. Handle system events.
         case CHRE_EVENT_WIFI_ASYNC_RESULT: {
@@ -98,9 +97,8 @@
 the test:
 
 ```cpp
-void (*handleEvent)(uint32_t, uint16_t,
-                    const void *) = [](uint32_t, uint16_t eventType,
-                                        const void *eventData) {
+decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                               const void *eventData) {
   switch (eventType) {
     case CHRE_EVENT_WIFI_ASYNC_RESULT: {
       // ...
@@ -139,9 +137,8 @@
 expectation. For example the status of an event:
 
 ```cpp
-  void (*handleEvent)(uint32_t, uint16_t,
-                      const void *) = [](uint32_t, uint16_t eventType,
-                                          const void *eventData) {
+  decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                 const void *eventData) {
     switch (eventType) {
       case CHRE_EVENT_WIFI_ASYNC_RESULT: {
         auto *event = static_cast<const chreAsyncResult *>(eventData);
@@ -163,9 +160,8 @@
 the data as the second argument to pushEvent:
 
 ```cpp
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         case CHRE_EVENT_WIFI_ASYNC_RESULT: {
           auto *event = static_cast<const chreAsyncResult *>(eventData);
@@ -208,9 +204,8 @@
 `handleEvent` function:
 
 ```cpp
-void (*handleEvent)(uint32_t, uint16_t,
-                    const void *) = [](uint32_t, uint16_t eventType,
-                                        const void *eventData) {
+decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                               const void *eventData) {
   switch (eventType) {
     // Test event are received with a CHRE_EVENT_TEST_EVENT type.
     case CHRE_EVENT_TEST_EVENT: {
@@ -241,9 +236,8 @@
 the `TestEvent`:
 
 ```cpp
-void (*handleEvent)(uint32_t, uint16_t,
-                    const void *) = [](uint32_t, uint16_t eventType,
-                                        const void *eventData) {
+decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                               const void *eventData) {
   switch (eventType) {
     // Test event are received with a CHRE_EVENT_TEST_EVENT type.
     case CHRE_EVENT_TEST_EVENT: {
diff --git a/test/simulation/audio_test.cc b/test/simulation/audio_test.cc
index 1e5b4fb..df03aaa 100644
--- a/test/simulation/audio_test.cc
+++ b/test/simulation/audio_test.cc
@@ -39,7 +39,7 @@
 struct AudioNanoapp : public TestNanoapp {
   uint32_t perms = NanoappPermissions::CHRE_PERMS_AUDIO;
 
-  bool (*start)() = []() {
+  decltype(nanoappStart) *start = []() {
     chreUserSettingConfigureEvents(CHRE_USER_SETTING_MICROPHONE,
                                    true /* enable */);
     return true;
@@ -50,9 +50,8 @@
   CREATE_CHRE_TEST_EVENT(CONFIGURE, 0);
 
   struct App : public AudioNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       static int count = 0;
 
       switch (eventType) {
@@ -119,9 +118,8 @@
   CREATE_CHRE_TEST_EVENT(CONFIGURE, 0);
 
   struct App : public AudioNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         case CHRE_EVENT_AUDIO_SAMPLING_CHANGE: {
           auto event =
diff --git a/test/simulation/ble_test.cc b/test/simulation/ble_test.cc
index eb65b2e..c1f505d 100644
--- a/test/simulation/ble_test.cc
+++ b/test/simulation/ble_test.cc
@@ -46,7 +46,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_TEST_EVENT: {
@@ -87,13 +87,13 @@
 struct BleTestNanoapp : public TestNanoapp {
   uint32_t perms = NanoappPermissions::CHRE_PERMS_BLE;
 
-  bool (*start)() = []() {
+  decltype(nanoappStart) *start = []() {
     chreUserSettingConfigureEvents(CHRE_USER_SETTING_BLE_AVAILABLE,
                                    true /* enable */);
     return true;
   };
 
-  void (*end)() = []() {
+  decltype(nanoappEnd) *end = []() {
     chreUserSettingConfigureEvents(CHRE_USER_SETTING_BLE_AVAILABLE,
                                    false /* enable */);
   };
@@ -110,9 +110,8 @@
   CREATE_CHRE_TEST_EVENT(SCAN_STOPPED, 3);
 
   struct App : public BleTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         case CHRE_EVENT_BLE_ASYNC_RESULT: {
           auto *event = static_cast<const struct chreAsyncResult *>(eventData);
@@ -176,9 +175,8 @@
   CREATE_CHRE_TEST_EVENT(SCAN_STARTED, 1);
 
   struct App : public BleTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         case CHRE_EVENT_BLE_ASYNC_RESULT: {
           auto *event = static_cast<const struct chreAsyncResult *>(eventData);
@@ -229,9 +227,8 @@
   CREATE_CHRE_TEST_EVENT(SCAN_STOPPED, 3);
 
   struct App : public BleTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         case CHRE_EVENT_BLE_ASYNC_RESULT: {
           auto *event = static_cast<const struct chreAsyncResult *>(eventData);
@@ -303,7 +300,7 @@
   CREATE_CHRE_TEST_EVENT(SCAN_STOPPED, 3);
 
   struct App : public BleTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_BLE_ASYNC_RESULT: {
@@ -367,9 +364,8 @@
   CREATE_CHRE_TEST_EVENT(SCAN_STOPPED, 3);
 
   struct App : public BleTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         case CHRE_EVENT_BLE_ASYNC_RESULT: {
           auto *event = static_cast<const struct chreAsyncResult *>(eventData);
@@ -454,9 +450,8 @@
   CREATE_CHRE_TEST_EVENT(START_SCAN, 0);
 
   struct App : public BleTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       switch (eventType) {
         case CHRE_EVENT_BLE_ASYNC_RESULT: {
           auto *event = static_cast<const struct chreAsyncResult *>(eventData);
@@ -518,7 +513,7 @@
   CREATE_CHRE_TEST_EVENT(SCAN_STOPPED, 3);
 
   struct App : public BleTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_BLE_ASYNC_RESULT: {
@@ -574,5 +569,67 @@
   waitForEvent(SCAN_STOPPED);
 }
 
+/**
+ * Test that a nanoapp can read RSSI successfully.
+ */
+TEST_F(TestBase, BleReadRssi) {
+  constexpr auto kConnectionHandle = 6;
+  constexpr auto kCookie = 123;
+
+  CREATE_CHRE_TEST_EVENT(RSSI_REQUEST, 1);
+  CREATE_CHRE_TEST_EVENT(RSSI_REQUEST_SENT, 2);
+
+  struct App : public BleTestNanoapp {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      switch (eventType) {
+        case CHRE_EVENT_BLE_RSSI_READ: {
+          auto *event =
+              static_cast<const struct chreBleReadRssiEvent *>(eventData);
+          if (event->result.errorCode == CHRE_ERROR_NONE) {
+            TestEventQueueSingleton::get()->pushEvent(CHRE_EVENT_BLE_RSSI_READ);
+          }
+          break;
+        }
+        case CHRE_EVENT_SETTING_CHANGED_BLE_AVAILABLE: {
+          auto *event =
+              static_cast<const chreUserSettingChangedEvent *>(eventData);
+          bool enabled =
+              (event->settingState == CHRE_USER_SETTING_STATE_ENABLED);
+          TestEventQueueSingleton::get()->pushEvent(
+              CHRE_EVENT_SETTING_CHANGED_BLE_AVAILABLE, enabled);
+          break;
+        }
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case RSSI_REQUEST: {
+              const bool success =
+                  chreBleReadRssiAsync(kConnectionHandle, (void *)kCookie);
+              TestEventQueueSingleton::get()->pushEvent(RSSI_REQUEST_SENT,
+                                                        success);
+              break;
+            }
+          }
+        }
+      }
+    };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+      Setting::BLE_AVAILABLE, true /* enabled */);
+  bool enabled;
+  waitForEvent(CHRE_EVENT_SETTING_CHANGED_BLE_AVAILABLE, &enabled);
+  EXPECT_TRUE(enabled);
+
+  bool success;
+  sendEventToNanoapp(app, RSSI_REQUEST);
+  waitForEvent(RSSI_REQUEST_SENT, &success);
+  EXPECT_TRUE(success);
+  waitForEvent(CHRE_EVENT_BLE_RSSI_READ);
+}
+
 }  // namespace
 }  // namespace chre
diff --git a/test/simulation/gnss_test.cc b/test/simulation/gnss_test.cc
index 74aa242..5b2a6dc 100644
--- a/test/simulation/gnss_test.cc
+++ b/test/simulation/gnss_test.cc
@@ -64,13 +64,13 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_GNSS;
 
-    bool (*start)() = []() {
+    decltype(nanoappStart) *start = []() {
       chreUserSettingConfigureEvents(CHRE_USER_SETTING_LOCATION,
                                      true /*enabled*/);
       return true;
     };
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           static uint32_t cookie;
           switch (eventType) {
@@ -115,7 +115,7 @@
           }
         };
 
-    void (*end)() = []() {
+    decltype(nanoappEnd) *end = []() {
       chreUserSettingConfigureEvents(CHRE_USER_SETTING_LOCATION,
                                      false /*enabled*/);
     };
@@ -176,9 +176,8 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_GNSS;
 
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       static uint32_t cookie;
       switch (eventType) {
         case CHRE_EVENT_GNSS_ASYNC_RESULT: {
@@ -249,9 +248,8 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_GNSS;
 
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       static uint32_t cookie;
       switch (eventType) {
         case CHRE_EVENT_GNSS_ASYNC_RESULT: {
@@ -313,7 +311,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_GNSS;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           static uint32_t cookie;
           switch (eventType) {
@@ -385,7 +383,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_GNSS;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           static uint32_t cookie;
           switch (eventType) {
@@ -443,7 +441,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_GNSS;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_TEST_EVENT: {
@@ -484,7 +482,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_GNSS;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_TEST_EVENT: {
diff --git a/test/simulation/host_endpoint_notification_test.cc b/test/simulation/host_endpoint_notification_test.cc
index a6847f8..67d3f98 100644
--- a/test/simulation/host_endpoint_notification_test.cc
+++ b/test/simulation/host_endpoint_notification_test.cc
@@ -23,7 +23,7 @@
 #include <thread>
 
 #include "chre/core/event_loop_manager.h"
-#include "chre/core/host_notifications.h"
+#include "chre/core/host_endpoint_manager.h"
 #include "chre/platform/log.h"
 #include "chre_api/chre/event.h"
 #include "test_event_queue.h"
@@ -36,6 +36,11 @@
 //! The host endpoint ID to use for this test.
 constexpr uint16_t kHostEndpointId = 123;
 
+//! Helper function to get Host Endpoint Manager from Event Loop Manager
+HostEndpointManager &getHostEndpointManager() {
+  return EventLoopManagerSingleton::get()->getHostEndpointManager();
+}
+
 /**
  * Verifies basic functionality of chreConfigureHostEndpointNotifications.
  */
@@ -48,7 +53,7 @@
   };
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION: {
@@ -81,7 +86,7 @@
   strcpy(&info.endpointName[0], "Test endpoint name");
   info.isTagValid = true;
   strcpy(&info.endpointTag[0], "Test tag");
-  postHostEndpointConnected(info);
+  getHostEndpointManager().postHostEndpointConnected(info);
 
   auto app = loadNanoapp<App>();
   Config config = {.enable = true, .endpointId = kHostEndpointId};
@@ -92,7 +97,8 @@
   EXPECT_TRUE(success);
 
   struct chreHostEndpointInfo retrievedInfo;
-  ASSERT_TRUE(getHostEndpointInfo(kHostEndpointId, &retrievedInfo));
+  ASSERT_TRUE(getHostEndpointManager().getHostEndpointInfo(kHostEndpointId,
+                                                           &retrievedInfo));
   ASSERT_EQ(retrievedInfo.hostEndpointId, info.hostEndpointId);
   ASSERT_EQ(retrievedInfo.hostEndpointType, info.hostEndpointType);
   ASSERT_EQ(retrievedInfo.isNameValid, info.isNameValid);
@@ -101,8 +107,7 @@
   ASSERT_EQ(strcmp(&retrievedInfo.endpointTag[0], &info.endpointTag[0]), 0);
 
   struct chreHostEndpointNotification notification;
-
-  postHostEndpointDisconnected(kHostEndpointId);
+  getHostEndpointManager().postHostEndpointDisconnected(kHostEndpointId);
   waitForEvent(CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION, &notification);
 
   ASSERT_EQ(notification.hostEndpointId, kHostEndpointId);
@@ -110,12 +115,15 @@
             HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT);
   ASSERT_EQ(notification.reserved, 0);
 
-  ASSERT_FALSE(getHostEndpointInfo(kHostEndpointId, &retrievedInfo));
+  ASSERT_FALSE(EventLoopManagerSingleton::get()
+                   ->getHostEndpointManager()
+                   .getHostEndpointInfo(kHostEndpointId, &retrievedInfo));
 }
 
 TEST_F(TestBase, HostEndpointNotRegisteredTest) {
   struct chreHostEndpointInfo retrievedInfo;
-  ASSERT_FALSE(getHostEndpointInfo(kHostEndpointId, &retrievedInfo));
+  ASSERT_FALSE(getHostEndpointManager().getHostEndpointInfo(kHostEndpointId,
+                                                            &retrievedInfo));
 }
 
 TEST_F(TestBase, HostEndpointDisconnectedTwiceTest) {
@@ -124,11 +132,11 @@
   info.hostEndpointType = CHRE_HOST_ENDPOINT_TYPE_FRAMEWORK;
   info.isNameValid = false;
   info.isTagValid = false;
-  postHostEndpointConnected(info);
+  getHostEndpointManager().postHostEndpointConnected(info);
 
-  postHostEndpointDisconnected(kHostEndpointId);
+  getHostEndpointManager().postHostEndpointDisconnected(kHostEndpointId);
   // The second invocation should be a silent no-op.
-  postHostEndpointDisconnected(kHostEndpointId);
+  getHostEndpointManager().postHostEndpointDisconnected(kHostEndpointId);
 }
 
 }  // anonymous namespace
diff --git a/test/simulation/inc/rpc_test.h b/test/simulation/inc/rpc_test.h
new file mode 100644
index 0000000..ce71d4e
--- /dev/null
+++ b/test/simulation/inc/rpc_test.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.
+ * 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 TEST_SIMULATION_RPC_TEST
+#define TEST_SIMULATION_RPC_TEST
+
+#include <stdint.h>
+
+#include "chre/util/pigweed/rpc_client.h"
+#include "chre/util/pigweed/rpc_server.h"
+#include "chre/util/singleton.h"
+#include "rpc_test.pb.h"
+#include "rpc_test.rpc.pb.h"
+
+namespace chre {
+
+constexpr uint64_t kPwRcpServerAppId = 0x0123456789000001;
+constexpr uint64_t kPwRcpClientAppId = 0x0123456789000002;
+
+class RpcTestService final
+    : public chre::rpc::pw_rpc::nanopb::RpcTestService::Service<
+          RpcTestService> {
+ public:
+  // Increment RPC unary service definition.
+  // See generated IncrementService::Service for more details.
+  pw::Status Increment(const chre_rpc_NumberMessage &request,
+                       chre_rpc_NumberMessage &response);
+};
+
+struct Env {
+  RpcTestService mRpcTestService;
+  chre::RpcServer mServer;
+  chre::RpcClient mClient{kPwRcpServerAppId};
+  pw::rpc::NanopbUnaryReceiver<chre_rpc_NumberMessage> mIncrementCall;
+  uint32_t mNumber;
+};
+
+typedef Singleton<Env> EnvSingleton;
+
+}  // namespace chre
+
+#endif  // TEST_SIMULATION_RPC_TEST
\ No newline at end of file
diff --git a/test/simulation/inc/test_event_queue.h b/test/simulation/inc/test_event_queue.h
index 0eb15f5..03c1364 100644
--- a/test/simulation/inc/test_event_queue.h
+++ b/test/simulation/inc/test_event_queue.h
@@ -88,9 +88,10 @@
 
   //! Block until the event happens.
   void waitForEvent(uint16_t eventType) {
+    LOGD("Waiting for event type 0x%" PRIx16, eventType);
     while (true) {
       auto event = mQueue.pop();
-      LOGD("Got event type 0x%" PRIx16, eventType);
+      LOGD("Got event type 0x%" PRIx16, event.type);
       ASSERT_NE(event.type, CHRE_EVENT_SIMULATION_TEST_TIMEOUT)
           << "Timeout waiting for event " << eventType;
       memoryFree(event.data);
@@ -104,9 +105,10 @@
   template <class T>
   void waitForEvent(uint16_t eventType, T *data) {
     static_assert(std::is_trivial<T>::value);
+    LOGD("Waiting for event type 0x%" PRIx16, eventType);
     while (true) {
       auto event = mQueue.pop();
-      LOGD("Got event type 0x%" PRIx16, eventType);
+      LOGD("Got event type 0x%" PRIx16, event.type);
       ASSERT_NE(event.type, CHRE_EVENT_SIMULATION_TEST_TIMEOUT)
           << "Timeout waiting for event " << eventType;
       if (event.type == eventType) {
diff --git a/test/simulation/inc/test_util.h b/test/simulation/inc/test_util.h
index 1465a3a..0a12e4c 100644
--- a/test/simulation/inc/test_util.h
+++ b/test/simulation/inc/test_util.h
@@ -42,6 +42,18 @@
     decltype(nanoappEnd) *endFunc);
 
 /**
+ * @return the statically loaded nanoapp based on the arguments, additionally
+ * sets info struct version
+ *
+ * @see chreNslNanoappInfo for param descriptions.
+ */
+UniquePtr<Nanoapp> createStaticNanoapp(
+    uint8_t infoStructVersion, const char *name, uint64_t appId,
+    uint32_t appVersion, uint32_t appPerms, decltype(nanoappStart) *startFunc,
+    decltype(nanoappHandleEvent) *handleEventFunc,
+    decltype(nanoappEnd) *endFunc);
+
+/**
  * Deletes memory allocated by createStaticNanoapp.
  *
  * This function must be called when the nanoapp is no more used.
@@ -145,13 +157,12 @@
   uint32_t version = 0;
   uint32_t perms = NanoappPermissions::CHRE_PERMS_NONE;
 
-  bool (*start)() = []() { return true; };
+  decltype(nanoappStart) *start = []() { return true; };
 
-  void (*handleEvent)(uint32_t senderInstanceId, uint16_t eventType,
-                      const void *eventData) = [](uint32_t, uint16_t,
-                                                  const void *) {};
+  decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t,
+                                                 const void *) {};
 
-  void (*end)() = []() {};
+  decltype(nanoappEnd) *end = []() {};
 };
 
 /**
diff --git a/test/simulation/info_struct_version_test.cc b/test/simulation/info_struct_version_test.cc
new file mode 100644
index 0000000..c2d6a0f
--- /dev/null
+++ b/test/simulation/info_struct_version_test.cc
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 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 "test_base.h"
+
+#include <gtest/gtest.h>
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/init.h"
+#include "chre/platform/linux/platform_log.h"
+#include "chre/util/time.h"
+#include "chre_api/chre/version.h"
+#include "inc/test_util.h"
+#include "test_util.h"
+
+namespace chre {
+
+TEST_F(TestBase, InfoStructOldVersionCheckForAppPermission) {
+  constexpr uint8_t kInfoStructVersionOld = 2;
+
+  constexpr uint64_t kAppId = 0x01234;
+  constexpr uint32_t kAppVersion = 0;
+  constexpr uint32_t kAppPerms = 0;
+
+  UniquePtr<Nanoapp> oldnanoapp = createStaticNanoapp(
+      kInfoStructVersionOld, "Test nanoapp", kAppId, kAppVersion, kAppPerms,
+      defaultNanoappStart, defaultNanoappHandleEvent, defaultNanoappEnd);
+
+  EXPECT_FALSE(oldnanoapp->supportsAppPermissions());
+}
+
+TEST_F(TestBase, InfoStructCurrentVersionCheckForAppPermission) {
+  constexpr uint8_t kInfoStructVersionCurrent = 3;
+
+  constexpr uint64_t kAppId = 0x56789;
+  constexpr uint32_t kAppVersion = 0;
+  constexpr uint32_t kAppPerms = 0;
+
+  UniquePtr<Nanoapp> currentnanoapp = createStaticNanoapp(
+      kInfoStructVersionCurrent, "Test nanoapp", kAppId, kAppVersion, kAppPerms,
+      defaultNanoappStart, defaultNanoappHandleEvent, defaultNanoappEnd);
+
+  EXPECT_TRUE(currentnanoapp->supportsAppPermissions());
+}
+
+TEST_F(TestBase, InfoStructFutureVersionCheckForAppPermission) {
+  constexpr uint8_t kInfoStructVersionFuture = 4;
+
+  constexpr uint64_t kAppId = 0xabcde;
+  constexpr uint32_t kAppVersion = 0;
+  constexpr uint32_t kAppPerms = 0;
+
+  UniquePtr<Nanoapp> futurenanoapp = createStaticNanoapp(
+      kInfoStructVersionFuture, "Test nanoapp", kAppId, kAppVersion, kAppPerms,
+      defaultNanoappStart, defaultNanoappHandleEvent, defaultNanoappEnd);
+
+  EXPECT_TRUE(futurenanoapp->supportsAppPermissions());
+}
+
+}  // namespace chre
diff --git a/test/simulation/memory_test.cc b/test/simulation/memory_test.cc
index 5556995..5acd2e9 100644
--- a/test/simulation/memory_test.cc
+++ b/test/simulation/memory_test.cc
@@ -50,28 +50,28 @@
   CREATE_CHRE_TEST_EVENT(FREE, 1);
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
-        [](uint32_t, uint16_t eventType, const void *eventData) {
-          switch (eventType) {
-            case CHRE_EVENT_TEST_EVENT: {
-              auto event = static_cast<const TestEvent *>(eventData);
-              switch (event->type) {
-                case ALLOCATE: {
-                  auto bytes = static_cast<const uint32_t *>(event->data);
-                  void *ptr = chreHeapAlloc(*bytes);
-                  TestEventQueueSingleton::get()->pushEvent(ALLOCATE, ptr);
-                  break;
-                }
-                case FREE: {
-                  auto ptr = static_cast<void **>(event->data);
-                  chreHeapFree(*ptr);
-                  TestEventQueueSingleton::get()->pushEvent(FREE);
-                  break;
-                }
-              }
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      switch (eventType) {
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case ALLOCATE: {
+              auto bytes = static_cast<const uint32_t *>(event->data);
+              void *ptr = chreHeapAlloc(*bytes);
+              TestEventQueueSingleton::get()->pushEvent(ALLOCATE, ptr);
+              break;
+            }
+            case FREE: {
+              auto ptr = static_cast<void **>(event->data);
+              chreHeapFree(*ptr);
+              TestEventQueueSingleton::get()->pushEvent(FREE);
+              break;
             }
           }
-        };
+        }
+      }
+    };
   };
 
   auto app = loadNanoapp<App>();
@@ -117,22 +117,22 @@
   CREATE_CHRE_TEST_EVENT(ALLOCATE, 0);
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
-        [](uint32_t, uint16_t eventType, const void *eventData) {
-          switch (eventType) {
-            case CHRE_EVENT_TEST_EVENT: {
-              auto event = static_cast<const TestEvent *>(eventData);
-              switch (event->type) {
-                case ALLOCATE: {
-                  auto bytes = static_cast<const uint32_t *>(event->data);
-                  void *ptr = chreHeapAlloc(*bytes);
-                  TestEventQueueSingleton::get()->pushEvent(ALLOCATE, ptr);
-                  break;
-                }
-              }
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      switch (eventType) {
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case ALLOCATE: {
+              auto bytes = static_cast<const uint32_t *>(event->data);
+              void *ptr = chreHeapAlloc(*bytes);
+              TestEventQueueSingleton::get()->pushEvent(ALLOCATE, ptr);
+              break;
             }
           }
-        };
+        }
+      }
+    };
   };
 
   auto app = loadNanoapp<App>();
@@ -171,28 +171,28 @@
   CREATE_CHRE_TEST_EVENT(FREE, 1);
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
-        [](uint32_t, uint16_t eventType, const void *eventData) {
-          switch (eventType) {
-            case CHRE_EVENT_TEST_EVENT: {
-              auto event = static_cast<const TestEvent *>(eventData);
-              switch (event->type) {
-                case ALLOCATE: {
-                  auto bytes = static_cast<const uint32_t *>(event->data);
-                  void *ptr = chreHeapAlloc(*bytes);
-                  TestEventQueueSingleton::get()->pushEvent(ALLOCATE, ptr);
-                  break;
-                }
-                case FREE: {
-                  auto ptr = static_cast<void **>(event->data);
-                  chreHeapFree(*ptr);
-                  TestEventQueueSingleton::get()->pushEvent(FREE);
-                  break;
-                }
-              }
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      switch (eventType) {
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case ALLOCATE: {
+              auto bytes = static_cast<const uint32_t *>(event->data);
+              void *ptr = chreHeapAlloc(*bytes);
+              TestEventQueueSingleton::get()->pushEvent(ALLOCATE, ptr);
+              break;
+            }
+            case FREE: {
+              auto ptr = static_cast<void **>(event->data);
+              chreHeapFree(*ptr);
+              TestEventQueueSingleton::get()->pushEvent(FREE);
+              break;
             }
           }
-        };
+        }
+      }
+    };
   };
 
   MemoryManager &memManager =
diff --git a/test/simulation/rpc/rpc_test.proto b/test/simulation/rpc/rpc_test.proto
new file mode 100644
index 0000000..536e422
--- /dev/null
+++ b/test/simulation/rpc/rpc_test.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+package chre.rpc;
+
+service RpcTestService {
+  // Increment a number.
+  rpc Increment(NumberMessage) returns (NumberMessage) {}
+}
+
+// Request and response for the Increment service.
+message NumberMessage {
+  uint32 number = 1;
+}
diff --git a/test/simulation/rpc_test.cc b/test/simulation/rpc_test.cc
new file mode 100644
index 0000000..ffcd564
--- /dev/null
+++ b/test/simulation/rpc_test.cc
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2022 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 "rpc_test.h"
+
+#include <cstdint>
+
+#include "chre/core/event_loop.h"
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/settings.h"
+#include "chre/util/nanoapp/log.h"
+#include "chre/util/time.h"
+#include "chre_api/chre/event.h"
+#include "chre_api/chre/re.h"
+
+#include "gtest/gtest.h"
+#include "inc/test_util.h"
+#include "test_base.h"
+#include "test_event.h"
+#include "test_event_queue.h"
+#include "test_util.h"
+
+namespace chre {
+
+pw::Status RpcTestService::Increment(const chre_rpc_NumberMessage &request,
+                                     chre_rpc_NumberMessage &response) {
+  EnvSingleton::get()->mServer.setPermissionForNextMessage(
+      CHRE_MESSAGE_PERMISSION_NONE);
+  response.number = request.number + 1;
+  return pw::OkStatus();
+}
+
+namespace {
+
+TEST_F(TestBase, PwRpcCanPublishServicesInNanoappStart) {
+  struct App : public TestNanoapp {
+    decltype(nanoappStart) *start = []() -> bool {
+      struct chreNanoappRpcService servicesA[] = {
+          {.id = 1, .version = 0},
+          {.id = 2, .version = 0},
+      };
+
+      struct chreNanoappRpcService servicesB[] = {
+          {.id = 3, .version = 0},
+          {.id = 4, .version = 0},
+      };
+
+      return chrePublishRpcServices(servicesA, 2 /* numServices */) &&
+             chrePublishRpcServices(servicesB, 2 /* numServices */);
+    };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  uint16_t instanceId;
+  EXPECT_TRUE(EventLoopManagerSingleton::get()
+                  ->getEventLoop()
+                  .findNanoappInstanceIdByAppId(app.id, &instanceId));
+
+  Nanoapp *napp =
+      EventLoopManagerSingleton::get()->getEventLoop().findNanoappByInstanceId(
+          instanceId);
+
+  ASSERT_FALSE(napp == nullptr);
+
+  EXPECT_EQ(napp->getRpcServices().size(), 4);
+  EXPECT_EQ(napp->getRpcServices()[0].id, 1);
+  EXPECT_EQ(napp->getRpcServices()[1].id, 2);
+  EXPECT_EQ(napp->getRpcServices()[2].id, 3);
+  EXPECT_EQ(napp->getRpcServices()[3].id, 4);
+}
+
+TEST_F(TestBase, PwRpcCanNotPublishDuplicateServices) {
+  struct App : public TestNanoapp {
+    decltype(nanoappStart) *start = []() -> bool {
+      struct chreNanoappRpcService servicesA[] = {
+          {.id = 1, .version = 0},
+          {.id = 2, .version = 0},
+      };
+
+      bool success = chrePublishRpcServices(servicesA, 2 /* numServices */);
+
+      EXPECT_FALSE(chrePublishRpcServices(servicesA, 2 /* numServices */));
+
+      struct chreNanoappRpcService servicesB[] = {
+          {.id = 5, .version = 0},
+          {.id = 5, .version = 0},
+      };
+
+      EXPECT_FALSE(chrePublishRpcServices(servicesB, 2 /* numServices */));
+
+      return success;
+    };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  uint16_t instanceId;
+  EXPECT_TRUE(EventLoopManagerSingleton::get()
+                  ->getEventLoop()
+                  .findNanoappInstanceIdByAppId(app.id, &instanceId));
+
+  Nanoapp *napp =
+      EventLoopManagerSingleton::get()->getEventLoop().findNanoappByInstanceId(
+          instanceId);
+
+  ASSERT_FALSE(napp == nullptr);
+
+  EXPECT_EQ(napp->getRpcServices().size(), 2);
+  EXPECT_EQ(napp->getRpcServices()[0].id, 1);
+  EXPECT_EQ(napp->getRpcServices()[1].id, 2);
+}
+
+TEST_F(TestBase, PwRpcDifferentAppCanPublishSameServices) {
+  struct App1 : public TestNanoapp {
+    uint64_t id = 0x01;
+
+    decltype(nanoappStart) *start = []() -> bool {
+      struct chreNanoappRpcService services[] = {
+          {.id = 1, .version = 0},
+          {.id = 2, .version = 0},
+      };
+
+      return chrePublishRpcServices(services, 2 /* numServices */);
+    };
+  };
+
+  struct App2 : public App1 {
+    uint64_t id = 0x02;
+  };
+
+  auto app1 = loadNanoapp<App1>();
+  auto app2 = loadNanoapp<App2>();
+
+  uint16_t instanceId1;
+  EXPECT_TRUE(EventLoopManagerSingleton::get()
+                  ->getEventLoop()
+                  .findNanoappInstanceIdByAppId(app1.id, &instanceId1));
+
+  Nanoapp *napp1 =
+      EventLoopManagerSingleton::get()->getEventLoop().findNanoappByInstanceId(
+          instanceId1);
+
+  ASSERT_FALSE(napp1 == nullptr);
+
+  EXPECT_EQ(napp1->getRpcServices().size(), 2);
+  EXPECT_EQ(napp1->getRpcServices()[0].id, 1);
+  EXPECT_EQ(napp1->getRpcServices()[1].id, 2);
+
+  uint16_t instanceId2;
+  EXPECT_TRUE(EventLoopManagerSingleton::get()
+                  ->getEventLoop()
+                  .findNanoappInstanceIdByAppId(app2.id, &instanceId2));
+
+  Nanoapp *napp2 =
+      EventLoopManagerSingleton::get()->getEventLoop().findNanoappByInstanceId(
+          instanceId2);
+
+  ASSERT_FALSE(napp2 == nullptr);
+
+  EXPECT_EQ(napp2->getRpcServices().size(), 2);
+  EXPECT_EQ(napp2->getRpcServices()[0].id, 1);
+  EXPECT_EQ(napp2->getRpcServices()[1].id, 2);
+}
+
+TEST_F(TestBase, PwRpcCanNotPublishServicesOutsideOfNanoappStart) {
+  CREATE_CHRE_TEST_EVENT(PUBLISH_SERVICES, 0);
+
+  struct App : public TestNanoapp {
+    decltype(nanoappHandleEvent) *handleEvent =
+        [](uint32_t, uint16_t eventType, const void *eventData) {
+          switch (eventType) {
+            case CHRE_EVENT_TEST_EVENT: {
+              auto event = static_cast<const TestEvent *>(eventData);
+              switch (event->type) {
+                case PUBLISH_SERVICES: {
+                  struct chreNanoappRpcService services[] = {
+                      {.id = 1, .version = 0},
+                      {.id = 2, .version = 0},
+                  };
+
+                  bool success =
+                      chrePublishRpcServices(services, 2 /* numServices */);
+                  TestEventQueueSingleton::get()->pushEvent(PUBLISH_SERVICES,
+                                                            success);
+                  break;
+                }
+              }
+            }
+          }
+        };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  bool success = true;
+  sendEventToNanoapp(app, PUBLISH_SERVICES);
+  waitForEvent(PUBLISH_SERVICES, &success);
+  EXPECT_FALSE(success);
+
+  uint16_t instanceId;
+  EXPECT_TRUE(EventLoopManagerSingleton::get()
+                  ->getEventLoop()
+                  .findNanoappInstanceIdByAppId(app.id, &instanceId));
+
+  Nanoapp *napp =
+      EventLoopManagerSingleton::get()->getEventLoop().findNanoappByInstanceId(
+          instanceId);
+
+  ASSERT_FALSE(napp == nullptr);
+
+  EXPECT_EQ(napp->getRpcServices().size(), 0);
+}
+
+TEST_F(TestBase, PwRpcRegisterServicesShouldGracefullyFailOnDuplicatedService) {
+  struct App : public TestNanoapp {
+    decltype(nanoappStart) *start = []() -> bool {
+      static RpcTestService testService;
+      EnvSingleton::init();
+      chre::RpcServer::Service service = {.service = testService,
+                                          .id = 0xca8f7150a3f05847,
+                                          .version = 0x01020034};
+
+      chre::RpcServer &server = EnvSingleton::get()->mServer;
+
+      bool status = server.registerServices(1, &service);
+
+      EXPECT_TRUE(status);
+
+      EXPECT_FALSE(server.registerServices(1, &service));
+
+      return status;
+    };
+  };
+
+  auto app = loadNanoapp<App>();
+}
+
+TEST_F(TestBase, PwRpcGetNanoappInfoByAppIdReturnsServices) {
+  CREATE_CHRE_TEST_EVENT(QUERY_INFO, 0);
+
+  struct App : public TestNanoapp {
+    decltype(nanoappStart) *start = []() -> bool {
+      struct chreNanoappRpcService services[] = {
+          {.id = 1, .version = 2},
+          {.id = 2, .version = 3},
+      };
+
+      return chrePublishRpcServices(services, 2 /* numServices */);
+    };
+
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      static struct chreNanoappInfo info;
+      switch (eventType) {
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case QUERY_INFO: {
+              auto id = static_cast<uint64_t *>(event->data);
+              bool success = chreGetNanoappInfoByAppId(*id, &info);
+              const struct chreNanoappInfo *pInfo = success ? &info : nullptr;
+              TestEventQueueSingleton::get()->pushEvent(QUERY_INFO, pInfo);
+              break;
+            }
+          }
+        }
+      }
+    };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  struct chreNanoappInfo *pInfo = nullptr;
+  sendEventToNanoapp(app, QUERY_INFO, app.id);
+  waitForEvent(QUERY_INFO, &pInfo);
+  EXPECT_TRUE(pInfo != nullptr);
+  EXPECT_EQ(pInfo->rpcServiceCount, 2);
+  EXPECT_EQ(pInfo->rpcServices[0].id, 1);
+  EXPECT_EQ(pInfo->rpcServices[0].version, 2);
+  EXPECT_EQ(pInfo->rpcServices[1].id, 2);
+  EXPECT_EQ(pInfo->rpcServices[1].version, 3);
+  EXPECT_EQ(pInfo->reserved[0], 0);
+  EXPECT_EQ(pInfo->reserved[1], 0);
+  EXPECT_EQ(pInfo->reserved[2], 0);
+}
+
+TEST_F(TestBase, PwRpcClientNanoappCanRequestServerNanoapp) {
+  CREATE_CHRE_TEST_EVENT(INCREMENT_REQUEST, 0);
+
+  struct ClientApp : public TestNanoapp {
+    uint64_t id = kPwRcpClientAppId;
+
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t senderInstanceId,
+                                                   uint16_t eventType,
+                                                   const void *eventData) {
+      Env *env = EnvSingleton::get();
+
+      env->mClient.handleEvent(senderInstanceId, eventType, eventData);
+      switch (eventType) {
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case INCREMENT_REQUEST: {
+              auto client =
+                  env->mClient
+                      .get<rpc::pw_rpc::nanopb::RpcTestService::Client>();
+              if (client.has_value()) {
+                chre_rpc_NumberMessage incrementRequest;
+                incrementRequest.number = *static_cast<uint32_t *>(event->data);
+                env->mIncrementCall = client->Increment(
+                    incrementRequest, [](const chre_rpc_NumberMessage &response,
+                                         pw::Status status) {
+                      if (status.ok()) {
+                        EnvSingleton::get()->mNumber = response.number;
+                        TestEventQueueSingleton::get()->pushEvent(
+                            INCREMENT_REQUEST, true);
+                      } else {
+                        TestEventQueueSingleton::get()->pushEvent(
+                            INCREMENT_REQUEST, false);
+                      }
+                    });
+              } else {
+                TestEventQueueSingleton::get()->pushEvent(INCREMENT_REQUEST,
+                                                          false);
+              }
+            }
+          }
+        }
+      }
+    };
+  };
+
+  struct ServerApp : public TestNanoapp {
+    uint64_t id = kPwRcpServerAppId;
+    decltype(nanoappStart) *start = []() {
+      EnvSingleton::init();
+      chre::RpcServer::Service service = {
+          .service = EnvSingleton::get()->mRpcTestService,
+          .id = 0xca8f7150a3f05847,
+          .version = 0x01020034};
+      return EnvSingleton::get()->mServer.registerServices(1, &service);
+    };
+
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t senderInstanceId,
+                                                   uint16_t eventType,
+                                                   const void *eventData) {
+      EnvSingleton::get()->mServer.handleEvent(senderInstanceId, eventType,
+                                               eventData);
+    };
+  };
+
+  auto server = loadNanoapp<ServerApp>();
+  auto client = loadNanoapp<ClientApp>();
+  bool status;
+  constexpr uint32_t kNumber = 101;
+
+  sendEventToNanoapp(client, INCREMENT_REQUEST, kNumber);
+  waitForEvent(INCREMENT_REQUEST, &status);
+  EXPECT_TRUE(status);
+  EXPECT_EQ(EnvSingleton::get()->mNumber, kNumber + 1);
+}
+
+TEST_F(TestBase, PwRpcRpcClientHasServiceCheckForAMatchingService) {
+  CREATE_CHRE_TEST_EVENT(QUERY_HAS_SERVICE, 0);
+
+  struct ServiceInfo {
+    uint64_t id;
+    uint32_t version;
+    uint64_t appId;
+  };
+
+  struct App : public TestNanoapp {
+    decltype(nanoappStart) *start = []() -> bool {
+      struct chreNanoappRpcService services[] = {{.id = 1, .version = 2}};
+
+      return chrePublishRpcServices(services, 1 /* numServices */);
+    };
+
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      static struct chreNanoappInfo info;
+      switch (eventType) {
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case QUERY_HAS_SERVICE: {
+              auto service =
+                  static_cast<const struct ServiceInfo *>(event->data);
+              RpcClient client{service->appId};
+              bool hasService =
+                  client.hasService(service->id, service->version);
+              TestEventQueueSingleton::get()->pushEvent(QUERY_HAS_SERVICE,
+                                                        hasService);
+              break;
+            }
+          }
+          break;
+        }
+      }
+    };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  ServiceInfo service;
+  bool hasService = false;
+
+  service = {.id = 1, .version = 2, .appId = app.id};
+  sendEventToNanoapp(app, QUERY_HAS_SERVICE, service);
+  waitForEvent(QUERY_HAS_SERVICE, &hasService);
+  EXPECT_TRUE(hasService);
+  service = {.id = 10, .version = 2, .appId = app.id};
+  sendEventToNanoapp(app, QUERY_HAS_SERVICE, service);
+  waitForEvent(QUERY_HAS_SERVICE, &hasService);
+  EXPECT_FALSE(hasService);
+}
+
+}  // namespace
+
+}  // namespace chre
diff --git a/test/simulation/sensor_test.cc b/test/simulation/sensor_test.cc
index 1f6fd44..f75a2c1 100644
--- a/test/simulation/sensor_test.cc
+++ b/test/simulation/sensor_test.cc
@@ -45,7 +45,7 @@
   };
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_SENSOR_SAMPLING_CHANGE: {
@@ -110,7 +110,7 @@
   };
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           switch (eventType) {
             case CHRE_EVENT_SENSOR_SAMPLING_CHANGE: {
diff --git a/test/simulation/settings_test.cc b/test/simulation/settings_test.cc
index 65e414f..c11731e 100644
--- a/test/simulation/settings_test.cc
+++ b/test/simulation/settings_test.cc
@@ -189,9 +189,9 @@
   waitForEvent(CHRE_EVENT_WIFI_NAN_SESSION_TERMINATED);
   waitForEvent(CHRE_EVENT_SETTING_CHANGED_WIFI_AVAILABLE);
 
+  gExpectedWifiSettingState = CHRE_USER_SETTING_STATE_ENABLED;
   EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
       Setting::WIFI_AVAILABLE, true /* enabled */);
-  gExpectedWifiSettingState = CHRE_USER_SETTING_STATE_ENABLED;
   waitForEvent(CHRE_EVENT_SETTING_CHANGED_WIFI_AVAILABLE);
   std::this_thread::sleep_for(std::chrono::milliseconds(100));
   ASSERT_TRUE(
diff --git a/test/simulation/test_base.cc b/test/simulation/test_base.cc
index f48ad83..622b84c 100644
--- a/test/simulation/test_base.cc
+++ b/test/simulation/test_base.cc
@@ -21,6 +21,7 @@
 #include "chre/core/event_loop_manager.h"
 #include "chre/core/init.h"
 #include "chre/platform/linux/platform_log.h"
+#include "chre/platform/linux/task_util/task_manager.h"
 #include "chre/util/time.h"
 #include "chre_api/chre/version.h"
 #include "inc/test_util.h"
@@ -43,6 +44,7 @@
  * this test.
  */
 void TestBase::SetUp() {
+  TaskManagerSingleton::init();
   TestEventQueueSingleton::init();
   chre::PlatformLogSingleton::init();
   chre::init();
@@ -72,6 +74,7 @@
   chre::deinit();
   chre::PlatformLogSingleton::deinit();
   TestEventQueueSingleton::deinit();
+  TaskManagerSingleton::deinit();
   deleteNanoappInfos();
 }
 
diff --git a/test/simulation/test_util.cc b/test/simulation/test_util.cc
index 3051c42..acc17fb 100644
--- a/test/simulation/test_util.cc
+++ b/test/simulation/test_util.cc
@@ -41,12 +41,22 @@
     decltype(nanoappStart) *startFunc,
     decltype(nanoappHandleEvent) *handleEventFunc,
     decltype(nanoappEnd) *endFunc) {
+  return createStaticNanoapp(CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION, name,
+                             appId, appVersion, appPerms, startFunc,
+                             handleEventFunc, endFunc);
+}
+
+UniquePtr<Nanoapp> createStaticNanoapp(
+    uint8_t infoStructVersion, const char *name, uint64_t appId,
+    uint32_t appVersion, uint32_t appPerms, decltype(nanoappStart) *startFunc,
+    decltype(nanoappHandleEvent) *handleEventFunc,
+    decltype(nanoappEnd) *endFunc) {
   auto nanoapp = MakeUnique<Nanoapp>();
   auto nanoappInfo = MakeUnique<chreNslNanoappInfo>();
   chreNslNanoappInfo *appInfo = nanoappInfo.get();
   gNanoappInfos.push_back(std::move(nanoappInfo));
   appInfo->magic = CHRE_NSL_NANOAPP_INFO_MAGIC;
-  appInfo->structMinorVersion = CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION;
+  appInfo->structMinorVersion = infoStructVersion;
   appInfo->targetApiVersion = CHRE_API_VERSION;
   appInfo->vendor = "Google";
   appInfo->name = name;
diff --git a/test/simulation/timer_test.cc b/test/simulation/timer_test.cc
index 9840675..1e81da0 100644
--- a/test/simulation/timer_test.cc
+++ b/test/simulation/timer_test.cc
@@ -47,10 +47,9 @@
   CREATE_CHRE_TEST_EVENT(STOP_TIMER, 1);
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
-      const uint32_t cookie = 123;
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      static const uint32_t cookie = 123;
       switch (eventType) {
         static int count = 0;
 
@@ -122,10 +121,9 @@
   CREATE_CHRE_TEST_EVENT(START_TIMER, 0);
 
   struct App : public TestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
-      const uint32_t cookie = 123;
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      static const uint32_t cookie = 123;
       switch (eventType) {
         static int count = 0;
 
diff --git a/test/simulation/wifi_nan_test.cc b/test/simulation/wifi_nan_test.cc
index 0e96fe7..560e97a 100644
--- a/test/simulation/wifi_nan_test.cc
+++ b/test/simulation/wifi_nan_test.cc
@@ -56,7 +56,7 @@
 struct NanTestNanoapp : public TestNanoapp {
   uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
 
-  bool (*start)() = []() {
+  decltype(nanoappStart) *start = []() {
     EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
         Setting::WIFI_AVAILABLE, true /* enabled */);
     PalNanEngineSingleton::get()->setFlags(PalNanEngine::Flags::NONE);
@@ -72,7 +72,7 @@
   CREATE_CHRE_TEST_EVENT(NAN_SUBSCRIBE, 0);
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           constexpr uint32_t kSubscribeCookie = 0x10aded;
 
@@ -123,7 +123,7 @@
   CREATE_CHRE_TEST_EVENT(NAN_SUBSCRIBE, 0);
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           const uint32_t kSubscribeCookie = 0x10aded;
 
@@ -189,7 +189,7 @@
   CREATE_CHRE_TEST_EVENT(NAN_SUBSCRIBE, 0);
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           const uint32_t kSubscribeCookie = 0x10aded;
 
@@ -250,7 +250,7 @@
   CREATE_CHRE_TEST_EVENT(NAN_SUBSCRIBE, 0);
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           const uint32_t kSubscribeCookie = 0x10aded;
 
@@ -306,9 +306,8 @@
   CREATE_CHRE_TEST_EVENT(NAN_SUBSCRIBE, 0);
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t,
-                        const void *) = [](uint32_t, uint16_t eventType,
-                                           const void *eventData) {
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
       const uint32_t kSubscribeCookie = 0x10aded;
 
       switch (eventType) {
@@ -392,7 +391,7 @@
   };
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           const uint32_t kSubscribeCookie = 0x10aded;
 
@@ -476,7 +475,7 @@
   CREATE_CHRE_TEST_EVENT(REQUEST_RANGING, 1);
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           const uint32_t kRangingCookie = 0xfa11;
           const uint32_t kSubscribeCookie = 0x10aded;
@@ -552,7 +551,7 @@
   CREATE_CHRE_TEST_EVENT(NAN_UNSUBSCRIBE_DONE, 3);
 
   struct App : public NanTestNanoapp {
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           const uint32_t kSubscribeCookie = 0x10aded;
 
@@ -619,7 +618,9 @@
   sendEventToNanoapp(app, NAN_UNSUBSCRIBE, id);
   waitForEvent(NAN_UNSUBSCRIBE_DONE, &success);
   ASSERT_TRUE(success);
-  EXPECT_EQ(wifiRequestManager.getNumNanSubscriptions(), 0);
+  // TODO(b/272351526): consider adding an async result event to catch when the
+  //                     unsubscribe has finished
+  // EXPECT_EQ(wifiRequestManager.getNumNanSubscriptions(), 0);
 }
 
 }  // anonymous namespace
diff --git a/test/simulation/wifi_scan_test.cc b/test/simulation/wifi_scan_test.cc
new file mode 100644
index 0000000..4f8d80d
--- /dev/null
+++ b/test/simulation/wifi_scan_test.cc
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2022 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 <cstdint>
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/settings.h"
+#include "chre/platform/linux/pal_nan.h"
+#include "chre/platform/linux/pal_wifi.h"
+#include "chre/platform/log.h"
+#include "chre/util/system/napp_permissions.h"
+#include "chre_api/chre/event.h"
+#include "chre_api/chre/wifi.h"
+#include "gtest/gtest.h"
+#include "test_base.h"
+#include "test_event.h"
+#include "test_event_queue.h"
+#include "test_util.h"
+
+namespace chre {
+namespace {
+
+CREATE_CHRE_TEST_EVENT(SCAN_REQUEST, 20);
+
+struct WifiAsyncData {
+  const uint32_t *cookie;
+  chreError errorCode;
+};
+
+class WifiScanRequestQueueTestBase : public TestBase {
+ public:
+  void SetUp() {
+    TestBase::SetUp();
+    // Add delay to make sure the requests are queued.
+    chrePalWifiDelayResponse(PalWifiAsyncRequestTypes::SCAN,
+                             std::chrono::seconds(1));
+  }
+
+  void TearDown() {
+    TestBase::TearDown();
+    chrePalWifiDelayResponse(PalWifiAsyncRequestTypes::SCAN,
+                             std::chrono::seconds(0));
+  }
+};
+
+struct WifiScanTestNanoapp : public TestNanoapp {
+  uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
+
+  decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                 const void *eventData) {
+    constexpr uint8_t kMaxPendingCookie = 10;
+    static uint32_t cookies[kMaxPendingCookie];
+    static uint8_t nextFreeCookieIndex = 0;
+
+    switch (eventType) {
+      case CHRE_EVENT_WIFI_ASYNC_RESULT: {
+        auto *event = static_cast<const chreAsyncResult *>(eventData);
+        TestEventQueueSingleton::get()->pushEvent(
+            CHRE_EVENT_WIFI_ASYNC_RESULT,
+            WifiAsyncData{
+                .cookie = static_cast<const uint32_t *>(event->cookie),
+                .errorCode = static_cast<chreError>(event->errorCode)});
+        break;
+      }
+
+      case CHRE_EVENT_WIFI_SCAN_RESULT: {
+        TestEventQueueSingleton::get()->pushEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
+        break;
+      }
+
+      case CHRE_EVENT_TEST_EVENT: {
+        auto event = static_cast<const TestEvent *>(eventData);
+        switch (event->type) {
+          case SCAN_REQUEST:
+            bool success = false;
+            if (nextFreeCookieIndex < kMaxPendingCookie) {
+              cookies[nextFreeCookieIndex] =
+                  *static_cast<uint32_t *>(event->data);
+              success = chreWifiRequestScanAsyncDefault(
+                  &cookies[nextFreeCookieIndex]);
+              nextFreeCookieIndex++;
+            } else {
+              LOGE("Too many cookies passed from test body!");
+            }
+            TestEventQueueSingleton::get()->pushEvent(SCAN_REQUEST, success);
+        }
+      }
+    }
+  };
+};
+
+TEST_F(TestBase, WifiScanBasicSettingTest) {
+  auto app = loadNanoapp<WifiScanTestNanoapp>();
+
+  EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+      Setting::WIFI_AVAILABLE, true /* enabled */);
+
+  constexpr uint32_t firstCookie = 0x1010;
+  bool success;
+  WifiAsyncData wifiAsyncData;
+
+  sendEventToNanoapp(app, SCAN_REQUEST, firstCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &wifiAsyncData);
+  EXPECT_EQ(wifiAsyncData.errorCode, CHRE_ERROR_NONE);
+  EXPECT_EQ(*wifiAsyncData.cookie, firstCookie);
+  waitForEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
+
+  EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+      Setting::WIFI_AVAILABLE, false /* enabled */);
+
+  constexpr uint32_t secondCookie = 0x2020;
+  sendEventToNanoapp(app, SCAN_REQUEST, secondCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &wifiAsyncData);
+  EXPECT_EQ(wifiAsyncData.errorCode, CHRE_ERROR_FUNCTION_DISABLED);
+  EXPECT_EQ(*wifiAsyncData.cookie, secondCookie);
+
+  EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+      Setting::WIFI_AVAILABLE, true /* enabled */);
+  unloadNanoapp(app);
+}
+
+TEST_F(WifiScanRequestQueueTestBase, WifiQueuedScanSettingChangeTest) {
+  struct WifiScanTestNanoappTwo : public WifiScanTestNanoapp {
+    uint64_t id = 0x1123456789abcdef;
+  };
+
+  auto firstApp = loadNanoapp<WifiScanTestNanoapp>();
+  auto secondApp = loadNanoapp<WifiScanTestNanoappTwo>();
+
+  constexpr uint32_t firstRequestCookie = 0x1010;
+  constexpr uint32_t secondRequestCookie = 0x2020;
+  bool success;
+  sendEventToNanoapp(firstApp, SCAN_REQUEST, firstRequestCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+  sendEventToNanoapp(secondApp, SCAN_REQUEST, secondRequestCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+      Setting::WIFI_AVAILABLE, false /* enabled */);
+
+  WifiAsyncData wifiAsyncData;
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &wifiAsyncData);
+  EXPECT_EQ(wifiAsyncData.errorCode, CHRE_ERROR_NONE);
+  EXPECT_EQ(*wifiAsyncData.cookie, firstRequestCookie);
+  waitForEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
+
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &wifiAsyncData);
+  EXPECT_EQ(wifiAsyncData.errorCode, CHRE_ERROR_FUNCTION_DISABLED);
+  EXPECT_EQ(*wifiAsyncData.cookie, secondRequestCookie);
+
+  EventLoopManagerSingleton::get()->getSettingManager().postSettingChange(
+      Setting::WIFI_AVAILABLE, true /* enabled */);
+
+  unloadNanoapp(firstApp);
+  unloadNanoapp(secondApp);
+}
+
+TEST_F(WifiScanRequestQueueTestBase, WifiScanRejectRequestFromSameNanoapp) {
+  auto app = loadNanoapp<WifiScanTestNanoapp>();
+
+  constexpr uint32_t firstRequestCookie = 0x1010;
+  constexpr uint32_t secondRequestCookie = 0x2020;
+  bool success;
+  sendEventToNanoapp(app, SCAN_REQUEST, firstRequestCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+  sendEventToNanoapp(app, SCAN_REQUEST, secondRequestCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_FALSE(success);
+
+  WifiAsyncData wifiAsyncData;
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &wifiAsyncData);
+  EXPECT_EQ(wifiAsyncData.errorCode, CHRE_ERROR_NONE);
+  EXPECT_EQ(*wifiAsyncData.cookie, firstRequestCookie);
+  waitForEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
+
+  unloadNanoapp(app);
+}
+
+TEST_F(WifiScanRequestQueueTestBase, WifiScanActiveScanFromDistinctNanoapps) {
+  CREATE_CHRE_TEST_EVENT(CONCURRENT_NANOAPP_RECEIVED_EXPECTED_ASYNC_EVENT_COUNT,
+                         1);
+  CREATE_CHRE_TEST_EVENT(CONCURRENT_NANOAPP_READ_COOKIE, 2);
+
+  struct AppCookies {
+    uint32_t sent = 0;
+    uint32_t received = 0;
+  };
+
+  constexpr uint64_t kAppOneId = 0x0123456789000001;
+  constexpr uint64_t kAppTwoId = 0x0123456789000002;
+
+  struct WifiScanTestConcurrentNanoappOne : public TestNanoapp {
+    uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
+    uint64_t id = kAppOneId;
+
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      constexpr uint8_t kExpectedReceiveAsyncResultCount = 2;
+      static uint8_t receivedCookieCount = 0;
+      static AppCookies appOneCookies;
+      static AppCookies appTwoCookies;
+
+      // Retrieve cookies from different apps that have the same access to
+      // static storage due to inheritance.
+      AppCookies *appCookies =
+          chreGetAppId() == kAppTwoId ? &appTwoCookies : &appOneCookies;
+
+      switch (eventType) {
+        case CHRE_EVENT_WIFI_ASYNC_RESULT: {
+          auto *event = static_cast<const chreAsyncResult *>(eventData);
+          if (event->errorCode == CHRE_ERROR_NONE) {
+            appCookies->received =
+                *static_cast<const uint32_t *>(event->cookie);
+            ++receivedCookieCount;
+          } else {
+            LOGE("Received failed async result");
+          }
+
+          if (receivedCookieCount == kExpectedReceiveAsyncResultCount) {
+            TestEventQueueSingleton::get()->pushEvent(
+                CONCURRENT_NANOAPP_RECEIVED_EXPECTED_ASYNC_EVENT_COUNT);
+          }
+          break;
+        }
+
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          bool success = false;
+          uint32_t expectedCookie;
+          switch (event->type) {
+            case SCAN_REQUEST:
+              appCookies->sent = *static_cast<uint32_t *>(event->data);
+              success = chreWifiRequestScanAsyncDefault(&(appCookies->sent));
+              TestEventQueueSingleton::get()->pushEvent(SCAN_REQUEST, success);
+              break;
+            case CONCURRENT_NANOAPP_READ_COOKIE:
+              TestEventQueueSingleton::get()->pushEvent(
+                  CONCURRENT_NANOAPP_READ_COOKIE, appCookies->received);
+              break;
+          }
+        }
+      };
+    };
+  };
+
+  struct WifiScanTestConcurrentNanoappTwo
+      : public WifiScanTestConcurrentNanoappOne {
+    uint64_t id = kAppTwoId;
+  };
+
+  auto appOne = loadNanoapp<WifiScanTestConcurrentNanoappOne>();
+  auto appTwo = loadNanoapp<WifiScanTestConcurrentNanoappTwo>();
+
+  constexpr uint32_t kAppOneRequestCookie = 0x1010;
+  constexpr uint32_t kAppTwoRequestCookie = 0x2020;
+  bool success;
+  sendEventToNanoapp(appOne, SCAN_REQUEST, kAppOneRequestCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+  sendEventToNanoapp(appTwo, SCAN_REQUEST, kAppTwoRequestCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  waitForEvent(CONCURRENT_NANOAPP_RECEIVED_EXPECTED_ASYNC_EVENT_COUNT);
+
+  uint32_t receivedCookie;
+  sendEventToNanoapp(appOne, CONCURRENT_NANOAPP_READ_COOKIE);
+  waitForEvent(CONCURRENT_NANOAPP_READ_COOKIE, &receivedCookie);
+  EXPECT_EQ(kAppOneRequestCookie, receivedCookie);
+
+  sendEventToNanoapp(appTwo, CONCURRENT_NANOAPP_READ_COOKIE);
+  waitForEvent(CONCURRENT_NANOAPP_READ_COOKIE, &receivedCookie);
+  EXPECT_EQ(kAppTwoRequestCookie, receivedCookie);
+
+  unloadNanoapp(appOne);
+  unloadNanoapp(appTwo);
+}
+
+}  // namespace
+}  // namespace chre
\ No newline at end of file
diff --git a/test/simulation/wifi_test.cc b/test/simulation/wifi_test.cc
index 6ce594f..43c9384 100644
--- a/test/simulation/wifi_test.cc
+++ b/test/simulation/wifi_test.cc
@@ -44,7 +44,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           static uint32_t cookie;
 
@@ -107,7 +107,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           static uint32_t cookie;
 
@@ -167,7 +167,7 @@
   struct App : public TestNanoapp {
     uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
 
-    void (*handleEvent)(uint32_t, uint16_t, const void *) =
+    decltype(nanoappHandleEvent) *handleEvent =
         [](uint32_t, uint16_t eventType, const void *eventData) {
           static uint32_t cookie;
 
diff --git a/test/simulation/wifi_timeout_test.cc b/test/simulation/wifi_timeout_test.cc
new file mode 100644
index 0000000..6e448b0
--- /dev/null
+++ b/test/simulation/wifi_timeout_test.cc
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 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 <cstdint>
+
+#include "chre/core/event_loop_manager.h"
+#include "chre/core/settings.h"
+#include "chre/platform/linux/pal_wifi.h"
+#include "chre/platform/log.h"
+#include "chre/util/system/napp_permissions.h"
+#include "chre_api/chre/event.h"
+#include "chre_api/chre/wifi.h"
+#include "gtest/gtest.h"
+#include "test_base.h"
+#include "test_event.h"
+#include "test_event_queue.h"
+#include "test_util.h"
+
+namespace chre {
+namespace {
+// WifiTimeoutTestBase needs to set timeout more than max wifi async timeout
+// time. If not, waitForEvent will timeout before actual timeout happens in
+// CHRE, making us unable to observe how system handles timeout.
+class WifiTimeoutTestBase : public TestBase {
+ protected:
+  uint64_t getTimeoutNs() const override {
+    return 2 * CHRE_TEST_WIFI_SCAN_RESULT_TIMEOUT_NS;
+  }
+};
+
+TEST_F(WifiTimeoutTestBase, WifiScanRequestTimeoutTest) {
+  CREATE_CHRE_TEST_EVENT(SCAN_REQUEST, 1);
+
+  struct App : public TestNanoapp {
+    uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
+
+    decltype(nanoappHandleEvent) *handleEvent = [](uint32_t, uint16_t eventType,
+                                                   const void *eventData) {
+      static uint32_t cookie;
+
+      switch (eventType) {
+        case CHRE_EVENT_WIFI_ASYNC_RESULT: {
+          auto *event = static_cast<const chreAsyncResult *>(eventData);
+          if (event->success) {
+            TestEventQueueSingleton::get()->pushEvent(
+                CHRE_EVENT_WIFI_ASYNC_RESULT,
+                *(static_cast<const uint32_t *>(event->cookie)));
+          }
+          break;
+        }
+
+        case CHRE_EVENT_WIFI_SCAN_RESULT: {
+          TestEventQueueSingleton::get()->pushEvent(
+              CHRE_EVENT_WIFI_SCAN_RESULT);
+          break;
+        }
+
+        case CHRE_EVENT_TEST_EVENT: {
+          auto event = static_cast<const TestEvent *>(eventData);
+          switch (event->type) {
+            case SCAN_REQUEST:
+              cookie = *static_cast<uint32_t *>(event->data);
+              bool success = chreWifiRequestScanAsyncDefault(&cookie);
+              TestEventQueueSingleton::get()->pushEvent(SCAN_REQUEST, success);
+          }
+        }
+      }
+    };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  constexpr uint32_t timeOutCookie = 0xdead;
+  chrePalWifiEnableResponse(PalWifiAsyncRequestTypes::SCAN, false);
+  sendEventToNanoapp(app, SCAN_REQUEST, timeOutCookie);
+  bool success;
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  // Add 1 second to prevent race condition.
+  constexpr uint8_t kWifiScanRequestTimeoutSec =
+      (CHRE_TEST_WIFI_SCAN_RESULT_TIMEOUT_NS / CHRE_NSEC_PER_SEC) + 1;
+  std::this_thread::sleep_for(std::chrono::seconds(kWifiScanRequestTimeoutSec));
+
+  // Make sure that we can still request scan after a timedout
+  // request.
+  constexpr uint32_t successCookie = 0x0101;
+  chrePalWifiEnableResponse(PalWifiAsyncRequestTypes::SCAN, true);
+  sendEventToNanoapp(app, SCAN_REQUEST, successCookie);
+  waitForEvent(SCAN_REQUEST, &success);
+  EXPECT_TRUE(success);
+  waitForEvent(CHRE_EVENT_WIFI_SCAN_RESULT);
+
+  unloadNanoapp(app);
+}
+
+TEST_F(WifiTimeoutTestBase, WifiScanMonitorTimeoutTest) {
+  CREATE_CHRE_TEST_EVENT(SCAN_MONITOR_REQUEST, 1);
+
+  struct MonitoringRequest {
+    bool enable;
+    uint32_t cookie;
+  };
+
+  struct App : public TestNanoapp {
+    uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
+
+    decltype(nanoappHandleEvent) *handleEvent =
+        [](uint32_t, uint16_t eventType, const void *eventData) {
+          static uint32_t cookie;
+
+          switch (eventType) {
+            case CHRE_EVENT_WIFI_ASYNC_RESULT: {
+              auto *event = static_cast<const chreAsyncResult *>(eventData);
+              if (event->success) {
+                TestEventQueueSingleton::get()->pushEvent(
+                    CHRE_EVENT_WIFI_ASYNC_RESULT,
+                    *(static_cast<const uint32_t *>(event->cookie)));
+              }
+              break;
+            }
+
+            case CHRE_EVENT_TEST_EVENT: {
+              auto event = static_cast<const TestEvent *>(eventData);
+              switch (event->type) {
+                case SCAN_MONITOR_REQUEST:
+                  auto request =
+                      static_cast<const MonitoringRequest *>(event->data);
+                  cookie = request->cookie;
+                  bool success = chreWifiConfigureScanMonitorAsync(
+                      request->enable, &cookie);
+                  TestEventQueueSingleton::get()->pushEvent(
+                      SCAN_MONITOR_REQUEST, success);
+              }
+            }
+          }
+        };
+  };
+
+  auto app = loadNanoapp<App>();
+
+  MonitoringRequest timeoutRequest{.enable = true, .cookie = 0xdead};
+  chrePalWifiEnableResponse(PalWifiAsyncRequestTypes::SCAN_MONITORING, false);
+  sendEventToNanoapp(app, SCAN_MONITOR_REQUEST, timeoutRequest);
+  bool success;
+  waitForEvent(SCAN_MONITOR_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  // Add 1 second to prevent race condition.
+  constexpr uint8_t kWifiConfigureScanMonitorTimeoutSec =
+      (CHRE_TEST_ASYNC_RESULT_TIMEOUT_NS / CHRE_NSEC_PER_SEC) + 1;
+  std::this_thread::sleep_for(
+      std::chrono::seconds(kWifiConfigureScanMonitorTimeoutSec));
+
+  // Make sure that we can still request to change scan monitor after a timedout
+  // request.
+  MonitoringRequest enableRequest{.enable = true, .cookie = 0x1010};
+  chrePalWifiEnableResponse(PalWifiAsyncRequestTypes::SCAN_MONITORING, true);
+  sendEventToNanoapp(app, SCAN_MONITOR_REQUEST, enableRequest);
+  waitForEvent(SCAN_MONITOR_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  uint32_t cookie;
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &cookie);
+  EXPECT_EQ(cookie, enableRequest.cookie);
+  EXPECT_TRUE(chrePalWifiIsScanMonitoringActive());
+
+  MonitoringRequest disableRequest{.enable = false, .cookie = 0x0101};
+  sendEventToNanoapp(app, SCAN_MONITOR_REQUEST, disableRequest);
+  waitForEvent(SCAN_MONITOR_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &cookie);
+  EXPECT_EQ(cookie, disableRequest.cookie);
+  EXPECT_FALSE(chrePalWifiIsScanMonitoringActive());
+
+  unloadNanoapp(app);
+}
+
+TEST_F(WifiTimeoutTestBase, WifiRequestRangingTimeoutTest) {
+  CREATE_CHRE_TEST_EVENT(RANGING_REQUEST, 0);
+  CREATE_CHRE_TEST_EVENT(RANGING_RESULT_TIMEOUT, 1);
+
+  struct App : public TestNanoapp {
+    uint32_t perms = NanoappPermissions::CHRE_PERMS_WIFI;
+
+    decltype(nanoappHandleEvent) *handleEvent =
+        [](uint32_t, uint16_t eventType, const void *eventData) {
+          static uint32_t cookie;
+
+          switch (eventType) {
+            case CHRE_EVENT_WIFI_ASYNC_RESULT: {
+              auto *event = static_cast<const chreAsyncResult *>(eventData);
+              if (event->success) {
+                if (event->errorCode == 0) {
+                  TestEventQueueSingleton::get()->pushEvent(
+                      CHRE_EVENT_WIFI_ASYNC_RESULT,
+                      *(static_cast<const uint32_t *>(event->cookie)));
+                }
+              } else if (event->errorCode == CHRE_ERROR_TIMEOUT) {
+                TestEventQueueSingleton::get()->pushEvent(
+                    RANGING_RESULT_TIMEOUT,
+                    *(static_cast<const uint32_t *>(event->cookie)));
+              }
+              break;
+            }
+
+            case CHRE_EVENT_TEST_EVENT: {
+              auto event = static_cast<const TestEvent *>(eventData);
+              switch (event->type) {
+                case RANGING_REQUEST:
+                  cookie = *static_cast<uint32_t *>(event->data);
+
+                  // Placeholder parameters since linux PAL does not use this to
+                  // generate response
+                  struct chreWifiRangingTarget dummyRangingTarget = {
+                      .macAddress = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc},
+                      .primaryChannel = 0xdef02468,
+                      .centerFreqPrimary = 0xace13579,
+                      .centerFreqSecondary = 0xbdf369cf,
+                      .channelWidth = 0x48,
+                  };
+
+                  struct chreWifiRangingParams dummyRangingParams = {
+                      .targetListLen = 1,
+                      .targetList = &dummyRangingTarget,
+                  };
+
+                  bool success =
+                      chreWifiRequestRangingAsync(&dummyRangingParams, &cookie);
+                  TestEventQueueSingleton::get()->pushEvent(RANGING_REQUEST,
+                                                            success);
+              }
+            }
+          }
+        };
+  };
+
+  auto app = loadNanoapp<App>();
+  uint32_t timeOutCookie = 0xdead;
+
+  chrePalWifiEnableResponse(PalWifiAsyncRequestTypes::RANGING, false);
+  sendEventToNanoapp(app, RANGING_REQUEST, timeOutCookie);
+  bool success;
+  waitForEvent(RANGING_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  // Add 1 second to prevent race condition
+  constexpr uint8_t kWifiRequestRangingTimeoutSec =
+      (CHRE_TEST_WIFI_RANGING_RESULT_TIMEOUT_NS / CHRE_NSEC_PER_SEC) + 1;
+  std::this_thread::sleep_for(
+      std::chrono::seconds(kWifiRequestRangingTimeoutSec));
+
+  // Make sure that we can still request ranging after a timedout request
+  uint32_t successCookie = 0x0101;
+  chrePalWifiEnableResponse(PalWifiAsyncRequestTypes::RANGING, true);
+  sendEventToNanoapp(app, RANGING_REQUEST, successCookie);
+  waitForEvent(RANGING_REQUEST, &success);
+  EXPECT_TRUE(success);
+
+  uint32_t cookie;
+  waitForEvent(CHRE_EVENT_WIFI_ASYNC_RESULT, &cookie);
+  EXPECT_EQ(cookie, successCookie);
+
+  unloadNanoapp(app);
+}
+
+}  // namespace
+}  // namespace chre
diff --git a/tools/todo_checker.py b/tools/todo_checker.py
index c520e79..e2a3f47 100755
--- a/tools/todo_checker.py
+++ b/tools/todo_checker.py
@@ -67,7 +67,7 @@
                                               shell=True,
                                               encoding='UTF-8') \
                                               .split('\n')
-  regex = r'TODO\(b\/([0-9]+)(?=[^\/]*$)'
+  regex = r'TODO\(b\/([0-9]+)'
 
   for line in diff_result_lines:
     if line.startswith('+') and not line.startswith('+++') and \
diff --git a/util/container_support.cc b/util/container_support.cc
index e9d3d1c..d2f3d00 100644
--- a/util/container_support.cc
+++ b/util/container_support.cc
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-#include <chre.h>
-
 #include <cinttypes>
 #include <cstdarg>
 #include <cstdio>
 #include <cstdlib>
+#include "chre_api/chre.h"
 
 #include "chre/util/macros.h"
 
diff --git a/util/include/chre/util/array_queue.h b/util/include/chre/util/array_queue.h
index d063d3b..760422d 100644
--- a/util/include/chre/util/array_queue.h
+++ b/util/include/chre/util/array_queue.h
@@ -22,6 +22,7 @@
 #include <type_traits>
 
 #include "chre/util/non_copyable.h"
+#include "chre/util/raw_storage.h"
 
 /**
  * @file
@@ -259,33 +260,6 @@
 };
 
 /**
- * Storage for ArrayQueue based on an array allocated inside this object.
- */
-template <typename ElementType, size_t kCapacity>
-class ArrayQueueInternalStorage : public NonCopyable {
- public:
-  ElementType *data() {
-    return reinterpret_cast<ElementType *>(mData);
-  }
-
-  const ElementType *data() const {
-    return reinterpret_cast<const ElementType *>(mData);
-  }
-
-  size_t capacity() const {
-    return kCapacity;
-  }
-
- private:
-  /**
-   * Storage for array queue elements. To avoid static initialization of
-   * members, std::aligned_storage is used.
-   */
-  typename std::aligned_storage<sizeof(ElementType), alignof(ElementType)>::type
-      mData[kCapacity];
-};
-
-/**
  * Storage for ArrayQueue based on a pointer to an array allocated elsewhere.
  */
 template <typename ElementType>
@@ -319,9 +293,8 @@
  */
 template <typename ElementType, size_t kCapacity>
 class ArrayQueue
-    : public internal::ArrayQueueCore<
-          ElementType,
-          internal::ArrayQueueInternalStorage<ElementType, kCapacity>> {
+    : public internal::ArrayQueueCore<ElementType,
+                                      RawStorage<ElementType, kCapacity>> {
  public:
   typedef ElementType value_type;
 };
@@ -355,9 +328,9 @@
   typedef std::forward_iterator_tag iterator_category;
 
   ArrayQueueIterator() = default;
-  ArrayQueueIterator(ValueType *pointer, ValueType *base, size_t tail,
+  ArrayQueueIterator(ValueType *start, ValueType *base, size_t tail,
                      size_t capacity)
-      : mPointer(pointer), mBase(base), mTail(tail), mCapacity(capacity) {}
+      : mPointer(start), mBase(base), mTail(tail), mCapacity(capacity) {}
 
   bool operator==(const ArrayQueueIterator &right) const {
     return (mPointer == right.mPointer);
diff --git a/util/include/chre/util/array_queue_impl.h b/util/include/chre/util/array_queue_impl.h
index cc65470..c2982f4 100644
--- a/util/include/chre/util/array_queue_impl.h
+++ b/util/include/chre/util/array_queue_impl.h
@@ -159,7 +159,7 @@
 
     // Move all the elements before the one just popped to the next storage
     // space.
-    // TODO: optimize by comparing headLength to mSize/2.
+    // TODO(b/258557394): optimize by comparing headLength to half of mSize.
     // If headLength < mSize/2, pull heads towards tail.
     // Otherwise, pull tails towards head.
     for (size_t i = 0; i < headLength; ++i) {
diff --git a/util/include/chre/util/blocking_segmented_queue.h b/util/include/chre/util/blocking_segmented_queue.h
new file mode 100644
index 0000000..f79664e
--- /dev/null
+++ b/util/include/chre/util/blocking_segmented_queue.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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_BLOCKING_SEGMENTED_QUEUE_H_
+#define CHRE_UTIL_BLOCKING_SEGMENTED_QUEUE_H_
+
+#include "chre/util/fixed_size_blocking_queue.h"
+#include "chre/util/segmented_queue.h"
+namespace chre {
+/**
+ * Similar but memory efficient version of chre::FixedSizeBlockingQueue.
+ * This data structure achieves memory efficiency by using chre::SegmentedQueue
+ * as the data storage. @see BlockingQueueCore and FixedSizeBlockingQueue for
+ * more information.
+ *
+ * @tparam ElementType Type of element that will be stored.
+ * @tparam kBlockSize number of elements that a block can contain.
+ */
+template <typename ElementType, size_t kBlockSize>
+class BlockingSegmentedQueue
+    : public blocking_queue_internal::BlockingQueueCore<
+          ElementType, SegmentedQueue<ElementType, kBlockSize>> {
+  using Container = ::chre::SegmentedQueue<ElementType, kBlockSize>;
+  using BlockingQueue =
+      ::chre::blocking_queue_internal::BlockingQueueCore<ElementType,
+                                                         Container>;
+
+ public:
+  typedef ElementType value_type;
+
+  /**
+   * Create the blocking segmented queue object
+   *
+   * @param maxBlockCount the maximum number of block that the queue can hold.
+   * @param staticBlockCount the number of block that will be construct by
+   * constructor and will only be removed by destructor.
+   */
+  BlockingSegmentedQueue(size_t maxBlockCount, size_t staticBlockCount = 1)
+      : BlockingQueue(maxBlockCount, staticBlockCount) {}
+  /**
+   * @return size_t the number of block that the queue is holding.
+   */
+  size_t block_count() {
+    return Container::block_count();
+  }
+
+  size_t removeMatchedFromBack(typename Container::MatchingFunction *matchFunc,
+                               size_t maxNumOfElementsRemoved,
+                               typename Container::FreeFunction *freeFunction,
+                               void *extraDataForFreeFunction) {
+    LockGuard<Mutex> lock(BlockingQueue::mMutex);
+    return Container::removeMatchedFromBack(matchFunc, maxNumOfElementsRemoved,
+                                            freeFunction,
+                                            extraDataForFreeFunction);
+  }
+};
+}  // namespace chre
+
+#endif  // CHRE_UTIL_BLOCKING_SEGMENTED_QUEUE_H_
diff --git a/util/include/chre/util/container_support.h b/util/include/chre/util/container_support.h
index e5ca697..93ff8e0 100644
--- a/util/include/chre/util/container_support.h
+++ b/util/include/chre/util/container_support.h
@@ -25,9 +25,14 @@
 
 #if defined CHRE_IS_NANOAPP_BUILD
 
-#include <chre.h>
-
 #include "chre/util/nanoapp/assert.h"
+#include "chre_api/chre.h"
+
+#ifdef CHRE_STANDALONE_POSIX_ALIGNED_ALLOC
+#include <stdlib.h>
+#else
+#include "chre/util/always_false.h"
+#endif  // CHRE_STANDALONE_POSIX_ALIGNED_ALLOC
 
 namespace chre {
 
@@ -42,6 +47,22 @@
   return chreHeapAlloc(static_cast<uint32_t>(size));
 }
 
+template <typename T>
+inline T *memoryAlignedAlloc() {
+#ifdef CHRE_STANDALONE_POSIX_ALIGNED_ALLOC
+  void *ptr;
+  int result = posix_memalign(&ptr, alignof(T), sizeof(T));
+  if (result != 0) {
+    ptr = nullptr;
+  }
+  return static_cast<T *>(ptr);
+#else
+  static_assert(AlwaysFalse<T>::value,
+                "memoryAlignedAlloc is unsupported on this platform");
+  return nullptr;
+#endif  // CHRE_STANDALONE_POSIX_ALIGNED_ALLOC
+}
+
 /**
  * Provides the memoryFree function that is normally provided by the CHRE
  * runtime. It maps into chreHeapFree.
@@ -64,6 +85,16 @@
   return malloc(size);
 }
 
+template <typename T>
+inline T *memoryAlignedAlloc() {
+  void *ptr;
+  int result = posix_memalign(&ptr, alignof(T), sizeof(T));
+  if (result != 0) {
+    ptr = nullptr;
+  }
+  return static_cast<T *>(ptr);
+}
+
 inline void memoryFree(void *pointer) {
   free(pointer);
 }
diff --git a/util/include/chre/util/copyable_fixed_size_vector.h b/util/include/chre/util/copyable_fixed_size_vector.h
new file mode 100644
index 0000000..97f7514
--- /dev/null
+++ b/util/include/chre/util/copyable_fixed_size_vector.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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_COPYABLE_FIXED_SIZE_VECTOR_H_
+#define CHRE_UTIL_COPYABLE_FIXED_SIZE_VECTOR_H_
+
+#include <cstring>
+#include <type_traits>
+
+#include "chre/util/fixed_size_vector.h"
+
+namespace chre {
+
+/**
+ * Equivalent to FixedSizeVector, but for situations where there's an explicit
+ * need for it to be copyable. This implies that we've weighed alternatives and
+ * are OK with the potential overhead involved, for example if we know this is
+ * a small size collection that we'd otherwise use a plain array for.
+ */
+template <typename ElementType, size_t kCapacity>
+class CopyableFixedSizeVector : public FixedSizeVector<ElementType, kCapacity> {
+ public:
+  CopyableFixedSizeVector() = default;
+
+  CopyableFixedSizeVector(const CopyableFixedSizeVector &other) {
+    copyFrom(other);
+  }
+
+  CopyableFixedSizeVector &operator=(const CopyableFixedSizeVector &other) {
+    copyFrom(other);
+    return *this;
+  }
+
+ private:
+  void copyFrom(const CopyableFixedSizeVector &other) {
+    this->mSize = other.mSize;
+    if (std::is_trivially_copy_constructible<ElementType>::value) {
+      std::memcpy(this->data(), other.data(),
+                  sizeof(ElementType) * this->mSize);
+    } else {
+      for (size_t i = 0; i < this->mSize; i++) {
+        new (&this->data()[i]) ElementType(other[i]);
+      }
+    }
+  }
+};
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_COPYABLE_FIXED_SIZE_VECTOR_H_
\ No newline at end of file
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/util/include/chre/util/enum.h
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to util/include/chre/util/enum.h
index 77a9da0..52baa31 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/util/include/chre/util/enum.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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,21 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
-
-#include <cinttypes>
+#include <type_traits>
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+// Helper that does explicit conversion of an enum class to its underlying/base
+// type.
+template <typename EnumType>
+inline constexpr typename std::underlying_type<EnumType>::type asBaseType(
+    EnumType value) {
+  return static_cast<typename std::underlying_type<EnumType>::type>(value);
+}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/util/include/chre/util/fixed_size_blocking_queue.h b/util/include/chre/util/fixed_size_blocking_queue.h
index 4a5d0b6..7163497 100644
--- a/util/include/chre/util/fixed_size_blocking_queue.h
+++ b/util/include/chre/util/fixed_size_blocking_queue.h
@@ -23,24 +23,40 @@
 #include "chre/platform/mutex.h"
 #include "chre/util/array_queue.h"
 #include "chre/util/non_copyable.h"
+#include "chre/util/segmented_queue.h"
 
 namespace chre {
 
+namespace blocking_queue_internal {
+
 /**
- * Implements a thread-safe blocking queue that blocks when popping an element
- * if necessary.
+ * The wrapper around queue storage (ArraryQueue or SegmentedQueue) that
+ * provide thread safety.
+ *
+ * The queue storage must provide the following APIs:
+ *    empty(), size(), push(), front(), pop(), remove(), operator[].
  */
-template <typename ElementType, size_t kSize>
-class FixedSizeBlockingQueue : public NonCopyable {
+template <typename ElementType, class QueueStorageType>
+class BlockingQueueCore : public QueueStorageType {
  public:
-  typedef ElementType value_type;
+  // Inherit constructors from QueueStorageType
+  using QueueStorageType::QueueStorageType;
+
+  /**
+   * Determines whether or not the BlockingQueue is empty.
+   */
+  bool empty();
+
+  /**
+   * Determines the current size of the BlockingQueue.
+   */
+  size_t size();
 
   /**
    * Pushes an element into the queue and notifies any waiting threads that an
    * element is available.
    *
    * @param The element to be pushed.
-   *
    * @return true if the element is pushed successfully.
    */
   bool push(const ElementType &element);
@@ -55,16 +71,6 @@
   ElementType pop();
 
   /**
-   * Determines whether or not the BlockingQueue is empty.
-   */
-  bool empty();
-
-  /**
-   * Determines the current size of the BlockingQueue.
-   */
-  size_t size();
-
-  /**
    * Removes an element from the array queue given an index. It returns false if
    * the index is out of bounds of the underlying array queue.
    *
@@ -75,36 +81,41 @@
 
   /**
    * Obtains an element of the array queue given an index. It is illegal to
-   * index this array queue out of bounds and the user of the API must check the
-   * size() function prior to indexing this array queue to ensure that they will
-   * not read out of bounds.
+   * index this array queue out of bounds and the user of the API must check
+   * the size() function prior to indexing this array queue to ensure that they
+   * will not read out of bounds.
    *
    * @param index Requested index in range [0,size()-1]
    * @return The element.
    */
   ElementType &operator[](size_t index);
-
-  /**
-   * Obtains a const element of the queue given an index. It is illegal to index
-   * this queue out of bounds and the user of the API must check the size()
-   * function prior to indexing this queue to ensure that they will not read out
-   * of bounds.
-   *
-   * @param index Requested index in the rante [0,size()-1]
-   * @return The element.
-   */
   const ElementType &operator[](size_t index) const;
 
- private:
-  //! The mutex used to ensure thread-safety. Mutable to allow const operator[].
+ protected:
+  //! The mutex used to ensure thread-safety. Mutable to allow const
+  //! operator[].
   mutable Mutex mMutex;
 
+ private:
   //! The condition variable used to implement the blocking behavior of the
   //! queue.
   ConditionVariable mConditionVariable;
+};
+}  // namespace blocking_queue_internal
 
-  //! The underlying fixed size container backing the queue.
-  ArrayQueue<ElementType, kSize> mQueue;
+/**
+ * Wrapper for the blocking queue implementation that uses chre::ArrayQueue as
+ * the data container.
+ *
+ * @tparam ElementType type of the item that will be stored in the queue.
+ * @tparam kSize maximum item count that this queue can hold
+ */
+template <typename ElementType, size_t kSize>
+class FixedSizeBlockingQueue
+    : public blocking_queue_internal::BlockingQueueCore<
+          ElementType, ArrayQueue<ElementType, kSize>> {
+ public:
+  typedef ElementType value_type;
 };
 
 }  // namespace chre
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 f5179c0..ec161c0 100644
--- a/util/include/chre/util/fixed_size_blocking_queue_impl.h
+++ b/util/include/chre/util/fixed_size_blocking_queue_impl.h
@@ -22,13 +22,47 @@
 
 namespace chre {
 
-template <typename ElementType, size_t kSize>
-bool FixedSizeBlockingQueue<ElementType, kSize>::push(
+namespace blocking_queue_internal {
+
+template <typename ElementType, typename QueueStorageType>
+bool BlockingQueueCore<ElementType, QueueStorageType>::empty() {
+  LockGuard<Mutex> lock(mMutex);
+  return QueueStorageType::empty();
+}
+
+template <typename ElementType, typename QueueStorageType>
+size_t BlockingQueueCore<ElementType, QueueStorageType>::size() {
+  LockGuard<Mutex> lock(mMutex);
+  return QueueStorageType::size();
+}
+
+template <typename ElementType, typename QueueStorageType>
+bool BlockingQueueCore<ElementType, QueueStorageType>::remove(size_t index) {
+  LockGuard<Mutex> lock(mMutex);
+  return QueueStorageType::remove(index);
+}
+
+template <typename ElementType, typename QueueStorageType>
+ElementType &BlockingQueueCore<ElementType, QueueStorageType>::operator[](
+    size_t index) {
+  LockGuard<Mutex> lock(mMutex);
+  return QueueStorageType::operator[](index);
+}
+
+template <typename ElementType, typename QueueStorageType>
+const ElementType &BlockingQueueCore<ElementType, QueueStorageType>::operator[](
+    size_t index) const {
+  LockGuard<Mutex> lock(mMutex);
+  return QueueStorageType::operator[](index);
+}
+
+template <typename ElementType, typename QueueStorageType>
+bool BlockingQueueCore<ElementType, QueueStorageType>::push(
     const ElementType &element) {
   bool success;
   {
     LockGuard<Mutex> lock(mMutex);
-    success = mQueue.push(element);
+    success = QueueStorageType::push(element);
   }
   if (success) {
     mConditionVariable.notify_one();
@@ -36,12 +70,13 @@
   return success;
 }
 
-template <typename ElementType, size_t kSize>
-bool FixedSizeBlockingQueue<ElementType, kSize>::push(ElementType &&element) {
+template <typename ElementType, typename QueueStorageType>
+bool BlockingQueueCore<ElementType, QueueStorageType>::push(
+    ElementType &&element) {
   bool success;
   {
     LockGuard<Mutex> lock(mMutex);
-    success = mQueue.push(std::move(element));
+    success = QueueStorageType::push(std::move(element));
   }
   if (success) {
     mConditionVariable.notify_one();
@@ -49,49 +84,19 @@
   return success;
 }
 
-template <typename ElementType, size_t kSize>
-ElementType FixedSizeBlockingQueue<ElementType, kSize>::pop() {
+template <typename ElementType, typename QueueStorageType>
+ElementType BlockingQueueCore<ElementType, QueueStorageType>::pop() {
   LockGuard<Mutex> lock(mMutex);
-  while (mQueue.empty()) {
+  while (QueueStorageType::empty()) {
     mConditionVariable.wait(mMutex);
   }
 
-  ElementType element(std::move(mQueue.front()));
-  mQueue.pop();
+  ElementType element(std::move(QueueStorageType::front()));
+  QueueStorageType::pop();
   return element;
 }
 
-template <typename ElementType, size_t kSize>
-bool FixedSizeBlockingQueue<ElementType, kSize>::empty() {
-  LockGuard<Mutex> lock(mMutex);
-  return mQueue.empty();
-}
-
-template <typename ElementType, size_t kSize>
-size_t FixedSizeBlockingQueue<ElementType, kSize>::size() {
-  LockGuard<Mutex> lock(mMutex);
-  return mQueue.size();
-}
-
-template <typename ElementType, size_t kSize>
-bool FixedSizeBlockingQueue<ElementType, kSize>::remove(size_t index) {
-  LockGuard<Mutex> lock(mMutex);
-  return mQueue.remove(index);
-}
-
-template <typename ElementType, size_t kCapacity>
-ElementType &FixedSizeBlockingQueue<ElementType, kCapacity>::operator[](
-    size_t index) {
-  LockGuard<Mutex> lock(mMutex);
-  return mQueue[index];
-}
-
-template <typename ElementType, size_t kCapacity>
-const ElementType &FixedSizeBlockingQueue<ElementType, kCapacity>::operator[](
-    size_t index) const {
-  LockGuard<Mutex> lock(mMutex);
-  return mQueue[index];
-}
+}  // namespace blocking_queue_internal
 
 }  // namespace chre
 
diff --git a/util/include/chre/util/fixed_size_vector.h b/util/include/chre/util/fixed_size_vector.h
index 07d1fca..9f9d6cd 100644
--- a/util/include/chre/util/fixed_size_vector.h
+++ b/util/include/chre/util/fixed_size_vector.h
@@ -21,6 +21,7 @@
 #include <type_traits>
 
 #include "chre/util/non_copyable.h"
+#include "chre/util/raw_storage.h"
 
 namespace chre {
 
@@ -99,6 +100,7 @@
    * @param The element to push onto the vector.
    */
   void push_back(const ElementType &element);
+  void push_back(ElementType &&element);
 
   /**
    * Constructs an element onto the back of the vector. It is illegal to
@@ -198,11 +200,9 @@
   typename FixedSizeVector<ElementType, kCapacity>::const_iterator end() const;
   typename FixedSizeVector<ElementType, kCapacity>::const_iterator cend() const;
 
- private:
-  //! Storage for vector elements. To avoid static initialization of members,
-  //! std::aligned_storage is used.
-  typename std::aligned_storage<sizeof(ElementType), alignof(ElementType)>::type
-      mData[kCapacity];
+ protected:
+  //! Provides storage for elements, initially uninitialized.
+  RawStorage<ElementType, kCapacity> mData;
 
   //! The number of elements in the vector. This will never be more than
   //! kCapacity.
diff --git a/util/include/chre/util/fixed_size_vector_impl.h b/util/include/chre/util/fixed_size_vector_impl.h
index bc54dea..0c3853d 100644
--- a/util/include/chre/util/fixed_size_vector_impl.h
+++ b/util/include/chre/util/fixed_size_vector_impl.h
@@ -57,12 +57,12 @@
 
 template <typename ElementType, size_t kCapacity>
 ElementType *FixedSizeVector<ElementType, kCapacity>::data() {
-  return reinterpret_cast<ElementType *>(mData);
+  return mData.data();
 }
 
 template <typename ElementType, size_t kCapacity>
 const ElementType *FixedSizeVector<ElementType, kCapacity>::data() const {
-  return reinterpret_cast<const ElementType *>(mData);
+  return mData.data();
 }
 
 template <typename ElementType, size_t kCapacity>
@@ -95,6 +95,14 @@
 }
 
 template <typename ElementType, size_t kCapacity>
+void FixedSizeVector<ElementType, kCapacity>::push_back(ElementType &&element) {
+  CHRE_ASSERT(!full());
+  if (!full()) {
+    new (&data()[mSize++]) ElementType(std::move(element));
+  }
+}
+
+template <typename ElementType, size_t kCapacity>
 template <typename... Args>
 void FixedSizeVector<ElementType, kCapacity>::emplace_back(Args &&... args) {
   CHRE_ASSERT(!full());
diff --git a/util/include/chre/util/flatbuffers/helpers.h b/util/include/chre/util/flatbuffers/helpers.h
index 5ecb338..e56a588 100644
--- a/util/include/chre/util/flatbuffers/helpers.h
+++ b/util/include/chre/util/flatbuffers/helpers.h
@@ -1,11 +1,11 @@
 /*
- * Copyright 2020 Google Inc. All rights reserved.
+ * Copyright (C) 2020 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
+ *      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,
diff --git a/util/include/chre/util/intrusive_list.h b/util/include/chre/util/intrusive_list.h
new file mode 100644
index 0000000..ba5f259
--- /dev/null
+++ b/util/include/chre/util/intrusive_list.h
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2022 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_INTRUSIVE_LIST_H_
+#define CHRE_UTIL_INTRUSIVE_LIST_H_
+
+#include <type_traits>
+#include <utility>
+
+#include "chre/util/intrusive_list_base.h"
+
+#include "chre/util/container_support.h"
+
+namespace chre {
+
+template <typename ElementType>
+struct ListNode {
+  // Check if the ElementType is appropriate. Inappropriate ElementType
+  // will lead to wrong behavior of the reinterpret_cast between
+  // Node and ListNode that we use the retrieve item.
+  static_assert(std::is_standard_layout<ElementType>::value,
+                "must be std layout to alias");
+
+  /**
+   * Node that allows the linked list to link data.
+   * This need to be the first member of ListNode or the reinterpret_cast
+   * between Node and ListNode will fail.
+   */
+  intrusive_list_internal::Node node;
+
+  /**
+   * The data that the user wants to store.
+   */
+  ElementType item;
+
+  /**
+   * Construct a new List Node object by forwarding the arguments to
+   * the constructor of ElementType.
+   *
+   * This breaks C++ assumptions of which constructor is called (move/copy) when
+   * using assignment operator. However, in this case, it is safe to do so since
+   * ListNode is not copyable (Node is not copyable).
+   */
+  template <typename... Args>
+  ListNode(Args &&...args) : item(std::forward<Args>(args)...) {}
+
+  ~ListNode() {
+    CHRE_ASSERT(node.prev == nullptr && node.next == nullptr);
+  }
+};
+
+/**
+ * A container for storing data in a linked list. Note that this container does
+ * not allocate any memory, the caller need to manage the memory of the
+ * data/node that it wants to insert.
+ *
+ * Caller need to turn the data into nodes by doing ListNode<ElementType> before
+ * using the linked list to manage data. This approach is preferred over the
+ * more intrusive way to let user create a new node structure that inherits from
+ * Node, since user will not need to worry about handling the extra node type
+ * but focus on interacting with the list.
+ *
+ * Note that although ListNode.node is accessible to client code, user should
+ * not modify them directly without using the linked list.
+ *
+ * Example:
+ *  typedef ListNode<int> ListIntNode;
+ *  ListIntNode node(10);
+ *  IntrusiveList<int> myList;
+ *  myList.push_back(node);
+ *
+ * Note that myList is declared after node so that myList will be destructed
+ * before node.
+ *
+ * @tparam ElementType: The data type that wants to be stored using the link
+ * list.
+ */
+template <typename ElementType>
+class IntrusiveList : private intrusive_list_internal::IntrusiveListBase {
+  // Check if the ListNode layout is appropriate. Inappropriate or
+  // ListNode will lead to wrong behavior of the reinterpret_cast between
+  // Node and ListNode that we use the retrieve item.
+  static_assert(offsetof(ListNode<ElementType>, node) == 0,
+                "node must be the first element");
+
+ public:
+  class Iterator {
+    using Node = ::chre::intrusive_list_internal::Node;
+
+   public:
+    Iterator(Node *node) : mNode(node){};
+
+    ListNode<ElementType> &operator*() const {
+      return *reinterpret_cast<ListNode<ElementType> *>(mNode);
+    }
+
+    ListNode<ElementType> *operator->() {
+      return reinterpret_cast<ListNode<ElementType> *>(mNode);
+    }
+
+    Iterator &operator++() {
+      mNode = mNode->next;
+      return *this;
+    }
+
+    Iterator &operator--() {
+      mNode = mNode->prev;
+      return *this;
+    }
+
+    bool operator==(Iterator other) const {
+      return mNode == other.mNode;
+    }
+    bool operator!=(Iterator other) const {
+      return mNode != other.mNode;
+    }
+
+   private:
+    Node *mNode;
+  };
+
+  /**
+   * Default construct a new Intrusive Linked List.
+   */
+  IntrusiveList() = default;
+
+  /**
+   * Unlink all node when destroy the Intrusive List object.
+   */
+  ~IntrusiveList();
+
+  /**
+   * Examines if the linked list is empty.
+   *
+   * @return true if the linked list has no linked node.
+   */
+  bool empty() const {
+    return mSize == 0;
+  }
+
+  /**
+   * Returns the number of nodes stored in this linked list.
+   *
+   * @return The number of nodes in the linked list.
+   */
+  size_t size() const {
+    return mSize;
+  }
+
+  /**
+   * Link a new node to the end of the linked list.
+   *
+   * @param newNode: the node to push to the pack of the linked list.
+   */
+  void link_back(ListNode<ElementType> *newNode);
+
+  /**
+   * Returns a reference to the first node of the linked list.
+   * It is not allowed to call this on a empty list.
+   *
+   * @return The first node of the linked list
+   */
+  ListNode<ElementType> &front();
+  const ListNode<ElementType> &front() const;
+
+  /**
+   * Unlink the first node from the list.
+   * It is not allowed to call this on a empty list.
+   * Note that this function does not free the memory of the node.
+   */
+  void unlink_front();
+
+  /**
+   * Returns a reference to the last node of the linked list.
+   * It is not allowed to call this on a empty list.
+   *
+   * @return The last node of the linked list
+   */
+  ListNode<ElementType> &back();
+  const ListNode<ElementType> &back() const;
+
+  /**
+   * Unlink the last node from the list.
+   * It is not allowed to call this on a empty list.
+   * Note that this function does not free the memory of the node.
+   */
+  void unlink_back();
+
+  /**
+   * Remove a node from its list.
+   *
+   * @param node: Node that need to be unlinked.
+   */
+  void unlink_node(ListNode<ElementType> *node);
+
+  /**
+   * Link a node after a given node.
+   *
+   * @param frontNode the old node that will be in front of the new node.
+   * @param newNode the new node that will be link to the list.
+   */
+  void link_after(ListNode<ElementType> *frontNode,
+                  ListNode<ElementType> *newNode);
+
+  /**
+   * @return Iterator from the beginning of the linked list.
+   */
+  Iterator begin() {
+    return mSentinelNode.next;
+  }
+
+  /**
+   * @return Iterator from the end of the linked list.
+   */
+  Iterator end() {
+    return &mSentinelNode;
+  }
+};
+
+}  // namespace chre
+
+#include "chre/util/intrusive_list_impl.h"
+
+#endif  // CHRE_UTIL_INTRUSIVE_LIST_H_
diff --git a/util/include/chre/util/intrusive_list_base.h b/util/include/chre/util/intrusive_list_base.h
new file mode 100644
index 0000000..212b524
--- /dev/null
+++ b/util/include/chre/util/intrusive_list_base.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 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_INTRUSIVE_LIST_BASE_H_
+#define CHRE_UTIL_INTRUSIVE_LIST_BASE_H_
+
+#include <cstddef>
+
+#include "chre/util/non_copyable.h"
+
+namespace chre {
+namespace intrusive_list_internal {
+
+struct Node : public NonCopyable {
+  Node *next = nullptr;
+  Node *prev = nullptr;
+
+  bool operator==(Node const &other) const {
+    return &other == this;
+  }
+
+  bool operator!=(Node const &other) const {
+    return &other != this;
+  }
+};
+
+class IntrusiveListBase : public NonCopyable {
+ protected:
+  /**
+   * The sentinel node for easier access to the first (mSentinelNode.next)
+   * and last (mSentinelNode.prev) element of the linked list.
+   */
+  Node mSentinelNode;
+
+  /**
+   * Number of elements currently stored in the linked list.
+   */
+  size_t mSize = 0;
+
+  IntrusiveListBase() {
+    mSentinelNode.next = &mSentinelNode;
+    mSentinelNode.prev = &mSentinelNode;
+  };
+
+  /**
+   * Link a new node to the end of the linked list.
+   *
+   * @param newNode: The node to push onto the linked list.
+   */
+  void doLinkBack(Node *newNode);
+
+  /**
+   * Unlink a node from the linked list.
+   *
+   * @param node: The node to remove from the linked list.
+   */
+  void doUnlinkNode(Node *node);
+
+  /**
+   * Link a node after a given node.
+   *
+   * @param frontNode: The node that will lead the new node.
+   * @param newNode: The new node to link.
+   */
+  void doLinkAfter(Node *frontNode, Node *newNode);
+
+  /**
+   * Unlinks all node in this list.
+   */
+  void doUnlinkAll();
+};
+
+}  // namespace intrusive_list_internal
+}  // namespace chre
+
+#endif  // CHRE_UTIL_INTRUSIVE_LIST_BASE_H_
diff --git a/util/include/chre/util/intrusive_list_impl.h b/util/include/chre/util/intrusive_list_impl.h
new file mode 100644
index 0000000..3a05d83
--- /dev/null
+++ b/util/include/chre/util/intrusive_list_impl.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 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_INTRUSIVE_LIST_IMPL_H_
+#define CHRE_UTIL_INTRUSIVE_LIST_IMPL_H_
+
+#include "chre/util/intrusive_list.h"
+
+#include "chre/util/container_support.h"
+
+namespace chre {
+
+template <typename ElementType>
+IntrusiveList<ElementType>::~IntrusiveList() {
+  IntrusiveListBase::doUnlinkAll();
+}
+
+template <typename ElementType>
+void IntrusiveList<ElementType>::link_back(ListNode<ElementType> *newNode) {
+  return IntrusiveListBase::doLinkBack(&newNode->node);
+}
+
+template <typename ElementType>
+ListNode<ElementType> &IntrusiveList<ElementType>::front() {
+  CHRE_ASSERT(mSize > 0);
+  return *reinterpret_cast<ListNode<ElementType> *>(mSentinelNode.next);
+}
+
+template <typename ElementType>
+const ListNode<ElementType> &IntrusiveList<ElementType>::front() const {
+  CHRE_ASSERT(mSize > 0);
+  return *reinterpret_cast<const ListNode<ElementType> *>(mSentinelNode.next);
+}
+
+template <typename ElementType>
+void IntrusiveList<ElementType>::unlink_front() {
+  CHRE_ASSERT(mSize > 0);
+  IntrusiveListBase::doUnlinkNode(mSentinelNode.next);
+}
+
+template <typename ElementType>
+ListNode<ElementType> &IntrusiveList<ElementType>::back() {
+  CHRE_ASSERT(mSize > 0);
+  return *reinterpret_cast<ListNode<ElementType> *>(mSentinelNode.prev);
+}
+
+template <typename ElementType>
+const ListNode<ElementType> &IntrusiveList<ElementType>::back() const {
+  CHRE_ASSERT(mSize > 0);
+  return *reinterpret_cast<const ListNode<ElementType> *>(mSentinelNode.prev);
+}
+
+template <typename ElementType>
+void IntrusiveList<ElementType>::unlink_back() {
+  CHRE_ASSERT(mSize > 0);
+  IntrusiveListBase::doUnlinkNode(mSentinelNode.prev);
+}
+
+template <typename ElementType>
+void IntrusiveList<ElementType>::unlink_node(ListNode<ElementType> *node) {
+  CHRE_ASSERT(mSize > 0);
+  IntrusiveListBase::doUnlinkNode(&node->node);
+}
+
+template <typename ElementType>
+void IntrusiveList<ElementType>::link_after(ListNode<ElementType> *frontNode,
+                                            ListNode<ElementType> *newNode) {
+  IntrusiveListBase::doLinkAfter(&frontNode->node, &newNode->node);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_INTRUSIVE_LIST_IMPL_H_
diff --git a/util/include/chre/util/macros.h b/util/include/chre/util/macros.h
index 475cd45..4f4877c 100644
--- a/util/include/chre/util/macros.h
+++ b/util/include/chre/util/macros.h
@@ -73,6 +73,9 @@
 //! Marks a symbol as weak, so that it may be overridden at link time
 #define WEAK_SYMBOL __attribute__((weak))
 
+//! Marks a function as malloc-like, for optimizations with the return pointer
+#define MALLOC_ATTR __attribute__((malloc))
+
 #else
 
 #warning "Missing compiler-specific macros"
diff --git a/util/include/chre/util/memory.h b/util/include/chre/util/memory.h
index c9f5925..6c1619b 100644
--- a/util/include/chre/util/memory.h
+++ b/util/include/chre/util/memory.h
@@ -59,6 +59,14 @@
 template <typename T, typename... Args>
 T *memoryAlloc(Args &&... args);
 
+/**
+ * Destroys an element and deallocate its memory.
+ *
+ * @param element the element to be destroy. Needs to be from memoryAlloc.
+ */
+template <typename T>
+void memoryFreeAndDestroy(T *element);
+
 }  // 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 d588798..201c986 100644
--- a/util/include/chre/util/memory_impl.h
+++ b/util/include/chre/util/memory_impl.h
@@ -111,7 +111,13 @@
 
 template <typename T, typename... Args>
 inline T *memoryAlloc(Args &&... args) {
-  auto *storage = static_cast<T *>(memoryAlloc(sizeof(T)));
+  T *storage = nullptr;
+  if constexpr (alignof(T) > alignof(std::max_align_t)) {
+    storage = memoryAlignedAlloc<T>();
+  } else {
+    storage = static_cast<T *>(memoryAlloc(sizeof(T)));
+  }
+
   if (storage != nullptr) {
     new (storage) T(std::forward<Args>(args)...);
   }
@@ -119,6 +125,14 @@
   return storage;
 }
 
+template <typename T>
+void memoryFreeAndDestroy(T *element) {
+  if (element != nullptr) {
+    element->~T();
+    memoryFree(element);
+  }
+}
+
 }  // namespace chre
 
 #endif  // CHRE_UTIL_MEMORY_IMPL_H_
diff --git a/util/include/chre/util/memory_pool.h b/util/include/chre/util/memory_pool.h
index 2edf135..375c482 100644
--- a/util/include/chre/util/memory_pool.h
+++ b/util/include/chre/util/memory_pool.h
@@ -21,6 +21,7 @@
 #include <type_traits>
 
 #include "chre/util/non_copyable.h"
+#include "chre/util/raw_storage.h"
 
 namespace chre {
 
@@ -76,10 +77,31 @@
   void deallocate(ElementType *element);
 
   /**
+   * Checks if the address of the element provided is within the range managed
+   * by this event pool.
+   * NOTE: If this method returns true, that does not mean that the provided
+   * element has not already been deallocated. It's up to the caller to ensure
+   * they don't double deallocate a given element.
+   *
+   * @param element Address to the element to check if it is in the current
+   * memory pool.
+   * @return true Returns true if the address of the provided element is
+   * managed by this pool.
+   */
+  bool containsAddress(ElementType *element);
+
+  /**
    * @return the number of unused blocks in this memory pool.
    */
   size_t getFreeBlockCount() const;
 
+  /**
+   * @return true Return true if this memory pool is empty.
+   */
+  bool empty() {
+    return mFreeBlockCount == kSize;
+  }
+
  private:
   /**
    * The unused storage for this MemoryPool maintains the list of free slots.
@@ -105,10 +127,19 @@
    */
   MemoryPoolBlock *blocks();
 
-  //! Storage for memory pool blocks. To avoid static initialization of members,
-  //! std::aligned_storage is used.
-  typename std::aligned_storage<sizeof(MemoryPoolBlock),
-                                alignof(MemoryPoolBlock)>::type mBlocks[kSize];
+  /**
+   * Calculate the block index that allocates the element if it belongs to
+   * this memory pool.
+   *
+   * @param element Address to the element.
+   * @param indexOutput Calculated block index output.
+   * @return false if the address of element does not belong to this memory
+   * pool.
+   */
+  bool getBlockIndex(ElementType *element, size_t *indexOutput);
+
+  //! Storage for memory pool blocks.
+  RawStorage<MemoryPoolBlock, kSize> mBlocks;
 
   //! The index of the head of the free slot list.
   size_t mNextFreeBlockIndex = 0;
diff --git a/util/include/chre/util/memory_pool_impl.h b/util/include/chre/util/memory_pool_impl.h
index 2e78804..d4e8666 100644
--- a/util/include/chre/util/memory_pool_impl.h
+++ b/util/include/chre/util/memory_pool_impl.h
@@ -17,11 +17,12 @@
 #ifndef CHRE_UTIL_MEMORY_POOL_IMPL_H_
 #define CHRE_UTIL_MEMORY_POOL_IMPL_H_
 
-#include "chre/util/memory_pool.h"
-
 #include <cinttypes>
 #include <utility>
 
+#include "chre/util/container_support.h"
+#include "chre/util/memory_pool.h"
+
 namespace chre {
 template <typename ElementType, size_t kSize>
 MemoryPool<ElementType, kSize>::MemoryPool() {
@@ -51,9 +52,8 @@
 
 template <typename ElementType, size_t kSize>
 void MemoryPool<ElementType, kSize>::deallocate(ElementType *element) {
-  uintptr_t elementAddress = reinterpret_cast<uintptr_t>(element);
-  uintptr_t baseAddress = reinterpret_cast<uintptr_t>(&blocks()[0].mElement);
-  size_t blockIndex = (elementAddress - baseAddress) / sizeof(MemoryPoolBlock);
+  size_t blockIndex;
+  CHRE_ASSERT(getBlockIndex(element, &blockIndex));
 
   blocks()[blockIndex].mElement.~ElementType();
   blocks()[blockIndex].mNextFreeBlockIndex = mNextFreeBlockIndex;
@@ -62,6 +62,25 @@
 }
 
 template <typename ElementType, size_t kSize>
+bool MemoryPool<ElementType, kSize>::containsAddress(ElementType *element) {
+  size_t temp;
+  return getBlockIndex(element, &temp);
+}
+
+template <typename ElementType, size_t kSize>
+bool MemoryPool<ElementType, kSize>::getBlockIndex(ElementType *element,
+                                                   size_t *indexOutput) {
+  uintptr_t elementAddress = reinterpret_cast<uintptr_t>(element);
+  uintptr_t baseAddress = reinterpret_cast<uintptr_t>(&blocks()[0].mElement);
+  *indexOutput = (elementAddress - baseAddress) / sizeof(MemoryPoolBlock);
+
+  return elementAddress >= baseAddress &&
+         elementAddress <=
+             reinterpret_cast<uintptr_t>(&blocks()[kSize - 1].mElement) &&
+         ((elementAddress - baseAddress) % sizeof(MemoryPoolBlock) == 0);
+}
+
+template <typename ElementType, size_t kSize>
 size_t MemoryPool<ElementType, kSize>::getFreeBlockCount() const {
   return mFreeBlockCount;
 }
@@ -69,7 +88,7 @@
 template <typename ElementType, size_t kSize>
 typename MemoryPool<ElementType, kSize>::MemoryPoolBlock *
 MemoryPool<ElementType, kSize>::blocks() {
-  return reinterpret_cast<MemoryPoolBlock *>(mBlocks);
+  return mBlocks.data();
 }
 
 }  // namespace chre
diff --git a/util/include/chre/util/nanoapp/app_id.h b/util/include/chre/util/nanoapp/app_id.h
index b81ea06..edc9803 100644
--- a/util/include/chre/util/nanoapp/app_id.h
+++ b/util/include/chre/util/nanoapp/app_id.h
@@ -84,6 +84,7 @@
 // 16 = Power Test TCM
 constexpr uint64_t kDebugDumpWorldAppId   = makeExampleNanoappId(17);
 constexpr uint64_t kBleWorldAppId         = makeExampleNanoappId(18);
+constexpr uint64_t kRpcWorldAppId         = makeExampleNanoappId(19);
 // clang-format on
 
 }  // namespace chre
diff --git a/util/include/chre/util/nanoapp/assert.h b/util/include/chre/util/nanoapp/assert.h
index 34f97a3..ddc7afe 100644
--- a/util/include/chre/util/nanoapp/assert.h
+++ b/util/include/chre/util/nanoapp/assert.h
@@ -25,7 +25,7 @@
 
 #ifdef CHRE_IS_NANOAPP_BUILD
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 /**
  * Provides the CHRE_ASSERT macro that uses chreAbort to abort the nanoapp upon
diff --git a/util/include/chre/util/nanoapp/ble.h b/util/include/chre/util/nanoapp/ble.h
new file mode 100644
index 0000000..c00ce30
--- /dev/null
+++ b/util/include/chre/util/nanoapp/ble.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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_NANOAPP_BLE_H_
+#define CHRE_UTIL_NANOAPP_BLE_H_
+
+#include <inttypes.h>
+
+#include "chre_api/chre.h"
+
+namespace chre {
+
+namespace ble_constants {
+
+/**
+ * The minimum threshold for RSSI. Used to filter out RSSI values below this.
+ */
+constexpr int8_t kRssiThreshold = -128;
+
+/**
+ * The length of the UUID data at the beginning of the data in the BLE packet.
+ */
+constexpr uint16_t kGoogleUuidDataLength = 2;
+
+/**
+ * The mask to get the UUID from the data in the BLE packet.
+ */
+constexpr uint8_t kGoogleUuidMask[kGoogleUuidDataLength] = {0xFF, 0xFF};
+
+/**
+ * The Google Eddystone BLE beacon UUID.
+ */
+constexpr uint8_t kGoogleEddystoneUuid[kGoogleUuidDataLength] = {0xAA, 0xFE};
+
+/**
+ * The Google Nearby Fastpair BLE beacon UUID.
+ */
+constexpr uint8_t kGoogleNearbyFastpairUuid[kGoogleUuidDataLength] = {0x2C,
+                                                                      0xFE};
+
+/**
+ * The number of generic filters (equal to the number of known beacons).
+ */
+constexpr uint8_t kNumScanFilters = 2;
+}  // namespace ble_constants
+
+/**
+ * Create a BLE generic filter object.
+ *
+ * @param type                              the filter type.
+ * @param len                               the filter length.
+ * @param data                              the filter data.
+ * @param mask                              the filter mask.
+ * @return                                  the filter.
+ */
+chreBleGenericFilter createBleGenericFilter(uint8_t type, uint8_t len,
+                                            const uint8_t *data,
+                                            const uint8_t *mask);
+
+/**
+ * Creates a chreBleScanFilter that filters for the Google eddystone UUID,
+ * the Google nearby fastpair UUID, and a RSSI threshold of kRssiThreshold.
+ *
+ * @param filter                            (out) the output filter.
+ * @param genericFilters                    (out) the output generic filters
+ * array.
+ * @param numGenericFilters                 the size of the generic filters
+ * array. must be >= kNumScanFilters.
+ *
+ * @return true                             the operation was successful
+ * @return false                            the operation was not successful
+ */
+bool createBleScanFilterForKnownBeacons(struct chreBleScanFilter &filter,
+                                        chreBleGenericFilter *genericFilters,
+                                        uint8_t numGenericFilters);
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_NANOAPP_BLE_H_
diff --git a/util/include/chre/util/nanoapp/log.h b/util/include/chre/util/nanoapp/log.h
index c75e927..e57efce 100644
--- a/util/include/chre/util/nanoapp/log.h
+++ b/util/include/chre/util/nanoapp/log.h
@@ -27,9 +27,8 @@
  */
 #ifdef CHRE_IS_NANOAPP_BUILD
 
-#include <chre.h>
-
 #include "chre/util/log_common.h"
+#include "chre_api/chre.h"
 
 #ifndef NANOAPP_MINIMUM_LOG_LEVEL
 #error "NANOAPP_MINIMUM_LOG_LEVEL must be defined"
diff --git a/util/include/chre/util/optional_impl.h b/util/include/chre/util/optional_impl.h
index f80d706..cc9834d 100644
--- a/util/include/chre/util/optional_impl.h
+++ b/util/include/chre/util/optional_impl.h
@@ -154,12 +154,12 @@
 
 template <typename ObjectType>
 ObjectType *Optional<ObjectType>::objectAddr() {
-  return reinterpret_cast<ObjectType *>(&mObject);
+  return std::launder(reinterpret_cast<ObjectType *>(&mObject));
 }
 
 template <typename ObjectType>
 const ObjectType *Optional<ObjectType>::objectAddr() const {
-  return reinterpret_cast<const ObjectType *>(&mObject);
+  return std::launder(reinterpret_cast<const ObjectType *>(&mObject));
 }
 
 }  // namespace chre
diff --git a/util/include/chre/util/pigweed/chre_channel_output.h b/util/include/chre/util/pigweed/chre_channel_output.h
index 1795262..9f815b8 100644
--- a/util/include/chre/util/pigweed/chre_channel_output.h
+++ b/util/include/chre/util/pigweed/chre_channel_output.h
@@ -17,11 +17,12 @@
 #ifndef CHRE_CHANNEL_OUTPUT_H_
 #define CHRE_CHANNEL_OUTPUT_H_
 
-#include <span>
+#include <cstdint>
 
-#include <chre.h>
-
+#include "chre/util/pigweed/permission.h"
+#include "chre_api/chre.h"
 #include "pw_rpc/channel.h"
+#include "pw_span/span.h"
 
 namespace chre {
 
@@ -31,7 +32,7 @@
  */
 struct ChrePigweedNanoappMessage {
   size_t msgSize;
-  uint8_t msg[];
+  void *msg;
 };
 
 /**
@@ -45,53 +46,79 @@
   // to not conflict with other CHRE messages the nanoapp and client may send.
   static constexpr uint32_t PW_RPC_CHRE_HOST_MESSAGE_TYPE = INT32_MAX - 10;
 
-  // Random value chosen to be towards the end of the nanoapp event type region
+  // Random values chosen to be towards the end of the nanoapp event type region
   // so it doesn't conflict with existing nanoapp messages that can be sent.
-  static constexpr uint16_t PW_RPC_CHRE_NAPP_EVENT_TYPE = UINT16_MAX - 10;
+  static constexpr uint16_t PW_RPC_CHRE_NAPP_REQUEST_EVENT_TYPE =
+      UINT16_MAX - 10;
+  static constexpr uint16_t PW_RPC_CHRE_NAPP_RESPONSE_EVENT_TYPE =
+      UINT16_MAX - 9;
 
   size_t MaximumTransmissionUnit() override;
 
  protected:
   ChreChannelOutputBase();
-
-  /**
-   * Sets the endpoint ID that the message should be sent to.
-   *
-   * @param endpointId Either a host endpoint ID or nanoapp instance ID
-   *     corresponding to the endpoint that should receive messages sent through
-   *     this channel output.
-   */
-  void setEndpointId(uint16_t endpointId);
-
-  uint16_t mEndpointId = CHRE_HOST_ENDPOINT_UNSPECIFIED;
 };
 
 /**
- * Channel output that must be used if the channel is between two nanoapps.
+ * Channel output that must be used on the server side of the channel between
+ * two nanoapps.
  */
-class ChreNanoappChannelOutput : public ChreChannelOutputBase {
+class ChreServerNanoappChannelOutput : public ChreChannelOutputBase {
  public:
+  explicit ChreServerNanoappChannelOutput(RpcPermission &permission)
+      : mPermission(permission) {}
   /**
    * Sets the nanoapp instance ID that is being communicated with over this
    * channel output.
    */
-  void setNanoappEndpoint(uint32_t nanoappInstanceId);
+  void setClient(uint32_t nanoappInstanceId);
 
-  pw::Status Send(std::span<const std::byte> buffer) override;
+  pw::Status Send(pw::span<const std::byte> buffer) override;
+
+ private:
+  uint16_t mClientInstanceId = 0;
+  RpcPermission &mPermission;
+};
+
+/**
+ * Channel output that must be used on the client side of the channel between
+ * two nanoapps.
+ */
+class ChreClientNanoappChannelOutput : public ChreChannelOutputBase {
+ public:
+  /**
+   * Sets the server instance ID.
+   *
+   * This method must only be called for clients.
+   *
+   * @param instanceId The instance ID of the server.
+   */
+  void setServer(uint32_t instanceId);
+
+  pw::Status Send(pw::span<const std::byte> buffer) override;
+
+ private:
+  uint16_t mServerInstanceId = 0;
 };
 
 /**
  * Channel output that must be used if the channel is between a nanoapp and
  * host client.
  */
-class ChreHostChannelOutput : public ChreChannelOutputBase {
+class ChreServerHostChannelOutput : public ChreChannelOutputBase {
  public:
+  explicit ChreServerHostChannelOutput(RpcPermission &permission)
+      : mPermission(permission) {}
   /**
    * Sets the host endpoint being communicated with.
    */
   void setHostEndpoint(uint16_t hostEndpoint);
 
-  pw::Status Send(std::span<const std::byte> buffer) override;
+  pw::Status Send(pw::span<const std::byte> buffer) override;
+
+ private:
+  uint16_t mEndpointId = CHRE_HOST_ENDPOINT_UNSPECIFIED;
+  RpcPermission &mPermission;
 };
 
 }  // namespace chre
diff --git a/util/include/chre/util/pigweed/permission.h b/util/include/chre/util/pigweed/permission.h
new file mode 100644
index 0000000..4757fff
--- /dev/null
+++ b/util/include/chre/util/pigweed/permission.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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_PIGWEED_PERMISSION_H_
+#define CHRE_UTIL_PIGWEED_PERMISSION_H_
+
+#include <cstdint>
+#include <optional>
+
+#include "chre/util/nanoapp/assert.h"
+#include "chre/util/non_copyable.h"
+#include "chre/util/optional.h"
+#include "chre_api/chre.h"
+
+namespace chre {
+
+/**
+ * Holds the permission for the next message sent by a server.
+ */
+class RpcPermission : public NonCopyable {
+ public:
+  void set(uint32_t permission) {
+    mPermission = permission;
+  }
+
+  uint32_t getAndReset() {
+    CHRE_ASSERT(mPermission.has_value());
+    uint32_t permission = mPermission.has_value()
+                              ? mPermission.value()
+                              : CHRE_MESSAGE_PERMISSION_NONE;
+    mPermission.reset();
+    return permission;
+  }
+
+ private:
+  /** Bitmasked CHRE_MESSAGE_PERMISSION_ */
+  Optional<uint32_t> mPermission;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_PIGWEED_PERMISSION_H_
\ No newline at end of file
diff --git a/util/include/chre/util/pigweed/rpc_client.h b/util/include/chre/util/pigweed/rpc_client.h
new file mode 100644
index 0000000..17756dc
--- /dev/null
+++ b/util/include/chre/util/pigweed/rpc_client.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 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_PIGWEED_RPC_CLIENT_H_
+#define CHRE_UTIL_PIGWEED_RPC_CLIENT_H_
+
+#include <cstdint>
+
+#include "chre/util/non_copyable.h"
+#include "chre/util/optional.h"
+#include "chre/util/pigweed/chre_channel_output.h"
+#include "chre/util/unique_ptr.h"
+#include "chre_api/chre.h"
+#include "pw_rpc/client.h"
+#include "pw_span/span.h"
+#include "rpc_helper.h"
+
+namespace chre {
+
+/**
+ * RPC Client wrapping a Pigweed RPC client.
+ *
+ * This helper class handles Pigweed RPC calls on the client side.
+ *
+ * The `handleEvent` method must be called at the beginning of the
+ * `nanoappHandleEvent` function to handle RPC responses from the server.
+ */
+class RpcClient : public NonCopyable {
+ public:
+  /**
+   * @param serverNanoappId Nanoapp ID of the server.
+   */
+  explicit RpcClient(uint64_t serverNanoappId)
+      : mServerNanoappId((serverNanoappId)) {}
+
+  ~RpcClient() {
+    chreConfigureNanoappInfoEvents(false);
+  }
+
+  /**
+   * Handles events related to RPC services.
+   *
+   * Handles the following events:
+   * - PW_RPC_CHRE_NAPP_RESPONSE_EVENT_TYPE: handle the server responses,
+   * - CHRE_EVENT_NANOAPP_STOPPED: close the channel when the server nanoapp
+   *   terminates.
+   *
+   * @param senderInstanceId The Instance ID for the source of this event.
+   * @param eventType The event type.
+   * @param eventData The associated data, if any, for this specific type of
+   *                  event.
+   * @return whether any event was handled successfully.
+   */
+  bool handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData);
+
+  /**
+   * Returns a service client.
+   *
+   * NOTE: The template parameter must be set to the Pigweed client type,
+   *       i.e. pw::rpc::pw_rpc::nanopb::<ServiceName>::Client
+
+   * @return The service client. It has no value on errors.
+   */
+  template <typename T>
+  Optional<T> get();
+
+  /**
+   * Returns whether the server nanoapp supports the service.
+   *
+   * Also returns false when the nanoapp is not loaded.
+   *
+   * @return whether the service is published by the server.
+   */
+  bool hasService(uint64_t id, uint32_t version);
+
+ private:
+  /**
+   * Handles responses from the server.
+   *
+   * This method must be called when nanoapps receive a
+   * PW_RPC_CHRE_NAPP_RESPONSE_EVENT_TYPE event.
+   *
+   * @param senderInstanceId The Instance ID for the source of this event.
+   * @param eventData  The associated data, if any.
+   * @return whether the RPC was handled successfully.
+   */
+  bool handleMessageFromServer(uint32_t senderInstanceId,
+                               const void *eventData);
+
+  /**
+   * Closes the Pigweed channel when the server terminates.
+   *
+   * @param notification The eventData associated to a
+   *    CHRE_EVENT_NANOAPP_STOPPED event.
+   */
+  void handleNanoappStopped(const void *eventData);
+
+  ChreClientNanoappChannelOutput mChannelOutput;
+  pw::rpc::Client mRpcClient;
+  uint64_t mServerNanoappId;
+  uint32_t mChannelId = 0;
+};
+
+template <typename T>
+Optional<T> RpcClient::get() {
+  if (mChannelId == 0) {
+    struct chreNanoappInfo info;
+
+    if (!chreGetNanoappInfoByAppId(mServerNanoappId, &info) ||
+        info.instanceId > kRpcNanoappMaxId) {
+      return Optional<T>();
+    }
+
+    mChannelId = chreGetInstanceId();
+    mChannelOutput.setServer(info.instanceId);
+    mRpcClient.OpenChannel(mChannelId, mChannelOutput);
+  }
+
+  chreConfigureNanoappInfoEvents(true);
+  return T(mRpcClient, mChannelId);
+}
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_PIGWEED_RPC_SERVER_H_
\ No newline at end of file
diff --git a/util/include/chre/util/pigweed/rpc_helper.h b/util/include/chre/util/pigweed/rpc_helper.h
new file mode 100644
index 0000000..032693a
--- /dev/null
+++ b/util/include/chre/util/pigweed/rpc_helper.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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_PIGWEED_RPC_HELPER_H_
+#define CHRE_UTIL_PIGWEED_RPC_HELPER_H_
+
+#include <cstdint>
+
+#include "chre_api/chre.h"
+
+namespace chre {
+
+/** The upper 16b of a channel ID are set to 1 for host clients. */
+constexpr uint32_t kChannelIdHostClient = 1 << 16;
+
+/** Mask to extract the host ID / nanoapp ID from a channel ID. */
+constexpr uint32_t kRpcClientIdMask = 0xffff;
+
+/** Maximum ID for a nanoapp as the value is encoded on 16b. */
+constexpr uint32_t kRpcNanoappMaxId = 0xffff;
+
+/**
+ * Returns whether the endpoint matches.
+ *
+ * Only the lower 16 bits are considered.
+ *
+ * @param expectedId Expected channel ID.
+ * @param actualId Actual channel ID.
+ * @return whether the nanoapp IDs match (lower 16b).
+ */
+bool rpcEndpointsMatch(uint32_t expectedId, uint32_t actualId);
+
+/**
+ * @param id Channel ID.
+ * @return whether the channel ID is a host client.
+ */
+bool isRpcChannelIdHostClient(uint32_t id);
+
+/**
+ * @param id Channel ID.
+ * @return whether the channel ID is a nanoapp client.
+ */
+bool isRpcChannelIdNanoappClient(uint32_t id);
+
+/**
+ * Validates that the host client sending the message matches the expected
+ * channel ID.
+ *
+ * @param msg Message received from the host client.
+ * @param channelId Channel ID extracted from the received packet.
+ * @return Whether the IDs match.
+ */
+bool validateHostChannelId(const chreMessageFromHostData *msg,
+                           uint32_t channelId);
+
+/**
+ * Validates that the nanoapp sending the message matches the expected
+ * channel ID.
+ *
+ * @param senderInstanceId ID of the nanoapp sending the message.
+ * @param channelId Channel ID extracted from the received packet.
+ * @return Whether the IDs match.
+ */
+bool validateNanoappChannelId(uint32_t nappId, uint32_t channelId);
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_PIGWEED_RPC_HELPER_H_
diff --git a/util/include/chre/util/pigweed/rpc_server.h b/util/include/chre/util/pigweed/rpc_server.h
new file mode 100644
index 0000000..df02621
--- /dev/null
+++ b/util/include/chre/util/pigweed/rpc_server.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 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_PIGWEED_RPC_SERVER_H_
+#define CHRE_UTIL_PIGWEED_RPC_SERVER_H_
+
+#include "chre/util/dynamic_vector.h"
+#include "chre/util/macros.h"
+#include "chre/util/non_copyable.h"
+#include "chre/util/pigweed/chre_channel_output.h"
+#include "chre/util/pigweed/permission.h"
+#include "pw_rpc/server.h"
+#include "pw_rpc/service.h"
+#include "pw_span/span.h"
+
+namespace chre {
+
+/**
+ * RPC Server wrapping a Pigweed RPC server.
+ *
+ * This helper class handles Pigweed RPC calls on the server side.
+ *
+ * The services must be registered from the `nanoappStart` function using
+ * the `registerService` method.
+ *
+ * The `handleEvent` method must be called at the beginning of the
+ * `nanoappHandleEvent` function to respond to RPC calls from the clients.
+ */
+class RpcServer : public NonCopyable {
+ public:
+  struct Service {
+    /** A Pigweed service. */
+    pw::rpc::Service &service;
+    /**
+     * The ID of the service, it must be generated according to RFC 4122, UUID
+     * version 4 (random). This ID must be unique within a given nanoapp.
+     */
+    uint64_t id;
+
+    /**
+     * The version of the service. It should be in sync with the version on the
+     * client side.
+     */
+    uint32_t version;
+  };
+
+  RpcServer() : mHostOutput(mPermission), mNanoappOutput(mPermission) {}
+  ~RpcServer();
+
+  /**
+   * Registers services to the server and to CHRE.
+   *
+   * This method must be called from `nanoappStart`.
+   *
+   * Although nanoapps are recommended to only call this API once with all
+   * services it intends to publish, if it is called multiple times, each
+   * call will append to the list of published services.
+   *
+   * @param numServices The number of services to register.
+   * @param services Service definitions.
+   * @return whether the registration was successful.
+   */
+  bool registerServices(size_t numServices, Service *services);
+
+  /**
+   * Sets the permission for the next message to a client.
+   *
+   * While the permission are only actually used for messages to host clients,
+   * the value passed here only applies to the next message whether the client
+   * is a host client or a nanoapp.
+   *
+   * This method must be called:
+   * - from the body of a service implementation method in the case of unary
+   *   RPCs,
+   * - right before invoking ServerReader.Finish for client streaming and
+   *   bidirectional streaming RPCs,
+   * - right before invoking ServerWriter.Write and ServerWriter.Finish for
+   *   server streaming and bidirectional streaming RPCs.
+   *
+   * @params permission Bitmasked CHRE_MESSAGE_PERMISSION_.
+   *
+   * @see chreSendMessageWithPermissions
+   */
+  void setPermissionForNextMessage(uint32_t permission);
+
+  /**
+   * Handles events related to RPC services.
+   *
+   * Handles the following events:
+   * - CHRE_EVENT_MESSAGE_FROM_HOST: respond to host RPC requests,
+   * - PW_RPC_CHRE_NAPP_REQUEST_EVENT_TYPE: respond to nanoapp RPC requests,
+   * - CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION: close the channel when the host
+   *   terminates,
+   * - CHRE_EVENT_NANOAPP_STOPPED: close the channel when a nanoapp
+   *   terminates.
+   *
+   * @param senderInstanceId The Instance ID for the source of this event.
+   * @param eventType The event type.
+   * @param eventData The associated data, if any, for this specific type of
+   *                  event.
+   * @return whether any event was handled successfully.
+   */
+  bool handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                   const void *eventData);
+
+ private:
+  /**
+   * Handles messages from host clients.
+   *
+   * This method must be called when nanoapps receive a
+   * CHRE_EVENT_MESSAGE_FROM_HOST event.
+   *
+   * @param eventData  The associated data, if any.
+   * @return whether the RPC was handled successfully.
+   */
+  bool handleMessageFromHost(const void *eventData);
+
+  /**
+   * Handles messages from nanoapp clients.
+   *
+   * This method must be called when nanoapps receive a
+   * PW_RPC_CHRE_NAPP_REQUEST_EVENT_TYPE event.
+   *
+   * @param eventData  The associated data, if any.
+   * @return whether the RPC was handled successfully.
+   */
+  bool handleMessageFromNanoapp(uint32_t senderInstanceId,
+                                const void *eventData);
+
+  /**
+   * Closes the Pigweed channel when a host client disconnects.
+   *
+   * @param notification The notification from the host client
+   */
+  void handleHostClientNotification(const void *eventData);
+
+  /**
+   * Closes the Pigweed channel when a nanoapp client disconnects.
+   *
+   * @param notification The eventData associated to a
+   *    CHRE_EVENT_NANOAPP_STOPPED event.
+   */
+  void handleNanoappStopped(const void *eventData);
+
+  /**
+   * Closes a Pigweed channel.
+   *
+   * @param id The channel ID.
+   * @return either an ok or not found status if the channel was not opened.
+   */
+  pw::Status closeChannel(uint32_t id);
+
+  // Permission for the next message sent by mServer.
+  RpcPermission mPermission;
+
+  pw::rpc::Server mServer;
+
+  ChreServerHostChannelOutput mHostOutput;
+  ChreServerNanoappChannelOutput mNanoappOutput;
+
+  // Host endpoints for the connected clients.
+  DynamicVector<uint16_t> mConnectedHosts;
+};
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_PIGWEED_RPC_SERVER_H_
\ No newline at end of file
diff --git a/util/include/chre/util/raw_storage.h b/util/include/chre/util/raw_storage.h
new file mode 100644
index 0000000..902770c
--- /dev/null
+++ b/util/include/chre/util/raw_storage.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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_RAW_STORAGE_H_
+#define CHRE_UTIL_RAW_STORAGE_H_
+
+#include <new>
+#include <type_traits>
+
+#include "chre/util/non_copyable.h"
+
+namespace chre {
+
+/**
+ * A simple wrapper around std::aligned_storage that provides a region of
+ * uninitialized memory suitable for storing an array of objects, with some
+ * convenience wrappers for constructing and accessing elements.
+ *
+ * This wrapper does not keep track of which indices contain active elements and
+ * therefore does not handle invoking the destructor - this is the
+ * responsibility of the code using it. Therefore this class is geared towards
+ * usage within another data structure, e.g. chre::ArrayQueue.
+ */
+template <typename ElementType, size_t kCapacity>
+class RawStorage : public NonCopyable {
+ public:
+  size_t capacity() const {
+    return kCapacity;
+  }
+
+  ElementType *data() {
+    return std::launder(reinterpret_cast<ElementType *>(mStorage));
+  }
+  const ElementType *data() const {
+    return std::launder(reinterpret_cast<const ElementType *>(mStorage));
+  }
+
+  ElementType &operator[](size_t index) {
+    return data()[index];
+  }
+  const ElementType &operator[](size_t index) const {
+    return data()[index];
+  }
+
+ private:
+  //! To avoid static initialization of members, std::aligned_storage is used.
+  std::aligned_storage_t<sizeof(ElementType), alignof(ElementType)>
+      mStorage[kCapacity];
+};
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_RAW_STORAGE_H_
\ No newline at end of file
diff --git a/util/include/chre/util/segmented_queue.h b/util/include/chre/util/segmented_queue.h
new file mode 100644
index 0000000..d3d7fb5
--- /dev/null
+++ b/util/include/chre/util/segmented_queue.h
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2022 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_SEGMENTED_QUEUE_H_
+#define CHRE_UTIL_SEGMENTED_QUEUE_H_
+
+#include <type_traits>
+#include <utility>
+
+#include "chre/util/dynamic_vector.h"
+#include "chre/util/non_copyable.h"
+#include "chre/util/raw_storage.h"
+#include "chre/util/unique_ptr.h"
+
+namespace chre {
+/**
+ * Data structure that is similar to chre::ArrayQueue but with the ability to
+ * expand dynamically. Also has segmented data storage to prevent heap
+ * fragmentation.
+ *
+ * Note that this data structure allocates storage dynamically and might need
+ * to move elements around during push_back(). It is important for ElementType
+ * to have a efficient move operator
+ *
+ * @tparam ElementType: The type of element for this SegmentedQueue to store.
+ * @tparam kBlockSize: The size of one block.
+ */
+template <typename ElementType, size_t kBlockSize>
+class SegmentedQueue : public NonCopyable {
+  using Block = ::chre::RawStorage<ElementType, kBlockSize>;
+  using BlockPtr = UniquePtr<Block>;
+  static_assert(kBlockSize > 0);
+
+ public:
+  /**
+   * Construct a new Segmented Queue object.
+   *
+   * @param maxBlockCount: The maximum number of block that this queue can hold.
+   * @param staticBlockCount the number of blocks that will be allocate in the
+   * constructor and will only be deallocate by the destructor. Needs to be
+   * bigger than zero to avoid thrashing
+   */
+  SegmentedQueue(size_t maxBlockCount, size_t staticBlockCount = 1);
+
+  ~SegmentedQueue();
+
+  /**
+   * @return size_t: Number of elements that this segmented queue holds.
+   */
+  size_t size() {
+    return mSize;
+  }
+
+  /**
+   * @return size_t: How many blocks does this segmented queue contains.
+   */
+  size_t block_count() {
+    return mRawStoragePtrs.size();
+  }
+
+  /**
+   * @return size_t: Number of items that this queue can store without pushing
+   * new blocks.
+   */
+  size_t capacity() {
+    return mRawStoragePtrs.size() * kBlockSize;
+  }
+
+  /**
+   * @return true: Return true if the segmented queue cannot accept new element.
+   */
+  bool full() {
+    return mSize == kMaxBlockCount * kBlockSize;
+  }
+
+  /**
+   * @return true: Return true if this segmented queue does not have any element
+   * stored.
+   */
+  bool empty() const {
+    return mSize == 0;
+  };
+
+  /**
+   * Push a element to the end of the segmented queue.
+   *
+   * @param element: The element that will be push to the back of the queue.
+   * @return false: Return false if the queue is full.
+   */
+  bool push_back(const ElementType &element);
+  bool push_back(ElementType &&element);
+
+  /**
+   * Provide the same push API like std::queue/chre::ArrayQueue that do
+   * push_back().
+   *
+   * @param element: The element that will be push to the back of the queue.
+   * @return false: Return false if the queue is full.
+   */
+  bool push(const ElementType &element);
+  bool push(ElementType &&element);
+
+  /**
+   * Constructs an element onto the back of the segmented queue.
+   *
+   * @param Arguments to the constructor of ElementType.
+   * @return: Return true if the element is constructed successfully.
+   */
+  template <typename... Args>
+  bool emplace_back(Args &&...args);
+
+  /**
+   * Obtains an element of the queue by its index.
+   * It is illegal to use a index that is bigger or equal to the size of the
+   * queue.
+   *
+   * @param index: Requested index in range [0, size()-1].
+   * @return ElementType&: Reference to the element.
+   */
+  ElementType &operator[](size_t index);
+  const ElementType &operator[](size_t index) const;
+
+  /**
+   * Obtain the last element in the queue.
+   * It is illegal to call this function when empty() == true.
+   *
+   * @return ElementType&: Reference to the last element.
+   */
+  ElementType &back();
+  const ElementType &back() const;
+
+  /**
+   * Obtain the first element in the queue.
+   * It is illegal to call this function when empty() == true.
+   *
+   * @return ElementType&: Reference to the first element.
+   */
+  ElementType &front();
+  const ElementType &front() const;
+
+  /**
+   * Remove the first element from the queue.
+   * It is illegal to call this function when empty() == true.
+   */
+  void pop_front();
+
+  /**
+   * Provide the same pop API like std::queue/chre::ArrayQueue that do
+   * pop_front(). It is illegal to call this function when empty() == true.
+   */
+  void pop();
+
+  /**
+   * Removes an element from the queue by given index.
+   *
+   * @param index: Index of the item that will be removed.
+   * @return false: Returns false if index >= size().
+   */
+  bool remove(size_t index);
+
+  /**
+   * Function used to decide if an element in the queue matches a certain
+   * condition.
+   *
+   * @see removeMatchesFromBack and searchMatches.
+   */
+  using MatchingFunction =
+      typename std::conditional<std::is_pointer<ElementType>::value ||
+                                    std::is_fundamental<ElementType>::value,
+                                bool(ElementType), bool(ElementType &)>::type;
+
+  using FreeFunction =
+      typename std::conditional<std::is_pointer<ElementType>::value ||
+                                    std::is_fundamental<ElementType>::value,
+                                void(ElementType, void *),
+                                void(ElementType &, void *)>::type;
+  /**
+   * Removes maxNumOfElementsRemoved of elements that satisfies matchFunc.
+   *
+   * If the queue has fewer items that matches the condition than
+   * maxNumOfElementsRemoved, it will remove all matching items and return the
+   * number of items that it actually removed.
+   *
+   * @param matchFunc                Function used to decide if an item should
+   *                                 be removed. Should return true if this
+   *                                 item needs to be removed.
+   * @param maxNumOfElementsRemoved  Number of elements to remove, also the
+   *                                 size of removedElements. It is not
+   *                                 guaranteed that the actual number of items
+   *                                 removed will equal to this parameter since
+   *                                 it will depend on the number of items that
+   *                                 matches the condition.
+   * @param removedElements          Stores the pointers that has been
+   *                                 removed. This cannot be a nullptr.
+   * @param freeFunction             Function to execute before the matched item
+   *                                 is removed. If not supplied, the destructor
+   *                                 of the element will be invoked.
+   * @param extraDataForFreeFunction  Additional data that freeFunction will
+   *                                 need.
+   *
+   * @return                         The number of pointers that is passed
+   *                                 out. Returns SIZE_MAX if removedElement is
+   *                                 a nullptr as error.
+   */
+  size_t removeMatchedFromBack(MatchingFunction *matchFunction,
+                               size_t maxNumOfElementsRemoved,
+                               FreeFunction *freeFunction = nullptr,
+                               void *extraDataForFreeFunction = nullptr);
+
+ private:
+  /**
+   * Push a new block to the end of storage to add storage space.
+   * The total block count after push cannot exceed kMaxBlockCount.
+   *
+   * @return true: Return true if a new block can be added.
+   */
+  bool pushOneBlock();
+
+  /**
+   * Insert one block to the underlying storage.
+   * The total block count after push cannot exceed kMaxBlockCount.
+   *
+   * @param blockIndex: The index to insert a block at.
+   * @return true: Return true if a new block can be added.
+   */
+  bool insertBlock(size_t blockIndex);
+
+  /**
+   * Move count number of elements from srcIndex to destIndex.
+   * Note that index here refers to absolute index that starts from the head of
+   * the DynamicVector.
+   *
+   * @param srcIndex: The index of the first element to be moved.
+   * @param destIndex: The index of the destination to place the first moved
+   * element.
+   * @param count: Number of element to move.
+   */
+
+  void moveElements(size_t srcIndex, size_t destIndex, size_t count);
+
+  /**
+   * Move a movable item from srcIndex to destIndex. Note that index here refers
+   * to absolute index that starts from the head of the DynamicVector.
+   *
+   * @param srcIndex: Index to the block that has the source element.
+   * @param destIndex: Index to the start of the destination block.
+   */
+  void doMove(size_t srcIndex, size_t destIndex, std::true_type);
+
+  /**
+   * Move a non-movable item from srcIndex to destIndex. Note that index here
+   * refers to absolute index that starts from the head of the DynamicVector.
+   *
+   * @param srcIndex: Index to the block that has the source element.
+   * @param destIndex: Index to the start of the destination block.
+   */
+  void doMove(size_t srcIndex, size_t destIndex, std::false_type);
+
+  /**
+   * Calculate the index with respect to mHead to absolute index with respect to
+   * the start of the storage dynamic vector.
+   *
+   * @param index: Relative index in the range [0, mSize - 1].
+   * @return size_t: The offset index in range [0, capacity() - 1].
+   */
+  size_t relativeIndexToAbsolute(size_t index);
+
+  /**
+   * Prepare push by pushing new blocks if needed and update mTail to point at
+   * the right index.
+   *
+   * @return false: Return false if the queue is already full.
+   */
+  bool prepareForPush();
+
+  /**
+   * Add 1 to the index if index is not at the end of the data storage. If so,
+   * wraps around to 0.
+   *
+   * @param index: Original index.
+   * @return size_t: New index after calculation.
+   */
+  size_t advanceOrWrapAround(size_t index);
+
+  /**
+   * Subtract k steps to the index and wrap around if needed.
+   *
+   * @param index Original index.
+   * @param steps Number of steps that it needs to be subtracted.
+   * @return      New index after calculation.
+   */
+  size_t subtractOrWrapAround(size_t index, size_t steps);
+
+  /**
+   * Locate the data reference by absolute index.
+   * Note that this function does not check if the address belongs to
+   * this queue.
+   *
+   * @param index: The absolute index to find that data.
+   * @return ElementType&: Reference to the data.
+   */
+  ElementType &locateDataAddress(size_t index);
+
+  /**
+   * Removes all the elements of the queue.
+   */
+  void clear();
+
+  /**
+   * Remove and destroy an object by the given index.
+   * Note that this function does not change any pointer nor fill the gap
+   * after removing.
+   *
+   * @param index: The absolute index for the item that will be removed.
+   */
+  void doRemove(size_t index);
+
+  /**
+   * Calculate the index with respect to the start of the storage to relevant
+   * index with respect to mHead.
+   *
+   * @param index: Absolute index in the range [0, capacity() - 1].
+   * @return size_t: Relative index in the range [0, mSize - 1].
+   */
+  size_t absoluteIndexToRelative(size_t index);
+
+  /**
+   * Resets the current queue to its initial state if the queue is empty.
+   * It is illegal to call this function if the queue is not empty.
+   */
+  void resetEmptyQueue();
+
+  /**
+   * Search the queue backwards to find foundIndicesLen of elements that matches
+   * a certain condition and return them by foundIndices. If the queue does not
+   * have enough elements, foundIndices will only be filled with the number that
+   * matches the condition.
+   *
+   * @param matchFunc          Function used to decide if an item should be
+   *                           returned. Should return true if this item need
+   *                           to be returned.
+   * @param foundIndicesLen    Length of foundIndices indicating how many index
+   *                           is targeted.
+   * @param foundIndices       Indices that contains the element that matches
+   *                           the condition. Note that the index is
+   *                           returned in reversed order, i.e. the first
+   *                           element will contain the index closest to the
+   *                           end.
+   * @return                   the number of element that matches.
+   */
+  size_t searchMatches(MatchingFunction *matchFunc, size_t foundIndicesLen,
+                       size_t foundIndices[]);
+
+  /**
+   * Move elements in this queue to fill the gaps.
+   *
+   * @param gapCount   Number of holes.
+   * @param gapIndices Indices that are empty. Need to be reverse order (first
+   *                   index is closest to the end of the queue).
+   */
+  void fillGaps(size_t gapCount, const size_t gapIndices[]);
+
+  // TODO(b/258771255): See if we can change the container to
+  // ArrayQueue<UniquePtr<Block>> to minimize block moving during push_back.
+  //! The data storage of this segmented queue.
+  DynamicVector<UniquePtr<Block>> mRawStoragePtrs;
+
+  //! Records how many items are in this queue.
+  size_t mSize = 0;
+
+  //! The maximum block count this queue can hold.
+  const size_t kMaxBlockCount;
+
+  //! How many blocks allocated in constructor.
+  const size_t kStaticBlockCount;
+
+  //! The offset of the first element of the queue starting from the start of
+  //! the DynamicVector.
+  size_t mHead = 0;
+
+  // TODO(b/258828257): Modify initialization logic to make it work when
+  // kStaticBlockCount = 0
+  //! The offset of the last element of the queue starting from the start of the
+  //! DynamicVector. Initialize it to the end of container for a easier
+  //! implementation of push_back().
+  size_t mTail = kBlockSize * kStaticBlockCount - 1;
+};
+
+}  // namespace chre
+
+#include "chre/util/segmented_queue_impl.h"
+
+#endif  // CHRE_UTIL_SEGMENTED_QUEUE_H_
\ No newline at end of file
diff --git a/util/include/chre/util/segmented_queue_impl.h b/util/include/chre/util/segmented_queue_impl.h
new file mode 100644
index 0000000..8bd1645
--- /dev/null
+++ b/util/include/chre/util/segmented_queue_impl.h
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2022 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_SEGMENTED_QUEUE_IMPL_H
+#define CHRE_UTIL_SEGMENTED_QUEUE_IMPL_H
+
+#include <type_traits>
+#include <utility>
+
+#include "chre/util/segmented_queue.h"
+
+#include "chre/util/container_support.h"
+
+namespace chre {
+
+template <typename ElementType, size_t kBlockSize>
+SegmentedQueue<ElementType, kBlockSize>::SegmentedQueue(size_t maxBlockCount,
+                                                        size_t staticBlockCount)
+    : kMaxBlockCount(maxBlockCount), kStaticBlockCount(staticBlockCount) {
+  CHRE_ASSERT(kMaxBlockCount >= kStaticBlockCount);
+  CHRE_ASSERT(kStaticBlockCount > 0);
+  CHRE_ASSERT(kMaxBlockCount * kBlockSize < SIZE_MAX);
+  mRawStoragePtrs.reserve(kMaxBlockCount);
+  for (size_t i = 0; i < kStaticBlockCount; i++) {
+    pushOneBlock();
+  }
+}
+
+template <typename ElementType, size_t kBlockSize>
+SegmentedQueue<ElementType, kBlockSize>::~SegmentedQueue() {
+  clear();
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::push_back(
+    const ElementType &element) {
+  if (!prepareForPush()) {
+    return false;
+  }
+  new (&locateDataAddress(mTail)) ElementType(element);
+  mSize++;
+
+  return true;
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::push_back(ElementType &&element) {
+  if (!prepareForPush()) {
+    return false;
+  }
+  new (&locateDataAddress(mTail)) ElementType(std::move(element));
+  mSize++;
+
+  return true;
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::push(const ElementType &element) {
+  return push_back(element);
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::push(ElementType &&element) {
+  return push_back(std::move(element));
+}
+
+template <typename ElementType, size_t kBlockSize>
+template <typename... Args>
+bool SegmentedQueue<ElementType, kBlockSize>::emplace_back(Args &&...args) {
+  if (!prepareForPush()) {
+    return false;
+  }
+  new (&locateDataAddress(mTail)) ElementType(std::forward<Args>(args)...);
+  mSize++;
+
+  return true;
+}
+
+template <typename ElementType, size_t kBlockSize>
+ElementType &SegmentedQueue<ElementType, kBlockSize>::operator[](size_t index) {
+  CHRE_ASSERT(index < mSize);
+
+  return locateDataAddress(relativeIndexToAbsolute(index));
+}
+
+template <typename ElementType, size_t kBlockSize>
+const ElementType &SegmentedQueue<ElementType, kBlockSize>::operator[](
+    size_t index) const {
+  CHRE_ASSERT(index < mSize);
+
+  return locateDataAddress(relativeIndexToAbsolute(index));
+}
+
+template <typename ElementType, size_t kBlockSize>
+ElementType &SegmentedQueue<ElementType, kBlockSize>::back() {
+  CHRE_ASSERT(!empty());
+
+  return locateDataAddress(mTail);
+}
+
+template <typename ElementType, size_t kBlockSize>
+const ElementType &SegmentedQueue<ElementType, kBlockSize>::back() const {
+  CHRE_ASSERT(!empty());
+
+  return locateDataAddress(mTail);
+}
+
+template <typename ElementType, size_t kBlockSize>
+ElementType &SegmentedQueue<ElementType, kBlockSize>::front() {
+  CHRE_ASSERT(!empty());
+
+  return locateDataAddress(mHead);
+}
+
+template <typename ElementType, size_t kBlockSize>
+const ElementType &SegmentedQueue<ElementType, kBlockSize>::front() const {
+  CHRE_ASSERT(!empty());
+
+  return locateDataAddress(mHead);
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::pop_front() {
+  CHRE_ASSERT(!empty());
+
+  doRemove(mHead);
+
+  if (mSize == 0) {
+    // TODO(b/258860898), Define a more proactive way to deallocate unused
+    // block.
+    resetEmptyQueue();
+  } else {
+    mHead = advanceOrWrapAround(mHead);
+  }
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::pop() {
+  pop_front();
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::remove(size_t index) {
+  if (index >= mSize) {
+    return false;
+  }
+  size_t absoluteIndex = relativeIndexToAbsolute(index);
+  doRemove(absoluteIndex);
+  if (mSize == 0) {
+    resetEmptyQueue();
+  } else {
+    // TODO(b/258557394): optimize by adding check to see if pull from head
+    // to tail is more efficient.
+    moveElements(advanceOrWrapAround(absoluteIndex), absoluteIndex,
+                 absoluteIndexToRelative(mTail) - index);
+    mTail = subtractOrWrapAround(mTail, /* steps= */ 1);
+  }
+  return true;
+}
+
+template <typename ElementType, size_t kBlockSize>
+size_t SegmentedQueue<ElementType, kBlockSize>::searchMatches(
+    MatchingFunction *matchFunc, size_t foundIndicesLen,
+    size_t foundIndices[]) {
+  size_t foundCount = 0;
+  size_t searchIndex = advanceOrWrapAround(mTail);
+  bool firstRound = true;
+
+  if (size() == 0) {
+    return 0;
+  }
+
+  // firstRound need to be checked since if the queue is full, the index after
+  // mTail will be mHead, leading to the loop falsely terminate in the first
+  // round.
+  while ((searchIndex != mHead || firstRound) &&
+         foundCount != foundIndicesLen) {
+    searchIndex = subtractOrWrapAround(searchIndex, 1 /* steps */);
+    firstRound = false;
+    if (matchFunc(locateDataAddress(searchIndex))) {
+      foundIndices[foundCount] = searchIndex;
+      ++foundCount;
+    }
+  }
+  return foundCount;
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::fillGaps(
+    size_t gapCount, const size_t gapIndices[]) {
+  if (gapCount == 0) {
+    return;
+  }
+
+  // TODO(b/264326627): Check if gapIndices is reverse order.
+  // TODO(b/264326627): Give a detailed explanation (example)\.
+  // Move the elements between each gap indices section by section from the
+  // section that is closest to the head. The destination index = the gap index
+  // - how many gaps has been filled.
+  for (size_t i = gapCount - 1; i > 0; --i) {
+    moveElements(advanceOrWrapAround(gapIndices[i]),
+                 subtractOrWrapAround(gapIndices[i], gapCount - 1 - i),
+                 absoluteIndexToRelative(gapIndices[i - 1]) -
+                     absoluteIndexToRelative(gapIndices[i]) - 1);
+  }
+
+  // Since mTail is not guaranteed to be a gap, we need to make a special case
+  // for moving the last section.
+  moveElements(
+      advanceOrWrapAround(gapIndices[0]),
+      subtractOrWrapAround(gapIndices[0], gapCount - 1),
+      absoluteIndexToRelative(mTail) - absoluteIndexToRelative(gapIndices[0]));
+  mTail = subtractOrWrapAround(mTail, gapCount);
+}
+
+template <typename ElementType, size_t kBlockSize>
+size_t SegmentedQueue<ElementType, kBlockSize>::removeMatchedFromBack(
+    MatchingFunction *matchFunc, size_t maxNumOfElementsRemoved,
+    FreeFunction *freeFunction, void *extraDataForFreeFunction) {
+  size_t removeIndex[maxNumOfElementsRemoved];
+  size_t removedItemCount =
+      searchMatches(matchFunc, maxNumOfElementsRemoved, removeIndex);
+
+  if (removedItemCount != 0) {
+    for (size_t i = 0; i < removedItemCount; ++i) {
+      if (freeFunction == nullptr) {
+        doRemove(removeIndex[i]);
+      } else {
+        --mSize;
+        freeFunction(locateDataAddress(removeIndex[i]),
+                     extraDataForFreeFunction);
+      }
+    }
+
+    if (mSize == 0) {
+      resetEmptyQueue();
+    } else {
+      fillGaps(removedItemCount, removeIndex);
+    }
+  }
+
+  return removedItemCount;
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::pushOneBlock() {
+  return insertBlock(mRawStoragePtrs.size());
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::insertBlock(size_t blockIndex) {
+  // Supporting inserting at any index since we started this data structure as
+  // std::deque and would like to support push_front() in the future. This
+  // function should not be needed once b/258771255 is implemented.
+  CHRE_ASSERT(mRawStoragePtrs.size() != kMaxBlockCount);
+  bool success = false;
+
+  Block *newBlockPtr = static_cast<Block *>(memoryAlloc(sizeof(Block)));
+  if (newBlockPtr != nullptr) {
+    success = mRawStoragePtrs.insert(blockIndex, UniquePtr(newBlockPtr));
+    if (success) {
+      if (!empty() && mHead >= blockIndex * kBlockSize) {
+        // If we are inserting a block before the current mHead, we need to
+        // increase the offset since we now have a new gap from mHead to the
+        // head of the container.
+        mHead += kBlockSize;
+      }
+
+      // If mTail is after the new block, we want to move mTail to make sure
+      // that the queue is continuous.
+      if (mTail >= blockIndex * kBlockSize) {
+        moveElements((blockIndex + 1) * kBlockSize, blockIndex * kBlockSize,
+                     (mTail + 1) % kBlockSize);
+      }
+    }
+  }
+  if (!success) {
+    LOG_OOM();
+  }
+  return success;
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::moveElements(size_t srcIndex,
+                                                           size_t destIndex,
+                                                           size_t count) {
+  // TODO(b/259281024): Make sure SegmentedQueue::moveElement() does not
+  // incorrectly overwrites elements.
+  while (count--) {
+    doMove(srcIndex, destIndex,
+           typename std::is_move_constructible<ElementType>::type());
+    srcIndex = advanceOrWrapAround(srcIndex);
+    destIndex = advanceOrWrapAround(destIndex);
+  }
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::doMove(size_t srcIndex,
+                                                     size_t destIndex,
+                                                     std::true_type) {
+  new (&locateDataAddress(destIndex))
+      ElementType(std::move(locateDataAddress(srcIndex)));
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::doMove(size_t srcIndex,
+                                                     size_t destIndex,
+                                                     std::false_type) {
+  new (&locateDataAddress(destIndex)) ElementType(locateDataAddress(srcIndex));
+}
+
+template <typename ElementType, size_t kBlockSize>
+size_t SegmentedQueue<ElementType, kBlockSize>::relativeIndexToAbsolute(
+    size_t index) {
+  size_t absoluteIndex = mHead + index;
+  if (absoluteIndex >= capacity()) {
+    absoluteIndex -= capacity();
+  }
+  return absoluteIndex;
+}
+
+template <typename ElementType, size_t kBlockSize>
+size_t SegmentedQueue<ElementType, kBlockSize>::absoluteIndexToRelative(
+    size_t index) {
+  if (mHead > index) {
+    index += capacity();
+  }
+  return index - mHead;
+}
+
+template <typename ElementType, size_t kBlockSize>
+bool SegmentedQueue<ElementType, kBlockSize>::prepareForPush() {
+  bool success = false;
+  if (full()) {
+    LOG_OOM();
+  } else {
+    if (mSize == capacity()) {
+      // TODO(b/258771255): index-based insert block should go away when we
+      // have a ArrayQueue based container.
+      size_t insertBlockIndex = (mTail + 1) / kBlockSize;
+      success = insertBlock(insertBlockIndex);
+    } else {
+      success = true;
+    }
+    if (success) {
+      mTail = advanceOrWrapAround(mTail);
+    }
+  }
+  return success;
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::clear() {
+  if (!std::is_trivially_destructible<ElementType>::value) {
+    while (!empty()) {
+      pop_front();
+    }
+  } else {
+    mSize = 0;
+    mHead = 0;
+    mTail = capacity() - 1;
+  }
+}
+
+template <typename ElementType, size_t kBlockSize>
+ElementType &SegmentedQueue<ElementType, kBlockSize>::locateDataAddress(
+    size_t index) {
+  return mRawStoragePtrs[index / kBlockSize].get()->data()[index % kBlockSize];
+}
+
+template <typename ElementType, size_t kBlockSize>
+size_t SegmentedQueue<ElementType, kBlockSize>::advanceOrWrapAround(
+    size_t index) {
+  return index >= capacity() - 1 ? 0 : index + 1;
+}
+
+template <typename ElementType, size_t kBlockSize>
+size_t SegmentedQueue<ElementType, kBlockSize>::subtractOrWrapAround(
+    size_t index, size_t steps) {
+  return index < steps ? capacity() + index - steps : index - steps;
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::doRemove(size_t index) {
+  mSize--;
+  locateDataAddress(index).~ElementType();
+}
+
+template <typename ElementType, size_t kBlockSize>
+void SegmentedQueue<ElementType, kBlockSize>::resetEmptyQueue() {
+  CHRE_ASSERT(empty());
+
+  while (mRawStoragePtrs.size() != kStaticBlockCount) {
+    mRawStoragePtrs.pop_back();
+  }
+  mHead = 0;
+  mTail = capacity() - 1;
+}
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_SEGMENTED_QUEUE_IMPL_H
\ No newline at end of file
diff --git a/util/include/chre/util/singleton_impl.h b/util/include/chre/util/singleton_impl.h
index 05660c0..b38c724 100644
--- a/util/include/chre/util/singleton_impl.h
+++ b/util/include/chre/util/singleton_impl.h
@@ -55,13 +55,13 @@
 
 template <typename ObjectType>
 ObjectType *Singleton<ObjectType>::get() {
-  return reinterpret_cast<ObjectType *>(&sObject);
+  return std::launder(reinterpret_cast<ObjectType *>(&sObject));
 }
 
 template <typename ObjectType>
 ObjectType *Singleton<ObjectType>::safeGet() {
   if (sIsInitialized) {
-    return reinterpret_cast<ObjectType *>(&sObject);
+    return std::launder(reinterpret_cast<ObjectType *>(&sObject));
   } else {
     return nullptr;
   }
diff --git a/util/include/chre/util/synchronized_expandable_memory_pool.h b/util/include/chre/util/synchronized_expandable_memory_pool.h
new file mode 100644
index 0000000..2fbe030
--- /dev/null
+++ b/util/include/chre/util/synchronized_expandable_memory_pool.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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_SYNCHRONIZED_EXPANDABLE_MEMORY_POOL_H_
+#define CHRE_UTIL_SYNCHRONIZED_EXPANDABLE_MEMORY_POOL_H_
+
+#include "chre/platform/mutex.h"
+#include "chre/util/fixed_size_vector.h"
+#include "chre/util/memory_pool.h"
+#include "chre/util/unique_ptr.h"
+
+namespace chre {
+
+/**
+ * This is a similar data structure to chre::SynchronizedMemoryPool with a more
+ * optimized memory usage. This data structure will allocate new storage as
+ * needed and in segments as appose to SynchronizedMemoryPool that allocates the
+ * entire storage once and as a continuous block. It will also deallocates
+ * storage once it's not used for a long time to balance memory usage and
+ * thrashing. These properties lead to a lower memory usage in average time and
+ * also prevents heap fragmentation.
+ *
+ * @tparam ElementType the element to store in ths expandable memory pool.
+ * @tparam kMemoryPoolSize the size of each element pool (each block).
+ * @tparam kMaxMemoryPoolCount the maximum number of memory blocks.
+ */
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+class SynchronizedExpandableMemoryPool : public NonCopyable {
+  using Block = ::chre::MemoryPool<ElementType, kMemoryPoolSize>;
+  static_assert(kMemoryPoolSize > 0);
+
+ public:
+  /**
+   * Construct a new Synchronized Expandable Memory Pool object.
+   * The maximum memory that this pool will allocate is maxMemoryPoolCount
+   * kMemoryPoolSize * sizeof(ElementType).
+   *
+   * @param staticBlockCount the number of blocks that will be allocate in the
+   * constructor and will only be deallocate by the destructor. Needs to be
+   * bigger than zero to avoid thrashing.
+   */
+  SynchronizedExpandableMemoryPool(size_t staticBlockCount = 1);
+
+  /**
+   * Allocates space for an object, constructs it and returns the pointer to
+   * that object. This method is thread-safe and a lock will be acquired
+   * upon entry to this method.
+   *
+   * @param  The arguments to be forwarded to the constructor of the object.
+   * @return A pointer to a constructed object or nullptr if the allocation
+   *         fails.
+   */
+  template <typename... Args>
+  ElementType *allocate(Args &&...args);
+
+  /**
+   * Releases the memory of a previously allocated element. The pointer provided
+   * here must be one that was produced by a previous call to the allocate()
+   * function. The destructor is invoked on the object. This method is
+   * thread-safe and a lock will be acquired upon entry to this method.
+   *
+   * @param A pointer to an element that was previously allocated by the
+   *        allocate() function.
+   */
+  void deallocate(ElementType *element);
+
+  /**
+   * @return the number of new element that this memory pool can add.
+   */
+  size_t getFreeSpaceCount();
+
+  /**
+   * @return size_t Return the number of blocks currently in the memory pool.
+   */
+  size_t getBlockCount();
+
+  /**
+   * @return if the memory pool is full.
+   */
+  inline bool full();
+
+ private:
+  //! Number of blocks that will be allocate in the beginning and will only be
+  //! deallocate by the destructor.
+  const size_t kStaticBlockCount;
+
+  //! Number of elements that this memory pool currently hold.
+  size_t mSize = 0;
+
+  //! The mutex used to guard access to this memory pool.
+  Mutex mMutex;
+
+  //! A fixed sized container that wraps around non-synchronized
+  //! MemoryPools used to implement this thread-safe and expandable version
+  //! version.
+  FixedSizeVector<UniquePtr<Block>, kMaxMemoryPoolCount> mMemoryPoolPtrs;
+
+  /**
+   * Push one memory pool to the end of the vector.
+   *
+   * @return true if a new memory pool can be allocated.
+   */
+  bool pushOneBlock();
+
+  /**
+   * @return true if this block is more than half full.
+   */
+  bool isHalfFullBlock(Block *block);
+};
+
+}  // namespace chre
+
+#include "chre/util/synchronized_expandable_memory_pool_impl.h"
+
+#endif  // CHRE_UTIL_SYNCHRONIZED_EXPANDABLE_MEMORY_POOL_H_
diff --git a/util/include/chre/util/synchronized_expandable_memory_pool_impl.h b/util/include/chre/util/synchronized_expandable_memory_pool_impl.h
new file mode 100644
index 0000000..8b58ce7
--- /dev/null
+++ b/util/include/chre/util/synchronized_expandable_memory_pool_impl.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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_SYNCHRONIZED_EXPANDABLE_MEMORY_POOL_IMPL_H_
+#define CHRE_UTIL_SYNCHRONIZED_EXPANDABLE_MEMORY_POOL_IMPL_H_
+
+#include "chre/util/lock_guard.h"
+#include "chre/util/memory_pool.h"
+#include "chre/util/synchronized_expandable_memory_pool.h"
+
+namespace chre {
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+SynchronizedExpandableMemoryPool<ElementType, kMemoryPoolSize,
+                                 kMaxMemoryPoolCount>::
+    SynchronizedExpandableMemoryPool(size_t staticBlockCount)
+    : kStaticBlockCount(staticBlockCount) {
+  CHRE_ASSERT(staticBlockCount > 0);
+  CHRE_ASSERT(kMaxMemoryPoolCount >= staticBlockCount);
+  for (uint8_t i = 0; i < kStaticBlockCount; i++) {
+    pushOneBlock();
+  }
+}
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+template <typename... Args>
+ElementType *SynchronizedExpandableMemoryPool<
+    ElementType, kMemoryPoolSize,
+    kMaxMemoryPoolCount>::allocate(Args &&...args) {
+  LockGuard<Mutex> lock(mMutex);
+  ElementType *result = nullptr;
+
+  // TODO(b/259286151): Optimizing using pointer to a non-full block
+  for (UniquePtr<Block> &memoryPool : mMemoryPoolPtrs) {
+    result = memoryPool->allocate(args...);
+    if (result != nullptr) {
+      break;
+    }
+  }
+
+  if (result == nullptr && pushOneBlock()) {
+    result = mMemoryPoolPtrs.back()->allocate(args...);
+  }
+
+  if (result != nullptr) {
+    ++mSize;
+  }
+
+  return result;
+}
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+void SynchronizedExpandableMemoryPool<
+    ElementType, kMemoryPoolSize,
+    kMaxMemoryPoolCount>::deallocate(ElementType *element) {
+  bool success = false;
+  LockGuard<Mutex> lock(mMutex);
+  for (UniquePtr<Block> &memoryPool : mMemoryPoolPtrs) {
+    if (memoryPool->containsAddress(element)) {
+      success = true;
+      memoryPool->deallocate(element);
+      break;
+    }
+  }
+  if (!success) {
+    CHRE_ASSERT(false);
+  } else {
+    --mSize;
+    while (
+        mMemoryPoolPtrs.size() > std::max(kStaticBlockCount, size_t(1)) &&
+        mMemoryPoolPtrs.back()->empty() &&
+        !isHalfFullBlock(mMemoryPoolPtrs[mMemoryPoolPtrs.size() - 2].get())) {
+      mMemoryPoolPtrs.pop_back();
+    }
+  }
+}
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+size_t SynchronizedExpandableMemoryPool<
+    ElementType, kMemoryPoolSize, kMaxMemoryPoolCount>::getFreeSpaceCount() {
+  LockGuard<Mutex> lock(mMutex);
+  return kMaxMemoryPoolCount * kMemoryPoolSize - mSize;
+}
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+bool SynchronizedExpandableMemoryPool<ElementType, kMemoryPoolSize,
+                                      kMaxMemoryPoolCount>::full() {
+  LockGuard<Mutex> lock(mMutex);
+  return kMaxMemoryPoolCount * kMemoryPoolSize == mSize;
+}
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+size_t SynchronizedExpandableMemoryPool<ElementType, kMemoryPoolSize,
+                                        kMaxMemoryPoolCount>::getBlockCount() {
+  LockGuard<Mutex> lock(mMutex);
+  return mMemoryPoolPtrs.size();
+}
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+bool SynchronizedExpandableMemoryPool<ElementType, kMemoryPoolSize,
+                                      kMaxMemoryPoolCount>::pushOneBlock() {
+  bool success = false;
+  if (mMemoryPoolPtrs.size() < kMaxMemoryPoolCount) {
+    auto newBlock = MakeUnique<Block>();
+    if (!newBlock.isNull()) {
+      success = true;
+      mMemoryPoolPtrs.push_back(std::move(newBlock));
+    }
+  }
+
+  if (!success) {
+    LOG_OOM();
+  }
+
+  return success;
+}
+
+template <typename ElementType, size_t kMemoryPoolSize,
+          size_t kMaxMemoryPoolCount>
+bool SynchronizedExpandableMemoryPool<
+    ElementType, kMemoryPoolSize,
+    kMaxMemoryPoolCount>::isHalfFullBlock(Block *block) {
+  return block->getFreeBlockCount() < kMemoryPoolSize / 2;
+}
+}  // namespace chre
+
+#endif  // CHRE_UTIL_SYNCHRONIZED_EXPANDABLE_MEMORY_POOL_IMPL_H_
diff --git a/util/include/chre/util/synchronized_memory_pool.h b/util/include/chre/util/synchronized_memory_pool.h
index c88e9d1..bce3886 100644
--- a/util/include/chre/util/synchronized_memory_pool.h
+++ b/util/include/chre/util/synchronized_memory_pool.h
@@ -56,6 +56,13 @@
    */
   size_t getFreeBlockCount();
 
+  /**
+   * @return true if this memory pool is full.
+   */
+  bool full() {
+    return getFreeBlockCount() == 0;
+  }
+
  private:
   //! The mutex used to guard access to this memory pool.
   Mutex mMutex;
diff --git a/util/include/chre/util/system/atomic_spsc_queue.h b/util/include/chre/util/system/atomic_spsc_queue.h
index 53c0fc0..d4c42bd 100644
--- a/util/include/chre/util/system/atomic_spsc_queue.h
+++ b/util/include/chre/util/system/atomic_spsc_queue.h
@@ -282,7 +282,7 @@
     size_t extractInternal(ElementType *dest, size_t elementsToCopy) {
       if (elementsToCopy > 0) {
         uint32_t headRaw = mQueue.mHead;
-        uint32_t headIndex = headRaw % kCapacity;
+        size_t headIndex = headRaw % kCapacity;
 
         size_t firstCopy = std::min(elementsToCopy, kCapacity - headIndex);
         uninitializedMoveOrCopy(&mQueue.data()[headIndex], firstCopy, dest);
diff --git a/util/include/chre/util/system/event_callbacks.h b/util/include/chre/util/system/event_callbacks.h
new file mode 100644
index 0000000..e72cf4b
--- /dev/null
+++ b/util/include/chre/util/system/event_callbacks.h
@@ -0,0 +1,33 @@
+/*
+ * 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_CORE_EVENT_HELPERS_H_
+#define CHRE_CORE_EVENT_HELPERS_H_
+
+#include <cstdint>
+
+namespace chre {
+
+/**
+ * Generic event free callback that can be used by any event where the event
+ * data is allocated via memoryAlloc, and no special processing is needed in the
+ * event complete callback other than freeing the event data.
+ */
+void freeEventDataCallback(uint16_t eventType, void *eventData);
+
+}  // namespace chre
+
+#endif  // CHRE_CORE_EVENT_HELPERS_H_
diff --git a/util/include/chre/util/system/stats_container.h b/util/include/chre/util/system/stats_container.h
index 5630459..d2e18d4 100644
--- a/util/include/chre/util/system/stats_container.h
+++ b/util/include/chre/util/system/stats_container.h
@@ -35,35 +35,62 @@
 
  public:
   /**
-   * Add a new value to the metric collection and update mean/max value
+   * @brief Construct a new Stats Container object
+   *
+   * @param averageWindow_ how many data stored before prioritizing new data,
+   * it should not be bigger than the default value to prevent rounding to 0
+   */
+  StatsContainer(uint32_t averageWindow_ = 512)
+      : mAverageWindow(averageWindow_){};
 
+  /**
+   * Add a new value to the metric collection and update mean/max value
+   * Mean calculated in rolling bases to prevent overflow by accumulating too
+   * much data.
+   *
+   * Before mCount reaches mAverageWindow, it calculates the normal average
+   * After mCount reaches mAverageWindow, weighted average is used to prioritize
+   * recent data where the new value always contributes 1/mAverageWindow amount
+   * to the average
    * @param value a T instance
    */
   void addValue(T value) {
-    mTotal += value;
-    ++mCount;
+    if (mCount < mAverageWindow) {
+      ++mCount;
+    }
+    mMean = (mCount - 1) * (mMean / mCount) + value / mCount;
     mMax = MAX(value, mMax);
   }
 
   /**
-   * return the average value
+   * @return the average value calculated by the description of the
+   * addValue method
    */
   T getMean() const {
-    return (mCount == 0) ? 0 : (mTotal / mCount);
+    return mMean;
   };
 
   /**
-   * return the max value
+   * @return the max value
    */
   T getMax() const {
     return mMax;
   };
 
+  /**
+   * @return the average window
+   */
+  uint32_t getAverageWindow() const {
+    return mAverageWindow;
+  }
+
  private:
-  //! Total sum of stats
-  T mTotal = 0;
-  //! Number of collections of this stat
-  uint64_t mCount = 0;
+  //! Mean of the collections of this stats
+  T mMean = 0;
+  //! Number of collections of this stats
+  uint32_t mCount = 0;
+  //! The Window that the container will not do weighted average
+  uint32_t mAverageWindow;
   //! Max of stats
   T mMax = 0;
 };
diff --git a/util/include/chre/util/system/wifi_util.h b/util/include/chre/util/system/wifi_util.h
index d1c6318..722936c 100644
--- a/util/include/chre/util/system/wifi_util.h
+++ b/util/include/chre/util/system/wifi_util.h
@@ -17,7 +17,7 @@
 #ifndef CHRE_UTIL_SYSTEM_WIFI_UTIL_H_
 #define CHRE_UTIL_SYSTEM_WIFI_UTIL_H_
 
-#include <chre.h>
+#include "chre_api/chre.h"
 
 namespace chre {
 
diff --git a/util/intrusive_list_base.cc b/util/intrusive_list_base.cc
new file mode 100644
index 0000000..f67d2a6
--- /dev/null
+++ b/util/intrusive_list_base.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/intrusive_list_base.h"
+
+#include "chre/util/container_support.h"
+
+namespace chre {
+namespace intrusive_list_internal {
+
+void IntrusiveListBase::doLinkBack(Node *newNode) {
+  Node *prevNode = mSentinelNode.prev;
+  prevNode->next = newNode;
+  newNode->prev = prevNode;
+  newNode->next = &mSentinelNode;
+  mSentinelNode.prev = newNode;
+  mSize++;
+}
+
+void IntrusiveListBase::doUnlinkNode(Node *node) {
+  node->prev->next = node->next;
+  node->next->prev = node->prev;
+  node->next = nullptr;
+  node->prev = nullptr;
+  mSize--;
+}
+
+void IntrusiveListBase::doLinkAfter(Node *frontNode, Node *newNode) {
+  Node *backNode = frontNode->next;
+  frontNode->next = newNode;
+  newNode->prev = frontNode;
+  newNode->next = backNode;
+  backNode->prev = newNode;
+  mSize++;
+}
+
+void IntrusiveListBase::doUnlinkAll() {
+  Node *currentNodePtr, *nextNodePtr;
+  currentNodePtr = mSentinelNode.next;
+
+  while (currentNodePtr != &mSentinelNode) {
+    nextNodePtr = currentNodePtr->next;
+    currentNodePtr->next = nullptr;
+    currentNodePtr->prev = nullptr;
+    currentNodePtr = nextNodePtr;
+  }
+}
+
+}  // namespace intrusive_list_internal
+}  // namespace chre
\ No newline at end of file
diff --git a/util/nanoapp/ble.cc b/util/nanoapp/ble.cc
new file mode 100644
index 0000000..725b052
--- /dev/null
+++ b/util/nanoapp/ble.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/nanoapp/ble.h"
+
+namespace chre {
+
+using ble_constants::kGoogleEddystoneUuid;
+using ble_constants::kGoogleNearbyFastpairUuid;
+using ble_constants::kGoogleUuidMask;
+using ble_constants::kNumScanFilters;
+using ble_constants::kRssiThreshold;
+
+chreBleGenericFilter createBleGenericFilter(uint8_t type, uint8_t len,
+                                            const uint8_t *data,
+                                            const uint8_t *mask) {
+  chreBleGenericFilter filter;
+  memset(&filter, 0, sizeof(filter));
+  filter.type = type;
+  filter.len = len;
+  memcpy(filter.data, data, sizeof(uint8_t) * len);
+  memcpy(filter.dataMask, mask, sizeof(uint8_t) * len);
+  return filter;
+}
+
+bool createBleScanFilterForKnownBeacons(struct chreBleScanFilter &filter,
+                                        chreBleGenericFilter *genericFilters,
+                                        uint8_t numGenericFilters) {
+  if (numGenericFilters < kNumScanFilters) {
+    return false;
+  }
+
+  genericFilters[0] = createBleGenericFilter(
+      CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE, kNumScanFilters,
+      kGoogleEddystoneUuid, kGoogleUuidMask);
+  genericFilters[1] = createBleGenericFilter(
+      CHRE_BLE_AD_TYPE_SERVICE_DATA_WITH_UUID_16_LE, kNumScanFilters,
+      kGoogleNearbyFastpairUuid, kGoogleUuidMask);
+
+  filter.rssiThreshold = kRssiThreshold;
+  filter.scanFilterCount = kNumScanFilters;
+  filter.scanFilters = genericFilters;
+  return true;
+}
+
+}  // namespace chre
diff --git a/util/pigweed/chre_channel_output.cc b/util/pigweed/chre_channel_output.cc
index a787e9b..f88e520 100644
--- a/util/pigweed/chre_channel_output.cc
+++ b/util/pigweed/chre_channel_output.cc
@@ -16,8 +16,10 @@
 
 #include "chre/util/pigweed/chre_channel_output.h"
 
-#include "chre/util/memory.h"
+#include <cstdint>
+
 #include "chre/util/nanoapp/callbacks.h"
+#include "chre/util/pigweed/rpc_helper.h"
 
 namespace chre {
 namespace {
@@ -26,67 +28,99 @@
   chreHeapFree(eventData);
 }
 
-}  // namespace
-
-ChreChannelOutputBase::ChreChannelOutputBase() : ChannelOutput("CHRE") {}
-
-void ChreChannelOutputBase::setEndpointId(uint16_t endpointId) {
-  mEndpointId = endpointId;
-}
-
-size_t ChreChannelOutputBase::MaximumTransmissionUnit() {
-  return CHRE_MESSAGE_TO_HOST_MAX_SIZE;
-}
-
-void ChreNanoappChannelOutput::setNanoappEndpoint(uint32_t nanoappInstanceId) {
-  CHRE_ASSERT(nanoappInstanceId <= UINT16_MAX);
-  if (nanoappInstanceId <= UINT16_MAX) {
-    mEndpointId = static_cast<uint16_t>(nanoappInstanceId);
-  } else {
-    mEndpointId = CHRE_HOST_ENDPOINT_UNSPECIFIED;
-  }
-}
-
-pw::Status ChreNanoappChannelOutput::Send(std::span<const std::byte> buffer) {
-  CHRE_ASSERT(mEndpointId != CHRE_HOST_ENDPOINT_UNSPECIFIED);
-  pw::Status returnCode = PW_STATUS_OK;
+/**
+ * Sends the buffer to the nanoapp.
+ *
+ * The buffer is first wrapped into a ChrePigweedNanoappMessage struct.
+ *
+ * @param targetInstanceId The nanoapp to send the message to
+ * @param eventType The event to send to the nanoapp
+ * @param buffer The buffer to send
+ * @return The status of the operation
+ */
+pw::Status sendToNanoapp(uint32_t targetInstanceId, uint16_t eventType,
+                         pw::span<const std::byte> buffer) {
+  CHRE_ASSERT(targetInstanceId != 0);
 
   if (buffer.size() > 0) {
     auto *data = static_cast<ChrePigweedNanoappMessage *>(
         chreHeapAlloc(buffer.size() + sizeof(ChrePigweedNanoappMessage)));
     if (data == nullptr) {
-      returnCode = PW_STATUS_RESOURCE_EXHAUSTED;
-    } else {
-      data->msgSize = buffer.size();
-      memcpy(data->msg, buffer.data(), buffer.size());
-      if (!chreSendEvent(PW_RPC_CHRE_NAPP_EVENT_TYPE, data, nappMessageFreeCb,
-                         mEndpointId)) {
-        returnCode = PW_STATUS_INVALID_ARGUMENT;
-      }
+      return PW_STATUS_RESOURCE_EXHAUSTED;
+    }
+
+    data->msgSize = buffer.size();
+    data->msg = &data[1];
+    memcpy(data->msg, buffer.data(), buffer.size());
+
+    if (!chreSendEvent(eventType, data, nappMessageFreeCb, targetInstanceId)) {
+      return PW_STATUS_INVALID_ARGUMENT;
     }
   }
 
-  return returnCode;
+  return PW_STATUS_OK;
 }
 
-void ChreHostChannelOutput::setHostEndpoint(uint16_t hostEndpoint) {
-  setEndpointId(hostEndpoint);
+}  // namespace
+
+ChreChannelOutputBase::ChreChannelOutputBase() : ChannelOutput("CHRE") {}
+
+size_t ChreChannelOutputBase::MaximumTransmissionUnit() {
+  return CHRE_MESSAGE_TO_HOST_MAX_SIZE - sizeof(ChrePigweedNanoappMessage);
 }
 
-pw::Status ChreHostChannelOutput::Send(std::span<const std::byte> buffer) {
+void ChreServerNanoappChannelOutput::setClient(uint32_t nanoappInstanceId) {
+  CHRE_ASSERT(nanoappInstanceId <= kRpcNanoappMaxId);
+  if (nanoappInstanceId <= kRpcNanoappMaxId) {
+    mClientInstanceId = static_cast<uint16_t>(nanoappInstanceId);
+  } else {
+    mClientInstanceId = 0;
+  }
+}
+
+pw::Status ChreServerNanoappChannelOutput::Send(
+    pw::span<const std::byte> buffer) {
+  // The permission is not enforced across nanoapps but we still need to
+  // reset the value as it is only applicable to the next message.
+  mPermission.getAndReset();
+
+  return sendToNanoapp(mClientInstanceId, PW_RPC_CHRE_NAPP_RESPONSE_EVENT_TYPE,
+                       buffer);
+}
+
+void ChreClientNanoappChannelOutput::setServer(uint32_t instanceId) {
+  CHRE_ASSERT(instanceId <= kRpcNanoappMaxId);
+  if (instanceId <= kRpcNanoappMaxId) {
+    mServerInstanceId = static_cast<uint16_t>(instanceId);
+  } else {
+    mServerInstanceId = 0;
+  }
+}
+
+pw::Status ChreClientNanoappChannelOutput::Send(
+    pw::span<const std::byte> buffer) {
+  return sendToNanoapp(mServerInstanceId, PW_RPC_CHRE_NAPP_REQUEST_EVENT_TYPE,
+                       buffer);
+}
+
+void ChreServerHostChannelOutput::setHostEndpoint(uint16_t hostEndpoint) {
+  mEndpointId = hostEndpoint;
+}
+
+pw::Status ChreServerHostChannelOutput::Send(pw::span<const std::byte> buffer) {
   CHRE_ASSERT(mEndpointId != CHRE_HOST_ENDPOINT_UNSPECIFIED);
   pw::Status returnCode = PW_STATUS_OK;
 
   if (buffer.size() > 0) {
-    uint8_t *data = memoryAlloc<uint8_t>(buffer.size());
+    uint32_t permission = mPermission.getAndReset();
+    uint8_t *data = static_cast<uint8_t *>(chreHeapAlloc(buffer.size()));
     if (data == nullptr) {
       returnCode = PW_STATUS_RESOURCE_EXHAUSTED;
     } else {
       memcpy(data, buffer.data(), buffer.size());
-      // TODO(b/210138227): Make this pass permissions too.
       if (!chreSendMessageWithPermissions(
               data, buffer.size(), PW_RPC_CHRE_HOST_MESSAGE_TYPE, mEndpointId,
-              CHRE_MESSAGE_PERMISSION_NONE, heapFreeMessageCallback)) {
+              permission, heapFreeMessageCallback)) {
         returnCode = PW_STATUS_INVALID_ARGUMENT;
       }
     }
diff --git a/util/pigweed/rpc_client.cc b/util/pigweed/rpc_client.cc
new file mode 100644
index 0000000..72c034a
--- /dev/null
+++ b/util/pigweed/rpc_client.cc
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/pigweed/rpc_client.h"
+
+#include <cinttypes>
+#include <cstdint>
+
+#include "chre/util/macros.h"
+#include "chre/util/nanoapp/log.h"
+#include "chre/util/pigweed/rpc_helper.h"
+#include "chre_api/chre.h"
+
+#ifndef LOG_TAG
+#define LOG_TAG "[RpcClient]"
+#endif  // LOG_TAG
+
+namespace chre {
+
+bool RpcClient::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                            const void *eventData) {
+  switch (eventType) {
+    case chre::ChreChannelOutputBase::PW_RPC_CHRE_NAPP_RESPONSE_EVENT_TYPE:
+      return handleMessageFromServer(senderInstanceId, eventData);
+    case CHRE_EVENT_NANOAPP_STOPPED:
+      handleNanoappStopped(eventData);
+      return true;
+  }
+
+  return true;
+}
+
+bool RpcClient::hasService(uint64_t id, uint32_t version) {
+  struct chreNanoappInfo info;
+  if (!chreGetNanoappInfoByAppId(mServerNanoappId, &info)) {
+    return false;
+  }
+
+  for (uint32_t i = 0; i < info.rpcServiceCount; i++) {
+    if (info.rpcServices[i].id == id) {
+      return info.rpcServices[i].version == version;
+    }
+  }
+
+  return false;
+}
+
+bool RpcClient::handleMessageFromServer(uint32_t senderInstanceId,
+                                        const void *eventData) {
+  auto data = static_cast<const chre::ChrePigweedNanoappMessage *>(eventData);
+  pw::span packet(static_cast<const std::byte *>(data->msg), data->msgSize);
+  struct chreNanoappInfo info;
+
+  if (!chreGetNanoappInfoByAppId(mServerNanoappId, &info) ||
+      info.instanceId > kRpcNanoappMaxId) {
+    return false;
+  }
+
+  if (!validateNanoappChannelId(senderInstanceId, info.instanceId)) {
+    return false;
+  }
+
+  pw::Status status = mRpcClient.ProcessPacket(packet);
+
+  if (status != pw::OkStatus()) {
+    LOGE("Failed to process the packet");
+    return false;
+  }
+
+  return true;
+}
+
+void RpcClient::handleNanoappStopped(const void *eventData) {
+  auto info = static_cast<const struct chreNanoappInfo *>(eventData);
+
+  if (info->instanceId > kRpcNanoappMaxId) {
+    LOGE("Invalid nanoapp Id 0x%08" PRIx32, info->instanceId);
+    return;
+  }
+
+  if (info->instanceId == mChannelId) {
+    mRpcClient.CloseChannel(mChannelId);
+    mChannelId = 0;
+  }
+}
+
+}  // namespace chre
\ No newline at end of file
diff --git a/util/pigweed/rpc_helper.cc b/util/pigweed/rpc_helper.cc
new file mode 100644
index 0000000..d833cf8
--- /dev/null
+++ b/util/pigweed/rpc_helper.cc
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/pigweed/rpc_helper.h"
+
+#include <cinttypes>
+
+#include "chre/util/nanoapp/log.h"
+
+#ifndef LOG_TAG
+#define LOG_TAG "[RpcHelper]"
+#endif  // LOG_TAG
+
+namespace chre {
+
+bool rpcEndpointsMatch(uint32_t expectedId, uint32_t actualId) {
+  if ((expectedId & kRpcClientIdMask) != (actualId & kRpcClientIdMask)) {
+    LOGE("Invalid endpoint 0x%04" PRIx32 " expected 0x%04" PRIx32, actualId,
+         expectedId);
+    return false;
+  }
+
+  return true;
+}
+
+bool isRpcChannelIdHost(uint32_t id) {
+  return id >> 16 == 1;
+}
+
+bool isRpcChannelIdNanoapp(uint32_t id) {
+  return id >> 16 == 0;
+}
+
+bool validateHostChannelId(const chreMessageFromHostData *msg,
+                           uint32_t channelId) {
+  struct chreHostEndpointInfo info;
+
+  if (!isRpcChannelIdHost(channelId) ||
+      !chreGetHostEndpointInfo(msg->hostEndpoint, &info)) {
+    LOGE("Invalid channelId for a host client 0x%08" PRIx32, channelId);
+    return false;
+  }
+
+  return rpcEndpointsMatch(channelId, static_cast<uint32_t>(msg->hostEndpoint));
+}
+
+bool validateNanoappChannelId(uint32_t nappId, uint32_t channelId) {
+  if (nappId > kRpcNanoappMaxId) {
+    LOGE("Invalid nanoapp Id 0x%08" PRIx32, nappId);
+    return false;
+  }
+
+  if (!isRpcChannelIdNanoapp(channelId)) {
+    LOGE("Invalid channelId for a nanoapp 0x%08" PRIx32, channelId);
+    return false;
+  }
+
+  return rpcEndpointsMatch(channelId, nappId);
+}
+
+}  // namespace chre
\ No newline at end of file
diff --git a/util/pigweed/rpc_server.cc b/util/pigweed/rpc_server.cc
new file mode 100644
index 0000000..1ee225a
--- /dev/null
+++ b/util/pigweed/rpc_server.cc
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/pigweed/rpc_server.h"
+
+#include <cinttypes>
+#include <cstdint>
+
+#include "chre/util/nanoapp/log.h"
+#include "chre/util/pigweed/rpc_helper.h"
+#include "chre_api/chre.h"
+
+#ifndef LOG_TAG
+#define LOG_TAG "[RpcServer]"
+#endif  // LOG_TAG
+
+namespace chre {
+
+RpcServer::~RpcServer() {
+  chreConfigureNanoappInfoEvents(false);
+  // TODO(b/251257328): Disable all notifications at once.
+  while (!mConnectedHosts.empty()) {
+    chreConfigureHostEndpointNotifications(mConnectedHosts[0], false);
+    mConnectedHosts.erase(0);
+  }
+}
+
+bool RpcServer::registerServices(size_t numServices,
+                                 RpcServer::Service *services) {
+  // Avoid blowing up the stack with chreServices.
+  constexpr size_t kMaxServices = 8;
+
+  if (numServices > kMaxServices) {
+    LOGE("Can not register more than %zu services at once", kMaxServices);
+    return false;
+  }
+
+  chreNanoappRpcService chreServices[kMaxServices];
+
+  for (size_t i = 0; i < numServices; ++i) {
+    const Service &service = services[i];
+
+    if (mServer.IsServiceRegistered(service.service)) {
+      return false;
+    }
+
+    chreServices[i] = {
+        .id = service.id,
+        .version = service.version,
+    };
+
+    mServer.RegisterService(service.service);
+  }
+
+  return chrePublishRpcServices(chreServices, numServices);
+}
+
+void RpcServer::setPermissionForNextMessage(uint32_t permission) {
+  mPermission.set(permission);
+}
+
+bool RpcServer::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
+                            const void *eventData) {
+  switch (eventType) {
+    case CHRE_EVENT_MESSAGE_FROM_HOST:
+      return handleMessageFromHost(eventData);
+    case ChreChannelOutputBase::PW_RPC_CHRE_NAPP_REQUEST_EVENT_TYPE:
+      return handleMessageFromNanoapp(senderInstanceId, eventData);
+    case CHRE_EVENT_HOST_ENDPOINT_NOTIFICATION:
+      handleHostClientNotification(eventData);
+      return true;
+    case CHRE_EVENT_NANOAPP_STOPPED:
+      handleNanoappStopped(eventData);
+      return true;
+    default:
+      return true;
+  }
+}
+
+bool RpcServer::handleMessageFromHost(const void *eventData) {
+  auto *hostMessage = static_cast<const chreMessageFromHostData *>(eventData);
+
+  if (hostMessage->messageType !=
+      ChreChannelOutputBase::PW_RPC_CHRE_HOST_MESSAGE_TYPE) {
+    return false;
+  }
+
+  pw::span packet(static_cast<const std::byte *>(hostMessage->message),
+                  hostMessage->messageSize);
+
+  pw::Result<uint32_t> result = pw::rpc::ExtractChannelId(packet);
+  if (result.status() != PW_STATUS_OK) {
+    LOGE("Unable to extract channel ID from packet");
+    return false;
+  }
+
+  if (!validateHostChannelId(hostMessage, result.value())) {
+    return false;
+  }
+
+  if (!chreConfigureHostEndpointNotifications(hostMessage->hostEndpoint,
+                                              true)) {
+    LOGW("Fail to register for host client updates");
+  }
+
+  size_t hostIndex = mConnectedHosts.find(hostMessage->hostEndpoint);
+  if (hostIndex == mConnectedHosts.size()) {
+    mConnectedHosts.push_back(hostMessage->hostEndpoint);
+  }
+
+  mHostOutput.setHostEndpoint(hostMessage->hostEndpoint);
+  mServer.OpenChannel(result.value(), mHostOutput);
+
+  pw::Status status = mServer.ProcessPacket(packet);
+
+  if (status != pw::OkStatus()) {
+    LOGE("Failed to process the packet");
+    return false;
+  }
+
+  return true;
+}
+
+// TODO(b/242301032): factor code with handleMessageFromHost
+bool RpcServer::handleMessageFromNanoapp(uint32_t senderInstanceId,
+                                         const void *eventData) {
+  const auto data = static_cast<const ChrePigweedNanoappMessage *>(eventData);
+  pw::span packet(static_cast<const std::byte *>(data->msg), data->msgSize);
+
+  pw::Result<uint32_t> result = pw::rpc::ExtractChannelId(packet);
+  if (result.status() != PW_STATUS_OK) {
+    LOGE("Unable to extract channel ID from packet");
+    return false;
+  }
+
+  if (!validateNanoappChannelId(senderInstanceId, result.value())) {
+    return false;
+  }
+
+  chreConfigureNanoappInfoEvents(true);
+
+  mNanoappOutput.setClient(senderInstanceId);
+  mServer.OpenChannel(result.value(), mNanoappOutput);
+
+  pw::Status success = mServer.ProcessPacket(packet);
+
+  if (success != pw::OkStatus()) {
+    LOGE("Failed to process the packet");
+    return false;
+  }
+
+  return true;
+}
+
+void RpcServer::handleHostClientNotification(const void *eventData) {
+  if (mConnectedHosts.empty()) {
+    return;
+  }
+
+  auto notif =
+      static_cast<const struct chreHostEndpointNotification *>(eventData);
+
+  if (notif->notificationType == HOST_ENDPOINT_NOTIFICATION_TYPE_DISCONNECT) {
+    size_t hostIndex = mConnectedHosts.find(notif->hostEndpointId);
+    if (hostIndex != mConnectedHosts.size()) {
+      mServer.CloseChannel(kChannelIdHostClient |
+                           static_cast<uint32_t>(notif->hostEndpointId));
+      mConnectedHosts.erase(hostIndex);
+    }
+  }
+}
+
+void RpcServer::handleNanoappStopped(const void *eventData) {
+  auto info = static_cast<const struct chreNanoappInfo *>(eventData);
+
+  if (info->instanceId > kRpcNanoappMaxId) {
+    LOGE("Invalid nanoapp Id 0x%08" PRIx32, info->instanceId);
+  } else {
+    mServer.CloseChannel(info->instanceId);
+  }
+}
+
+pw::Status RpcServer::closeChannel(uint32_t id) {
+  return mServer.CloseChannel(id);
+}
+
+}  // namespace chre
\ No newline at end of file
diff --git a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h b/util/system/event_callbacks.cc
similarity index 60%
copy from apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
copy to util/system/event_callbacks.cc
index 77a9da0..5861b9d 100644
--- a/apps/test/common/chre_settings_test/inc/chre_settings_test_util.h
+++ b/util/system/event_callbacks.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,21 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_SETTINGS_TEST_UTIL_H_
-#define CHRE_SETTINGS_TEST_UTIL_H_
+#include "chre/util/system/event_callbacks.h"
 
-#include <cinttypes>
+#include "chre/platform/memory.h"
 
 namespace chre {
 
-namespace settings_test {
-
-void sendTestResultToHost(uint16_t hostEndpointId, bool success);
-
-void sendEmptyMessageToHost(uint16_t hostEndpointId, uint32_t messageType);
-
-}  // namespace settings_test
+void freeEventDataCallback(uint16_t /*eventType*/, void *eventData) {
+  memoryFree(eventData);
+}
 
 }  // namespace chre
-
-#endif  // CHRE_SETTINGS_TEST_UTIL_H_
diff --git a/util/tests/array_queue_test.cc b/util/tests/array_queue_test.cc
index 1160473..fbacaed 100644
--- a/util/tests/array_queue_test.cc
+++ b/util/tests/array_queue_test.cc
@@ -1,3 +1,19 @@
+/*
+ * 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/util/array_queue.h"
 #include "gtest/gtest.h"
 
@@ -251,15 +267,9 @@
       q.push(e);
       q[i].setValue(i);
     }
-
-    q.~ArrayQueue();
-
-    for (size_t i = 0; i < 3; ++i) {
-      EXPECT_EQ(1, destructor_count[i]);
-    }
   }
 
-  // Check destructor count.
+  // q should now be destroyed - check destructor count.
   for (size_t i = 0; i < 3; ++i) {
     EXPECT_EQ(1, destructor_count[i]);
   }
diff --git a/util/tests/blocking_queue_test.cc b/util/tests/blocking_queue_test.cc
index 25f63fb..a05f657 100644
--- a/util/tests/blocking_queue_test.cc
+++ b/util/tests/blocking_queue_test.cc
@@ -16,19 +16,43 @@
 
 #include "gtest/gtest.h"
 
+#include "chre/util/blocking_segmented_queue.h"
 #include "chre/util/fixed_size_blocking_queue.h"
 #include "chre/util/unique_ptr.h"
 
+using chre::BlockingSegmentedQueue;
 using chre::FixedSizeBlockingQueue;
 using chre::MakeUnique;
 using chre::UniquePtr;
 
-TEST(FixedSizeBlockingQueue, IsEmptyByDefault) {
+namespace {
+class ConstructorCount {
+ public:
+  ConstructorCount(int value_, ssize_t *constructedCount)
+      : sConstructedCounter(constructedCount), value(value_) {
+    (*sConstructedCounter)++;
+  }
+  ~ConstructorCount() {
+    (*sConstructedCounter)--;
+  }
+  int getValue() {
+    return value;
+  }
+
+  ssize_t *sConstructedCounter;
+
+ private:
+  int value;
+};
+
+}  // namespace
+
+TEST(BlockingQueue, IsEmptyByDefault) {
   FixedSizeBlockingQueue<int, 16> blockingQueue;
   ASSERT_TRUE(blockingQueue.empty());
 }
 
-TEST(FixedSizeBlockingQueue, PushPopVerifyOrder) {
+TEST(BlockingQueue, PushPopVerifyOrder) {
   FixedSizeBlockingQueue<int, 16> blockingQueue;
 
   ASSERT_TRUE(blockingQueue.push(0x1337));
@@ -38,7 +62,7 @@
   ASSERT_EQ(blockingQueue.pop(), 0xcafe);
 }
 
-TEST(FixedSizeBlockingQueue, PushPopMove) {
+TEST(BlockingQueue, PushPopMove) {
   static constexpr int kVal = 0xbeef;
   UniquePtr<int> ptr = MakeUnique<int>();
   *ptr = kVal;
@@ -49,3 +73,13 @@
   ASSERT_TRUE(ptr.isNull());
   ASSERT_EQ(*(blockingQueue.pop()), kVal);
 }
+
+TEST(BlockingSegmentedQueue, InitState) {
+  constexpr uint8_t blockSize = 16;
+  constexpr uint8_t maxBlockCount = 3;
+  constexpr uint8_t staticBlockCount = 2;
+  BlockingSegmentedQueue<int, blockSize> blockingQueue(maxBlockCount,
+                                                       staticBlockCount);
+  ASSERT_TRUE(blockingQueue.empty());
+  ASSERT_EQ(blockingQueue.block_count(), staticBlockCount);
+}
diff --git a/util/tests/copyable_fixed_size_vector_test.cc b/util/tests/copyable_fixed_size_vector_test.cc
new file mode 100644
index 0000000..cc448ec
--- /dev/null
+++ b/util/tests/copyable_fixed_size_vector_test.cc
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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/util/copyable_fixed_size_vector.h"
+#include "chre/util/fixed_size_vector.h"
+
+using chre::CopyableFixedSizeVector;
+using chre::FixedSizeVector;
+
+TEST(CopyableFixedSizeVector, CopyConstructible) {
+  constexpr int kValue = 1234;
+  CopyableFixedSizeVector<int, 2> a;
+  a.push_back(kValue);
+
+  CopyableFixedSizeVector<int, 2> b(a);
+  EXPECT_EQ(b.size(), 1);
+  EXPECT_EQ(a[0], kValue);
+  EXPECT_EQ(b[0], kValue);
+}
+
+TEST(CopyableFixedSizeVector, CopyAssignable) {
+  constexpr int kValue = 1234;
+  CopyableFixedSizeVector<int, 2> a;
+  a.push_back(kValue);
+
+  CopyableFixedSizeVector<int, 2> b;
+  EXPECT_EQ(b.size(), 0);
+  b = a;
+  EXPECT_EQ(b.size(), 1);
+  EXPECT_EQ(a[0], kValue);
+  EXPECT_EQ(b[0], kValue);
+}
+
+TEST(CopyableFixedSizeVector, NonTrivialElement) {
+  static int ctorCount;
+  static int dtorCount;
+  class Foo {
+   public:
+    Foo() {
+      ctorCount++;
+    }
+    Foo(const Foo & /*other*/) {
+      ctorCount++;
+    }
+
+    ~Foo() {
+      dtorCount++;
+    }
+  };
+
+  ctorCount = dtorCount = 0;
+  {
+    CopyableFixedSizeVector<Foo, 4> v;
+    {
+      Foo f;
+      EXPECT_EQ(ctorCount, 1);
+      v.push_back(f);
+    }
+    EXPECT_EQ(ctorCount, 2);
+    EXPECT_EQ(dtorCount, 1);
+    v.pop_back();
+    EXPECT_EQ(dtorCount, 2);
+    v.emplace_back();
+    EXPECT_EQ(ctorCount, 3);
+  }
+  EXPECT_EQ(dtorCount, 3);
+}
+
+TEST(CopyableFixedSizeVector, Nestable) {
+  struct Foo {
+    int id;
+    CopyableFixedSizeVector<float, 3> vec;
+  };
+
+  FixedSizeVector<Foo, 4> container;
+
+  Foo f;
+  f.id = 1;
+  container.push_back(f);
+  container.emplace_back();
+  container.back().id = 2;
+  container.back().vec.push_back(1.23f);
+  container.back().vec.push_back(3.21f);
+  EXPECT_EQ(container.front().id, 1);
+  container.erase(0);
+  EXPECT_EQ(container.front().id, 2);
+  EXPECT_EQ(container.front().vec.size(), 2);
+  EXPECT_EQ(container.front().vec[0], 1.23f);
+}
diff --git a/util/tests/heap_test.cc b/util/tests/heap_test.cc
index 014b7b1..8f721e6 100644
--- a/util/tests/heap_test.cc
+++ b/util/tests/heap_test.cc
@@ -1,3 +1,19 @@
+/*
+ * 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/util/heap.h"
 #include "gtest/gtest.h"
 
diff --git a/util/tests/intrusive_list_test.cc b/util/tests/intrusive_list_test.cc
new file mode 100644
index 0000000..1a019ca
--- /dev/null
+++ b/util/tests/intrusive_list_test.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/intrusive_list.h"
+
+#include "gtest/gtest.h"
+
+using chre::IntrusiveList;
+using chre::ListNode;
+
+TEST(IntrusiveList, EmptyByDefault) {
+  IntrusiveList<int> testLinkedList;
+  EXPECT_EQ(testLinkedList.size(), 0);
+  EXPECT_TRUE(testLinkedList.empty());
+}
+
+TEST(IntrusiveList, PushReadAndPop) {
+  typedef ListNode<int> ListIntNode;
+
+  ListIntNode nodeA(0);
+  ListIntNode nodeB(1);
+  ListIntNode nodeC(2);
+  ListIntNode nodeD(3);
+  IntrusiveList<int> testLinkedList;
+
+  testLinkedList.link_back(&nodeA);
+  testLinkedList.link_back(&nodeB);
+  testLinkedList.link_back(&nodeC);
+  EXPECT_EQ(testLinkedList.size(), 3);
+
+  EXPECT_EQ(testLinkedList.front().item, nodeA.item);
+  EXPECT_EQ(testLinkedList.back().item, nodeC.item);
+
+  testLinkedList.unlink_front();
+  EXPECT_EQ(testLinkedList.size(), 2);
+  EXPECT_EQ(testLinkedList.front().item, nodeB.item);
+
+  testLinkedList.unlink_back();
+  EXPECT_EQ(testLinkedList.size(), 1);
+  EXPECT_EQ(testLinkedList.back().item, nodeB.item);
+
+  testLinkedList.unlink_back();
+  EXPECT_EQ(testLinkedList.size(), 0);
+  EXPECT_TRUE(testLinkedList.empty());
+
+  testLinkedList.link_back(&nodeD);
+  EXPECT_EQ(testLinkedList.size(), 1);
+  EXPECT_EQ(testLinkedList.back().item, nodeD.item);
+  EXPECT_EQ(testLinkedList.front().item, nodeD.item);
+}
+
+TEST(IntrusiveList, CatchInvalidCallToEmptyList) {
+  IntrusiveList<int> testList;
+  ASSERT_DEATH(testList.front(), "");
+  ASSERT_DEATH(testList.back(), "");
+  ASSERT_DEATH(testList.unlink_front(), "");
+  ASSERT_DEATH(testList.unlink_back(), "");
+}
+
+TEST(IntrusiveList, DestructorCleanUpLink) {
+  typedef ListNode<int> ListIntNode;
+
+  ListIntNode testInput[]{
+      ListIntNode(0), ListIntNode(1), ListIntNode(2),
+      ListIntNode(3), ListIntNode(4),
+  };
+
+  {
+    IntrusiveList<int> testLinkedList;
+    for (auto &node : testInput) {
+      testLinkedList.link_back(&node);
+    }
+
+    int idx = 0;
+    for (auto const &node : testLinkedList) {
+      EXPECT_EQ(node.item, idx++);
+    }
+  }
+
+  for (auto &node : testInput) {
+    EXPECT_EQ(node.node.next, nullptr);
+    EXPECT_EQ(node.node.prev, nullptr);
+  }
+}
+
+TEST(IntrusiveList, AccessMiddleTest) {
+  typedef ListNode<int> ListIntNode;
+
+  ListIntNode testListIntNodes[]{
+      ListIntNode(0), ListIntNode(1), ListIntNode(2),
+      ListIntNode(3), ListIntNode(4),
+  };
+
+  IntrusiveList<int> testLinkedList;
+
+  for (auto &node : testListIntNodes) {
+    testLinkedList.link_back(&node);
+  }
+
+  testLinkedList.unlink_node(&testListIntNodes[1]);  // removes ListIntNode(1)
+  EXPECT_EQ(testListIntNodes[0].node.next, &testListIntNodes[2].node);
+  EXPECT_EQ(testLinkedList.size(), 4);
+
+  testLinkedList.link_after(&testListIntNodes[0],
+                            &testListIntNodes[1]);  // add back ListIntNode(1)
+  EXPECT_EQ(testListIntNodes[0].node.next, &testListIntNodes[1].node);
+  EXPECT_EQ(testLinkedList.size(), 5);
+}
diff --git a/util/tests/memory_pool_test.cc b/util/tests/memory_pool_test.cc
index c8153a4..6dac18b 100644
--- a/util/tests/memory_pool_test.cc
+++ b/util/tests/memory_pool_test.cc
@@ -1,3 +1,19 @@
+/*
+ * 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 "gtest/gtest.h"
 
 #include "chre/util/memory_pool.h"
@@ -20,6 +36,18 @@
   EXPECT_EQ(memoryPool.getFreeBlockCount(), 0);
 }
 
+TEST(MemoryPool, OwnershipDeallocation) {
+  MemoryPool<int, 3> firstMemoryPool;
+  MemoryPool<int, 3> secondMemoryPool;
+
+  int *firstMemoryElement = firstMemoryPool.allocate();
+  EXPECT_TRUE(firstMemoryPool.containsAddress(firstMemoryElement));
+  EXPECT_FALSE(secondMemoryPool.containsAddress(firstMemoryElement));
+
+  EXPECT_DEATH(secondMemoryPool.deallocate(firstMemoryElement), "");
+  firstMemoryPool.deallocate(firstMemoryElement);
+}
+
 TEST(MemoryPool, ExhaustPoolThenDeallocateOneAndAllocateOne) {
   MemoryPool<int, 3> memoryPool;
 
diff --git a/util/tests/priority_queue_test.cc b/util/tests/priority_queue_test.cc
index 88986fe..f6deb66 100644
--- a/util/tests/priority_queue_test.cc
+++ b/util/tests/priority_queue_test.cc
@@ -1,3 +1,19 @@
+/*
+ * 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/util/priority_queue.h"
 #include "gtest/gtest.h"
 
diff --git a/util/tests/raw_storage_test.cc b/util/tests/raw_storage_test.cc
new file mode 100644
index 0000000..2ee0cb5
--- /dev/null
+++ b/util/tests/raw_storage_test.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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/util/raw_storage.h"
+
+using chre::RawStorage;
+
+static_assert(sizeof(RawStorage<int, 10>) == sizeof(int[10]),
+              "RawStorage must allocate the correct size");
+
+TEST(RawStorageTest, Capacity) {
+  RawStorage<int, 42> rs;
+  EXPECT_EQ(rs.capacity(), 42);
+}
+
+TEST(RawStorageTest, ArraySubscript) {
+  struct Foo {
+    const int x = 1;
+    int y = 2;
+  };
+
+  RawStorage<Foo, 2> rs;
+  new (&rs[0]) Foo();
+  EXPECT_EQ(rs[0].x, 1);
+  EXPECT_EQ(rs[0].y, 2);
+  EXPECT_EQ(++rs[0].y, 3);
+  new (&rs[0]) Foo();
+  EXPECT_EQ(rs[0].x, 1);
+  EXPECT_EQ(rs[0].y, 2);
+}
\ No newline at end of file
diff --git a/util/tests/segmented_queue_test.cc b/util/tests/segmented_queue_test.cc
new file mode 100644
index 0000000..ca36e6a
--- /dev/null
+++ b/util/tests/segmented_queue_test.cc
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/segmented_queue.h"
+
+#include <cstdlib>
+#include <deque>
+#include <vector>
+
+#include "chre/util/enum.h"
+#include "chre/util/non_copyable.h"
+#include "gtest/gtest.h"
+
+using chre::SegmentedQueue;
+using std::deque;
+using std::vector;
+
+namespace {
+
+class ConstructorCount {
+ public:
+  ConstructorCount() = default;
+  ConstructorCount(int value_, ssize_t *constructedCount)
+      : sConstructedCounter(constructedCount), value(value_) {
+    (*sConstructedCounter)++;
+  }
+  ~ConstructorCount() {
+    (*sConstructedCounter)--;
+  }
+  int getValue() {
+    return value;
+  }
+
+  ssize_t *sConstructedCounter;
+
+ private:
+  int value;
+};
+
+constexpr int kConstructedMagic = 0xdeadbeef;
+class CopyableButNonMovable {
+ public:
+  CopyableButNonMovable(int value) : mValue(value) {}
+
+  CopyableButNonMovable(const CopyableButNonMovable &other) {
+    mValue = other.mValue;
+  }
+
+  CopyableButNonMovable &operator=(const CopyableButNonMovable &other) {
+    CHRE_ASSERT(mMagic == kConstructedMagic);
+    mValue = other.mValue;
+    return *this;
+  }
+
+  CopyableButNonMovable(CopyableButNonMovable &&other) = delete;
+  CopyableButNonMovable &operator=(CopyableButNonMovable &&other) = delete;
+
+  int getValue() const {
+    return mValue;
+  }
+
+ private:
+  int mMagic = kConstructedMagic;
+  int mValue;
+};
+
+class MovableButNonCopyable : public chre::NonCopyable {
+ public:
+  MovableButNonCopyable(int value) : mValue(value) {}
+
+  MovableButNonCopyable(MovableButNonCopyable &&other) {
+    mValue = other.mValue;
+    other.mValue = -1;
+  }
+
+  MovableButNonCopyable &operator=(MovableButNonCopyable &&other) {
+    CHRE_ASSERT(mMagic == kConstructedMagic);
+    mValue = other.mValue;
+    other.mValue = -1;
+    return *this;
+  }
+
+  int getValue() const {
+    return mValue;
+  }
+
+ private:
+  int mMagic = kConstructedMagic;
+  int mValue;
+};
+
+enum class OperationType : uint8_t {
+  EMPLACE_BACK = 0,
+  PUSH_BACK,
+  POP_FRONT,
+  REMOVE,
+  BATCH_REMOVE,
+
+  OPERATION_TYPE_COUNT,  // Must be at the end.
+};
+
+}  // namespace
+
+TEST(SegmentedQueue, InitialzedState) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 3;
+  constexpr uint8_t staticBlockCount = 2;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount,
+                                                staticBlockCount);
+
+  EXPECT_EQ(segmentedQueue.block_count(), staticBlockCount);
+  EXPECT_EQ(segmentedQueue.capacity(), staticBlockCount * blockSize);
+  EXPECT_EQ(segmentedQueue.size(), 0);
+}
+
+TEST(SegmentedQueue, PushAndRead) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 3;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount);
+
+  for (uint32_t queueSize = 0; queueSize < blockSize * maxBlockCount;
+       queueSize++) {
+    EXPECT_TRUE(segmentedQueue.push_back(queueSize));
+    EXPECT_EQ(segmentedQueue.size(), queueSize + 1);
+    EXPECT_EQ(segmentedQueue[queueSize], queueSize);
+    EXPECT_EQ(segmentedQueue.back(), queueSize);
+  }
+
+  EXPECT_FALSE(segmentedQueue.push_back(10000));
+  EXPECT_EQ(segmentedQueue.size(), maxBlockCount * blockSize);
+  EXPECT_TRUE(segmentedQueue.full());
+}
+
+TEST(SegmentedQueue, EmplaceAndRead) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 3;
+  ssize_t constructorCount = 0;
+  SegmentedQueue<ConstructorCount, blockSize> segmentedQueue(maxBlockCount);
+
+  for (uint32_t queueSize = 0; queueSize < blockSize * maxBlockCount;
+       queueSize++) {
+    ssize_t oldConstructedCounter = constructorCount;
+    EXPECT_TRUE(segmentedQueue.emplace_back(queueSize, &constructorCount));
+    EXPECT_EQ(segmentedQueue.size(), queueSize + 1);
+    EXPECT_EQ(segmentedQueue[queueSize].getValue(), queueSize);
+    EXPECT_EQ(segmentedQueue.back().getValue(), queueSize);
+    EXPECT_EQ(constructorCount, oldConstructedCounter + 1);
+  }
+
+  EXPECT_FALSE(segmentedQueue.emplace_back(10000, &constructorCount));
+  EXPECT_EQ(segmentedQueue.size(), maxBlockCount * blockSize);
+  EXPECT_TRUE(segmentedQueue.full());
+}
+
+TEST(SegmentedQueue, PushAndReadCopyableButNonMovable) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 3;
+  SegmentedQueue<CopyableButNonMovable, blockSize> segmentedQueue(
+      maxBlockCount);
+
+  for (uint32_t queueSize = 0; queueSize < blockSize * maxBlockCount;
+       queueSize++) {
+    CopyableButNonMovable cbnm(queueSize);
+    EXPECT_TRUE(segmentedQueue.push_back(cbnm));
+    EXPECT_EQ(segmentedQueue.size(), queueSize + 1);
+    EXPECT_EQ(segmentedQueue[queueSize].getValue(), queueSize);
+    EXPECT_EQ(segmentedQueue.back().getValue(), queueSize);
+  }
+}
+
+TEST(SegmentedQueue, PushAndReadMovableButNonCopyable) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 3;
+  SegmentedQueue<MovableButNonCopyable, blockSize> segmentedQueue(
+      maxBlockCount);
+
+  for (uint8_t blockIndex = 0; blockIndex < maxBlockCount; blockIndex++) {
+    for (uint8_t index = 0; index < blockSize; index++) {
+      int value = segmentedQueue.size();
+      EXPECT_TRUE(segmentedQueue.emplace_back(value));
+      EXPECT_EQ(segmentedQueue.size(), value + 1);
+      EXPECT_EQ(segmentedQueue[value].getValue(), value);
+      EXPECT_EQ(segmentedQueue.back().getValue(), value);
+    }
+  }
+}
+
+TEST(SegmentedQueue, ReadAndPop) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 3;
+  SegmentedQueue<ConstructorCount, blockSize> segmentedQueue(maxBlockCount);
+  ssize_t constructedCounter = 0;
+
+  for (uint32_t index = 0; index < blockSize * maxBlockCount; index++) {
+    EXPECT_TRUE(segmentedQueue.emplace_back(index, &constructedCounter));
+  }
+
+  uint8_t originalQueueSize = segmentedQueue.size();
+  for (uint8_t index = 0; index < originalQueueSize; index++) {
+    EXPECT_EQ(segmentedQueue[index].getValue(), index);
+  }
+
+  size_t capacityBeforePop = segmentedQueue.capacity();
+  while (!segmentedQueue.empty()) {
+    ASSERT_EQ(segmentedQueue.front().getValue(),
+              originalQueueSize - segmentedQueue.size());
+    ssize_t oldConstructedCounter = constructedCounter;
+    segmentedQueue.pop_front();
+    EXPECT_EQ(oldConstructedCounter - 1, constructedCounter);
+  }
+
+  EXPECT_EQ(segmentedQueue.size(), 0);
+  EXPECT_TRUE(segmentedQueue.empty());
+  EXPECT_LT(segmentedQueue.capacity(), capacityBeforePop);
+  EXPECT_GT(segmentedQueue.capacity(), 0);
+}
+
+TEST(SegmentedQueue, RemoveTest) {
+  constexpr uint8_t blockSize = 2;
+  constexpr uint8_t maxBlockCount = 3;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount);
+
+  for (uint32_t index = 0; index < blockSize * maxBlockCount; index++) {
+    EXPECT_TRUE(segmentedQueue.push_back(index));
+  }
+
+  // segmentedQueue = [[0, 1], [2, 3], [4, 5]]
+  EXPECT_FALSE(segmentedQueue.remove(segmentedQueue.size()));
+
+  EXPECT_TRUE(segmentedQueue.remove(4));
+  EXPECT_EQ(segmentedQueue[4], 5);
+  EXPECT_EQ(segmentedQueue[3], 3);
+  EXPECT_EQ(segmentedQueue.size(), 5);
+
+  EXPECT_TRUE(segmentedQueue.remove(1));
+  EXPECT_EQ(segmentedQueue[3], 5);
+  EXPECT_EQ(segmentedQueue[1], 2);
+  EXPECT_EQ(segmentedQueue[0], 0);
+  EXPECT_EQ(segmentedQueue.size(), 4);
+
+  size_t currentSize = segmentedQueue.size();
+  size_t capacityBeforeRemove = segmentedQueue.capacity();
+  while (currentSize--) {
+    EXPECT_TRUE(segmentedQueue.remove(0));
+  }
+
+  EXPECT_EQ(segmentedQueue.size(), 0);
+  EXPECT_TRUE(segmentedQueue.empty());
+  EXPECT_LT(segmentedQueue.capacity(), capacityBeforeRemove);
+  EXPECT_GT(segmentedQueue.capacity(), 0);
+}
+
+TEST(SegmentedQueue, MiddleBlockTest) {
+  // This test tests that the SegmentedQueue will behave correctly when
+  // the reference of front() and back() are not aligned to the head/back
+  // of a block.
+
+  constexpr uint8_t blockSize = 3;
+  constexpr uint8_t maxBlockCount = 3;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount);
+
+  for (uint32_t index = 0; index < blockSize * (maxBlockCount - 1); index++) {
+    EXPECT_TRUE(segmentedQueue.push_back(index));
+  }
+
+  segmentedQueue.pop_front();
+  segmentedQueue.pop_front();
+  EXPECT_TRUE(segmentedQueue.push_back(6));
+  EXPECT_TRUE(segmentedQueue.push_back(7));
+
+  // segmentedQueue = [[6, 7, 2], [3, 4, 5], [X]]
+  EXPECT_EQ(segmentedQueue.front(), 2);
+  EXPECT_EQ(segmentedQueue.back(), 7);
+
+  EXPECT_TRUE(segmentedQueue.push_back(8));
+  EXPECT_EQ(segmentedQueue.back(), 8);
+
+  // segmentedQueue = [[x, x, 2], [3, 4, 5], [6, 7, 8]]
+  EXPECT_TRUE(segmentedQueue.push_back(9));
+  EXPECT_TRUE(segmentedQueue.push_back(10));
+
+  for (int i = 0; i < segmentedQueue.size(); i++) {
+    EXPECT_EQ(segmentedQueue[i], i + 2);
+  }
+}
+
+TEST(SegmentedQueue, RemoveMatchesEnoughItem) {
+  constexpr uint8_t blockSize = 3;
+  constexpr uint8_t maxBlockCount = 2;
+  ssize_t constCounter = 0;
+  SegmentedQueue<ConstructorCount, blockSize> segmentedQueue(maxBlockCount);
+
+  for (uint32_t index = 0; index < blockSize * maxBlockCount; index++) {
+    EXPECT_TRUE(segmentedQueue.emplace_back(index, &constCounter));
+  }
+
+  EXPECT_EQ(
+      3, segmentedQueue.removeMatchedFromBack(
+             [](ConstructorCount &element) { return element.getValue() <= 4; },
+             3));
+
+  EXPECT_EQ(segmentedQueue[0].getValue(), 0);
+  EXPECT_EQ(segmentedQueue[1].getValue(), 1);
+  EXPECT_EQ(segmentedQueue[2].getValue(), 5);
+  EXPECT_EQ(segmentedQueue.size(), blockSize * maxBlockCount - 3);
+  EXPECT_EQ(segmentedQueue.front().getValue(), 0);
+  EXPECT_EQ(segmentedQueue.back().getValue(), 5);
+  EXPECT_EQ(constCounter, 3);
+}
+
+TEST(SegmentedQueue, RemoveMatchesEmptyQueue) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 2;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount);
+
+  EXPECT_EQ(0, segmentedQueue.removeMatchedFromBack(
+                   [](int element) { return element >= 5; }, 3));
+  EXPECT_EQ(segmentedQueue.size(), 0);
+}
+
+TEST(SegmentedQueue, RemoveMatchesSingleElementQueue) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 2;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount);
+
+  EXPECT_TRUE(segmentedQueue.push_back(1));
+
+  EXPECT_EQ(1, segmentedQueue.removeMatchedFromBack(
+                   [](int element) { return element == 1; }, 3));
+  EXPECT_EQ(segmentedQueue.size(), 0);
+}
+
+TEST(SegmentedQueue, RemoveMatchesTemp) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 2;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount);
+
+  EXPECT_TRUE(segmentedQueue.push_back(1));
+
+  EXPECT_EQ(1, segmentedQueue.removeMatchedFromBack(
+                   [](int element) { return element == 1; }, 3));
+  EXPECT_EQ(segmentedQueue.size(), 0);
+}
+
+TEST(SegmentedQueue, RemoveMatchesTailInMiddle) {
+  constexpr uint8_t blockSize = 5;
+  constexpr uint8_t maxBlockCount = 2;
+  SegmentedQueue<int, blockSize> segmentedQueue(maxBlockCount);
+
+  for (uint32_t index = 0; index < blockSize * maxBlockCount; index++) {
+    EXPECT_TRUE(segmentedQueue.emplace_back(index));
+  }
+
+  segmentedQueue.pop();
+  segmentedQueue.pop();
+  segmentedQueue.push_back(blockSize * maxBlockCount);
+  segmentedQueue.push_back(blockSize * maxBlockCount + 1);
+
+  EXPECT_EQ(5, segmentedQueue.removeMatchedFromBack(
+                   [](int item) { return item % 2 == 0; }, 10));
+  EXPECT_EQ(segmentedQueue.size(), 5);
+
+  EXPECT_EQ(segmentedQueue[0], 3);
+  EXPECT_EQ(segmentedQueue[1], 5);
+  EXPECT_EQ(segmentedQueue[2], 7);
+  EXPECT_EQ(segmentedQueue[3], 9);
+  EXPECT_EQ(segmentedQueue[4], 11);
+
+  EXPECT_EQ(segmentedQueue.front(), 3);
+  EXPECT_EQ(segmentedQueue.back(), 11);
+}
+
+TEST(SegmentedQueue, RemoveMatchesWithFreeCallback) {
+  constexpr uint8_t blockSize = 3;
+  constexpr uint8_t maxBlockCount = 2;
+  int8_t counter = 0;
+  SegmentedQueue<uint8_t, blockSize> segmentedQueue(maxBlockCount);
+
+  for (uint8_t index = 0; index < blockSize * maxBlockCount; ++index) {
+    EXPECT_TRUE(segmentedQueue.push_back(index));
+  }
+
+  EXPECT_EQ(3, segmentedQueue.removeMatchedFromBack(
+                   [](uint8_t item) { return item % 2 == 0; }, 3,
+                   [](uint8_t item, void *counter) {
+                     *static_cast<int8_t *>(counter) -= item;
+                   },
+                   &counter));
+
+  EXPECT_EQ(counter, -6);  // item 0, 2, 4 is removed.
+  EXPECT_EQ(segmentedQueue.size(), 3);
+  EXPECT_EQ(segmentedQueue.back(), 5);
+  EXPECT_EQ(segmentedQueue.front(), 1);
+}
+
+TEST(SegmentedQueue, PseudoRandomStressTest) {
+  // This test uses std::deque as reference implementation to make sure
+  // that chre::SegmentedQueue is functioning correctly.
+
+  constexpr uint32_t maxIteration = 200;
+
+  constexpr uint32_t totalSize = 1024;
+  constexpr uint32_t blockSize = 16;
+
+  ssize_t referenceQueueConstructedCounter = 0;
+  ssize_t segmentedQueueConstructedCounter = 0;
+
+  std::srand(0xbeef);
+
+  deque<ConstructorCount> referenceDeque;
+  SegmentedQueue<ConstructorCount, blockSize> testSegmentedQueue(totalSize /
+                                                                 blockSize);
+
+  for (uint32_t currentIteration = 0; currentIteration < maxIteration;
+       currentIteration++) {
+    OperationType operationType = static_cast<OperationType>(
+        std::rand() % chre::asBaseType(OperationType::OPERATION_TYPE_COUNT));
+    int temp = std::rand();
+    switch (operationType) {
+      case OperationType::PUSH_BACK:
+        if (referenceDeque.size() < totalSize) {
+          ASSERT_TRUE(testSegmentedQueue.push_back(
+              ConstructorCount(temp, &segmentedQueueConstructedCounter)));
+          referenceDeque.push_back(
+              ConstructorCount(temp, &referenceQueueConstructedCounter));
+        } else {
+          ASSERT_FALSE(testSegmentedQueue.push_back(
+              ConstructorCount(temp, &segmentedQueueConstructedCounter)));
+        }
+        break;
+
+      case OperationType::EMPLACE_BACK:
+        if (referenceDeque.size() < totalSize) {
+          ASSERT_TRUE(testSegmentedQueue.emplace_back(
+              temp, &segmentedQueueConstructedCounter));
+          referenceDeque.emplace_back(temp, &referenceQueueConstructedCounter);
+        } else {
+          ASSERT_FALSE(testSegmentedQueue.emplace_back(
+              temp, &segmentedQueueConstructedCounter));
+        }
+        break;
+
+      case OperationType::POP_FRONT:
+        ASSERT_EQ(testSegmentedQueue.empty(), referenceDeque.empty());
+        if (!testSegmentedQueue.empty()) {
+          testSegmentedQueue.pop_front();
+          referenceDeque.pop_front();
+        }
+        break;
+
+      case OperationType::REMOVE: {
+        ASSERT_EQ(testSegmentedQueue.size(), referenceDeque.size());
+        if (!testSegmentedQueue.empty()) {
+          // Creates 50% chance for removing index that is out of bound
+          size_t index = std::rand() % (testSegmentedQueue.size() * 2);
+          if (index >= referenceDeque.size()) {
+            ASSERT_FALSE(testSegmentedQueue.remove(index));
+          } else {
+            ASSERT_TRUE(testSegmentedQueue.remove(index));
+            referenceDeque.erase(referenceDeque.begin() + index);
+          }
+        }
+      } break;
+
+      case OperationType::BATCH_REMOVE: {
+        ASSERT_EQ(testSegmentedQueue.size(), referenceDeque.size());
+        // Always try to remove a quarter of elements
+        size_t targetRemoveElement = referenceDeque.size() * 0.25;
+        vector<size_t> removedIndex;
+        for (int i = referenceDeque.size() - 1; i >= 0; i--) {
+          if (removedIndex.size() == targetRemoveElement) {
+            break;
+          } else if (referenceDeque[i].getValue() % 2 == 0) {
+            removedIndex.push_back(i);
+          }
+        }
+        for (auto idx : removedIndex) {
+          referenceDeque.erase(referenceDeque.begin() + idx);
+        }
+
+        ASSERT_EQ(removedIndex.size(), testSegmentedQueue.removeMatchedFromBack(
+                                           [](ConstructorCount &item) {
+                                             return item.getValue() % 2 == 0;
+                                           },
+                                           targetRemoveElement));
+      } break;
+
+      case OperationType::OPERATION_TYPE_COUNT:
+        // Should not be here, create this to prevent compiler error from
+        // -Wswitch.
+        FAIL();
+        break;
+    }
+
+    // Complete check
+    ASSERT_EQ(segmentedQueueConstructedCounter,
+              referenceQueueConstructedCounter);
+    ASSERT_EQ(testSegmentedQueue.size(), referenceDeque.size());
+    ASSERT_EQ(testSegmentedQueue.empty(), referenceDeque.empty());
+    if (!testSegmentedQueue.empty()) {
+      ASSERT_EQ(testSegmentedQueue.back().getValue(),
+                referenceDeque.back().getValue());
+      ASSERT_EQ(testSegmentedQueue.front().getValue(),
+                referenceDeque.front().getValue());
+    }
+    for (size_t idx = 0; idx < testSegmentedQueue.size(); idx++) {
+      ASSERT_EQ(testSegmentedQueue[idx].getValue(),
+                referenceDeque[idx].getValue());
+    }
+  }
+}
\ No newline at end of file
diff --git a/util/tests/stats_container_test.cc b/util/tests/stats_container_test.cc
new file mode 100644
index 0000000..d3fba3c
--- /dev/null
+++ b/util/tests/stats_container_test.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/system/stats_container.h"
+#include "gtest/gtest.h"
+
+TEST(StatsContainer, MeanBasicTest) {
+  chre::StatsContainer<uint8_t> testContainer;
+
+  ASSERT_EQ(testContainer.getMean(), 0);
+
+  testContainer.addValue(10);
+  testContainer.addValue(20);
+  ASSERT_EQ(testContainer.getMean(), 15);
+
+  testContainer.addValue(40);
+  ASSERT_EQ(testContainer.getMean(), (10 + 20 + 40) / 3);
+}
+
+TEST(StatsContainer, UINTMeanOverflowTest) {
+  chre::StatsContainer<uint8_t> testContainer;
+
+  testContainer.addValue(200);
+  testContainer.addValue(100);
+  ASSERT_EQ(testContainer.getMean(), 150);
+}
+
+TEST(StatsContainer, AddSmallerValueThanMeanCheck) {
+  chre::StatsContainer<uint16_t> testContainer;
+
+  testContainer.addValue(10);
+  testContainer.addValue(20);
+  testContainer.addValue(30);
+  ASSERT_EQ(testContainer.getMean(), 20);
+
+  testContainer.addValue(4);
+  ASSERT_EQ(testContainer.getMean(), 16);
+}
+
+TEST(StatsContainer, AddBiggerValueThanMeanCheck) {
+  chre::StatsContainer<uint16_t> testContainer;
+
+  testContainer.addValue(10);
+  testContainer.addValue(20);
+  testContainer.addValue(30);
+  ASSERT_EQ(testContainer.getMean(), 20);
+
+  testContainer.addValue(40);
+  ASSERT_EQ(testContainer.getMean(), 25);
+}
+
+TEST(StatsContainer, OverAverageWindowCheck) {
+  uint64_t maxCount = 3;
+  chre::StatsContainer<uint16_t> testContainer(maxCount);
+
+  testContainer.addValue(10);
+  testContainer.addValue(20);
+  testContainer.addValue(30);
+  ASSERT_EQ(testContainer.getMean(), 20);
+
+  testContainer.addValue(40);
+
+  /**
+   * Only check if StatsContainer still works after have more element than its
+   * averageWindow. Does not check the correctness of the estimated value
+   */
+  ASSERT_GT(testContainer.getMean(), 20);
+}
\ No newline at end of file
diff --git a/util/tests/synchronized_expandable_memory_pool_test.cc b/util/tests/synchronized_expandable_memory_pool_test.cc
new file mode 100644
index 0000000..3b88e59
--- /dev/null
+++ b/util/tests/synchronized_expandable_memory_pool_test.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/util/synchronized_expandable_memory_pool.h"
+
+#include "chre/util/synchronized_memory_pool.h"
+#include "gtest/gtest.h"
+
+using chre::SynchronizedExpandableMemoryPool;
+
+namespace {
+
+class ConstructorCount {
+ public:
+  ConstructorCount(int value_) : value(value_) {
+    sConstructedCounter++;
+  }
+  ~ConstructorCount() {
+    sConstructedCounter--;
+  }
+  int getValue() {
+    return value;
+  }
+
+  static ssize_t sConstructedCounter;
+
+ private:
+  const int value;
+};
+
+ssize_t ConstructorCount::sConstructedCounter = 0;
+
+}  // namespace
+
+TEST(SynchronizedExpandAbleMemoryPool, InitStateTest) {
+  constexpr uint8_t blockSize = 3;
+  constexpr uint8_t maxBlockCount = 5;
+  constexpr uint8_t staticBlockCount = 3;
+  SynchronizedExpandableMemoryPool<int, blockSize, maxBlockCount>
+      testMemoryPool(staticBlockCount);
+
+  ASSERT_EQ(testMemoryPool.getFreeSpaceCount(), blockSize * maxBlockCount);
+  ASSERT_EQ(testMemoryPool.getBlockCount(), staticBlockCount);
+}
+
+TEST(SynchronizedExpandAbleMemoryPool, OneAllocateAndDeallocate) {
+  constexpr uint8_t blockSize = 3;
+  constexpr uint8_t maxBlockCount = 5;
+
+  SynchronizedExpandableMemoryPool<ConstructorCount, blockSize, maxBlockCount>
+      testMemoryPool;
+  ASSERT_EQ(testMemoryPool.getBlockCount(), 1);
+
+  ConstructorCount *temp = testMemoryPool.allocate(10);
+  ASSERT_NE(temp, nullptr);
+  ASSERT_EQ(ConstructorCount::sConstructedCounter, 1);
+  ASSERT_EQ(testMemoryPool.getFreeSpaceCount(), blockSize * maxBlockCount - 1);
+  testMemoryPool.deallocate(temp);
+  ASSERT_EQ(ConstructorCount::sConstructedCounter, 0);
+  ASSERT_EQ(testMemoryPool.getFreeSpaceCount(), blockSize * maxBlockCount);
+}
+
+TEST(SynchronizedExpandAbleMemoryPool, HysteresisDeallocation) {
+  constexpr uint8_t blockSize = 3;
+  constexpr uint8_t maxBlockCount = 4;
+  constexpr uint8_t staticBlockCount = 2;
+
+  SynchronizedExpandableMemoryPool<int, blockSize, maxBlockCount>
+      testMemoryPool(staticBlockCount);
+  int *tempDataPtrs[blockSize * maxBlockCount];
+
+  for (int i = 0; i < blockSize * maxBlockCount; i++) {
+    tempDataPtrs[i] = testMemoryPool.allocate(i);
+  }
+  EXPECT_EQ(testMemoryPool.getBlockCount(), maxBlockCount);
+
+  for (int i = blockSize * maxBlockCount - 1;
+       i >= (maxBlockCount - 1) * blockSize; i--) {
+    testMemoryPool.deallocate(tempDataPtrs[i]);
+  }
+
+  // Should not remove the last block if it just got empty.
+  EXPECT_EQ(testMemoryPool.getBlockCount(), maxBlockCount);
+
+  for (int i = 0; i < (maxBlockCount - 1) * blockSize; i++) {
+    testMemoryPool.deallocate(tempDataPtrs[i]);
+  }
+
+  // Once it is empty, it should not still hold maxBlockCount as before.
+  EXPECT_EQ(testMemoryPool.getFreeSpaceCount(), blockSize * maxBlockCount);
+  EXPECT_EQ(testMemoryPool.getBlockCount(), staticBlockCount);
+}
\ No newline at end of file
diff --git a/util/tests/unique_ptr_test.cc b/util/tests/unique_ptr_test.cc
index d005b08..e4f7ebd 100644
--- a/util/tests/unique_ptr_test.cc
+++ b/util/tests/unique_ptr_test.cc
@@ -1,3 +1,19 @@
+/*
+ * 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"
@@ -143,3 +159,15 @@
 
   EXPECT_EQ(Value::constructionCounter, 0);
 }
+
+TEST(UniquePtr, OverAlignedTest) {
+  // Explicitly overaligned structure larger than std::max_align_t.
+  struct alignas(32) OverAlignedStruct {
+    uint32_t x[32];
+  };
+  static_assert(alignof(OverAlignedStruct) > alignof(std::max_align_t));
+
+  UniquePtr<OverAlignedStruct> ptr = MakeUnique<OverAlignedStruct>();
+  ASSERT_EQ(reinterpret_cast<uintptr_t>(ptr.get()) % alignof(OverAlignedStruct),
+            0);
+}
\ No newline at end of file
diff --git a/util/util.mk b/util/util.mk
index 0a42dae..7c6337e 100644
--- a/util/util.mk
+++ b/util/util.mk
@@ -11,10 +11,13 @@
 
 COMMON_SRCS += $(CHRE_PREFIX)/util/buffer_base.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/dynamic_vector_base.cc
+COMMON_SRCS += $(CHRE_PREFIX)/util/intrusive_list_base.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/audio.cc
+COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/ble.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/callbacks.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/debug.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/nanoapp/wifi.cc
+COMMON_SRCS += $(CHRE_PREFIX)/util/system/event_callbacks.cc
 COMMON_SRCS += $(CHRE_PREFIX)/util/system/debug_dump.cc
 
 # GoogleTest Source Files ######################################################
@@ -23,17 +26,23 @@
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/atomic_spsc_queue_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/blocking_queue_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/buffer_test.cc
+GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/copyable_fixed_size_vector_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/debug_dump_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/dynamic_vector_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/fixed_size_vector_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/heap_test.cc
+GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/intrusive_list_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/lock_guard_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/memory_pool_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/optional_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/priority_queue_test.cc
+GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/raw_storage_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/ref_base_test.cc
+GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/segmented_queue_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/shared_ptr_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/singleton_test.cc
+GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/stats_container_test.cc
+GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/synchronized_expandable_memory_pool_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/time_test.cc
 GOOGLETEST_SRCS += $(CHRE_PREFIX)/util/tests/unique_ptr_test.cc
 
diff --git a/variant/exynos-embos/static_nanoapps.cc b/variant/exynos-embos/static_nanoapps.cc
new file mode 100644
index 0000000..0f7eccf
--- /dev/null
+++ b/variant/exynos-embos/static_nanoapps.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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/core/static_nanoapps.h"
+#include "chre/apps/apps.h"
+#include "chre/util/macros.h"
+
+namespace chre {
+
+//! The default list of static nanoapps to load.
+const StaticNanoappInitFunction kStaticNanoappList[] = {
+    //  initializeStaticNanoappAudioWorld,
+    //  initializeStaticNanoappDebugDumpWorld,
+    //  initializeStaticNanoappGnssWorld,
+    //  initializeStaticNanoappHelloWorld,
+    //  initializeStaticNanoappMessageWorld,
+    //  initializeStaticNanoappSensorWorld,
+    //  initializeStaticNanoappSpammer,
+    //  initializeStaticNanoappTimerWorld,
+    //  initializeStaticNanoappUnloadTester,
+    //  initializeStaticNanoappWifiWorld,
+    //  initializeStaticNanoappWwanWorld,
+};
+
+//! The size of the default static nanoapp list.
+const size_t kStaticNanoappCount = ARRAY_SIZE(kStaticNanoappList);
+
+}  // namespace chre
diff --git a/variant/exynos-embos/variant.mk b/variant/exynos-embos/variant.mk
new file mode 100644
index 0000000..03868f4
--- /dev/null
+++ b/variant/exynos-embos/variant.mk
@@ -0,0 +1,45 @@
+
+ifeq ($(ANDROID_BUILD_TOP),)
+$(error "You should supply an ANDROID_BUILD_TOP environment variable \
+         containing a path to the Android source tree. This is typically \
+         provided by initializing the Android build environment.")
+endif
+
+# Variant Prefix ###############################################################
+
+VARIANT_PREFIX = $(ANDROID_BUILD_TOP)/system/chre/variant
+
+# Chre Version String ##########################################################
+
+COMMIT_HASH_COMMAND = git describe --always --long --dirty
+COMMIT_HASH = $(shell $(COMMIT_HASH_COMMAND))
+
+COMMON_CFLAGS += -DCHRE_VERSION_STRING="\"chre=embos@$(COMMIT_HASH)\""
+
+# Common Compiler Flags ########################################################
+
+# Supply a symbol to indicate that the build variant supplies the static
+# nanoapp list.
+COMMON_CFLAGS += -DCHRE_VARIANT_SUPPLIES_STATIC_NANOAPP_LIST
+
+# CHRE event count #############################################################
+
+EMBOS_CFLAGS += -DCHRE_EVENT_PER_BLOCK=32
+EMBOS_CFLAGS += -DCHRE_MAX_EVENT_BLOCKS=4
+
+EMBOS_CFLAGS += -DCHRE_UNSCHEDULED_EVENT_PER_BLOCK=32
+EMBOS_CFLAGS += -DCHRE_MAX_UNSCHEDULED_EVENT_BLOCKS=4
+
+# Optional Features ############################################################
+
+CHRE_AUDIO_SUPPORT_ENABLED = true
+CHRE_GNSS_SUPPORT_ENABLED = false
+CHRE_SENSORS_SUPPORT_ENABLED = true
+CHRE_WIFI_SUPPORT_ENABLED = false
+CHRE_WWAN_SUPPORT_ENABLED = false
+CHRE_BLE_SUPPORT_ENABLED = false
+
+# Common Source Files ##########################################################
+
+COMMON_SRCS += $(VARIANT_PREFIX)/exynos-embos/static_nanoapps.cc
+
diff --git a/variant/tinysys/static_nanoapps.cc b/variant/tinysys/static_nanoapps.cc
new file mode 100644
index 0000000..6af61de
--- /dev/null
+++ b/variant/tinysys/static_nanoapps.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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/core/static_nanoapps.h"
+#include "chre/apps/apps.h"
+#include "chre/util/macros.h"
+
+namespace chre {
+
+//! The default list of static nanoapps to load.
+const StaticNanoappInitFunction kStaticNanoappList[] = {
+    // initializeStaticNanoappAudioWorld,
+    // initializeStaticNanoappBleWorld,
+    // initializeStaticNanoappDebugDumpWorld,
+    // initializeStaticNanoappGnssWorld,
+    // initializeStaticNanoappHelloWorld,
+    // initializeStaticNanoappMessageWorld,
+    // initializeStaticNanoappSensorWorld,
+    // initializeStaticNanoappSpammer,
+    // initializeStaticNanoappTimerWorld,
+    // initializeStaticNanoappUnloadTester,
+    // initializeStaticNanoappWifiWorld,
+    // initializeStaticNanoappWwanWorld,
+};
+
+//! The size of the default static nanoapp list.
+const size_t kStaticNanoappCount = ARRAY_SIZE(kStaticNanoappList);
+
+}  // namespace chre
diff --git a/variant/tinysys/variant.mk b/variant/tinysys/variant.mk
new file mode 100644
index 0000000..15cf183
--- /dev/null
+++ b/variant/tinysys/variant.mk
@@ -0,0 +1,77 @@
+
+ifeq ($(ANDROID_BUILD_TOP),)
+$(error "You should supply an ANDROID_BUILD_TOP environment variable \
+         containing a path to the Android source tree. This is typically \
+         provided by initializing the Android build environment.")
+endif
+
+# Variant Prefix ###############################################################
+
+VARIANT_PREFIX = $(ANDROID_BUILD_TOP)/system/chre/variant
+
+# Chre Version String ##########################################################
+
+COMMIT_HASH_COMMAND = git describe --always --long --dirty
+COMMIT_HASH = $(shell $(COMMIT_HASH_COMMAND))
+
+COMMON_CFLAGS += -DCHRE_VERSION_STRING="\"chre=tinysys@$(COMMIT_HASH)\""
+
+# Platform-specific Settings ###################################################
+
+TINYSYS_CFLAGS += -DP_MODE_0
+TINYSYS_CFLAGS += -DCFG_AMP_CORE1_EN
+TINYSYS_CFLAGS += -DCFG_DMA_SUPPORT
+
+TINYSYS_CFLAGS += -DCHRE_FREERTOS_TASK_PRIORITY=2
+
+# Platform-specific Includes ###################################################
+
+# Tinysys include paths
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/kernel/FreeRTOS_v10.1.0.1/FreeRTOS/Source/include
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/kernel/FreeRTOS_v10.1.0.1/FreeRTOS/Source/portable/LLVM/RV55
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/common/drivers/dma/v3/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/common/drivers/irq/v3/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/common/drivers/mbox/v2/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/common/include
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/common/middleware/MemMang/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/drivers/RV55_A/$(TINYSYS_PLATFORM)/dma
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/drivers/RV55_A/$(TINYSYS_PLATFORM)/intc/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/drivers/RV55_A/$(TINYSYS_PLATFORM)/mbox
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/drivers/RV55_A/$(TINYSYS_PLATFORM)/resrc_req/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/drivers/common/dma/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/drivers/common/dram_region_mgmt
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/drivers/common/xgpt/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/middleware/sensorhub/include
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/project/RV55_A/$(TINYSYS_PLATFORM)/platform/inc
+TINYSYS_CFLAGS += -I$(RISCV_TINYSYS_PREFIX)/scp/project/RV55_A/common/platform/inc
+
+# Clang include paths
+TINYSYS_CFLAGS += -I$(RISCV_TOOLCHAIN_PATH)/lib/clang/9.0.1/include
+TINYSYS_CFLAGS += -I$(RISCV_TOOLCHAIN_PATH)/dkwlib/MRV55E03v/include
+
+# Common Compiler Flags ########################################################
+
+# Supply a symbol to indicate that the build variant supplies the static
+# nanoapp list.
+COMMON_CFLAGS += -DCHRE_VARIANT_SUPPLIES_STATIC_NANOAPP_LIST
+
+# CHRE event count #############################################################
+
+TINYSYS_CFLAGS += -DCHRE_EVENT_PER_BLOCK=32
+TINYSYS_CFLAGS += -DCHRE_MAX_EVENT_BLOCKS=4
+
+TINYSYS_CFLAGS += -DCHRE_UNSCHEDULED_EVENT_PER_BLOCK=32
+TINYSYS_CFLAGS += -DCHRE_MAX_UNSCHEDULED_EVENT_BLOCKS=4
+
+# Optional Features ############################################################
+
+CHRE_AUDIO_SUPPORT_ENABLED = true
+CHRE_GNSS_SUPPORT_ENABLED = false
+CHRE_SENSORS_SUPPORT_ENABLED = true
+CHRE_WIFI_SUPPORT_ENABLED = false
+CHRE_WWAN_SUPPORT_ENABLED = false
+CHRE_BLE_SUPPORT_ENABLED = true
+
+# Common Source Files ##########################################################
+
+COMMON_SRCS += $(VARIANT_PREFIX)/tinysys/static_nanoapps.cc