merge in oc-release history after reset to master
diff --git a/Android.bp b/Android.bp
index e039773..8e2c849 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,10 @@
 cc_binary {
     name: "chre_test_client",
     proprietary: true,
+    local_include_dirs: [
+        "chre_api/include/chre_api",
+        "util/include",
+    ],
     srcs: [
         "host/common/test/chre_test_client.cc",
     ],
diff --git a/Makefile b/Makefile
index f8a6c99..4066d5b 100644
--- a/Makefile
+++ b/Makefile
@@ -41,6 +41,7 @@
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/core/api/dal
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/core/api/mproc
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/core/api/systemdrivers
+HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/platform/rtld/inc
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/api
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/common/idl/inc
 HEXAGON_CFLAGS += -I$(SLPI_PREFIX)/Sensors/common/util/mathtools/inc
diff --git a/apps/gnss_world/gnss_world.cc b/apps/gnss_world/gnss_world.cc
index 291641c..7ebb895 100644
--- a/apps/gnss_world/gnss_world.cc
+++ b/apps/gnss_world/gnss_world.cc
@@ -22,8 +22,6 @@
 #define LOG_TAG "[GnssWorld]"
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
@@ -62,14 +60,16 @@
   // TODO: Implement this.
 }
 
-void nanoappStop() {
+void nanoappEnd() {
   LOGI("Stopped");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(GnssWorld);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(GnssWorld, chre::kGnssWorldAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/apps/hello_world/Makefile b/apps/hello_world/Makefile
index c98268d..981d01c 100644
--- a/apps/hello_world/Makefile
+++ b/apps/hello_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 = hello_world
+NANOAPP_ID = 0x0123456789000001
+NANOAPP_VERSION = 0x00000001
+
+NANOAPP_NAME_STRING = \"Hello\ World\"
 
 # Common Compiler Flags ########################################################
 
diff --git a/apps/hello_world/hello_world.cc b/apps/hello_world/hello_world.cc
index 71ff230..3b49b75 100644
--- a/apps/hello_world/hello_world.cc
+++ b/apps/hello_world/hello_world.cc
@@ -15,40 +15,43 @@
  */
 
 #include <chre.h>
-#include <cinttypes>
+#include <inttypes.h>
 
-#include "chre/util/nanoapp/log.h"
-
-#define LOG_TAG "[HelloWorld]"
+/**
+ * @file
+ * A very basic example nanoapp that just logs activity in its entry points.
+ * Note that code wrapped in #ifdef CHRE_NANOAPP_INTERNAL is only relevant when
+ * a nanoapp is to be statically built into the CHRE system binary, which would
+ * make it a "system nanoapp" rather than a true dynamically loadable nanoapp.
+ */
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
 
 bool nanoappStart() {
-  LOGI("App started on platform ID %" PRIx64, chreGetPlatformId());
+  chreLog(CHRE_LOG_INFO, "Hello, world!");
   return true;
 }
 
 void nanoappHandleEvent(uint32_t senderInstanceId,
                         uint16_t eventType,
-                        const void *eventData) {
-  uint64_t currentTime = chreGetTime();
-  LOGI("Received event 0x%" PRIx16 " at time %" PRIu64,
-       eventType, currentTime);
+                        const void * /*eventData*/) {
+  chreLog(CHRE_LOG_INFO, "Received event 0x%" PRIx16 " from 0x%" PRIx32 " at "
+          "time %" PRIu64, eventType, senderInstanceId, chreGetTime());
 }
 
-void nanoappStop() {
-  LOGI("Stopped");
+void nanoappEnd() {
+  chreLog(CHRE_LOG_INFO, "Goodbye, world!");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(HelloWorld);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(HelloWorld, chre::kHelloWorldAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/apps/imu_cal/imu_cal.cc b/apps/imu_cal/imu_cal.cc
index 127d892..9f540d5 100644
--- a/apps/imu_cal/imu_cal.cc
+++ b/apps/imu_cal/imu_cal.cc
@@ -25,8 +25,6 @@
 #define LOG_TAG "[ImuCal]"
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
@@ -162,15 +160,17 @@
   }
 }
 
-void nanoappStop() {
+void nanoappEnd() {
   // TODO: Unscribe to sensors
   LOGI("Stopped");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(ImuCal);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(ImuCal, chre::kImuCalAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/apps/include/chre/apps/apps.h b/apps/include/chre/apps/apps.h
index 70d4781..61ae234 100644
--- a/apps/include/chre/apps/apps.h
+++ b/apps/include/chre/apps/apps.h
@@ -17,18 +17,19 @@
 #ifndef CHRE_APPS_APPS_H_
 #define CHRE_APPS_APPS_H_
 
-#include "chre/platform/platform_nanoapp.h"
+#include "chre/core/nanoapp.h"
+#include "chre/util/unique_ptr.h"
 
 namespace chre {
 
-extern PlatformNanoapp gNanoappGnssWorld;
-extern PlatformNanoapp gNanoappHelloWorld;
-extern PlatformNanoapp gNanoappImuCal;
-extern PlatformNanoapp gNanoappMessageWorld;
-extern PlatformNanoapp gNanoappSensorWorld;
-extern PlatformNanoapp gNanoappTimerWorld;
-extern PlatformNanoapp gNanoappWifiWorld;
-extern PlatformNanoapp gNanoappWwanWorld;
+extern UniquePtr<Nanoapp> *gNanoappGnssWorld;
+extern UniquePtr<Nanoapp> *gNanoappHelloWorld;
+extern UniquePtr<Nanoapp> *gNanoappImuCal;
+extern UniquePtr<Nanoapp> *gNanoappMessageWorld;
+extern UniquePtr<Nanoapp> *gNanoappSensorWorld;
+extern UniquePtr<Nanoapp> *gNanoappTimerWorld;
+extern UniquePtr<Nanoapp> *gNanoappWifiWorld;
+extern UniquePtr<Nanoapp> *gNanoappWwanWorld;
 
 } // namespace chre
 
diff --git a/apps/message_world/message_world.cc b/apps/message_world/message_world.cc
index 5bd8b3e..a6ac289 100644
--- a/apps/message_world/message_world.cc
+++ b/apps/message_world/message_world.cc
@@ -22,8 +22,6 @@
 #define LOG_TAG "[MsgWorld]"
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
@@ -75,14 +73,16 @@
   }
 }
 
-void nanoappStop() {
+void nanoappEnd() {
   LOGI("Stopped");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(MessageWorld);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(MessageWorld, chre::kMessageWorldAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/apps/sensor_world/sensor_world.cc b/apps/sensor_world/sensor_world.cc
index f084626..62aac96 100644
--- a/apps/sensor_world/sensor_world.cc
+++ b/apps/sensor_world/sensor_world.cc
@@ -25,8 +25,6 @@
 #define LOG_TAG "[SensorWorld]"
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
@@ -255,14 +253,16 @@
   }
 }
 
-void nanoappStop() {
+void nanoappEnd() {
   LOGI("Stopped");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(SensorWorld);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(SensorWorld, chre::kSensorWorldAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/apps/timer_world/timer_world.cc b/apps/timer_world/timer_world.cc
index 5a65231..48dbed2 100644
--- a/apps/timer_world/timer_world.cc
+++ b/apps/timer_world/timer_world.cc
@@ -22,8 +22,6 @@
 #define LOG_TAG "[TimerWorld]"
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
@@ -71,14 +69,16 @@
   }
 }
 
-void nanoappStop() {
+void nanoappEnd() {
   LOGI("Stopped");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(TimerWorld);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(TimerWorld, chre::kTimerWorldAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/apps/wifi_world/wifi_world.cc b/apps/wifi_world/wifi_world.cc
index 0a0f0f8..0a9e3c5 100644
--- a/apps/wifi_world/wifi_world.cc
+++ b/apps/wifi_world/wifi_world.cc
@@ -26,8 +26,6 @@
 //#define WIFI_WORLD_VERBOSE_WIFI_RESULT_LOGS
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
@@ -214,14 +212,16 @@
   }
 }
 
-void nanoappStop() {
+void nanoappEnd() {
   LOGI("Wifi world app stopped");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(WifiWorld);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(WifiWorld, chre::kWifiWorldAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/apps/wwan_world/wwan_world.cc b/apps/wwan_world/wwan_world.cc
index a803bb2..7717000 100644
--- a/apps/wwan_world/wwan_world.cc
+++ b/apps/wwan_world/wwan_world.cc
@@ -22,8 +22,6 @@
 #define LOG_TAG "[WwanWorld]"
 
 #ifdef CHRE_NANOAPP_INTERNAL
-#include "chre/platform/platform_static_nanoapp_init.h"
-
 namespace chre {
 namespace {
 #endif  // CHRE_NANOAPP_INTERNAL
@@ -55,14 +53,16 @@
   // TODO: Implement this.
 }
 
-void nanoappStop() {
+void nanoappEnd() {
   LOGI("Stopped");
 }
 
 #ifdef CHRE_NANOAPP_INTERNAL
-}  // namespace
-
-PLATFORM_STATIC_NANOAPP_INIT(WwanWorld);
-
+}  // anonymous namespace
 }  // namespace chre
+
+#include "chre/util/nanoapp/app_id.h"
+#include "chre/platform/static_nanoapp_init.h"
+
+CHRE_STATIC_NANOAPP_INIT(WwanWorld, chre::kWwanWorldAppId, 0);
 #endif  // CHRE_NANOAPP_INTERNAL
diff --git a/build/app_support/google_slpi/libchre_slpi_skel.so b/build/app_support/google_slpi/libchre_slpi_skel.so
new file mode 100755
index 0000000..05ba1b5
--- /dev/null
+++ b/build/app_support/google_slpi/libchre_slpi_skel.so
Binary files differ
diff --git a/build/arch/x86.mk b/build/arch/x86.mk
index 63858b9..116d8e3 100644
--- a/build/arch/x86.mk
+++ b/build/arch/x86.mk
@@ -22,6 +22,9 @@
 # Add x86 compiler flags.
 TARGET_CFLAGS += $(X86_CFLAGS)
 
+# x86 is purely used for testing, so always include debugging symbols
+TARGET_CFLAGS += -g
+
 # Enable position independence.
 TARGET_CFLAGS += -fpic
 
diff --git a/build/nanoapp/app.mk b/build/nanoapp/app.mk
index fdb8bcf..0d9be6e 100644
--- a/build/nanoapp/app.mk
+++ b/build/nanoapp/app.mk
@@ -22,6 +22,22 @@
          This should be assigned by the Makefile that includes app.mk.")
 endif
 
+ifeq ($(NANOAPP_NAME_STRING),)
+$(error The NANOAPP_NAME_STRING variable must be set to the friendly name of \
+        the nanoapp. This should be assigned by the Makefile that includes \
+        app.mk.)
+endif
+
+ifeq ($(NANOAPP_VENDOR_STRING),)
+$(info NANOAPP_VENDOR_STRING not supplied, defaulting to "Google".)
+NANOAPP_VENDOR_STRING = \"Google\"
+endif
+
+ifeq ($(NANOAPP_IS_SYSTEM_NANOAPP),)
+$(info NANOAPP_IS_SYSTEM_NANOAPP not supplied, defaulting to 0.)
+NANOAPP_IS_SYSTEM_NANOAPP = 0
+endif
+
 # Nanoapp Build ################################################################
 
 # This variable indicates to the variants that some post-processing may be
@@ -40,7 +56,10 @@
 # Variant-specific Nanoapp Support Source Files ################################
 
 APP_SUPPORT_PATH = $(CHRE_PREFIX)/build/app_support
+DSO_SUPPORT_LIB_PATH = $(CHRE_PREFIX)/platform/shared/nanoapp
 
+GOOGLE_HEXAGONV60_SLPI_SRCS += $(DSO_SUPPORT_LIB_PATH)/nanoapp_support_lib_dso.c
+GOOGLE_HEXAGONV62_SLPI_SRCS += $(DSO_SUPPORT_LIB_PATH)/nanoapp_support_lib_dso.c
 QCOM_HEXAGONV60_NANOHUB_SRCS += $(APP_SUPPORT_PATH)/qcom_nanohub/app_support.cc
 
 # Makefile Includes ############################################################
diff --git a/build/nanoapp/google_slpi.mk b/build/nanoapp/google_slpi.mk
index dfcb317..2e629bb 100644
--- a/build/nanoapp/google_slpi.mk
+++ b/build/nanoapp/google_slpi.mk
@@ -15,6 +15,14 @@
 #
 ################################################################################
 
+TARGET_CFLAGS += -DNANOAPP_ID=$(NANOAPP_ID)
+TARGET_CFLAGS += -DNANOAPP_VERSION=$(NANOAPP_VERSION)
+TARGET_CFLAGS += -DNANOAPP_VENDOR_STRING=$(NANOAPP_VENDOR_STRING)
+TARGET_CFLAGS += -DNANOAPP_NAME_STRING=$(NANOAPP_NAME_STRING)
+TARGET_CFLAGS += -DNANOAPP_IS_SYSTEM_NANOAPP=$(NANOAPP_IS_SYSTEM_NANOAPP)
+TARGET_CFLAGS += -I$(CHRE_PREFIX)/platform/shared/include
+TARGET_CFLAGS += -I$(CHRE_PREFIX)/util/include
+
 ifndef GOOGLE_SLPI_NANOAPP_BUILD_TEMPLATE
 define GOOGLE_SLPI_NANOAPP_BUILD_TEMPLATE
 
diff --git a/build/variant/google_hexagonv60_slpi.mk b/build/variant/google_hexagonv60_slpi.mk
index 410076c..1fe3e43 100644
--- a/build/variant/google_hexagonv60_slpi.mk
+++ b/build/variant/google_hexagonv60_slpi.mk
@@ -11,10 +11,11 @@
 HEXAGON_ARCH = v60
 
 ifneq ($(filter $(TARGET_NAME)% all, $(MAKECMDGOALS)),)
-include $(CHRE_PREFIX)/build/arch/hexagon.mk
-include $(CHRE_PREFIX)/build/build_template.mk
-
 ifneq ($(IS_NANOAPP_BUILD),)
+TARGET_SO_LATE_LIBS += $(CHRE_PREFIX)/build/app_support/google_slpi/libchre_slpi_skel.so
 include $(CHRE_PREFIX)/build/nanoapp/google_slpi.mk
 endif
+
+include $(CHRE_PREFIX)/build/arch/hexagon.mk
+include $(CHRE_PREFIX)/build/build_template.mk
 endif
diff --git a/build/variant/google_hexagonv62_slpi.mk b/build/variant/google_hexagonv62_slpi.mk
index c55b96d..266bc68 100644
--- a/build/variant/google_hexagonv62_slpi.mk
+++ b/build/variant/google_hexagonv62_slpi.mk
@@ -11,10 +11,11 @@
 HEXAGON_ARCH = v62
 
 ifneq ($(filter $(TARGET_NAME)% all, $(MAKECMDGOALS)),)
-include $(CHRE_PREFIX)/build/arch/hexagon.mk
-include $(CHRE_PREFIX)/build/build_template.mk
-
 ifneq ($(IS_NANOAPP_BUILD),)
+TARGET_SO_LATE_LIBS += $(CHRE_PREFIX)/build/app_support/google_slpi/libchre_slpi_skel.so
 include $(CHRE_PREFIX)/build/nanoapp/google_slpi.mk
 endif
+
+include $(CHRE_PREFIX)/build/arch/hexagon.mk
+include $(CHRE_PREFIX)/build/build_template.mk
 endif
diff --git a/chre_api/include/chre_api/chre/version.h b/chre_api/include/chre_api/chre/version.h
index 8544749..32b066c 100644
--- a/chre_api/include/chre_api/chre/version.h
+++ b/chre_api/include/chre_api/chre/version.h
@@ -22,7 +22,7 @@
  * Definitions and methods for the versioning of the Context Hub Runtime
  * Environment.
  *
- * The CHRE API versioning pertains to all the chre_*.h files and chre.h.
+ * The CHRE API versioning pertains to all header files in the CHRE API.
  */
 
 #include <stdint.h>
@@ -82,6 +82,41 @@
  */
 #define CHRE_API_VERSION CHRE_API_VERSION_1_1
 
+/**
+ * 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()
+ *
+ * @returns The API major version in the least significant byte, e.g. 0x01
+ */
+#define CHRE_EXTRACT_MAJOR_VERSION(version) \
+    (((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
+ *
+ * @returns The API minor version in the least significant byte, e.g. 0x01
+ */
+#define CHRE_EXTRACT_MINOR_VERSION(version) \
+    (((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()
+ *
+ * @returns 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)  ((version) & UINT32_C(0xFFFF))
+
 
 /**
  * Get the API version the CHRE implementation was compiled against.
diff --git a/core/core.mk b/core/core.mk
index 413eb71..3e60c4e 100644
--- a/core/core.mk
+++ b/core/core.mk
@@ -9,8 +9,10 @@
 
 # Common Source Files ##########################################################
 
+COMMON_SRCS += core/event.cc
 COMMON_SRCS += core/event_loop.cc
 COMMON_SRCS += core/event_loop_manager.cc
+COMMON_SRCS += core/event_ref_queue.cc
 COMMON_SRCS += core/gnss_request_manager.cc
 COMMON_SRCS += core/host_comms_manager.cc
 COMMON_SRCS += core/init.cc
diff --git a/core/event.cc b/core/event.cc
new file mode 100644
index 0000000..8af721e
--- /dev/null
+++ b/core/event.cc
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/core/event.h"
+
+#include "chre/platform/assert.h"
+
+namespace chre {
+
+Event::Event(uint16_t eventType_, void *eventData_,
+             chreEventCompleteFunction *freeCallback_,
+             uint32_t senderInstanceId_, uint32_t targetInstanceId_)
+    : eventType(eventType_), eventData(eventData_), freeCallback(freeCallback_),
+      senderInstanceId(senderInstanceId_),
+      targetInstanceId(targetInstanceId_) {}
+
+void Event::incrementRefCount() {
+  mRefCount++;
+  CHRE_ASSERT(mRefCount != 0);
+}
+
+void Event::decrementRefCount() {
+  CHRE_ASSERT(mRefCount > 0);
+  mRefCount--;
+}
+
+bool Event::isUnreferenced() const {
+  return (mRefCount == 0);
+}
+
+}  // namespace chre
diff --git a/core/event_loop.cc b/core/event_loop.cc
index 51c0c67..7331e28 100644
--- a/core/event_loop.cc
+++ b/core/event_loop.cc
@@ -17,6 +17,7 @@
 #include "chre/core/event_loop.h"
 
 #include "chre/core/event.h"
+#include "chre/core/event_loop_manager.h"
 #include "chre/core/nanoapp.h"
 #include "chre/platform/context.h"
 #include "chre/platform/log.h"
@@ -39,7 +40,7 @@
   }
 
   bool found = false;
-  for (auto& app : mNanoapps) {
+  for (const UniquePtr<Nanoapp>& app : mNanoapps) {
     if (app->getAppId() == appId) {
       *instanceId = app->getInstanceId();
       found = true;
@@ -60,7 +61,7 @@
     mNanoappsLock.lock();
   }
 
-  for (const auto& nanoapp : mNanoapps) {
+  for (const UniquePtr<Nanoapp>& nanoapp : mNanoapps) {
     callback(nanoapp.get(), data);
   }
 
@@ -83,7 +84,7 @@
       // there is no safety mechanism that ensures an event is not freed twice,
       // or that its free callback is invoked in the proper EventLoop, etc.
       Event *event = mEvents.pop();
-      for (auto& app : mNanoapps) {
+      for (const UniquePtr<Nanoapp>& app : mNanoapps) {
         if ((event->targetInstanceId == chre::kBroadcastInstanceId
                 && app->isRegisteredForBroadcastEvent(event->eventType))
             || event->targetInstanceId == app->getInstanceId()) {
@@ -105,53 +106,51 @@
     // TODO: most basic round-robin implementation - we might want to have some
     // kind of priority in the future, but this should be good enough for now
     havePendingEvents = false;
-    for (auto& app : mNanoapps) {
+    for (const UniquePtr<Nanoapp>& app : mNanoapps) {
       if (app->hasPendingEvent()) {
-        // TODO: cleaner way to set/clear this? RAII-style?
-        mCurrentApp = app.get();
-        Event *event = app->processNextEvent();
-        mCurrentApp = nullptr;
-
-        if (event->isUnreferenced()) {
-          freeEvent(event);
-        }
-
-        havePendingEvents |= app->hasPendingEvent();
+        havePendingEvents |= deliverNextEvent(app);
       }
     }
   }
 
+  // Drop any events pending distribution
+  while (!mEvents.empty()) {
+    freeEvent(mEvents.pop());
+  }
+
+  // Stop all running nanoapps
   while (!mNanoapps.empty()) {
-    stopNanoapp(mNanoapps.front().get());
+    stopNanoapp(mNanoapps.size() - 1);
   }
 
   LOGI("Exiting EventLoop");
-
-  // TODO: need to purge/cleanup events, etc.
 }
 
-bool EventLoop::startNanoapp(PlatformNanoapp *platformNanoapp) {
-  CHRE_ASSERT(platformNanoapp != nullptr);
-
+bool EventLoop::startNanoapp(UniquePtr<Nanoapp>& nanoapp) {
+  CHRE_ASSERT(!nanoapp.isNull());
   bool success = false;
-  if (!mNanoapps.prepareForPush()) {
+  auto *eventLoopManager = EventLoopManagerSingleton::get();
+  uint32_t existingInstanceId;
+
+  if (nanoapp.isNull()) {
+    // no-op, invalid argument
+  } else if (eventLoopManager->findNanoappInstanceIdByAppId(nanoapp->getAppId(),
+                                                            &existingInstanceId,
+                                                            nullptr)) {
+    LOGE("App with ID 0x%016" PRIx64 " already exists as instance ID 0x%"
+         PRIx32, nanoapp->getAppId(), existingInstanceId);
+  } else if (!mNanoapps.prepareForPush()) {
     LOGE("Failed to allocate space for new nanoapp");
   } else {
-    // TODO: get these parameters from somewhere
-    UniquePtr<Nanoapp> nanoapp(0, 1, CHRE_API_VERSION_1_1, getNextInstanceId(),
-                               true, platformNanoapp);
-    if (nanoapp.isNull()) {
-      LOGE("Failed to allocate new nanoapp");
+    nanoapp->setInstanceId(eventLoopManager->getNextInstanceId());
+    mCurrentApp = nanoapp.get();
+    success = nanoapp->start();
+    mCurrentApp = nullptr;
+    if (!success) {
+      LOGE("Nanoapp %" PRIu32 " failed to start", nanoapp->getInstanceId());
     } else {
-      mCurrentApp = nanoapp.get();
-      success = nanoapp->start();
-      mCurrentApp = nullptr;
-      if (!success) {
-        LOGE("Nanoapp %" PRIu32 " failed to start", nanoapp->getInstanceId());
-      } else {
-        LockGuard<Mutex> lock(mNanoappsLock);
-        mNanoapps.push_back(std::move(nanoapp));
-      }
+      LockGuard<Mutex> lock(mNanoappsLock);
+      mNanoapps.push_back(std::move(nanoapp));
     }
   }
 
@@ -159,18 +158,9 @@
 }
 
 void EventLoop::stopNanoapp(Nanoapp *nanoapp) {
-  CHRE_ASSERT(nanoapp != nullptr);
-
   for (size_t i = 0; i < mNanoapps.size(); i++) {
     if (nanoapp == mNanoapps[i].get()) {
-      {
-        LockGuard<Mutex> lock(mNanoappsLock);
-        mNanoapps.erase(i);
-      }
-
-      mCurrentApp = nanoapp;
-      nanoapp->stop();
-      mCurrentApp = nullptr;
+      stopNanoapp(i);
       return;
     }
   }
@@ -182,20 +172,24 @@
 bool EventLoop::postEvent(uint16_t eventType, void *eventData,
     chreEventCompleteFunction *freeCallback, uint32_t senderInstanceId,
     uint32_t targetInstanceId) {
-  // TODO: Consider adding a CHRE_ASSERT(mRunning) here.
   bool success = false;
-  Event *event = mEventPool.allocate(eventType, eventData, freeCallback,
-      senderInstanceId, targetInstanceId);
-  if (event != nullptr) {
-    success = mEvents.push(event);
-  } else {
-    LOGE("Failed to allocate event");
+
+  if (mRunning) {
+    Event *event = mEventPool.allocate(eventType, eventData, freeCallback,
+        senderInstanceId, targetInstanceId);
+    if (event != nullptr) {
+      success = mEvents.push(event);
+    } else {
+      LOGE("Failed to allocate event");
+    }
   }
+
   return success;
 }
 
 void EventLoop::stop() {
   postEvent(0, nullptr, nullptr, kSystemInstanceId, kSystemInstanceId);
+  // Stop accepting new events and tell the main loop to finish
   mRunning = false;
 }
 
@@ -209,25 +203,6 @@
   return mNanoapps.size();
 }
 
-uint32_t EventLoop::getNextInstanceId() {
-  // This is a simple unique ID generator that checks a newly generated ID
-  // against all existing IDs using a search (currently linear search).
-  // Instance ID generation will slow with more apps. We generally expect there
-  // to be few apps and IDs are generated infrequently.
-  //
-  // The benefit of generating IDs this way is that there is no memory overhead
-  // to track issued IDs.
-  uint32_t nextInstanceId = mLastInstanceId + 1;
-  while (nextInstanceId == kSystemInstanceId
-      || nextInstanceId == kBroadcastInstanceId
-      || lookupAppByInstanceId(nextInstanceId) != nullptr) {
-    nextInstanceId++;
-  }
-
-  mLastInstanceId = nextInstanceId;
-  return nextInstanceId;
-}
-
 TimerPool& EventLoop::getTimerPool() {
   return mTimerPool;
 }
@@ -247,11 +222,35 @@
   return nanoapp;
 }
 
+void EventLoop::freeEvent(Event *event) {
+  if (event->freeCallback != nullptr) {
+    // TODO: find a better way to set the context to the creator of the event
+    mCurrentApp = lookupAppByInstanceId(event->senderInstanceId);
+    event->freeCallback(event->eventType, event->eventData);
+    mCurrentApp = nullptr;
+
+    mEventPool.deallocate(event);
+  }
+}
+
+bool EventLoop::deliverNextEvent(const UniquePtr<Nanoapp>& app) {
+  // TODO: cleaner way to set/clear this? RAII-style?
+  mCurrentApp = app.get();
+  Event *event = app->processNextEvent();
+  mCurrentApp = nullptr;
+
+  if (event->isUnreferenced()) {
+    freeEvent(event);
+  }
+
+  return app->hasPendingEvent();
+}
+
 Nanoapp *EventLoop::lookupAppByInstanceId(uint32_t instanceId) {
   // The system instance ID always has nullptr as its Nanoapp pointer, so can
   // skip iterating through the nanoapp list for that case
   if (instanceId != kSystemInstanceId) {
-    for (auto& app : mNanoapps) {
+    for (const UniquePtr<Nanoapp>& app : mNanoapps) {
       if (app->getInstanceId() == instanceId) {
         return app.get();
       }
@@ -261,14 +260,32 @@
   return nullptr;
 }
 
-void EventLoop::freeEvent(Event *event) {
-  if (event->freeCallback != nullptr) {
-    // TODO: find a better way to set the context to the creator of the event
-    mCurrentApp = lookupAppByInstanceId(event->senderInstanceId);
-    event->freeCallback(event->eventType, event->eventData);
-    mCurrentApp = nullptr;
+void EventLoop::stopNanoapp(size_t index) {
+  const UniquePtr<Nanoapp>& nanoapp = mNanoapps[index];
 
-    mEventPool.deallocate(event);
+  // Process any events pending in this app's queue. Note that since we're
+  // running in the context of this EventLoop, no new events will be added to
+  // this nanoapp's event queue while we're doing this, so once it's empty, we
+  // can be assured it will stay that way.
+  while (nanoapp->hasPendingEvent()) {
+    deliverNextEvent(nanoapp);
+  }
+
+  // TODO: to safely stop a nanoapp while the EventLoop is still running, we
+  // need to deliver/purge any events that the nanoapp sent itself prior to
+  // calling end(), so that we won't try to invoke a free callback after
+  // unloading the nanoapp where that callback resides. Likewise, we need to
+  // make sure any messages to the host from this nanoapp are flushed as well.
+
+  // Let the app know it's going away
+  mCurrentApp = nanoapp.get();
+  nanoapp->end();
+  mCurrentApp = nullptr;
+
+  // Destroy the Nanoapp instance
+  {
+    LockGuard<Mutex> lock(mNanoappsLock);
+    mNanoapps.erase(index);
   }
 }
 
diff --git a/core/event_loop_manager.cc b/core/event_loop_manager.cc
index 6d7b5b5..cc57a19 100644
--- a/core/event_loop_manager.cc
+++ b/core/event_loop_manager.cc
@@ -17,6 +17,7 @@
 #include "chre/core/event_loop_manager.h"
 
 #include "chre/platform/context.h"
+#include "chre/platform/fatal_error.h"
 #include "chre/util/lock_guard.h"
 
 namespace chre {
@@ -36,7 +37,7 @@
   // support multiple EventLoop instances, for example the Event freeing
   // mechanism is not thread-safe.
   CHRE_ASSERT(mEventLoops.empty());
-  if (!mEventLoops.emplace_back()) {
+  if (!mEventLoops.emplace_back(MakeUnique<EventLoop>())) {
     return nullptr;
   }
 
@@ -85,6 +86,23 @@
   return nanoapp;
 }
 
+uint32_t EventLoopManager::getNextInstanceId() {
+  // TODO: this needs to be an atomic integer when we have > 1 event loop, or
+  // use a mutex
+  ++mLastInstanceId;
+
+  // ~4 billion instance IDs should be enough for anyone... if we need to
+  // support wraparound for stress testing load/unload, then we can set a flag
+  // when wraparound occurs and use EventLoop::findNanoappByInstanceId to ensure
+  // we avoid conflicts
+  if (mLastInstanceId == kBroadcastInstanceId
+      || mLastInstanceId == kSystemInstanceId) {
+    FATAL_ERROR("Exhausted instance IDs!");
+  }
+
+  return mLastInstanceId;
+}
+
 bool EventLoopManager::postEvent(uint16_t eventType, void *eventData,
                                  chreEventCompleteFunction *freeCallback,
                                  uint32_t senderInstanceId,
diff --git a/core/event_ref_queue.cc b/core/event_ref_queue.cc
new file mode 100644
index 0000000..68cec92
--- /dev/null
+++ b/core/event_ref_queue.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/core/event_ref_queue.h"
+
+#include "chre/platform/assert.h"
+
+namespace chre {
+
+EventRefQueue::~EventRefQueue() {
+  CHRE_ASSERT_LOG(empty(), "Potentially leaking events if queue not empty "
+                  "when destroyed");
+}
+
+bool EventRefQueue::empty() const {
+  return mQueue.empty();
+}
+
+bool EventRefQueue::push(Event *event) {
+  CHRE_ASSERT(event != nullptr);
+
+  bool pushed = mQueue.push(event);
+  if (pushed) {
+    event->incrementRefCount();
+  }
+
+  return pushed;
+}
+
+Event *EventRefQueue::pop() {
+  CHRE_ASSERT(!mQueue.empty());
+
+  Event *event = mQueue.front();
+  mQueue.pop();
+  event->decrementRefCount();
+
+  return event;
+}
+
+}  // namespace chre
diff --git a/core/include/chre/core/event.h b/core/include/chre/core/event.h
index f20a196..d073c63 100644
--- a/core/include/chre/core/event.h
+++ b/core/include/chre/core/event.h
@@ -18,47 +18,33 @@
 #define CHRE_CORE_EVENT_H_
 
 #include "chre_api/chre/event.h"
-#include "chre/platform/assert.h"
-#include "chre/platform/log.h"
 #include "chre/util/non_copyable.h"
 
 #include <cstdint>
 
 namespace chre {
 
-// TODO: put these in a more common place
+//! Instance ID used for events sent by the system
 constexpr uint32_t kSystemInstanceId = 0;
+
+//! Target instance ID used to deliver a message to all nanoapps registered for
+//! the event
 constexpr uint32_t kBroadcastInstanceId = UINT32_MAX;
 
-// Can be used in the context of a specific Nanoapp's instance ID
+//! This value can be used in a nanoapp's own instance ID to indicate that the
+//! ID is invalid/not assigned yet
 constexpr uint32_t kInvalidInstanceId = kBroadcastInstanceId;
 
 class Event : public NonCopyable {
  public:
-  Event(uint16_t eventType_,
-        void *eventData_,
-        chreEventCompleteFunction *freeCallback_,
-        uint32_t senderInstanceId_ = kSystemInstanceId,
-        uint32_t targetInstanceId_ = kBroadcastInstanceId)
-      : eventType(eventType_),
-        eventData(eventData_),
-        freeCallback(freeCallback_),
-        senderInstanceId(senderInstanceId_),
-        targetInstanceId(targetInstanceId_) {}
+  Event(uint16_t eventType, void *eventData,
+        chreEventCompleteFunction *freeCallback,
+        uint32_t senderInstanceId = kSystemInstanceId,
+        uint32_t targetInstanceId = kBroadcastInstanceId);
 
-  void incrementRefCount() {
-    mRefCount++;
-    CHRE_ASSERT(mRefCount != 0);
-  }
-
-  void decrementRefCount() {
-    CHRE_ASSERT(mRefCount > 0);
-    mRefCount--;
-  }
-
-  bool isUnreferenced() {
-    return (mRefCount == 0);
-  }
+  void incrementRefCount();
+  void decrementRefCount();
+  bool isUnreferenced() const;
 
   const uint16_t eventType;
   void * const eventData;
diff --git a/core/include/chre/core/event_loop.h b/core/include/chre/core/event_loop.h
index 1719b10..dcf4abd 100644
--- a/core/include/chre/core/event_loop.h
+++ b/core/include/chre/core/event_loop.h
@@ -73,21 +73,25 @@
   void forEachNanoapp(NanoappCallbackFunction *callback, void *data);
 
   /**
-   * Starts a nanoapp by constructing a Nanoapp instance, invoking the start
-   * entry point and adding it to the list of nanoapps owned by this event
-   * loop.
+   * Invokes the Nanoapp's start callback, and if successful, adds it to the
+   * set of Nanoapps managed by this EventLoop. This function must only be
+   * called from the context of the thread that runs this event loop (i.e. from
+   * the same thread that will call run() or from a callback invoked within
+   * run()).
    *
-   * @param platformNanoapp A pointer to the platform nanoapp to start. This
-   *        pointer must remain valid after this function returns.
-   * @return True if the app was started successfully.
+   * @param nanoapp The nanoapp that will be started. Upon success, this
+   *        UniquePtr will become invalid, as the underlying Nanoapp instance
+   *        will have been transferred to be managed by this EventLoop.
+   * @return true if the app was started successfully
    */
-  bool startNanoapp(PlatformNanoapp *platformNanoapp);
+  bool startNanoapp(UniquePtr<Nanoapp>& nanoapp);
 
   /**
    * Stops a nanoapp by invoking the stop entry point. The nanoapp passed in
-   * must have been previously started by the startNanoapp method.
+   * must have been previously started by the startNanoapp method. After this
+   * function returns, all references to the Nanoapp are invalid.
    *
-   * @param A pointer to the nanoapp to stop.
+   * @param nanoapp A pointer to the nanoapp to stop.
    */
   void stopNanoapp(Nanoapp *nanoapp);
 
@@ -110,13 +114,16 @@
    *
    * This function is safe to call from any thread.
    *
-   * @param The type of data being posted.
-   * @param The data being posted.
-   * @param The callback to invoke when the event is no longer needed.
-   * @param The instance ID of the sender of this event.
-   * @param The instance ID of the destination of this event.
+   * @param eventType The type of data being posted.
+   * @param eventData The data being posted.
+   * @param freeCallback The callback to invoke when the event is no longer
+   *        needed.
+   * @param senderInstanceId The instance ID of the sender of this event.
+   * @param targetInstanceId The instance ID of the destination of this event.
    *
    * @return true if the event was successfully added to the queue
+   *
+   * @see chreSendEvent
    */
   bool postEvent(uint16_t eventType, void *eventData,
                  chreEventCompleteFunction *freeCallback,
@@ -141,16 +148,6 @@
   size_t getNanoappCount() const;
 
   /**
-   * Returns a guaranteed unique instance ID that can be used to construct a
-   * nanoapp.
-   *
-   * @return a unique instance ID to assign to a nanoapp.
-   */
-  // TODO: move this to EventLoopManager as it must be unique across all event
-  // loops; we currently really only support one EventLoop so it's OK for now
-  uint32_t getNextInstanceId();
-
-  /**
    * Obtains the TimerPool associated with this event loop.
    *
    * @return The timer pool owned by this event loop.
@@ -176,9 +173,6 @@
   //! events are in a queue to be distributed to apps.
   static constexpr size_t kMaxUnscheduledEventCount = 1024;
 
-  //! The instance ID that was previously generated by getNextInstanceId.
-  uint32_t mLastInstanceId = kSystemInstanceId;
-
   //! The memory pool to allocate incoming events from.
   SynchronizedMemoryPool<Event, kMaxEventCount> mEventPool;
 
@@ -206,6 +200,15 @@
   Nanoapp *mCurrentApp = nullptr;
 
   /**
+   * Delivers the next event pending in the Nanoapp's queue, and takes care of
+   * freeing events once they have been delivered to all nanoapps. Must only be
+   * called after confirming that the app has at least 1 pending event.
+   *
+   * @return true if the nanoapp has another event pending in its queue
+   */
+  bool deliverNextEvent(const UniquePtr<Nanoapp>& app);
+
+  /**
    * Call after when an Event has been delivered to all intended recipients.
    * Invokes the event's free callback (if given) and releases resources.
    *
@@ -222,6 +225,11 @@
    * @return Nanoapp with the given instanceId, or nullptr if not found
    */
   Nanoapp *lookupAppByInstanceId(uint32_t instanceId);
+
+  /**
+   * Stops the Nanoapp at the given index in mNanoapps
+   */
+  void stopNanoapp(size_t index);
 };
 
 }  // namespace chre
diff --git a/core/include/chre/core/event_loop_manager.h b/core/include/chre/core/event_loop_manager.h
index f10d0e6..fc12e34 100644
--- a/core/include/chre/core/event_loop_manager.h
+++ b/core/include/chre/core/event_loop_manager.h
@@ -43,6 +43,7 @@
   WifiHandleScanEvent,
   NanoappListResponse,
   SensorLastEventUpdate,
+  FinishLoadingNanoapp,
 };
 
 //! The function signature of a system callback mirrors the CHRE event free
@@ -128,6 +129,14 @@
                                    EventLoop **eventLoop = nullptr);
 
   /**
+   * Returns a guaranteed unique instance identifier to associate with a newly
+   * constructed nanoapp.
+   *
+   * @return a unique instance ID
+   */
+  uint32_t getNextInstanceId();
+
+  /**
    * Posts an event to all event loops owned by this event loop manager. This
    * method is thread-safe and is used to post events that all event loops would
    * be interested in, such as sensor event data.
@@ -186,6 +195,9 @@
   //! of event order between event loops.
   Mutex mMutex;
 
+  //! The instance ID that was previously generated by getNextInstanceId()
+  uint32_t mLastInstanceId = kSystemInstanceId;
+
   //! The list of event loops managed by this event loop manager. The EventLoops
   //! are stored in UniquePtr because they are large objects. They do not
   //! provide an implementation of the move constructor so it is best left to
diff --git a/core/include/chre/core/event_ref_queue.h b/core/include/chre/core/event_ref_queue.h
index 3bacb7c..32c523b 100644
--- a/core/include/chre/core/event_ref_queue.h
+++ b/core/include/chre/core/event_ref_queue.h
@@ -14,45 +14,45 @@
  * limitations under the License.
  */
 
-#ifndef CHRE_EVENT_QUEUE_H
-#define CHRE_EVENT_QUEUE_H
+#ifndef CHRE_EVENT_REF_QUEUE_H
+#define CHRE_EVENT_REF_QUEUE_H
 
 #include "chre/core/event.h"
-#include "chre/platform/assert.h"
 #include "chre/util/array_queue.h"
 
 namespace chre {
 
 /**
- * TODO
- * NOT thread-safe, non-blocking.
+ * A non-thread-safe, non-blocking wrapper around ArrayQueue that stores Event*
+ * and manages the Event reference counter.
+ * TODO: make this a template specialization? Or rework the ref count design?
  */
 class EventRefQueue {
  public:
-  bool empty() {
-    return mQueue.empty();
-  }
+  ~EventRefQueue();
 
-  bool push(Event *event) {
-    CHRE_ASSERT(event != nullptr);
+  /**
+   * @return true if there are no events in the queue
+   */
+  bool empty() const;
 
-    bool pushed = mQueue.push(event);
-    if (pushed) {
-      event->incrementRefCount();
-    }
+  /**
+   * Adds an event to the queue, and increments its reference counter
+   *
+   * @param event The event to add
+   * @return true on success
+   */
+  bool push(Event *event);
 
-    return pushed;
-  }
-
-  Event *pop() {
-    CHRE_ASSERT(!mQueue.empty());
-
-    Event *event = mQueue.front();
-    mQueue.pop();
-    event->decrementRefCount();
-
-    return event;
-  }
+  /**
+   * Removes the oldest event from the queue, and decrements its reference
+   * counter. Does not trigger freeing of the event if the reference count
+   * reaches 0 as a result of this function call. The queue must be non-empty as
+   * a precondition to calling this function, or undefined behavior will result.
+   *
+   * @return Pointer to the next event in the queue
+   */
+  Event *pop();
 
  private:
   //! The maximum number of events that can be outstanding for an app.
@@ -64,4 +64,4 @@
 
 }  // namespace chre
 
-#endif //CHRE_EVENT_QUEUE_H
+#endif  // CHRE_EVENT_REF_QUEUE_H
diff --git a/core/include/chre/core/nanoapp.h b/core/include/chre/core/nanoapp.h
index 8cecb8b..ab40f30 100644
--- a/core/include/chre/core/nanoapp.h
+++ b/core/include/chre/core/nanoapp.h
@@ -23,45 +23,37 @@
 #include "chre/core/event_ref_queue.h"
 #include "chre/platform/platform_nanoapp.h"
 #include "chre/util/dynamic_vector.h"
-#include "chre/util/non_copyable.h"
 
 namespace chre {
 
 /**
  * A class that tracks the state of a Nanoapp including incoming events and
  * event registrations.
+ *
+ * Inheritance is used to separate the common interface with common
+ * implementation part (chre::Nanoapp) from the common interface with
+ * platform-specific implementation part (chre::PlatformNanoapp) from the purely
+ * platform-specific part (chre::PlatformNanoappBase). However, this inheritance
+ * relationship does *not* imply polymorphism, and this object must only be
+ * referred to via the most-derived type, i.e. chre::Nanoapp.
  */
-class Nanoapp : public NonCopyable {
+class Nanoapp : public PlatformNanoapp {
  public:
   /**
-   * Constructs a Nanoapp that manages the lifecycle of events and calls into
-   * the entry points of the app.
-   *
-   * @param appId Identifies the nanoapp vendor and the app itself
-   * @param appVersion An application-defined version number
-   * @param targetApiVersion The CHRE API version that this app was compiled
-   *        against
-   * @param instanceId A unique identifier for this application instance that
-   *        can be used to target unicast events, etc.
-   * @param isSystemNanoapp true if this is a nanoapp that should not appear in
-   *        the list of nanoapps exposed through the context hub HAL. System
-   *        nanoapps can be used to leverage CHRE to implement device
-   *        functionality below the HAL, where the nanoapp does not communicate
-   *        with host-side entities through the context hub HAL.
-   * @param platformNanoapp A pointer to the platform-specific nanoapp
-   *        functionality which allows calling the entry points and managing the
-   *        lifecycle of the app (such as unloading the app).
+   * @return uint32_t The globally unique identifier for this Nanoapp instance
    */
-  Nanoapp(uint64_t appId, uint32_t appVersion, uint32_t targetApiVersion,
-          uint32_t instanceId, bool isSystemNanoapp,
-          PlatformNanoapp *platformNanoapp);
-
-  uint64_t getAppId() const;
-  uint32_t getAppVersion() const;
   uint32_t getInstanceId() const;
-  uint32_t getTargetApiVersion() const;
-  bool isSystemNanoapp() const;
 
+  /**
+   * Assigns an instance ID to this Nanoapp. This must be called prior to
+   * starting this nanoapp.
+   */
+  void setInstanceId(uint32_t instanceId);
+
+  /**
+   * @return true if the nanoapp should receive broadcast events with the given
+   *         type
+   */
   bool isRegisteredForBroadcastEvent(uint16_t eventType) const;
 
   /**
@@ -88,19 +80,6 @@
   void postEvent(Event *event);
 
   /**
-   * Starts the nanoapp by invoking the start handler and returns the result of
-   * the handler.
-   *
-   * @return True indicating that the app was started successfully.
-   */
-  bool start();
-
-  /**
-   * Stops the nanoapp by invoking the stop handler.
-   */
-  void stop();
-
-  /**
    * Indicates whether there are any pending events in this apps queue.
    *
    * @return True indicating that there are events available to be processed.
@@ -116,12 +95,7 @@
   Event *processNextEvent();
 
  private:
-  const uint64_t mAppId;
-  const uint32_t mAppVersion;
-  const uint32_t mTargetApiVersion;
-  const uint32_t mInstanceId;
-  const bool mIsSystemNanoapp;
-  PlatformNanoapp * const mPlatformNanoapp;
+  uint32_t mInstanceId = kInvalidInstanceId;
 
   //! The set of broadcast events that this app is registered for.
   // TODO: Implement a set container and replace DynamicVector here. There may
diff --git a/core/nanoapp.cc b/core/nanoapp.cc
index 3d30678..8a8e983 100644
--- a/core/nanoapp.cc
+++ b/core/nanoapp.cc
@@ -16,18 +16,20 @@
 
 #include "chre/core/nanoapp.h"
 
+#include "chre/core/event_loop_manager.h"
 #include "chre/platform/assert.h"
 #include "chre/platform/fatal_error.h"
 #include "chre/platform/log.h"
 
 namespace chre {
 
-Nanoapp::Nanoapp(
-    uint64_t appId, uint32_t appVersion, uint32_t targetApiVersion,
-    uint32_t instanceId, bool isSystemNanoapp, PlatformNanoapp *platformNanoapp)
-    : mAppId(appId), mAppVersion(appVersion),
-      mTargetApiVersion(targetApiVersion), mInstanceId(instanceId),
-      mIsSystemNanoapp(isSystemNanoapp), mPlatformNanoapp(platformNanoapp) {}
+uint32_t Nanoapp::getInstanceId() const {
+  return mInstanceId;
+}
+
+void Nanoapp::setInstanceId(uint32_t instanceId) {
+  mInstanceId = instanceId;
+}
 
 bool Nanoapp::isRegisteredForBroadcastEvent(uint16_t eventType) const {
   return (mRegisteredEvents.find(eventType) != mRegisteredEvents.size());
@@ -55,38 +57,10 @@
   return true;
 }
 
-uint64_t Nanoapp::getAppId() const {
-  return mAppId;
-}
-
-uint32_t Nanoapp::getAppVersion() const {
-  return mAppVersion;
-}
-
-uint32_t Nanoapp::getInstanceId() const {
-  return mInstanceId;
-}
-
-uint32_t Nanoapp::getTargetApiVersion() const {
-  return mTargetApiVersion;
-}
-
-bool Nanoapp::isSystemNanoapp() const {
-  return mIsSystemNanoapp;
-}
-
 void Nanoapp::postEvent(Event *event) {
   mEventQueue.push(event);
 }
 
-bool Nanoapp::start() {
-  return mPlatformNanoapp->start();
-}
-
-void Nanoapp::stop() {
-  mPlatformNanoapp->stop();
-}
-
 bool Nanoapp::hasPendingEvent() {
   return !mEventQueue.empty();
 }
@@ -96,8 +70,7 @@
 
   CHRE_ASSERT_LOG(event != nullptr, "Tried delivering event, but queue empty");
   if (event != nullptr) {
-    mPlatformNanoapp->handleEvent(event->senderInstanceId, event->eventType,
-                                  event->eventData);
+    handleEvent(event->senderInstanceId, event->eventType, event->eventData);
   }
 
   return event;
diff --git a/host/common/host_protocol_host.cc b/host/common/host_protocol_host.cc
index 2f17cdd..a00ff79 100644
--- a/host/common/host_protocol_host.cc
+++ b/host/common/host_protocol_host.cc
@@ -100,6 +100,15 @@
         break;
       }
 
+      case fbs::ChreMessage::LoadNanoappResponse: {
+        const auto *resp = static_cast<const fbs::LoadNanoappResponse *>(
+            container->message());
+        fbs::LoadNanoappResponseT response;
+        resp->UnPackTo(&response);
+        handlers.handleLoadNanoappResponse(response);
+        break;
+      }
+
       default:
         LOGW("Got invalid/unexpected message type %" PRIu8,
              static_cast<uint8_t>(container->message_type()));
@@ -112,16 +121,55 @@
 
 void HostProtocolHost::encodeHubInfoRequest(FlatBufferBuilder& builder) {
   auto request = fbs::CreateHubInfoRequest(builder);
-  auto container = fbs::CreateMessageContainer(
-      builder, fbs::ChreMessage::HubInfoRequest, request.Union());
-  builder.Finish(container);
+  finalize(builder, fbs::ChreMessage::HubInfoRequest, request.Union());
+}
+
+void HostProtocolHost::encodeLoadNanoappRequest(
+    FlatBufferBuilder& builder, uint32_t transactionId, uint64_t appId,
+    uint32_t appVersion, uint32_t targetApiVersion,
+    const std::vector<uint8_t>& nanoappBinary) {
+  auto appBinary = builder.CreateVector(nanoappBinary);
+  auto request = fbs::CreateLoadNanoappRequest(
+      builder, transactionId, appId, appVersion, targetApiVersion, appBinary);
+  finalize(builder, fbs::ChreMessage::LoadNanoappRequest, request.Union());
 }
 
 void HostProtocolHost::encodeNanoappListRequest(FlatBufferBuilder& builder) {
   auto request = fbs::CreateNanoappListRequest(builder);
-  auto container = fbs::CreateMessageContainer(
-      builder, fbs::ChreMessage::NanoappListRequest, request.Union());
-  builder.Finish(container);
+  finalize(builder, fbs::ChreMessage::NanoappListRequest, request.Union());
+}
+
+bool HostProtocolHost::extractHostClientId(const void *message,
+                                           size_t messageLen,
+                                           uint16_t *hostClientId) {
+  bool success = verifyMessage(message, messageLen);
+
+  if (success && hostClientId != nullptr) {
+    const fbs::MessageContainer *container = fbs::GetMessageContainer(message);
+    // host_addr guaranteed to be non-null via verifyMessage (it's a required
+    // field)
+    *hostClientId = container->host_addr()->client_id();
+    success = true;
+  }
+
+  return success;
+}
+
+bool HostProtocolHost::mutateHostClientId(void *message, size_t messageLen,
+                                          uint16_t hostClientId) {
+  bool success = verifyMessage(message, messageLen);
+
+  if (!success) {
+    LOGE("Message verification failed - can't mutate host ID");
+  } else {
+    fbs::MessageContainer *container = fbs::GetMutableMessageContainer(message);
+    // host_addr guaranteed to be non-null via verifyMessage (it's a required
+    // field)
+    container->mutable_host_addr()->mutate_client_id(hostClientId);
+    success = true;
+  }
+
+  return success;
 }
 
 }  // namespace chre
diff --git a/host/common/include/chre_host/host_messages_generated.h b/host/common/include/chre_host/host_messages_generated.h
index d23e447..4dfe1d7 100644
--- a/host/common/include/chre_host/host_messages_generated.h
+++ b/host/common/include/chre_host/host_messages_generated.h
@@ -27,6 +27,14 @@
 struct NanoappListResponse;
 struct NanoappListResponseT;
 
+struct LoadNanoappRequest;
+struct LoadNanoappRequestT;
+
+struct LoadNanoappResponse;
+struct LoadNanoappResponseT;
+
+struct HostAddress;
+
 struct MessageContainer;
 struct MessageContainerT;
 
@@ -39,8 +47,10 @@
   HubInfoResponse = 3,
   NanoappListRequest = 4,
   NanoappListResponse = 5,
+  LoadNanoappRequest = 6,
+  LoadNanoappResponse = 7,
   MIN = NONE,
-  MAX = NanoappListResponse
+  MAX = LoadNanoappResponse
 };
 
 inline const char **EnumNamesChreMessage() {
@@ -51,6 +61,8 @@
     "HubInfoResponse",
     "NanoappListRequest",
     "NanoappListResponse",
+    "LoadNanoappRequest",
+    "LoadNanoappResponse",
     nullptr
   };
   return names;
@@ -85,6 +97,14 @@
   static const ChreMessage enum_value = ChreMessage::NanoappListResponse;
 };
 
+template<> struct ChreMessageTraits<LoadNanoappRequest> {
+  static const ChreMessage enum_value = ChreMessage::LoadNanoappRequest;
+};
+
+template<> struct ChreMessageTraits<LoadNanoappResponse> {
+  static const ChreMessage enum_value = ChreMessage::LoadNanoappResponse;
+};
+
 struct ChreMessageUnion {
   ChreMessage type;
   flatbuffers::NativeTable *table;
@@ -130,11 +150,42 @@
     return type == ChreMessage::NanoappListResponse ?
       reinterpret_cast<NanoappListResponseT *>(table) : nullptr;
   }
+  LoadNanoappRequestT *AsLoadNanoappRequest() {
+    return type == ChreMessage::LoadNanoappRequest ?
+      reinterpret_cast<LoadNanoappRequestT *>(table) : nullptr;
+  }
+  LoadNanoappResponseT *AsLoadNanoappResponse() {
+    return type == ChreMessage::LoadNanoappResponse ?
+      reinterpret_cast<LoadNanoappResponseT *>(table) : nullptr;
+  }
 };
 
 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);
 
+MANUALLY_ALIGNED_STRUCT(2) HostAddress FLATBUFFERS_FINAL_CLASS {
+ private:
+  uint16_t client_id_;
+
+ public:
+  HostAddress() {
+    memset(this, 0, sizeof(HostAddress));
+  }
+  HostAddress(const HostAddress &_o) {
+    memcpy(this, &_o, sizeof(HostAddress));
+  }
+  HostAddress(uint16_t _client_id)
+      : client_id_(flatbuffers::EndianScalar(_client_id)) {
+  }
+  uint16_t client_id() const {
+    return flatbuffers::EndianScalar(client_id_);
+  }
+  void mutate_client_id(uint16_t _client_id) {
+    flatbuffers::WriteScalar(&client_id_, _client_id);
+  }
+};
+STRUCT_END(HostAddress, 2);
+
 struct NanoappMessageT : public flatbuffers::NativeTable {
   typedef NanoappMessage TableType;
   uint64_t app_id;
@@ -772,9 +823,216 @@
 
 flatbuffers::Offset<NanoappListResponse> CreateNanoappListResponse(flatbuffers::FlatBufferBuilder &_fbb, const NanoappListResponseT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
 
+struct LoadNanoappRequestT : public flatbuffers::NativeTable {
+  typedef LoadNanoappRequest TableType;
+  uint32_t transaction_id;
+  uint64_t app_id;
+  uint32_t app_version;
+  uint32_t target_api_version;
+  std::vector<uint8_t> app_binary;
+  LoadNanoappRequestT()
+      : transaction_id(0),
+        app_id(0),
+        app_version(0),
+        target_api_version(0) {
+  }
+};
+
+struct LoadNanoappRequest FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  typedef LoadNanoappRequestT NativeTableType;
+  enum {
+    VT_TRANSACTION_ID = 4,
+    VT_APP_ID = 6,
+    VT_APP_VERSION = 8,
+    VT_TARGET_API_VERSION = 10,
+    VT_APP_BINARY = 12
+  };
+  uint32_t transaction_id() const {
+    return GetField<uint32_t>(VT_TRANSACTION_ID, 0);
+  }
+  bool mutate_transaction_id(uint32_t _transaction_id) {
+    return SetField(VT_TRANSACTION_ID, _transaction_id);
+  }
+  uint64_t app_id() const {
+    return GetField<uint64_t>(VT_APP_ID, 0);
+  }
+  bool mutate_app_id(uint64_t _app_id) {
+    return SetField(VT_APP_ID, _app_id);
+  }
+  uint32_t app_version() const {
+    return GetField<uint32_t>(VT_APP_VERSION, 0);
+  }
+  bool mutate_app_version(uint32_t _app_version) {
+    return SetField(VT_APP_VERSION, _app_version);
+  }
+  uint32_t target_api_version() const {
+    return GetField<uint32_t>(VT_TARGET_API_VERSION, 0);
+  }
+  bool mutate_target_api_version(uint32_t _target_api_version) {
+    return SetField(VT_TARGET_API_VERSION, _target_api_version);
+  }
+  const flatbuffers::Vector<uint8_t> *app_binary() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_APP_BINARY);
+  }
+  flatbuffers::Vector<uint8_t> *mutable_app_binary() {
+    return GetPointer<flatbuffers::Vector<uint8_t> *>(VT_APP_BINARY);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint32_t>(verifier, VT_TRANSACTION_ID) &&
+           VerifyField<uint64_t>(verifier, VT_APP_ID) &&
+           VerifyField<uint32_t>(verifier, VT_APP_VERSION) &&
+           VerifyField<uint32_t>(verifier, VT_TARGET_API_VERSION) &&
+           VerifyFieldRequired<flatbuffers::uoffset_t>(verifier, VT_APP_BINARY) &&
+           verifier.Verify(app_binary()) &&
+           verifier.EndTable();
+  }
+  LoadNanoappRequestT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+  void UnPackTo(LoadNanoappRequestT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+  static flatbuffers::Offset<LoadNanoappRequest> Pack(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappRequestT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+};
+
+struct LoadNanoappRequestBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_transaction_id(uint32_t transaction_id) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_TRANSACTION_ID, transaction_id, 0);
+  }
+  void add_app_id(uint64_t app_id) {
+    fbb_.AddElement<uint64_t>(LoadNanoappRequest::VT_APP_ID, app_id, 0);
+  }
+  void add_app_version(uint32_t app_version) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_APP_VERSION, app_version, 0);
+  }
+  void add_target_api_version(uint32_t target_api_version) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_TARGET_API_VERSION, target_api_version, 0);
+  }
+  void add_app_binary(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> app_binary) {
+    fbb_.AddOffset(LoadNanoappRequest::VT_APP_BINARY, app_binary);
+  }
+  LoadNanoappRequestBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  LoadNanoappRequestBuilder &operator=(const LoadNanoappRequestBuilder &);
+  flatbuffers::Offset<LoadNanoappRequest> Finish() {
+    const auto end = fbb_.EndTable(start_, 5);
+    auto o = flatbuffers::Offset<LoadNanoappRequest>(end);
+    fbb_.Required(o, LoadNanoappRequest::VT_APP_BINARY);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<LoadNanoappRequest> CreateLoadNanoappRequest(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t transaction_id = 0,
+    uint64_t app_id = 0,
+    uint32_t app_version = 0,
+    uint32_t target_api_version = 0,
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> app_binary = 0) {
+  LoadNanoappRequestBuilder builder_(_fbb);
+  builder_.add_app_id(app_id);
+  builder_.add_app_binary(app_binary);
+  builder_.add_target_api_version(target_api_version);
+  builder_.add_app_version(app_version);
+  builder_.add_transaction_id(transaction_id);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<LoadNanoappRequest> CreateLoadNanoappRequestDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t transaction_id = 0,
+    uint64_t app_id = 0,
+    uint32_t app_version = 0,
+    uint32_t target_api_version = 0,
+    const std::vector<uint8_t> *app_binary = nullptr) {
+  return chre::fbs::CreateLoadNanoappRequest(
+      _fbb,
+      transaction_id,
+      app_id,
+      app_version,
+      target_api_version,
+      app_binary ? _fbb.CreateVector<uint8_t>(*app_binary) : 0);
+}
+
+flatbuffers::Offset<LoadNanoappRequest> CreateLoadNanoappRequest(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappRequestT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+
+struct LoadNanoappResponseT : public flatbuffers::NativeTable {
+  typedef LoadNanoappResponse TableType;
+  uint32_t transaction_id;
+  bool success;
+  LoadNanoappResponseT()
+      : transaction_id(0),
+        success(false) {
+  }
+};
+
+struct LoadNanoappResponse FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  typedef LoadNanoappResponseT NativeTableType;
+  enum {
+    VT_TRANSACTION_ID = 4,
+    VT_SUCCESS = 6
+  };
+  uint32_t transaction_id() const {
+    return GetField<uint32_t>(VT_TRANSACTION_ID, 0);
+  }
+  bool mutate_transaction_id(uint32_t _transaction_id) {
+    return SetField(VT_TRANSACTION_ID, _transaction_id);
+  }
+  bool success() const {
+    return GetField<uint8_t>(VT_SUCCESS, 0) != 0;
+  }
+  bool mutate_success(bool _success) {
+    return SetField(VT_SUCCESS, static_cast<uint8_t>(_success));
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint32_t>(verifier, VT_TRANSACTION_ID) &&
+           VerifyField<uint8_t>(verifier, VT_SUCCESS) &&
+           verifier.EndTable();
+  }
+  LoadNanoappResponseT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+  void UnPackTo(LoadNanoappResponseT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+  static flatbuffers::Offset<LoadNanoappResponse> Pack(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappResponseT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+};
+
+struct LoadNanoappResponseBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_transaction_id(uint32_t transaction_id) {
+    fbb_.AddElement<uint32_t>(LoadNanoappResponse::VT_TRANSACTION_ID, transaction_id, 0);
+  }
+  void add_success(bool success) {
+    fbb_.AddElement<uint8_t>(LoadNanoappResponse::VT_SUCCESS, static_cast<uint8_t>(success), 0);
+  }
+  LoadNanoappResponseBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  LoadNanoappResponseBuilder &operator=(const LoadNanoappResponseBuilder &);
+  flatbuffers::Offset<LoadNanoappResponse> Finish() {
+    const auto end = fbb_.EndTable(start_, 2);
+    auto o = flatbuffers::Offset<LoadNanoappResponse>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<LoadNanoappResponse> CreateLoadNanoappResponse(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t transaction_id = 0,
+    bool success = false) {
+  LoadNanoappResponseBuilder builder_(_fbb);
+  builder_.add_transaction_id(transaction_id);
+  builder_.add_success(success);
+  return builder_.Finish();
+}
+
+flatbuffers::Offset<LoadNanoappResponse> CreateLoadNanoappResponse(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappResponseT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+
 struct MessageContainerT : public flatbuffers::NativeTable {
   typedef MessageContainer TableType;
   ChreMessageUnion message;
+  std::unique_ptr<HostAddress> host_addr;
   MessageContainerT() {
   }
 };
@@ -786,7 +1044,8 @@
   typedef MessageContainerT NativeTableType;
   enum {
     VT_MESSAGE_TYPE = 4,
-    VT_MESSAGE = 6
+    VT_MESSAGE = 6,
+    VT_HOST_ADDR = 8
   };
   ChreMessage message_type() const {
     return static_cast<ChreMessage>(GetField<uint8_t>(VT_MESSAGE_TYPE, 0));
@@ -800,11 +1059,24 @@
   void *mutable_message() {
     return GetPointer<void *>(VT_MESSAGE);
   }
+  /// 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
+  /// value by the entity guarding access to CHRE.
+  /// This is wrapped in a struct to ensure that it is always included when
+  /// encoding the message, so it can be mutated by the host daemon.
+  const HostAddress *host_addr() const {
+    return GetStruct<const HostAddress *>(VT_HOST_ADDR);
+  }
+  HostAddress *mutable_host_addr() {
+    return GetStruct<HostAddress *>(VT_HOST_ADDR);
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<uint8_t>(verifier, VT_MESSAGE_TYPE) &&
            VerifyFieldRequired<flatbuffers::uoffset_t>(verifier, VT_MESSAGE) &&
            VerifyChreMessage(verifier, message(), message_type()) &&
+           VerifyFieldRequired<HostAddress>(verifier, VT_HOST_ADDR) &&
            verifier.EndTable();
   }
   MessageContainerT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const;
@@ -821,15 +1093,19 @@
   void add_message(flatbuffers::Offset<void> message) {
     fbb_.AddOffset(MessageContainer::VT_MESSAGE, message);
   }
+  void add_host_addr(const HostAddress *host_addr) {
+    fbb_.AddStruct(MessageContainer::VT_HOST_ADDR, host_addr);
+  }
   MessageContainerBuilder(flatbuffers::FlatBufferBuilder &_fbb)
         : fbb_(_fbb) {
     start_ = fbb_.StartTable();
   }
   MessageContainerBuilder &operator=(const MessageContainerBuilder &);
   flatbuffers::Offset<MessageContainer> Finish() {
-    const auto end = fbb_.EndTable(start_, 2);
+    const auto end = fbb_.EndTable(start_, 3);
     auto o = flatbuffers::Offset<MessageContainer>(end);
     fbb_.Required(o, MessageContainer::VT_MESSAGE);
+    fbb_.Required(o, MessageContainer::VT_HOST_ADDR);
     return o;
   }
 };
@@ -837,8 +1113,10 @@
 inline flatbuffers::Offset<MessageContainer> CreateMessageContainer(
     flatbuffers::FlatBufferBuilder &_fbb,
     ChreMessage message_type = ChreMessage::NONE,
-    flatbuffers::Offset<void> message = 0) {
+    flatbuffers::Offset<void> message = 0,
+    const HostAddress *host_addr = 0) {
   MessageContainerBuilder builder_(_fbb);
+  builder_.add_host_addr(host_addr);
   builder_.add_message(message);
   builder_.add_message_type(message_type);
   return builder_.Finish();
@@ -1041,6 +1319,71 @@
       _nanoapps);
 }
 
+inline LoadNanoappRequestT *LoadNanoappRequest::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
+  auto _o = new LoadNanoappRequestT();
+  UnPackTo(_o, _resolver);
+  return _o;
+}
+
+inline void LoadNanoappRequest::UnPackTo(LoadNanoappRequestT *_o, const flatbuffers::resolver_function_t *_resolver) const {
+  (void)_o;
+  (void)_resolver;
+  { auto _e = transaction_id(); _o->transaction_id = _e; };
+  { auto _e = app_id(); _o->app_id = _e; };
+  { auto _e = app_version(); _o->app_version = _e; };
+  { auto _e = target_api_version(); _o->target_api_version = _e; };
+  { auto _e = app_binary(); if (_e) for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->app_binary.push_back(_e->Get(_i)); } };
+}
+
+inline flatbuffers::Offset<LoadNanoappRequest> LoadNanoappRequest::Pack(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappRequestT* _o, const flatbuffers::rehasher_function_t *_rehasher) {
+  return CreateLoadNanoappRequest(_fbb, _o, _rehasher);
+}
+
+inline flatbuffers::Offset<LoadNanoappRequest> CreateLoadNanoappRequest(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappRequestT *_o, const flatbuffers::rehasher_function_t *_rehasher) {
+  (void)_rehasher;
+  (void)_o;
+  auto _transaction_id = _o->transaction_id;
+  auto _app_id = _o->app_id;
+  auto _app_version = _o->app_version;
+  auto _target_api_version = _o->target_api_version;
+  auto _app_binary = _fbb.CreateVector(_o->app_binary);
+  return chre::fbs::CreateLoadNanoappRequest(
+      _fbb,
+      _transaction_id,
+      _app_id,
+      _app_version,
+      _target_api_version,
+      _app_binary);
+}
+
+inline LoadNanoappResponseT *LoadNanoappResponse::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
+  auto _o = new LoadNanoappResponseT();
+  UnPackTo(_o, _resolver);
+  return _o;
+}
+
+inline void LoadNanoappResponse::UnPackTo(LoadNanoappResponseT *_o, const flatbuffers::resolver_function_t *_resolver) const {
+  (void)_o;
+  (void)_resolver;
+  { auto _e = transaction_id(); _o->transaction_id = _e; };
+  { auto _e = success(); _o->success = _e; };
+}
+
+inline flatbuffers::Offset<LoadNanoappResponse> LoadNanoappResponse::Pack(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappResponseT* _o, const flatbuffers::rehasher_function_t *_rehasher) {
+  return CreateLoadNanoappResponse(_fbb, _o, _rehasher);
+}
+
+inline flatbuffers::Offset<LoadNanoappResponse> CreateLoadNanoappResponse(flatbuffers::FlatBufferBuilder &_fbb, const LoadNanoappResponseT *_o, const flatbuffers::rehasher_function_t *_rehasher) {
+  (void)_rehasher;
+  (void)_o;
+  auto _transaction_id = _o->transaction_id;
+  auto _success = _o->success;
+  return chre::fbs::CreateLoadNanoappResponse(
+      _fbb,
+      _transaction_id,
+      _success);
+}
+
 inline MessageContainerT *MessageContainer::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
   auto _o = new MessageContainerT();
   UnPackTo(_o, _resolver);
@@ -1052,6 +1395,7 @@
   (void)_resolver;
   { auto _e = message_type(); _o->message.type = _e; };
   { auto _e = message(); if (_e) _o->message.table = ChreMessageUnion::UnPack(_e, message_type(),_resolver); };
+  { auto _e = host_addr(); if (_e) _o->host_addr = std::unique_ptr<HostAddress>(new HostAddress(*_e)); };
 }
 
 inline flatbuffers::Offset<MessageContainer> MessageContainer::Pack(flatbuffers::FlatBufferBuilder &_fbb, const MessageContainerT* _o, const flatbuffers::rehasher_function_t *_rehasher) {
@@ -1063,10 +1407,12 @@
   (void)_o;
   auto _message_type = _o->message.type;
   auto _message = _o->message.Pack(_fbb);
+  auto _host_addr = _o->host_addr ? _o->host_addr.get() : 0;
   return chre::fbs::CreateMessageContainer(
       _fbb,
       _message_type,
-      _message);
+      _message,
+      _host_addr);
 }
 
 inline bool VerifyChreMessage(flatbuffers::Verifier &verifier, const void *obj, ChreMessage type) {
@@ -1094,6 +1440,14 @@
       auto ptr = reinterpret_cast<const NanoappListResponse *>(obj);
       return verifier.VerifyTable(ptr);
     }
+    case ChreMessage::LoadNanoappRequest: {
+      auto ptr = reinterpret_cast<const LoadNanoappRequest *>(obj);
+      return verifier.VerifyTable(ptr);
+    }
+    case ChreMessage::LoadNanoappResponse: {
+      auto ptr = reinterpret_cast<const LoadNanoappResponse *>(obj);
+      return verifier.VerifyTable(ptr);
+    }
     default: return false;
   }
 }
@@ -1131,6 +1485,14 @@
       auto ptr = reinterpret_cast<const NanoappListResponse *>(obj);
       return ptr->UnPack(resolver);
     }
+    case ChreMessage::LoadNanoappRequest: {
+      auto ptr = reinterpret_cast<const LoadNanoappRequest *>(obj);
+      return ptr->UnPack(resolver);
+    }
+    case ChreMessage::LoadNanoappResponse: {
+      auto ptr = reinterpret_cast<const LoadNanoappResponse *>(obj);
+      return ptr->UnPack(resolver);
+    }
     default: return nullptr;
   }
 }
@@ -1157,6 +1519,14 @@
       auto ptr = reinterpret_cast<const NanoappListResponseT *>(table);
       return CreateNanoappListResponse(_fbb, ptr, _rehasher).Union();
     }
+    case ChreMessage::LoadNanoappRequest: {
+      auto ptr = reinterpret_cast<const LoadNanoappRequestT *>(table);
+      return CreateLoadNanoappRequest(_fbb, ptr, _rehasher).Union();
+    }
+    case ChreMessage::LoadNanoappResponse: {
+      auto ptr = reinterpret_cast<const LoadNanoappResponseT *>(table);
+      return CreateLoadNanoappResponse(_fbb, ptr, _rehasher).Union();
+    }
     default: return 0;
   }
 }
@@ -1188,6 +1558,16 @@
       delete ptr;
       break;
     }
+    case ChreMessage::LoadNanoappRequest: {
+      auto ptr = reinterpret_cast<LoadNanoappRequestT *>(table);
+      delete ptr;
+      break;
+    }
+    case ChreMessage::LoadNanoappResponse: {
+      auto ptr = reinterpret_cast<LoadNanoappResponseT *>(table);
+      delete ptr;
+      break;
+    }
     default: break;
   }
   table = nullptr;
diff --git a/host/common/include/chre_host/host_protocol_host.h b/host/common/include/chre_host/host_protocol_host.h
index c6b1cd9..c11d1b7 100644
--- a/host/common/include/chre_host/host_protocol_host.h
+++ b/host/common/include/chre_host/host_protocol_host.h
@@ -48,6 +48,9 @@
 
   virtual void handleNanoappListResponse(
       const ::chre::fbs::NanoappListResponseT& response) = 0;
+
+  virtual void handleLoadNanoappResponse(
+      const ::chre::fbs::LoadNanoappResponseT& response) = 0;
 };
 
 /**
@@ -81,12 +84,51 @@
   static void encodeHubInfoRequest(flatbuffers::FlatBufferBuilder& builder);
 
   /**
+   * Encodes a message requesting to load a nanoapp specified by the included
+   * binary payload and metadata.
+   *
+   * @param builder A newly constructed FlatBufferBuilder that will be used to
+   *        construct the message
+   */
+  static void encodeLoadNanoappRequest(
+      flatbuffers::FlatBufferBuilder& builder, uint32_t transactionId,
+      uint64_t appId, uint32_t appVersion, uint32_t targetApiVersion,
+      const std::vector<uint8_t>& nanoappBinary);
+
+  /**
    * Encodes a message requesting the list of loaded nanoapps from CHRE
    *
    * @param builder A newly constructed FlatBufferBuilder that will be used to
    *        construct the message
    */
   static void encodeNanoappListRequest(flatbuffers::FlatBufferBuilder& builder);
+
+  /**
+   * Decodes the host client ID included in the message container
+   *
+   * @param message Buffer containing a complete FlatBuffers CHRE message
+   * @param messageLen Size of the message, in bytes
+   * @param hostClientId Output parameter that will be populated with the client
+   *        ID included in the message on success
+   *
+   * @return true if the host client ID was successfully decoded from the
+   *         message
+   */
+  static bool extractHostClientId(const void *message, size_t messageLen,
+                                  uint16_t *hostClientId);
+
+  /**
+   * Update the host client ID field in the MessageContainer.
+   *
+   * @param message Buffer containing a complete FlatBuffers CHRE message
+   * @param messageLen Size of the message, in bytes
+   * @param hostClientId The value to set the host client ID to
+   *
+   * @return true if the message was verified successfully, and we were able to
+   *         modify the host client ID field
+   */
+  static bool mutateHostClientId(void *message, size_t messageLen,
+                                 uint16_t hostClientId);
 };
 
 }  // namespace chre
diff --git a/host/common/include/chre_host/socket_server.h b/host/common/include/chre_host/socket_server.h
index a14447d..5cbbdb0 100644
--- a/host/common/include/chre_host/socket_server.h
+++ b/host/common/include/chre_host/socket_server.h
@@ -43,7 +43,7 @@
    * @param data Pointer to buffer containing the raw message data
    * @param len Number of bytes of data received
    */
-  typedef std::function<void(uint16_t clientId, const void *data, size_t len)>
+  typedef std::function<void(uint16_t clientId, void *data, size_t len)>
       ClientMessageCallback;
 
   /**
@@ -68,6 +68,18 @@
    */
   void sendToAllClients(const void *data, size_t length);
 
+  /**
+   * Sends a message to one client, specified via its unique client ID. This
+   * method is thread-safe.
+   *
+   * @param data
+   * @param length
+   * @param clientId
+   *
+   * @return true if the message was successfully sent to the specified client
+   */
+  bool sendToClientById(const void *data, size_t length, uint16_t clientId);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(SocketServer);
 
@@ -96,6 +108,8 @@
   void acceptClientConnection();
   void disconnectClient(int clientSocket);
   void handleClientData(int clientSocket);
+  bool sendToClientSocket(const void *data, size_t length, int clientSocket,
+                          uint16_t clientId);
   void serviceSocket();
 
   static std::atomic<bool> sSignalReceived;
diff --git a/host/common/socket_server.cc b/host/common/socket_server.cc
index b0d2d8a..53c5d3f 100644
--- a/host/common/socket_server.cc
+++ b/host/common/socket_server.cc
@@ -109,21 +109,12 @@
   for (const auto& pair : mClients) {
     int clientSocket = pair.first;
     uint16_t clientId = pair.second.clientId;
-
-    ssize_t bytesSent = send(clientSocket, data, length, 0);
-    if (bytesSent < 0) {
-      LOGE("Error sending packet of size %zu to client %" PRIu16 ": %s",
-           length, clientId, strerror(errno));
-      if (errno == EINTR) {
-        break;
-      }
-    } else if (bytesSent == 0) {
-      LOGW("Client %" PRIu16 " disconnected before message could be delivered",
-           clientId);
-    } else {
-      LOGV("Delivered message of size %zu bytes to client %" PRIu16, length,
-           clientId);
+    if (sendToClientSocket(data, length, clientSocket, clientId)) {
       deliveredCount++;
+    } else if (errno == EINTR) {
+      // Exit early if we were interrupted - we should only get this for
+      // SIGINT/SIGTERM, so we should exit quickly
+      break;
     }
   }
 
@@ -132,6 +123,23 @@
   }
 }
 
+bool SocketServer::sendToClientById(const void *data, size_t length,
+                                    uint16_t clientId) {
+  std::lock_guard<std::mutex> lock(mClientsMutex);
+
+  bool sent = false;
+  for (const auto& pair : mClients) {
+    uint16_t thisClientId = pair.second.clientId;
+    if (thisClientId == clientId) {
+      int clientSocket = pair.first;
+      sent = sendToClientSocket(data, length, clientSocket, thisClientId);
+      break;
+    }
+  }
+
+  return sent;
+}
+
 void SocketServer::acceptClientConnection() {
   int clientSocket = accept(mSockFd, NULL, NULL);
   if (clientSocket < 0) {
@@ -216,6 +224,24 @@
   }
 }
 
+bool SocketServer::sendToClientSocket(const void *data, size_t length,
+                                      int clientSocket, uint16_t clientId) {
+  errno = 0;
+  ssize_t bytesSent = send(clientSocket, data, length, 0);
+  if (bytesSent < 0) {
+    LOGE("Error sending packet of size %zu to client %" PRIu16 ": %s",
+         length, clientId, strerror(errno));
+  } else if (bytesSent == 0) {
+    LOGW("Client %" PRIu16 " disconnected before message could be delivered",
+         clientId);
+  } else {
+    LOGV("Delivered message of size %zu bytes to client %" PRIu16, length,
+         clientId);
+  }
+
+  return (bytesSent > 0);
+}
+
 void SocketServer::serviceSocket() {
   constexpr size_t kListenIndex = 0;
   static_assert(kListenIndex == 0, "Code assumes that the first index is "
diff --git a/host/common/test/chre_test_client.cc b/host/common/test/chre_test_client.cc
index 0fe9c3e..d155b3f 100644
--- a/host/common/test/chre_test_client.cc
+++ b/host/common/test/chre_test_client.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "chre/util/nanoapp/app_id.h"
 #include "chre_host/host_protocol_host.h"
 #include "chre_host/log.h"
 #include "chre_host/socket_client.h"
@@ -22,6 +23,7 @@
 #include <sys/socket.h>
 #include <sys/types.h>
 
+#include <fstream>
 #include <thread>
 
 #include <cutils/sockets.h>
@@ -97,7 +99,7 @@
   }
 
   void handleNanoappListResponse(
-      const fbs::NanoappListResponseT& response) {
+      const fbs::NanoappListResponseT& response) override {
     LOGI("Got nanoapp list response with %zu apps:", response.nanoapps.size());
     for (const std::unique_ptr<fbs::NanoappListEntryT>& nanoapp
            : response.nanoapps) {
@@ -106,6 +108,12 @@
            nanoapp->is_system);
     }
   }
+
+  void handleLoadNanoappResponse(
+      const ::chre::fbs::LoadNanoappResponseT& response) override {
+    LOGI("Got load nanoapp response, transaction ID 0x%" PRIx32 " result %d",
+         response.transaction_id, response.success);
+  }
 };
 
 void requestHubInfo(SocketClient& client) {
@@ -132,7 +140,8 @@
   FlatBufferBuilder builder(64);
   uint8_t messageData[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
   HostProtocolHost::encodeNanoappMessage(
-      builder, 0, kHostEndpoint, 1234, messageData, sizeof(messageData));
+      builder, chre::kMessageWorldAppId, kHostEndpoint, 1234, messageData,
+      sizeof(messageData));
 
   LOGI("Sending message to nanoapp (%u bytes w/%zu bytes of payload)",
        builder.GetSize(), sizeof(messageData));
@@ -141,8 +150,34 @@
   }
 }
 
+void sendLoadNanoappRequest(SocketClient& client, const char *filename) {
+  std::ifstream file(filename, std::ios::binary | std::ios::ate);
+  if (!file) {
+    LOGE("Couldn't open file '%s': %s", filename, strerror(errno));
+    return;
+  }
+  ssize_t size = file.tellg();
+  file.seekg(0, std::ios::beg);
+
+  std::vector<uint8_t> buffer(size);
+  if (!file.read(reinterpret_cast<char *>(buffer.data()), size)) {
+    LOGE("Couldn't read from file: %s", strerror(errno));
+    return;
+  }
+
+  FlatBufferBuilder builder(size + 128);
+  HostProtocolHost::encodeLoadNanoappRequest(
+      builder, 1, 0x476f6f676c00100b, 0, 0x01000000, buffer);
+
+  LOGI("Sending load nanoapp request (%u bytes total w/%zu bytes of payload)",
+       builder.GetSize(), buffer.size());
+  if (!client.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
+    LOGE("Failed to send message");
+  }
 }
 
+}  // anonymous namespace
+
 int main() {
   int ret = -1;
   SocketClient client;
@@ -154,6 +189,7 @@
     requestHubInfo(client);
     requestNanoappList(client);
     sendMessageToNanoapp(client);
+    sendLoadNanoappRequest(client, "/data/activity.so");
 
     LOGI("Sleeping, waiting on responses");
     std::this_thread::sleep_for(std::chrono::seconds(5));
diff --git a/host/hal_generic/generic_context_hub.cc b/host/hal_generic/generic_context_hub.cc
index 2a7fd21..a122bea 100644
--- a/host/hal_generic/generic_context_hub.cc
+++ b/host/hal_generic/generic_context_hub.cc
@@ -15,6 +15,7 @@
  */
 
 #define LOG_TAG "ContextHubHal"
+#define LOG_NDEBUG 0
 
 #include "generic_context_hub.h"
 
@@ -31,7 +32,9 @@
 namespace implementation {
 
 using ::android::hardware::Return;
+using ::android::hardware::contexthub::V1_0::AsyncEventType;
 using ::android::hardware::contexthub::V1_0::Result;
+using ::android::hardware::contexthub::V1_0::TransactionResult;
 using ::android::chre::HostProtocolHost;
 using ::flatbuffers::FlatBufferBuilder;
 
@@ -149,12 +152,30 @@
 
 Return<Result> GenericContextHub::loadNanoApp(
     uint32_t hubId, const NanoAppBinary& appBinary, uint32_t transactionId) {
-  // TODO
-  UNUSED(hubId);
-  UNUSED(appBinary);
-  UNUSED(transactionId);
+  Result result;
   ALOGV("%s", __func__);
-  return Result::UNKNOWN_FAILURE;
+
+  if (hubId != kDefaultHubId) {
+    result = Result::BAD_PARAMS;
+  } else {
+    FlatBufferBuilder builder(128 + appBinary.customBinary.size());
+    uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) |
+                                (appBinary.targetChreApiMinorVersion << 16);
+    HostProtocolHost::encodeLoadNanoappRequest(
+        builder, transactionId, appBinary.appId, appBinary.appVersion,
+        targetApiVersion, appBinary.customBinary);
+    if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
+      result = Result::UNKNOWN_FAILURE;
+    } else {
+      result = Result::OK;
+    }
+  }
+
+  ALOGD("Attempted to send load nanoapp request for app of size %zu with ID "
+        "0x%016" PRIx64 " as transaction ID %" PRIu32 ": result %" PRIu32,
+        appBinary.customBinary.size(), appBinary.appId, transactionId, result);
+
+  return result;
 }
 
 Return<Result> GenericContextHub::unloadNanoApp(
@@ -188,9 +209,21 @@
 }
 
 Return<Result> GenericContextHub::queryApps(uint32_t hubId) {
-  // TODO
-  UNUSED(hubId);
+  Result result;
   ALOGV("%s", __func__);
+
+  if (hubId != kDefaultHubId) {
+    result = Result::BAD_PARAMS;
+  } else {
+    FlatBufferBuilder builder(64);
+    HostProtocolHost::encodeNanoappListRequest(builder);
+    if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) {
+      result = Result::UNKNOWN_FAILURE;
+    } else {
+      result = Result::OK;
+    }
+  }
+
   return Result::UNKNOWN_FAILURE;
 }
 
@@ -204,6 +237,20 @@
   }
 }
 
+void GenericContextHub::SocketCallbacks::onConnected() {
+  if (mHaveConnected) {
+    ALOGI("Reconnected to CHRE daemon");
+    if (mParent.mCallbacks != nullptr) {
+      mParent.mCallbacks->handleHubEvent(AsyncEventType::RESTARTED);
+    }
+  }
+  mHaveConnected = true;
+}
+
+void GenericContextHub::SocketCallbacks::onDisconnected() {
+  ALOGW("Lost connection to CHRE daemon");
+}
+
 void GenericContextHub::SocketCallbacks::handleNanoappMessage(
     uint64_t appId, uint32_t messageType, uint16_t hostEndpoint,
     const void *messageData, size_t messageDataLen) {
@@ -294,6 +341,16 @@
   mParent.mCallbacks->handleAppsInfo(appInfoList);
 }
 
+void GenericContextHub::SocketCallbacks::handleLoadNanoappResponse(
+    const ::chre::fbs::LoadNanoappResponseT& response) {
+  ALOGV("Got load nanoapp response for transaction %" PRIu32 " with result %d",
+        response.transaction_id, response.success);
+
+  TransactionResult result = (response.success) ?
+      TransactionResult::SUCCESS : TransactionResult::FAILURE;
+  mParent.mCallbacks->handleTxnResult(response.transaction_id, result);
+}
+
 IContexthub* HIDL_FETCH_IContexthub(const char* /* name */) {
   return new GenericContextHub();
 }
diff --git a/host/hal_generic/generic_context_hub.h b/host/hal_generic/generic_context_hub.h
index 2e140ef..6b3abb6 100644
--- a/host/hal_generic/generic_context_hub.h
+++ b/host/hal_generic/generic_context_hub.h
@@ -66,6 +66,8 @@
    public:
     SocketCallbacks(GenericContextHub& parent);
     void onMessageReceived(const void *data, size_t length) override;
+    void onConnected() override;
+    void onDisconnected() override;
 
     void handleNanoappMessage(
         uint64_t appId, uint32_t messageType, uint16_t hostEndpoint,
@@ -80,8 +82,12 @@
     void handleNanoappListResponse(
         const ::chre::fbs::NanoappListResponseT& response) override;
 
+    void handleLoadNanoappResponse(
+      const ::chre::fbs::LoadNanoappResponseT& response) override;
+
    private:
     GenericContextHub& mParent;
+    bool mHaveConnected = false;
   };
 
   sp<SocketCallbacks> mSocketCallbacks;
diff --git a/host/msm/daemon/chre_daemon.cc b/host/msm/daemon/chre_daemon.cc
index 023e61f..9e838dd 100644
--- a/host/msm/daemon/chre_daemon.cc
+++ b/host/msm/daemon/chre_daemon.cc
@@ -40,6 +40,8 @@
  * should be fully converted to C++.
  */
 
+#define LOG_NDEBUG 0  // TODO: for initial testing only
+
 #include <ctype.h>
 #include <pthread.h>
 #include <stdbool.h>
@@ -50,9 +52,12 @@
 
 #include "chre/platform/slpi/fastrpc.h"
 #include "chre_host/log.h"
+#include "chre_host/host_protocol_host.h"
 #include "chre_host/socket_server.h"
 #include "generated/chre_slpi.h"
 
+using android::chre::HostProtocolHost;
+
 typedef void *(thread_entry_point_f)(void *);
 
 struct reverse_monitor_thread_data {
@@ -79,7 +84,13 @@
   char line_chars[32];
   int offset_chars = 0;
 
-  LOGD("Dumping buffer of size %zu bytes", size);
+  size_t orig_size = size;
+  if (size > 128) {
+    size = 128;
+    LOGV("Dumping first 128 bytes of buffer of size %zu", orig_size);
+  } else {
+    LOGV("Dumping buffer of size %zu bytes", size);
+  }
   for (size_t i = 1; i <= size; ++i) {
     offset += snprintf(&line[offset], sizeof(line) - offset, "%02x ",
                        buffer[i - 1]);
@@ -87,7 +98,7 @@
         &line_chars[offset_chars], sizeof(line_chars) - offset_chars,
         "%c", (isprint(buffer[i - 1])) ? buffer[i - 1] : '.');
     if ((i % 8) == 0) {
-      LOGD("  %s\t%s", line, line_chars);
+      LOGV("  %s\t%s", line, line_chars);
       offset = 0;
       offset_chars = 0;
     } else if ((i % 4) == 0) {
@@ -103,7 +114,7 @@
       offset += 8;
     }
     *pos = '\0';
-    LOGD("  %s%s%s", line, tabs, line_chars);
+    LOGV("  %s%s%s", line, tabs, line_chars);
   }
 }
 
@@ -131,7 +142,21 @@
       break;
     } else if (result == CHRE_FASTRPC_SUCCESS && messageLen > 0) {
       log_buffer(messageBuffer, messageLen);
-      server->sendToAllClients(messageBuffer, static_cast<size_t>(messageLen));
+      uint16_t hostClientId;
+      if (!HostProtocolHost::extractHostClientId(messageBuffer, messageLen,
+                                                 &hostClientId)) {
+        LOGW("Failed to extract host client ID from message - sending "
+             "broadcast");
+        hostClientId = chre::kHostClientIdUnspecified;
+      }
+
+      if (hostClientId == chre::kHostClientIdUnspecified) {
+        server->sendToAllClients(messageBuffer,
+                                 static_cast<size_t>(messageLen));
+      } else {
+        server->sendToClientById(messageBuffer,
+                                 static_cast<size_t>(messageLen), hostClientId);
+      }
     }
   }
 
@@ -227,8 +252,7 @@
 
 namespace {
 
-void onMessageReceivedFromClient(uint16_t /*clientId*/, const void *data,
-                                 size_t length) {
+void onMessageReceivedFromClient(uint16_t clientId, void *data, size_t length) {
   constexpr size_t kMaxPayloadSize = 1024 * 1024;  // 1 MiB
 
   // This limitation is due to FastRPC, but there's no case where we should come
@@ -239,6 +263,8 @@
   if (length > kMaxPayloadSize) {
     LOGE("Message too large to pass to SLPI (got %zu, max %zu bytes)", length,
          kMaxPayloadSize);
+  } else if (!HostProtocolHost::mutateHostClientId(data, length, clientId)) {
+    LOGE("Couldn't set host client ID in message container!");
   } else {
     LOGD("Delivering message from host (size %zu)", length);
     log_buffer(static_cast<const uint8_t *>(data), length);
diff --git a/platform/include/chre/platform/platform_nanoapp.h b/platform/include/chre/platform/platform_nanoapp.h
index f0725e4..47b78eb 100644
--- a/platform/include/chre/platform/platform_nanoapp.h
+++ b/platform/include/chre/platform/platform_nanoapp.h
@@ -19,41 +19,86 @@
 
 #include <cstdint>
 
+#include "chre/util/non_copyable.h"
 #include "chre/target_platform/platform_nanoapp_base.h"
 
 namespace chre {
 
 /**
- * An interface for calling into nanoapp entry points and managing the
- * lifecycle of a nanoapp.
- *
- * TODO: Look at unloading the app and freeing events that originate from this
- * nanoapp.
+ * The common interface to Nanoapp functionality that has platform-specific
+ * implementation but must be supported for every platform.
  */
-class PlatformNanoapp : public PlatformNanoappBase {
+class PlatformNanoapp : public PlatformNanoappBase, public NonCopyable {
  public:
   /**
-   * Calls the start function of the nanoapp.
+   * Unloads the nanoapp from memory.
+   */
+  ~PlatformNanoapp();
+
+  /**
+   * 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 Returns true if the app was able to start successfully.
+   * @return true if the app was able to start successfully
+   *
+   * @see nanoappStart
    */
   bool start();
 
   /**
-   * Calls the handleEvent function of the nanoapp.
+   * Passes an event to the nanoapp.
    *
-   * @param the instance ID of the sender
-   * @param the type of the event being sent
-   * @param the data passed in
+   * @see nanoappHandleEvent
    */
-  void handleEvent(uint32_t senderInstanceId,
-                   uint16_t eventType,
+  void handleEvent(uint32_t senderInstanceId, uint16_t eventType,
                    const void *eventData);
 
   /**
-   * Calls the stop function of the nanoapp.
+   * Calls the nanoapp's end callback. For dynamically loaded nanoapps, this
+   * must also result in calling through to any of the nanoapp's static global
+   * destructors, atexit functions, etc., after nanoappEnd returns.
+   *
+   * This function must leave the nanoapp in a state where it can be started
+   * again via start().
+   *
+   * After this function returns, the only
+   *
+   * @see nanoappEnd
    */
-  void stop();
+  void end();
+
+  /**
+   * Retrieves the nanoapp's 64-bit identifier. This function must always return
+   * a valid identifier - either the one supplied by the host via the HAL (from
+   * the header), or the authoritative value inside the nanoapp binary if one
+   * exists. In the event that both are available and they do not match, the
+   * platform implementation must return false from start().
+   */
+  uint64_t getAppId() const;
+
+  /**
+   * Retrieves the nanoapp's own version number. The same restrictions apply
+   * here as for getAppId().
+   *
+   * @see #getAppId
+   */
+  uint32_t getAppVersion() const;
+
+  /**
+   * Retrieves the API version that this nanoapp was compiled against. This
+   * function must only be called while the nanoapp is running (i.e. between
+   * calls to start() and end()).
+   */
+  uint32_t getTargetApiVersion() const;
+
+  /**
+   * Returns true if the nanoapp should not appear in the context hub HAL list
+   * of nanoapps, e.g. because it implements some device functionality purely
+   * beneath the HAL.
+   */
+  bool isSystemNanoapp() const;
 };
 
 }  // namespace chre
diff --git a/platform/include/chre/platform/platform_static_nanoapp_init.h b/platform/include/chre/platform/platform_static_nanoapp_init.h
deleted file mode 100644
index 3c4555f..0000000
--- a/platform/include/chre/platform/platform_static_nanoapp_init.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CHRE_PLATFORM_PLATFORM_STATIC_NANOAPP_INIT_H_
-#define CHRE_PLATFORM_PLATFORM_STATIC_NANOAPP_INIT_H_
-
-/**
- * @file
- *
- * Includes the appropriate platform-specific header file that supplies the
- * macro to initialize a static nanoapp. The platform header file must supply the
- * following macro:
- *
- * PLATFORM_STATIC_NANOAPP_INIT(appName)
- *
- * Where appName is the name of a global variable that will be created with type
- * PlatformNanoapp.
- */
-
-#include "chre/platform/platform_nanoapp.h"
-#include "chre/target_platform/platform_static_nanoapp_init.h"
-
-#ifndef PLATFORM_STATIC_NANOAPP_INIT
-#error "PLATFORM_STATIC_NANOAPP_INIT must be defined"
-#endif  // PLATFORM_STATIC_NANOAPP_INIT
-
-#endif  // CHRE_PLATFORM_PLATFORM_STATIC_NANOAPP_INIT_H_
diff --git a/platform/include/chre/platform/static_nanoapp_init.h b/platform/include/chre/platform/static_nanoapp_init.h
new file mode 100644
index 0000000..c607a82
--- /dev/null
+++ b/platform/include/chre/platform/static_nanoapp_init.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_STATIC_NANOAPP_INIT_H_
+#define CHRE_PLATFORM_STATIC_NANOAPP_INIT_H_
+
+/**
+ * @file
+ * Includes the appropriate platform-specific header file that supplies the
+ * macro to initialize a static nanoapp. The platform header file must supply
+ * the following macro:
+ *
+ * CHRE_STATIC_NANOAPP_INIT(appName, appId, appVersion)
+ *
+ * Where appName is the name of a global variable that will be created with type
+ * PlatformNanoapp, appId is the app's 64-bit identifier, and appVersion is the
+ * application-defined 32-bit version number.
+ */
+
+// Since this file is included in nanoapp code, it's likely that the nanoapp log
+// macro will have been included, resulting in conflicting definitions of the
+// log macros with the ones pulled in via nanoapp.h. Undefine these macros to
+// allow their redefinition for CHRE system code.
+#ifdef CHRE_UTIL_NANOAPP_LOG_H_
+#undef LOGE
+#undef LOGW
+#undef LOGI
+#undef LOGD
+#endif // CHRE_UTIL_NANOAPP_LOG_H_
+
+#include "chre/target_platform/static_nanoapp_init.h"
+
+#ifndef CHRE_STATIC_NANOAPP_INIT
+#error "CHRE_STATIC_NANOAPP_INIT must be defined by the target platform's static_nanoapp_init.h"
+#endif
+
+#endif  // CHRE_PLATFORM_STATIC_NANOAPP_INIT_H_
diff --git a/platform/shared/include/chre/platform/static_nanoapps.h b/platform/include/chre/platform/static_nanoapps.h
similarity index 95%
rename from platform/shared/include/chre/platform/static_nanoapps.h
rename to platform/include/chre/platform/static_nanoapps.h
index f2f14f7..4bbf7d0 100644
--- a/platform/shared/include/chre/platform/static_nanoapps.h
+++ b/platform/include/chre/platform/static_nanoapps.h
@@ -22,7 +22,7 @@
 namespace chre {
 
 //! The list of static nanoapps to load.
-extern PlatformNanoapp *const kStaticNanoappList[];
+extern UniquePtr<Nanoapp> *const kStaticNanoappList[];
 
 //! The number of static nanoapps to load.
 extern const size_t kStaticNanoappCount;
diff --git a/platform/linux/include/chre/target_platform/platform_nanoapp_base.h b/platform/linux/include/chre/target_platform/platform_nanoapp_base.h
index ae30ece..1dd1543 100644
--- a/platform/linux/include/chre/target_platform/platform_nanoapp_base.h
+++ b/platform/linux/include/chre/target_platform/platform_nanoapp_base.h
@@ -28,13 +28,16 @@
  */
 struct PlatformNanoappBase {
   //! The function pointer of the nanoapp start entry point.
-  NanoappStartFunction *mStart;
+  chreNanoappStartFunction *mStart;
 
   //! The function pointer of the nanoapp handle event entry point.
-  NanoappHandleEventFunction *mHandleEvent;
+  chreNanoappHandleEventFunction *mHandleEvent;
 
-  //! The function pointer of the nanoapp stop entry point.
-  NanoappStopFunction *mStop;
+  //! The function pointer of the nanoapp end entry point.
+  chreNanoappEndFunction *mEnd;
+
+  uint64_t mAppId;
+  uint32_t mAppVersion;
 };
 
 }  // namespace chre
diff --git a/platform/linux/include/chre/target_platform/static_nanoapp_init.h b/platform/linux/include/chre/target_platform/static_nanoapp_init.h
new file mode 100644
index 0000000..c9fb5bf
--- /dev/null
+++ b/platform/linux/include/chre/target_platform/static_nanoapp_init.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_LINUX_STATIC_NANOAPP_INIT_H_
+#define CHRE_PLATFORM_LINUX_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 Linux 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
+ */
+#define CHRE_STATIC_NANOAPP_INIT(appName, appId, appVersion) \
+namespace chre {                                             \
+UniquePtr<Nanoapp> *gNanoapp##appName;                       \
+                                                             \
+__attribute__((constructor))                                 \
+static void initializeStaticNanoapp##appName() {             \
+  static UniquePtr<Nanoapp> nanoapp = MakeUnique<Nanoapp>(); \
+  if (nanoapp.isNull()) {                                    \
+    FATAL_ERROR("Failed to allocate nanoapp " #appName);     \
+  } else {                                                   \
+    nanoapp->mStart = nanoappStart;                          \
+    nanoapp->mHandleEvent = nanoappHandleEvent;              \
+    nanoapp->mEnd = nanoappEnd;                              \
+    nanoapp->mAppId = appId;                                 \
+    nanoapp->mAppVersion = appVersion;                       \
+    gNanoapp##appName = &nanoapp;                            \
+  }                                                          \
+}                                                            \
+}  /* namespace chre */
+
+#endif  // CHRE_PLATFORM_LINUX_STATIC_NANOAPP_INIT_H_
diff --git a/platform/shared/platform_nanoapp.cc b/platform/linux/platform_nanoapp.cc
similarity index 70%
rename from platform/shared/platform_nanoapp.cc
rename to platform/linux/platform_nanoapp.cc
index 88f6756..39266c8 100644
--- a/platform/shared/platform_nanoapp.cc
+++ b/platform/linux/platform_nanoapp.cc
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
+#include "chre_api/chre/version.h"
 #include "chre/platform/platform_nanoapp.h"
 
 namespace chre {
 
+PlatformNanoapp::~PlatformNanoapp() {}
+
 bool PlatformNanoapp::start() {
   return mStart();
 }
@@ -28,8 +31,24 @@
   mHandleEvent(senderInstanceId, eventType, eventData);
 }
 
-void PlatformNanoapp::stop() {
-  mStop();
+void PlatformNanoapp::end() {
+  mEnd();
+}
+
+uint64_t PlatformNanoapp::getAppId() const {
+  return mAppId;
+}
+
+uint32_t PlatformNanoapp::getAppVersion() const {
+  return mAppVersion;
+}
+
+uint32_t PlatformNanoapp::getTargetApiVersion() const {
+  return CHRE_API_VERSION;
+}
+
+bool PlatformNanoapp::isSystemNanoapp() const {
+  return true;
 }
 
 }  // namespace chre
diff --git a/platform/linux/static_nanoapps.cc b/platform/linux/static_nanoapps.cc
index 8df55ff..817a535 100644
--- a/platform/linux/static_nanoapps.cc
+++ b/platform/linux/static_nanoapps.cc
@@ -24,15 +24,15 @@
 
 //! The list of static nanoapps to load for the Linux platform.
 __attribute__((weak))
-PlatformNanoapp *const kStaticNanoappList[] = {
-  &gNanoappGnssWorld,
-  &gNanoappHelloWorld,
-  &gNanoappImuCal,
-  &gNanoappMessageWorld,
-  &gNanoappSensorWorld,
-  &gNanoappTimerWorld,
-  &gNanoappWifiWorld,
-  &gNanoappWwanWorld,
+UniquePtr<Nanoapp> *const kStaticNanoappList[] = {
+  gNanoappGnssWorld,
+  gNanoappHelloWorld,
+  gNanoappImuCal,
+  gNanoappMessageWorld,
+  gNanoappSensorWorld,
+  gNanoappTimerWorld,
+  gNanoappWifiWorld,
+  gNanoappWwanWorld,
 };
 
 //! The size of the static nanoapp list.
diff --git a/platform/platform.mk b/platform/platform.mk
index 8389a41..f060c8e 100644
--- a/platform/platform.mk
+++ b/platform/platform.mk
@@ -34,7 +34,6 @@
 HEXAGON_SRCS += platform/shared/memory.cc
 HEXAGON_SRCS += platform/shared/pal_system_api.cc
 HEXAGON_SRCS += platform/shared/platform_gnss.cc
-HEXAGON_SRCS += platform/shared/platform_nanoapp.cc
 HEXAGON_SRCS += platform/shared/platform_sensor.cc
 HEXAGON_SRCS += platform/shared/platform_wifi.cc
 HEXAGON_SRCS += platform/shared/platform_wwan.cc
@@ -42,6 +41,7 @@
 HEXAGON_SRCS += platform/shared/system_time.cc
 HEXAGON_SRCS += platform/slpi/host_link.cc
 HEXAGON_SRCS += platform/slpi/init.cc
+HEXAGON_SRCS += platform/slpi/platform_nanoapp.cc
 HEXAGON_SRCS += platform/slpi/platform_sensor.cc
 HEXAGON_SRCS += platform/slpi/platform_sensor_util.cc
 HEXAGON_SRCS += platform/slpi/static_nanoapps.cc
@@ -60,6 +60,7 @@
 X86_SRCS += platform/linux/static_nanoapps.cc
 X86_SRCS += platform/linux/system_time.cc
 X86_SRCS += platform/linux/system_timer.cc
+X86_SRCS += platform/linux/platform_nanoapp.cc
 X86_SRCS += platform/linux/platform_sensor.cc
 X86_SRCS += platform/shared/chre_api_core.cc
 X86_SRCS += platform/shared/chre_api_gnss.cc
@@ -74,7 +75,6 @@
 X86_SRCS += platform/shared/pal_wwan_stub.cc
 X86_SRCS += platform/shared/pal_system_api.cc
 X86_SRCS += platform/shared/platform_gnss.cc
-X86_SRCS += platform/shared/platform_nanoapp.cc
 X86_SRCS += platform/shared/platform_sensor.cc
 X86_SRCS += platform/shared/platform_wifi.cc
 X86_SRCS += platform/shared/platform_wwan.cc
diff --git a/platform/shared/dso_csl_api.cc b/platform/shared/dso_csl_api.cc
deleted file mode 100644
index 5c96372..0000000
--- a/platform/shared/dso_csl_api.cc
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "chre/target_platform/dso_csl_api.h"
-
-bool chreDsoCslGetApi(uint32_t apiId, void **apiHandle) {
-  // TODO: implement this some time after nanoapps become DSOs
-  return false;
-}
diff --git a/platform/shared/host_protocol_chre.cc b/platform/shared/host_protocol_chre.cc
index 43cc762..dccdc2b 100644
--- a/platform/shared/host_protocol_chre.cc
+++ b/platform/shared/host_protocol_chre.cc
@@ -36,6 +36,7 @@
          messageLen);
   } else {
     const fbs::MessageContainer *container = fbs::GetMessageContainer(message);
+    uint16_t hostClientId = container->host_addr()->client_id();
 
     switch (container->message_type()) {
       case fbs::ChreMessage::NanoappMessage: {
@@ -51,13 +52,24 @@
       }
 
       case fbs::ChreMessage::HubInfoRequest:
-        HostMessageHandlers::handleHubInfoRequest();
+        HostMessageHandlers::handleHubInfoRequest(hostClientId);
         break;
 
       case fbs::ChreMessage::NanoappListRequest:
-        HostMessageHandlers::handleNanoappListRequest();
+        HostMessageHandlers::handleNanoappListRequest(hostClientId);
         break;
 
+      case fbs::ChreMessage::LoadNanoappRequest: {
+        const auto *request = static_cast<const fbs::LoadNanoappRequest *>(
+            container->message());
+        const flatbuffers::Vector<uint8_t> *appBinary = request->app_binary();
+        HostMessageHandlers::handleLoadNanoappRequest(
+            hostClientId, request->transaction_id(), request->app_id(),
+            request->app_version(), request->target_api_version(),
+            appBinary->data(), appBinary->size());
+        break;
+      }
+
       default:
         LOGW("Got invalid/unexpected message type %" PRIu8,
              static_cast<uint8_t>(container->message_type()));
@@ -73,7 +85,7 @@
     const char *toolchain, uint32_t legacyPlatformVersion,
     uint32_t legacyToolchainVersion, float peakMips, float stoppedPower,
     float sleepPower, float peakPower, uint32_t maxMessageLen,
-    uint64_t platformId, uint32_t version) {
+    uint64_t platformId, uint32_t version, uint16_t hostClientId) {
   auto nameOffset = addStringAsByteVector(builder, name);
   auto vendorOffset = addStringAsByteVector(builder, vendor);
   auto toolchainOffset = addStringAsByteVector(builder, toolchain);
@@ -82,9 +94,8 @@
       builder, nameOffset, vendorOffset, toolchainOffset, legacyPlatformVersion,
       legacyToolchainVersion, peakMips, stoppedPower, sleepPower, peakPower,
       maxMessageLen, platformId, version);
-  auto container = fbs::CreateMessageContainer(
-      builder, fbs::ChreMessage::HubInfoResponse, response.Union());
-  builder.Finish(container);
+  finalize(builder, fbs::ChreMessage::HubInfoResponse, response.Union(),
+           hostClientId);
 }
 
 void HostProtocolChre::addNanoappListEntry(
@@ -100,13 +111,22 @@
 
 void HostProtocolChre::finishNanoappListResponse(
     FlatBufferBuilder& builder,
-    DynamicVector<Offset<fbs::NanoappListEntry>>& offsetVector) {
+    DynamicVector<Offset<fbs::NanoappListEntry>>& offsetVector,
+    uint16_t hostClientId) {
   auto vectorOffset = builder.CreateVector<Offset<fbs::NanoappListEntry>>(
       offsetVector);
   auto response = fbs::CreateNanoappListResponse(builder, vectorOffset);
-  auto container = fbs::CreateMessageContainer(
-      builder, fbs::ChreMessage::NanoappListResponse, response.Union());
-  builder.Finish(container);
+  finalize(builder, fbs::ChreMessage::NanoappListResponse, response.Union(),
+           hostClientId);
+}
+
+void HostProtocolChre::encodeLoadNanoappResponse(
+    flatbuffers::FlatBufferBuilder& builder, uint16_t hostClientId,
+    uint32_t transactionId, bool success) {
+  auto response = fbs::CreateLoadNanoappResponse(builder, transactionId,
+                                                 success);
+  finalize(builder, fbs::ChreMessage::LoadNanoappResponse, response.Union(),
+           hostClientId);
 }
 
 }  // namespace chre
diff --git a/platform/shared/host_protocol_common.cc b/platform/shared/host_protocol_common.cc
index f7d187a..eab72bf 100644
--- a/platform/shared/host_protocol_common.cc
+++ b/platform/shared/host_protocol_common.cc
@@ -26,12 +26,32 @@
 
 namespace chre {
 
+void HostProtocolCommon::encodeNanoappMessage(
+    FlatBufferBuilder& builder, uint64_t appId, uint32_t messageType,
+    uint16_t hostEndpoint, const void *messageData, size_t messageDataLen) {
+  auto messageDataOffset = builder.CreateVector(
+      static_cast<const uint8_t *>(messageData), messageDataLen);
+
+  auto nanoappMessage = fbs::CreateNanoappMessage(
+      builder, appId, messageType, hostEndpoint, messageDataOffset);
+  finalize(builder, fbs::ChreMessage::NanoappMessage, nanoappMessage.Union());
+}
+
 Offset<Vector<int8_t>> HostProtocolCommon::addStringAsByteVector(
     FlatBufferBuilder& builder, const char *str) {
   return builder.CreateVector(reinterpret_cast<const int8_t *>(str),
                               strlen(str) + 1);
 }
 
+void HostProtocolCommon::finalize(
+    FlatBufferBuilder& builder, fbs::ChreMessage messageType,
+    flatbuffers::Offset<void> message, uint16_t hostClientId) {
+  fbs::HostAddress hostAddr(hostClientId);
+  auto container = fbs::CreateMessageContainer(
+      builder, messageType, message, &hostAddr);
+  builder.Finish(container);
+}
+
 bool HostProtocolCommon::verifyMessage(const void *message, size_t messageLen) {
   bool valid = false;
 
@@ -45,17 +65,5 @@
   return valid;
 }
 
-void HostProtocolCommon::encodeNanoappMessage(
-    FlatBufferBuilder& builder, uint64_t appId, uint32_t messageType,
-    uint16_t hostEndpoint, const void *messageData, size_t messageDataLen) {
-  auto messageDataOffset = builder.CreateVector(
-      static_cast<const uint8_t *>(messageData), messageDataLen);
-
-  auto nanoappMessage = fbs::CreateNanoappMessage(
-      builder, appId, messageType, hostEndpoint, messageDataOffset);
-  auto container = fbs::CreateMessageContainer(
-      builder, fbs::ChreMessage::NanoappMessage, nanoappMessage.Union());
-  builder.Finish(container);
-}
 
 }  // namespace chre
diff --git a/platform/shared/idl/host_messages.fbs b/platform/shared/idl/host_messages.fbs
index 30bb2fc..7472553 100644
--- a/platform/shared/idl/host_messages.fbs
+++ b/platform/shared/idl/host_messages.fbs
@@ -82,6 +82,23 @@
   nanoapps:[NanoappListEntry] (required);
 }
 
+table LoadNanoappRequest {
+  transaction_id:uint;
+
+  app_id:ulong;
+  app_version:uint;
+  target_api_version:uint;
+
+  app_binary:[ubyte] (required);
+}
+
+table LoadNanoappResponse {
+  transaction_id:uint;
+  success:bool;
+
+  // TODO: detailed error code?
+}
+
 /// A union that joins together all possible messages. Note that in FlatBuffers,
 /// unions have an implicit type
 union ChreMessage {
@@ -93,14 +110,29 @@
   NanoappListRequest,
   NanoappListResponse,
 
+  LoadNanoappRequest,
+  LoadNanoappResponse,
+
   // TODO: extend with system-specific messages, e.g. load app command, etc.
 }
 
+struct HostAddress {
+  client_id:ushort;
+}
+
 /// 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.
 table MessageContainer {
   message:ChreMessage (required);
+
+  /// 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
+  /// value by the entity guarding access to CHRE.
+  /// This is wrapped in a struct to ensure that it is always included when
+  /// encoding the message, so it can be mutated by the host daemon.
+  host_addr:HostAddress (required);
 }
 
 root_type MessageContainer;
diff --git a/platform/shared/include/chre/platform/shared/host_messages_generated.h b/platform/shared/include/chre/platform/shared/host_messages_generated.h
index 1d65ade..45bd73a 100644
--- a/platform/shared/include/chre/platform/shared/host_messages_generated.h
+++ b/platform/shared/include/chre/platform/shared/host_messages_generated.h
@@ -21,6 +21,12 @@
 
 struct NanoappListResponse;
 
+struct LoadNanoappRequest;
+
+struct LoadNanoappResponse;
+
+struct HostAddress;
+
 struct MessageContainer;
 
 /// A union that joins together all possible messages. Note that in FlatBuffers,
@@ -32,8 +38,10 @@
   HubInfoResponse = 3,
   NanoappListRequest = 4,
   NanoappListResponse = 5,
+  LoadNanoappRequest = 6,
+  LoadNanoappResponse = 7,
   MIN = NONE,
-  MAX = NanoappListResponse
+  MAX = LoadNanoappResponse
 };
 
 inline const char **EnumNamesChreMessage() {
@@ -44,6 +52,8 @@
     "HubInfoResponse",
     "NanoappListRequest",
     "NanoappListResponse",
+    "LoadNanoappRequest",
+    "LoadNanoappResponse",
     nullptr
   };
   return names;
@@ -78,9 +88,37 @@
   static const ChreMessage enum_value = ChreMessage::NanoappListResponse;
 };
 
+template<> struct ChreMessageTraits<LoadNanoappRequest> {
+  static const ChreMessage enum_value = ChreMessage::LoadNanoappRequest;
+};
+
+template<> struct ChreMessageTraits<LoadNanoappResponse> {
+  static const ChreMessage enum_value = ChreMessage::LoadNanoappResponse;
+};
+
 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);
 
+MANUALLY_ALIGNED_STRUCT(2) HostAddress FLATBUFFERS_FINAL_CLASS {
+ private:
+  uint16_t client_id_;
+
+ public:
+  HostAddress() {
+    memset(this, 0, sizeof(HostAddress));
+  }
+  HostAddress(const HostAddress &_o) {
+    memcpy(this, &_o, sizeof(HostAddress));
+  }
+  HostAddress(uint16_t _client_id)
+      : client_id_(flatbuffers::EndianScalar(_client_id)) {
+  }
+  uint16_t client_id() const {
+    return flatbuffers::EndianScalar(client_id_);
+  }
+};
+STRUCT_END(HostAddress, 2);
+
 /// Represents a message sent to/from a nanoapp from/to a client on the host
 struct NanoappMessage FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   enum {
@@ -546,13 +584,162 @@
       nanoapps ? _fbb.CreateVector<flatbuffers::Offset<NanoappListEntry>>(*nanoapps) : 0);
 }
 
+struct LoadNanoappRequest FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum {
+    VT_TRANSACTION_ID = 4,
+    VT_APP_ID = 6,
+    VT_APP_VERSION = 8,
+    VT_TARGET_API_VERSION = 10,
+    VT_APP_BINARY = 12
+  };
+  uint32_t transaction_id() const {
+    return GetField<uint32_t>(VT_TRANSACTION_ID, 0);
+  }
+  uint64_t app_id() const {
+    return GetField<uint64_t>(VT_APP_ID, 0);
+  }
+  uint32_t app_version() const {
+    return GetField<uint32_t>(VT_APP_VERSION, 0);
+  }
+  uint32_t target_api_version() const {
+    return GetField<uint32_t>(VT_TARGET_API_VERSION, 0);
+  }
+  const flatbuffers::Vector<uint8_t> *app_binary() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_APP_BINARY);
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint32_t>(verifier, VT_TRANSACTION_ID) &&
+           VerifyField<uint64_t>(verifier, VT_APP_ID) &&
+           VerifyField<uint32_t>(verifier, VT_APP_VERSION) &&
+           VerifyField<uint32_t>(verifier, VT_TARGET_API_VERSION) &&
+           VerifyFieldRequired<flatbuffers::uoffset_t>(verifier, VT_APP_BINARY) &&
+           verifier.Verify(app_binary()) &&
+           verifier.EndTable();
+  }
+};
+
+struct LoadNanoappRequestBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_transaction_id(uint32_t transaction_id) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_TRANSACTION_ID, transaction_id, 0);
+  }
+  void add_app_id(uint64_t app_id) {
+    fbb_.AddElement<uint64_t>(LoadNanoappRequest::VT_APP_ID, app_id, 0);
+  }
+  void add_app_version(uint32_t app_version) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_APP_VERSION, app_version, 0);
+  }
+  void add_target_api_version(uint32_t target_api_version) {
+    fbb_.AddElement<uint32_t>(LoadNanoappRequest::VT_TARGET_API_VERSION, target_api_version, 0);
+  }
+  void add_app_binary(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> app_binary) {
+    fbb_.AddOffset(LoadNanoappRequest::VT_APP_BINARY, app_binary);
+  }
+  LoadNanoappRequestBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  LoadNanoappRequestBuilder &operator=(const LoadNanoappRequestBuilder &);
+  flatbuffers::Offset<LoadNanoappRequest> Finish() {
+    const auto end = fbb_.EndTable(start_, 5);
+    auto o = flatbuffers::Offset<LoadNanoappRequest>(end);
+    fbb_.Required(o, LoadNanoappRequest::VT_APP_BINARY);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<LoadNanoappRequest> CreateLoadNanoappRequest(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t transaction_id = 0,
+    uint64_t app_id = 0,
+    uint32_t app_version = 0,
+    uint32_t target_api_version = 0,
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> app_binary = 0) {
+  LoadNanoappRequestBuilder builder_(_fbb);
+  builder_.add_app_id(app_id);
+  builder_.add_app_binary(app_binary);
+  builder_.add_target_api_version(target_api_version);
+  builder_.add_app_version(app_version);
+  builder_.add_transaction_id(transaction_id);
+  return builder_.Finish();
+}
+
+inline flatbuffers::Offset<LoadNanoappRequest> CreateLoadNanoappRequestDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t transaction_id = 0,
+    uint64_t app_id = 0,
+    uint32_t app_version = 0,
+    uint32_t target_api_version = 0,
+    const std::vector<uint8_t> *app_binary = nullptr) {
+  return chre::fbs::CreateLoadNanoappRequest(
+      _fbb,
+      transaction_id,
+      app_id,
+      app_version,
+      target_api_version,
+      app_binary ? _fbb.CreateVector<uint8_t>(*app_binary) : 0);
+}
+
+struct LoadNanoappResponse FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+  enum {
+    VT_TRANSACTION_ID = 4,
+    VT_SUCCESS = 6
+  };
+  uint32_t transaction_id() const {
+    return GetField<uint32_t>(VT_TRANSACTION_ID, 0);
+  }
+  bool success() const {
+    return GetField<uint8_t>(VT_SUCCESS, 0) != 0;
+  }
+  bool Verify(flatbuffers::Verifier &verifier) const {
+    return VerifyTableStart(verifier) &&
+           VerifyField<uint32_t>(verifier, VT_TRANSACTION_ID) &&
+           VerifyField<uint8_t>(verifier, VT_SUCCESS) &&
+           verifier.EndTable();
+  }
+};
+
+struct LoadNanoappResponseBuilder {
+  flatbuffers::FlatBufferBuilder &fbb_;
+  flatbuffers::uoffset_t start_;
+  void add_transaction_id(uint32_t transaction_id) {
+    fbb_.AddElement<uint32_t>(LoadNanoappResponse::VT_TRANSACTION_ID, transaction_id, 0);
+  }
+  void add_success(bool success) {
+    fbb_.AddElement<uint8_t>(LoadNanoappResponse::VT_SUCCESS, static_cast<uint8_t>(success), 0);
+  }
+  LoadNanoappResponseBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+        : fbb_(_fbb) {
+    start_ = fbb_.StartTable();
+  }
+  LoadNanoappResponseBuilder &operator=(const LoadNanoappResponseBuilder &);
+  flatbuffers::Offset<LoadNanoappResponse> Finish() {
+    const auto end = fbb_.EndTable(start_, 2);
+    auto o = flatbuffers::Offset<LoadNanoappResponse>(end);
+    return o;
+  }
+};
+
+inline flatbuffers::Offset<LoadNanoappResponse> CreateLoadNanoappResponse(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    uint32_t transaction_id = 0,
+    bool success = false) {
+  LoadNanoappResponseBuilder builder_(_fbb);
+  builder_.add_transaction_id(transaction_id);
+  builder_.add_success(success);
+  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.
 struct MessageContainer FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   enum {
     VT_MESSAGE_TYPE = 4,
-    VT_MESSAGE = 6
+    VT_MESSAGE = 6,
+    VT_HOST_ADDR = 8
   };
   ChreMessage message_type() const {
     return static_cast<ChreMessage>(GetField<uint8_t>(VT_MESSAGE_TYPE, 0));
@@ -560,11 +747,21 @@
   const void *message() const {
     return GetPointer<const void *>(VT_MESSAGE);
   }
+  /// 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
+  /// value by the entity guarding access to CHRE.
+  /// This is wrapped in a struct to ensure that it is always included when
+  /// encoding the message, so it can be mutated by the host daemon.
+  const HostAddress *host_addr() const {
+    return GetStruct<const HostAddress *>(VT_HOST_ADDR);
+  }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<uint8_t>(verifier, VT_MESSAGE_TYPE) &&
            VerifyFieldRequired<flatbuffers::uoffset_t>(verifier, VT_MESSAGE) &&
            VerifyChreMessage(verifier, message(), message_type()) &&
+           VerifyFieldRequired<HostAddress>(verifier, VT_HOST_ADDR) &&
            verifier.EndTable();
   }
 };
@@ -578,15 +775,19 @@
   void add_message(flatbuffers::Offset<void> message) {
     fbb_.AddOffset(MessageContainer::VT_MESSAGE, message);
   }
+  void add_host_addr(const HostAddress *host_addr) {
+    fbb_.AddStruct(MessageContainer::VT_HOST_ADDR, host_addr);
+  }
   MessageContainerBuilder(flatbuffers::FlatBufferBuilder &_fbb)
         : fbb_(_fbb) {
     start_ = fbb_.StartTable();
   }
   MessageContainerBuilder &operator=(const MessageContainerBuilder &);
   flatbuffers::Offset<MessageContainer> Finish() {
-    const auto end = fbb_.EndTable(start_, 2);
+    const auto end = fbb_.EndTable(start_, 3);
     auto o = flatbuffers::Offset<MessageContainer>(end);
     fbb_.Required(o, MessageContainer::VT_MESSAGE);
+    fbb_.Required(o, MessageContainer::VT_HOST_ADDR);
     return o;
   }
 };
@@ -594,8 +795,10 @@
 inline flatbuffers::Offset<MessageContainer> CreateMessageContainer(
     flatbuffers::FlatBufferBuilder &_fbb,
     ChreMessage message_type = ChreMessage::NONE,
-    flatbuffers::Offset<void> message = 0) {
+    flatbuffers::Offset<void> message = 0,
+    const HostAddress *host_addr = 0) {
   MessageContainerBuilder builder_(_fbb);
+  builder_.add_host_addr(host_addr);
   builder_.add_message(message);
   builder_.add_message_type(message_type);
   return builder_.Finish();
@@ -626,6 +829,14 @@
       auto ptr = reinterpret_cast<const NanoappListResponse *>(obj);
       return verifier.VerifyTable(ptr);
     }
+    case ChreMessage::LoadNanoappRequest: {
+      auto ptr = reinterpret_cast<const LoadNanoappRequest *>(obj);
+      return verifier.VerifyTable(ptr);
+    }
+    case ChreMessage::LoadNanoappResponse: {
+      auto ptr = reinterpret_cast<const LoadNanoappResponse *>(obj);
+      return verifier.VerifyTable(ptr);
+    }
     default: return false;
   }
 }
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 737d9a8..ee2642d 100644
--- a/platform/shared/include/chre/platform/shared/host_protocol_chre.h
+++ b/platform/shared/include/chre/platform/shared/host_protocol_chre.h
@@ -37,8 +37,14 @@
     uint64_t appId, uint32_t messageType, uint16_t hostEndpoint,
     const void *messageData, size_t messageDataLen);
 
-  static void handleHubInfoRequest();
-  static void handleNanoappListRequest();
+  static void handleHubInfoRequest(uint16_t hostClientId);
+
+  static void handleNanoappListRequest(uint16_t hostClientId);
+
+  static void handleLoadNanoappRequest(
+      uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+      uint32_t appVersion, uint32_t targetApiVersion, const void *appBinary,
+      size_t appBinaryLen);
 };
 
 /**
@@ -70,7 +76,7 @@
       const char *vendor, const char *toolchain, uint32_t legacyPlatformVersion,
       uint32_t legacyToolchainVersion, float peakMips, float stoppedPower,
       float sleepPower, float peakPower, uint32_t maxMessageLen,
-      uint64_t platformId, uint32_t version);
+      uint64_t platformId, uint32_t version, uint16_t hostClientId);
 
   /**
    * Supports construction of a NanoappListResponse by adding a single
@@ -101,12 +107,22 @@
    *
    * @param builder The FlatBufferBuilder used with addNanoappListEntry()
    * @param offsetVector The vector used with addNanoappListEntry()
+   * @param hostClientId
    *
    * @see addNanoappListEntry()
    */
   static void finishNanoappListResponse(
       flatbuffers::FlatBufferBuilder& builder,
-      DynamicVector<NanoappListEntryOffset>& offsetVector);
+      DynamicVector<NanoappListEntryOffset>& offsetVector,
+      uint16_t hostClientId);
+
+  /**
+   * Encodes a response to the host communicating the result of dynamically
+   * loading a nanoapp.
+   */
+  static void encodeLoadNanoappResponse(
+      flatbuffers::FlatBufferBuilder& builder, uint16_t hostClientId,
+      uint32_t transactionId, bool success);
 };
 
 }  // namespace chre
diff --git a/platform/shared/include/chre/platform/shared/host_protocol_common.h b/platform/shared/include/chre/platform/shared/host_protocol_common.h
index 68ec136..69e487f 100644
--- a/platform/shared/include/chre/platform/shared/host_protocol_common.h
+++ b/platform/shared/include/chre/platform/shared/host_protocol_common.h
@@ -23,6 +23,20 @@
 
 namespace chre {
 
+namespace fbs {
+
+// Forward declaration of the ChreMessage enum defined in the generated
+// FlatBuffers header file
+enum class ChreMessage : uint8_t;
+
+}  // namespace fbs
+
+//! On a message sent from CHRE, specifies that the host daemon should determine
+//! which client to send the message to. Usually, this is all clients, but for a
+//! message from a nanoapp, the host daemon can use the endpoint ID to determine
+//! the destination client ID.
+constexpr uint16_t kHostClientIdUnspecified = 0;
+
 /**
  * Functions that are shared between the CHRE and host to assist with
  * communications between the two. Note that normally these functions are
@@ -48,6 +62,25 @@
    static flatbuffers::Offset<flatbuffers::Vector<int8_t>>
        addStringAsByteVector(flatbuffers::FlatBufferBuilder& builder,
                              const char *str);
+
+   /**
+    * Constructs the message container and finalizes the FlatBufferBuilder
+    *
+    * @param builder The FlatBufferBuilder that was used to construct the
+    *        message prior to adding the container
+    * @param messageType Type of message that was constructed
+    * @param message Offset of the message to include (normally the return value
+    *        of flatbuffers::Offset::Union() on the message offset)
+    * @param hostClientId The source/client ID of the host-side entity that
+    *        sent/should receive this message. Leave unspecified (default 0)
+    *        when constructing a message on the host, as this field will be
+    *        set before the message is sent to CHRE.
+    */
+   static void finalize(
+       flatbuffers::FlatBufferBuilder& builder, fbs::ChreMessage messageType,
+       flatbuffers::Offset<void> message,
+       uint16_t hostClientId = kHostClientIdUnspecified);
+
    static bool verifyMessage(const void *message, size_t messageLen);
 };
 
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
new file mode 100644
index 0000000..e824927
--- /dev/null
+++ b/platform/shared/include/chre/platform/shared/nanoapp_support_lib_dso.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_SHARED_NANOAPP_SUPPORT_LIB_DSO_H_
+#define CHRE_PLATFORM_SHARED_NANOAPP_SUPPORT_LIB_DSO_H_
+
+/**
+ * @file
+ * This provides the interface that the dynamic shared object (DSO) nanoapp
+ * nanoapp support library (NSL) uses to interface with the underlying CHRE
+ * implementation in a compatible manner.
+ *
+ * This header file must retain compatibility with C, and have minimal or no
+ * dependencies on other CHRE system header files, as it will be used when
+ * compiling external/dynamic nanoapps.
+ */
+
+#include "chre/util/entry_points.h"
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//! Special magic value to uniquely identify the nanoapp info structure
+#define CHRE_NSL_NANOAPP_INFO_MAGIC  UINT32_C(0x50e69977)
+
+//! The minor version in the nanoapp info structure helps identify whether
+#define CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION  UINT8_C(0)
+
+//! The symbol name expected from the nanoapp's definition of its info struct
+#define CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME  "_chreNslDsoNanoappInfo"
+
+//! Maximum length of vendor and name strings
+#define CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN  (32)
+
+/**
+ * DSO-based nanoapps must expose this struct under a symbol whose name is given
+ * by CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME. When the nanoapp is loaded, dlsym()
+ * will be used to locate this symbol to register the nanoapp with the system.
+ */
+struct chreNslNanoappInfo {
+  //! @see CHRE_NSL_NANOAPP_INFO_MAGIC
+  uint32_t magic;
+
+  //! @see CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION
+  uint8_t structMinorVersion;
+
+  //! Set to 1 if this nanoapp is a "system nanoapp" that should not show up in
+  //! the context hub HAL, likely because it implements some device
+  //! functionality beneath the HAL.
+  uint8_t isSystemNanoapp:1;
+
+  //! Reserved for future use, set to 0. Assignment of this field to some use
+  //! must be accompanied by an increase of the struct minor version.
+  uint8_t reservedFlags:7;
+  uint8_t reserved;
+
+  //! The CHRE API version that the nanoapp was compiled against
+  uint32_t targetApiVersion;
+
+  //! A human-friendly name of the nanoapp vendor (null-terminated string,
+  //! maximum length CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN)
+  const char *vendor;
+
+  //! A human-friendly name for the nanoapp (null-terminated string, maximum
+  //! length CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN)
+  const char *name;
+
+  //! Identifies the vendor (most significant 5 bytes) and application
+  uint64_t appId;
+
+  //! Application-specific version number
+  uint32_t appVersion;
+
+  struct {
+    chreNanoappStartFunction *start;
+    chreNanoappHandleEventFunction *handleEvent;
+    chreNanoappEndFunction *end;
+  } entryPoints;
+};
+
+/**
+ * Defined as a placeholder to enable future functionality extension.
+ *
+ * @param apiId
+ * @param apiHandle If this function returns true, this will be set to a pointer
+ *        to the associated structure containing the API
+ *
+ * @return true if the requested API is supported, false otherwise
+ */
+bool chreNslDsoGetApi(uint32_t apiId, void **apiHandle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CHRE_PLATFORM_SHARED_NANOAPP_SUPPORT_LIB_DSO_H_
diff --git a/platform/shared/include/chre/target_platform/dso_csl_api.h b/platform/shared/include/chre/target_platform/dso_csl_api.h
deleted file mode 100644
index 3d0a489..0000000
--- a/platform/shared/include/chre/target_platform/dso_csl_api.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CHRE_PLATFORM_DSO_CSL_API_H_
-#define CHRE_PLATFORM_DSO_CSL_API_H_
-
-/**
- * @file
- * This provides the interface that the dynamic shared object (DSO) nanoapp
- * client support library (CSL) uses to interface with the underlying CHRE
- * implementation in a compatible manner. These functions are not directly
- * called by the nanoapp itself, but rather the nanoapp calls CHRE APIs, which
- * are implemented in the CSL to call through to APIs retrieved via this
- * interface. While originally intended for use with the SLPI, this API can be
- * applied to any platform where the nanoapp is a dynamic module that can
- * directly call system functions, but the nanoapp does not know ahead of time
- * which functions are implemented, therefore a layer of indirection is
- * necessary to avoid unresolved symbol errors when running on older platforms.
- *
- * Note that this is *not* required to implemented/supported on all CHRE
- * platforms, only those that use the DSO CSL.
- */
-
-// TODO: sidenote - not planning on using this for Linux, at least not
-// initially... we can just implement the CHRE APIs directly and compile
-// nanoapps into the system executable for initial testing. Will need to use
-// this for SLPI, though, for compatibility reasons. And once it's used on SLPI,
-// it would make sense to have it there on Linux too, to make sure we are
-// testing apples to apples.
-
-#include <cstdint>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct {
-  // TODO: populate with references to any function calls that were not present
-  // in the initial API release, or could potentially be changed in the future.
-  // note that we're not completely against the nanoapp calling directly into
-  // the CHRE system for things that are unlikely to change; we would just need
-  // to use this method after any change happens. consider the case where we
-  // have:
-  //   chreFoo(int x); // v1.0
-  //   chreFoo(int x, int y); // v1.1
-  // if nanoapps called chreFoo(int) directly @ v1.0, then that version of the
-  // function must be kept like that indefinitely for v1.0 apps to use. v1.1
-  // apps would need to go through this indirection to call into something like
-  // chreFoo_v1_1
-
-  char placeholder;
-} chreSlpiCoreSystemApi;
-
-typedef struct {
-  // TODO
-  char placeholder;
-} chreSlpiGnssApi;
-
-typedef struct {
-  // TODO
-  char placeholder;
-} chreSlpiWifiApi;
-
-typedef struct {
-  // TODO
-  char placeholder;
-} chreSlpiWwanApi;
-
-enum chreDsoCslApiId {
-  CHRE_DSO_CSL_API_ID_CORE_SYSTEM = 1,
-  CHRE_DSO_CSL_API_ID_SENSORS = 2,
-  CHRE_DSO_CSL_API_ID_GNSS = 3,
-  CHRE_DSO_CSL_API_ID_WIFI = 4,
-  CHRE_DSO_CSL_API_ID_WWAN = 5,
-};
-
-/**
- * TODO
- *
- * @param apiId
- * @param apiHandle If this function returns true, this will be set to a pointer
- *        to the associated structure containing the API
- *
- * @return true if the requested API is supported, false otherwise
- */
-extern "C" bool chreDsoCslGetApi(uint32_t apiId, void **apiHandle);
-
-#ifdef __cplusplus
-}
-#endif
-
-
-#endif  // CHRE_NANOAPP_API_H_
diff --git a/platform/shared/include/chre/target_platform/platform_static_nanoapp_init.h b/platform/shared/include/chre/target_platform/platform_static_nanoapp_init.h
deleted file mode 100644
index 7774124..0000000
--- a/platform/shared/include/chre/target_platform/platform_static_nanoapp_init.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef CHRE_PLATFORM_SHARED_PLATFORM_STATIC_NANOAPP_INIT_H_
-#define CHRE_PLATFORM_SHARED_PLATFORM_STATIC_NANOAPP_INIT_H_
-
-/**
- * Initializes a nanoapp that is based on the shared implementation of
- * PlatformNanoappBase.
- *
- * @param appName the name of the nanoapp. This will be prefixed by gNanoapp
- * when creating the global instance of the nanoapp.
- */
-#define PLATFORM_STATIC_NANOAPP_INIT(appName)          \
-::chre::PlatformNanoapp gNanoapp##appName;             \
-                                                       \
-__attribute__((constructor))                           \
-static void InitializeStaticNanoapp() {                \
-  gNanoapp##appName.mStart = nanoappStart;             \
-  gNanoapp##appName.mHandleEvent = nanoappHandleEvent; \
-  gNanoapp##appName.mStop = nanoappStop;               \
-}
-
-#endif  // CHRE_PLATFORM_SHARED_PLATFORM_STATIC_NANOAPP_INIT_H_
diff --git a/platform/shared/nanoapp/nanoapp_support_lib_dso.c b/platform/shared/nanoapp/nanoapp_support_lib_dso.c
new file mode 100644
index 0000000..8d1ab05
--- /dev/null
+++ b/platform/shared/nanoapp/nanoapp_support_lib_dso.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
+
+#include <chre.h>
+
+/**
+ * @file
+ * The Nanoapp Support Library (NSL) that gets built with nanoapps to act as an
+ * intermediary to the reference CHRE implementation. It provides hooks so the
+ * app can be registered with the system, and also provides a layer where we can
+ * implement cross-version compatibility features as needed.
+ */
+
+__attribute__((used)) __attribute__((visibility("default")))
+const struct chreNslNanoappInfo _chreNslDsoNanoappInfo = {
+  .magic = CHRE_NSL_NANOAPP_INFO_MAGIC,
+  .structMinorVersion = CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION,
+  .targetApiVersion = CHRE_API_VERSION,
+
+  // These values are supplied by the build environment
+  .vendor = NANOAPP_VENDOR_STRING,
+  .name = NANOAPP_NAME_STRING,
+  .isSystemNanoapp = NANOAPP_IS_SYSTEM_NANOAPP,
+  .appId = NANOAPP_ID,
+  .appVersion = NANOAPP_VERSION,
+
+  .entryPoints = {
+    .start = nanoappStart,
+    .handleEvent = nanoappHandleEvent,
+    .end = nanoappEnd,
+  },
+};
+
diff --git a/platform/shared/static_nanoapps.cc b/platform/shared/static_nanoapps.cc
index 2b5d764..eea6b9f 100644
--- a/platform/shared/static_nanoapps.cc
+++ b/platform/shared/static_nanoapps.cc
@@ -20,7 +20,7 @@
 
 void loadStaticNanoapps(EventLoop *eventLoop) {
   for (size_t i = 0; i < kStaticNanoappCount; i++) {
-    eventLoop->startNanoapp(kStaticNanoappList[i]);
+    eventLoop->startNanoapp(*(kStaticNanoappList[i]));
   }
 }
 
diff --git a/platform/slpi/host_link.cc b/platform/slpi/host_link.cc
index c63ce27..39e3485 100644
--- a/platform/slpi/host_link.cc
+++ b/platform/slpi/host_link.cc
@@ -28,6 +28,7 @@
 #include "chre/platform/slpi/fastrpc.h"
 #include "chre/util/fixed_size_blocking_queue.h"
 #include "chre/util/macros.h"
+#include "chre/util/unique_ptr.h"
 #include "chre_api/chre/version.h"
 
 using flatbuffers::FlatBufferBuilder;
@@ -38,17 +39,39 @@
 
 constexpr size_t kOutboundQueueSize = 32;
 
+//! Used to pass the client ID through the user data pointer in deferCallback
+union HostClientIdCallbackData {
+  uint16_t hostClientId;
+  void *ptr;
+};
+static_assert(sizeof(uint16_t) <= sizeof(void*),
+              "Pointer must at least fit a u16 for passing the host client ID");
+
+struct LoadNanoappCallbackData {
+  uint64_t appId;
+  uint32_t transactionId;
+  uint16_t hostClientId;
+  UniquePtr<Nanoapp> nanoapp = MakeUnique<Nanoapp>();
+};
+
 enum class PendingMessageType {
   Shutdown,
   NanoappMessageToHost,
   HubInfoResponse,
   NanoappListResponse,
+  LoadNanoappResponse,
 };
 
 struct PendingMessage {
-  PendingMessage(PendingMessageType msgType, const void *msgData = nullptr) {
+  PendingMessage(PendingMessageType msgType, uint16_t hostClientId) {
     type = msgType;
-    data.data = msgData;
+    data.hostClientId = hostClientId;
+  }
+
+  PendingMessage(PendingMessageType msgType,
+                 const MessageToHost *msgToHost = nullptr) {
+    type = msgType;
+    data.msgToHost = msgToHost;
   }
 
   PendingMessage(PendingMessageType msgType, FlatBufferBuilder *builder) {
@@ -59,7 +82,7 @@
   PendingMessageType type;
   union {
     const MessageToHost *msgToHost;
-    const void *data;
+    uint16_t hostClientId;
     FlatBufferBuilder *builder;
   } data;
 };
@@ -78,7 +101,6 @@
          size, bufferSize);
     result = CHRE_FASTRPC_ERROR;
   } else {
-    LOGD("Copy %zu bytes to buffer @ %p", size, buffer);
     memcpy(buffer, data, size);
     *messageLen = size;
     result = CHRE_FASTRPC_SUCCESS;
@@ -87,11 +109,13 @@
   return result;
 }
 
-
-void constructNanoappListCallback(uint16_t /*eventType*/, void * /*data*/) {
+void constructNanoappListCallback(uint16_t /*eventType*/, void *deferCbData) {
   constexpr size_t kFixedOverhead = 56;
   constexpr size_t kPerNanoappSize = 16;
 
+  HostClientIdCallbackData clientIdCbData;
+  clientIdCbData.ptr = deferCbData;
+
   // TODO: need to add support for getting apps from multiple event loops
   bool pushed = false;
   EventLoop *eventLoop = getCurrentEventLoop();
@@ -125,7 +149,8 @@
     // Add a NanoappListEntry to the FlatBuffer for each nanoapp
     CallbackData cbData(*builder, nanoappEntries);
     eventLoop->forEachNanoapp(callback, &cbData);
-    HostProtocolChre::finishNanoappListResponse(*builder, nanoappEntries);
+    HostProtocolChre::finishNanoappListResponse(*builder, nanoappEntries,
+                                                clientIdCbData.hostClientId);
 
     pushed = gOutboundQueue.push(
         PendingMessage(PendingMessageType::NanoappListResponse, builder));
@@ -139,6 +164,33 @@
   }
 }
 
+void finishLoadingNanoappCallback(uint16_t /*eventType*/, void *data) {
+  UniquePtr<LoadNanoappCallbackData> cbData(
+      static_cast<LoadNanoappCallbackData *>(data));
+
+  EventLoop *eventLoop = getCurrentEventLoop();
+  bool startedSuccessfully = (cbData->nanoapp->isLoaded()) ?
+      eventLoop->startNanoapp(cbData->nanoapp) : false;
+
+  constexpr size_t kInitialBufferSize = 48;
+  auto *builder = memoryAlloc<FlatBufferBuilder>(kInitialBufferSize);
+  if (builder == nullptr) {
+    LOGE("Couldn't allocate memory for load nanoapp response");
+  } else {
+    HostProtocolChre::encodeLoadNanoappResponse(
+        *builder, cbData->hostClientId, cbData->transactionId,
+        startedSuccessfully);
+
+    // TODO: if this fails, ideally we should block for some timeout until
+    // there's space in the queue (like up to 1 second)
+    if (!gOutboundQueue.push(PendingMessage(
+            PendingMessageType::LoadNanoappResponse, builder))) {
+      LOGE("Couldn't push load nanoapp response to outbound queue");
+      memoryFree(builder);
+    }
+  }
+}
+
 int generateMessageToHost(const MessageToHost *msgToHost, unsigned char *buffer,
                           size_t bufferSize, unsigned int *messageLen) {
   // TODO: ideally we'd construct our flatbuffer directly in the
@@ -159,8 +211,8 @@
   return result;
 }
 
-int generateHubInfoResponse(unsigned char *buffer, size_t bufferSize,
-                            unsigned int *messageLen) {
+int generateHubInfoResponse(uint16_t hostClientId, unsigned char *buffer,
+                            size_t bufferSize, unsigned int *messageLen) {
   constexpr size_t kInitialBufferSize = 192;
 
   constexpr char kHubName[] = "CHRE on SLPI";
@@ -184,12 +236,12 @@
       builder, kHubName, kVendor, kToolchain, kLegacyPlatformVersion,
       kLegacyToolchainVersion, kPeakMips, kStoppedPower, kSleepPower,
       kPeakPower, CHRE_MESSAGE_TO_HOST_MAX_SIZE, chreGetPlatformId(),
-      chreGetVersion());
+      chreGetVersion(), hostClientId);
 
   return copyToHostBuffer(builder, buffer, bufferSize, messageLen);
 }
 
-int generateNanoappListResponse(
+int generateMessageFromBuilder(
     FlatBufferBuilder *builder, unsigned char *buffer, size_t bufferSize,
     unsigned int *messageLen) {
   CHRE_ASSERT(builder != nullptr);
@@ -235,12 +287,14 @@
         break;
 
       case PendingMessageType::HubInfoResponse:
-        result = generateHubInfoResponse(buffer, bufferSize, messageLen);
+        result = generateHubInfoResponse(pendingMsg.data.hostClientId, buffer,
+                                         bufferSize, messageLen);
         break;
 
       case PendingMessageType::NanoappListResponse:
-        result = generateNanoappListResponse(pendingMsg.data.builder,
-                                             buffer, bufferSize, messageLen);
+      case PendingMessageType::LoadNanoappResponse:
+        result = generateMessageFromBuilder(pendingMsg.data.builder,
+                                            buffer, bufferSize, messageLen);
         break;
 
       default:
@@ -248,6 +302,7 @@
     }
   }
 
+  LOGD("Returning message to host (result %d length %u)", result, *messageLen);
   return result;
 }
 
@@ -334,15 +389,49 @@
       appId, messageType, hostEndpoint, messageData, messageDataLen);
 }
 
-void HostMessageHandlers::handleHubInfoRequest() {
+void HostMessageHandlers::handleHubInfoRequest(uint16_t hostClientId) {
   // We generate the response in the context of chre_slpi_get_message_to_host
-  gOutboundQueue.push(PendingMessage(PendingMessageType::HubInfoResponse));
+  LOGD("Got hub info request from client ID %" PRIu16, hostClientId);
+  gOutboundQueue.push(PendingMessage(
+      PendingMessageType::HubInfoResponse, hostClientId));
 }
 
-void HostMessageHandlers::handleNanoappListRequest() {
+void HostMessageHandlers::handleNanoappListRequest(uint16_t hostClientId) {
+  LOGD("Got nanoapp list request from client ID %" PRIu16, hostClientId);
+  HostClientIdCallbackData cbData = {};
+  cbData.hostClientId = hostClientId;
   EventLoopManagerSingleton::get()->deferCallback(
-      SystemCallbackType::NanoappListResponse, nullptr,
+      SystemCallbackType::NanoappListResponse, cbData.ptr,
       constructNanoappListCallback);
 }
 
+void HostMessageHandlers::handleLoadNanoappRequest(
+    uint16_t hostClientId, uint32_t transactionId, uint64_t appId,
+    uint32_t appVersion, uint32_t targetApiVersion, const void *appBinary,
+    size_t appBinaryLen) {
+  auto cbData = MakeUnique<LoadNanoappCallbackData>();
+
+  LOGD("Got load nanoapp request (txnId %" PRIu32 ") for appId 0x%016" PRIx64
+       " version 0x%" PRIx32 " target API version 0x%08" PRIx32 " size %zu",
+       transactionId, appId, appVersion, targetApiVersion, appBinaryLen);
+  if (cbData.isNull() || cbData->nanoapp.isNull()) {
+    LOGE("Couldn't allocate load nanoapp callback data");
+  } else {
+    cbData->transactionId = transactionId;
+    cbData->hostClientId  = hostClientId;
+    cbData->appId = appId;
+
+    // Note that if this fails, we'll generate the error response in
+    // the normal deferred callback
+    cbData->nanoapp->loadFromBuffer(appId, appVersion, appBinary, appBinaryLen);
+    if (!EventLoopManagerSingleton::get()->deferCallback(
+            SystemCallbackType::FinishLoadingNanoapp, cbData.get(),
+            finishLoadingNanoappCallback)) {
+      LOGE("Couldn't post callback to finish loading nanoapp");
+    } else {
+      cbData.release();
+    }
+  }
+}
+
 }  // namespace chre
diff --git a/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h b/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
index cf3a381..8a0dde7 100644
--- a/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
+++ b/platform/slpi/include/chre/target_platform/platform_nanoapp_base.h
@@ -17,8 +17,10 @@
 #ifndef CHRE_PLATFORM_SLPI_PLATFORM_NANOAPP_BASE_H_
 #define CHRE_PLATFORM_SLPI_PLATFORM_NANOAPP_BASE_H_
 
+#include <cstddef>
 #include <cstdint>
 
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
 #include "chre/util/entry_points.h"
 
 namespace chre {
@@ -26,15 +28,76 @@
 /**
  * SLPI-specific nanoapp functionality.
  */
-struct PlatformNanoappBase {
-  //! The function pointer of the nanoapp start entry point.
-  NanoappStartFunction *mStart;
+class PlatformNanoappBase {
+ public:
+  /**
+   * Copies the supplied application binary data into a new buffer. The
+   * application may be invalid - full checking and initialization happens just
+   * before invoking start() nanoapp entry point.
+   *
+   * @param appId The unique app identifier associated with this binary
+   * @param appVersion An application-defined version number
+   * @param appBinary Buffer containing the complete ELF binary for this
+   *        nanoapp, without any CHRE-specific header
+   * @param appBinaryLen Size of appBinary, in bytes
+   *
+   * @return true if the allocation was successful
+   */
+  bool loadFromBuffer(uint64_t appId, uint32_t appVersion,
+                      const void *appBinary, size_t appBinaryLen);
 
-  //! The function pointer of the nanoapp handle event entry point.
-  NanoappHandleEventFunction *mHandleEvent;
+  /**
+   * Associate this PlatformNanoapp with a nanoapp that is statically built into
+   * the CHRE binary with the given app info structure.
+   */
+  void loadStatic(const struct chreNslNanoappInfo *appInfo);
 
-  //! The function pointer of the nanoapp stop entry point.
-  NanoappStopFunction *mStop;
+  /**
+   * @return true if the app's binary data is resident in memory, i.e. a
+   *         previous call to loadFromBuffer() or loadStatic() was successful
+   */
+  bool isLoaded() const;
+
+ 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;
+
+  //! Buffer containing the complete DSO binary
+  void *mAppBinary = nullptr;
+  size_t mAppBinaryLen = 0;
+
+  //! The dynamic shared object (DSO) handle returned by dlopen[buf]()
+  void *mDsoHandle = nullptr;
+
+  //! Pointer to the app info structure within this nanoapp
+  const struct chreNslNanoappInfo *mAppInfo = nullptr;
+
+  //! 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;
+
+  /**
+   * Calls dlopenbuf on the app binary, and fetches and validates the app info
+   * pointer. This will result in execution of any on-load handlers (e.g. static
+   * global constructors) in the nanoapp.
+   *
+   * @return true if the app was opened successfully and the app info structure
+   *         passed validation
+   */
+  bool 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
diff --git a/platform/slpi/include/chre/target_platform/static_nanoapp_init.h b/platform/slpi/include/chre/target_platform/static_nanoapp_init.h
new file mode 100644
index 0000000..26bc4b2
--- /dev/null
+++ b/platform/slpi/include/chre/target_platform/static_nanoapp_init.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_PLATFORM_SLPI_STATIC_NANOAPP_INIT_H_
+#define CHRE_PLATFORM_SLPI_STATIC_NANOAPP_INIT_H_
+
+#include "chre/core/nanoapp.h"
+#include "chre/platform/fatal_error.h"
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
+#include "chre/util/unique_ptr.h"
+
+/**
+ * Initializes a static nanoapp that is based on the SLPI 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
+ */
+#define CHRE_STATIC_NANOAPP_INIT(appName, appId_, appVersion_) \
+namespace chre {                                               \
+UniquePtr<Nanoapp> *gNanoapp##appName;                         \
+                                                               \
+__attribute__((constructor))                                   \
+static void initializeStaticNanoapp##appName() {               \
+  static UniquePtr<Nanoapp> nanoapp = MakeUnique<Nanoapp>();   \
+  static struct chreNslNanoappInfo appInfo;                    \
+  appInfo.magic = CHRE_NSL_NANOAPP_INFO_MAGIC;                 \
+  appInfo.structMinorVersion =                                 \
+    CHRE_NSL_NANOAPP_INFO_STRUCT_MINOR_VERSION;                \
+  appInfo.targetApiVersion = CHRE_API_VERSION;                 \
+  appInfo.vendor = "Google"; /* TODO: make this configurable */\
+  appInfo.name = #appName;                                     \
+  appInfo.isSystemNanoapp = true;                              \
+  appInfo.appId = appId_;                                      \
+  appInfo.appVersion = appVersion_;                            \
+  appInfo.entryPoints.start = nanoappStart;                    \
+  appInfo.entryPoints.handleEvent = nanoappHandleEvent;        \
+  appInfo.entryPoints.end = nanoappEnd;                        \
+  if (nanoapp.isNull()) {                                      \
+    FATAL_ERROR("Failed to allocate nanoapp " #appName);       \
+  } else {                                                     \
+    nanoapp->loadStatic(&appInfo);                             \
+    gNanoapp##appName = &nanoapp;                              \
+  }                                                            \
+}                                                              \
+}  // namespace chre
+
+#endif  // CHRE_PLATFORM_SLPI_STATIC_NANOAPP_INIT_H_
diff --git a/platform/slpi/platform_nanoapp.cc b/platform/slpi/platform_nanoapp.cc
new file mode 100644
index 0000000..14a6df7
--- /dev/null
+++ b/platform/slpi/platform_nanoapp.cc
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "chre/platform/platform_nanoapp.h"
+
+#include "chre/platform/assert.h"
+#include "chre/platform/log.h"
+#include "chre/platform/memory.h"
+#include "chre/platform/shared/nanoapp_support_lib_dso.h"
+#include "chre_api/chre/version.h"
+
+#include "dlfcn.h"
+
+#include <inttypes.h>
+#include <string.h>
+
+namespace chre {
+
+namespace {
+
+/**
+ * Performs sanity checks on the app info structure included in a dynamically
+ * loaded nanoapp.
+ *
+ * @param expectedAppId
+ * @param expectedAppVersion
+ * @param appInfo
+ *
+ * @return true if validation was successful
+ */
+bool validateAppInfo(uint64_t expectedAppId, uint32_t expectedAppVersion,
+                     const struct chreNslNanoappInfo *appInfo) {
+  uint32_t ourApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(chreGetApiVersion());
+  uint32_t targetApiMajorVersion = CHRE_EXTRACT_MAJOR_VERSION(
+      appInfo->targetApiVersion);
+
+  bool success = false;
+  if (appInfo->magic != CHRE_NSL_NANOAPP_INFO_MAGIC) {
+    LOGE("Invalid app info magic: got 0x%08" PRIx32 " expected 0x%08" PRIx32,
+         appInfo->magic, CHRE_NSL_NANOAPP_INFO_MAGIC);
+  } else if (appInfo->appId == 0) {
+    LOGE("Rejecting invalid app ID 0");
+  } else if (expectedAppId != appInfo->appId) {
+    LOGE("Expected app ID (0x%016" PRIx64 ") doesn't match internal one (0x%016"
+         PRIx64 ")", expectedAppId, appInfo->appId);
+  } else if (expectedAppVersion != appInfo->appVersion) {
+    LOGE("Expected app version (0x%" PRIx32 ") doesn't match internal one (0x%"
+         PRIx32 ")", expectedAppVersion, appInfo->appVersion);
+  } else if (targetApiMajorVersion != ourApiMajorVersion) {
+    LOGE("App targets a different major API version (%" PRIu32 ") than what we "
+         "provide (%" PRIu32 ")", targetApiMajorVersion, ourApiMajorVersion);
+  } else if (strlen(appInfo->name) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
+    LOGE("App name is too long");
+  } else if (strlen(appInfo->name) > CHRE_NSL_DSO_NANOAPP_STRING_MAX_LEN) {
+    LOGE("App vendor is too long");
+  } else {
+    success = true;
+  }
+
+  return success;
+}
+
+}  // anonymous namespace
+
+PlatformNanoapp::~PlatformNanoapp() {
+  closeNanoapp();
+  if (mAppBinary != nullptr) {
+    memoryFree(mAppBinary);
+  }
+}
+
+bool PlatformNanoapp::start() {
+  // Invoke the start entry point after successfully opening the app
+  return (mIsStatic || openNanoapp()) ? mAppInfo->entryPoints.start() : false;
+}
+
+void PlatformNanoapp::handleEvent(uint32_t senderInstanceId,
+                                  uint16_t eventType,
+                                  const void *eventData) {
+  mAppInfo->entryPoints.handleEvent(senderInstanceId, eventType, eventData);
+}
+
+void PlatformNanoapp::end() {
+  mAppInfo->entryPoints.end();
+  closeNanoapp();
+}
+
+bool PlatformNanoappBase::loadFromBuffer(uint64_t appId, uint32_t appVersion,
+                                         const void *appBinary,
+                                         size_t appBinaryLen) {
+  CHRE_ASSERT(!isLoaded());
+  bool success = false;
+  constexpr size_t kMaxAppSize = 2 * 1024 * 1024;  // 2 MiB
+
+  if (appBinaryLen > kMaxAppSize) {
+    LOGE("Rejecting app size %zu above limit %zu", appBinaryLen, kMaxAppSize);
+  } else {
+    mAppBinary = memoryAlloc(appBinaryLen);
+    if (mAppBinary == nullptr) {
+      LOGE("Couldn't allocate %zu byte buffer for nanoapp 0x%016" PRIx64,
+           appBinaryLen, appId);
+    } else {
+      mExpectedAppId = appId;
+      mExpectedAppVersion = appVersion;
+      mAppBinaryLen = appBinaryLen;
+      memcpy(mAppBinary, appBinary, appBinaryLen);
+      success = true;
+    }
+  }
+
+  return success;
+}
+
+void PlatformNanoappBase::loadStatic(const struct chreNslNanoappInfo *appInfo) {
+  CHRE_ASSERT(!isLoaded());
+  mIsStatic = true;
+  mAppInfo = appInfo;
+}
+
+bool PlatformNanoappBase::isLoaded() const {
+  return (mIsStatic || mAppBinary != nullptr);
+}
+
+void PlatformNanoappBase::closeNanoapp() {
+  if (mDsoHandle != nullptr) {
+    if (dlclose(mDsoHandle) != 0) {
+      const char *name = (mAppInfo != nullptr) ? mAppInfo->name : "unknown";
+      LOGE("dlclose of %s failed: %s", name, dlerror());
+    }
+    mDsoHandle = nullptr;
+  }
+}
+
+bool PlatformNanoappBase::openNanoapp() {
+  bool success = false;
+
+  // Populate a filename string (just a requirement of the dlopenbuf API)
+  constexpr size_t kMaxFilenameLen = 17;
+  char filename[kMaxFilenameLen];
+  snprintf(filename, sizeof(filename), "%016" PRIx64, mExpectedAppId);
+
+  CHRE_ASSERT(mAppBinary != nullptr);
+  CHRE_ASSERT_LOG(mDsoHandle == nullptr, "Re-opening nanoapp");
+  mDsoHandle = dlopenbuf(
+      filename, static_cast<const char *>(mAppBinary),
+      static_cast<int>(mAppBinaryLen), RTLD_NOW);
+  if (mDsoHandle == nullptr) {
+    LOGE("Failed to load nanoapp: %s", dlerror());
+  } else {
+    mAppInfo = static_cast<const struct chreNslNanoappInfo *>(
+        dlsym(mDsoHandle, CHRE_NSL_DSO_NANOAPP_INFO_SYMBOL_NAME));
+    if (mAppInfo == nullptr) {
+      LOGE("Failed to find app info symbol: %s", dlerror());
+    } else {
+      success = validateAppInfo(mExpectedAppId, mExpectedAppVersion, mAppInfo);
+      if (!success) {
+        mAppInfo = nullptr;
+      } else {
+        LOGI("Successfully loaded nanoapp: %s (0x%016" PRIx64 ") version 0x%"
+             PRIx32, mAppInfo->name, mAppInfo->appId, mAppInfo->appVersion);
+      }
+    }
+  }
+
+  return success;
+}
+
+uint64_t PlatformNanoapp::getAppId() const {
+  return (mAppInfo != nullptr) ? mAppInfo->appId : mExpectedAppId;
+}
+
+uint32_t PlatformNanoapp::getAppVersion() const {
+  return (mAppInfo != nullptr) ? mAppInfo->appVersion : mExpectedAppVersion;
+}
+
+uint32_t PlatformNanoapp::getTargetApiVersion() const {
+  return (mAppInfo != nullptr) ? mAppInfo->targetApiVersion : 0;
+}
+
+bool PlatformNanoapp::isSystemNanoapp() const {
+  // Right now, we assume that system nanoapps are always static nanoapps. Since
+  // mAppInfo can only be null either prior to loading the app (in which case
+  // this function is not expected to return a valid value anyway), or when a
+  // dynamic nanoapp is not running, "false" is the correct return value in that
+  // case.
+  return (mAppInfo != nullptr) ? mAppInfo->isSystemNanoapp : false;
+}
+
+}  // namespace chre
diff --git a/platform/slpi/static_nanoapps.cc b/platform/slpi/static_nanoapps.cc
index 17368ad..f3ac3d0 100644
--- a/platform/slpi/static_nanoapps.cc
+++ b/platform/slpi/static_nanoapps.cc
@@ -26,15 +26,15 @@
 //! disabled by default, uncomment them as needed for local testing. This is
 //! supplied as a weak symbol so that device builds can override this list.
 __attribute__((weak))
-PlatformNanoapp *const kStaticNanoappList[] = {
-//  &gNanoappGnssWorld,
-//  &gNanoappHelloWorld,
-//  &gNanoappImuCal,
-//  &gNanoappMessageWorld,
-//  &gNanoappSensorWorld,
-//  &gNanoappTimerWorld,
-//  &gNanoappWifiWorld,
-//  &gNanoappWwanWorld,
+UniquePtr<Nanoapp> *const kStaticNanoappList[] = {
+//  gNanoappGnssWorld,
+//  gNanoappHelloWorld,
+//  gNanoappImuCal,
+//  gNanoappMessageWorld,
+//  gNanoappSensorWorld,
+//  gNanoappTimerWorld,
+//  gNanoappWifiWorld,
+//  gNanoappWwanWorld,
 };
 
 //! The size of the static nanoapp list.
diff --git a/util/include/chre/util/array_queue.h b/util/include/chre/util/array_queue.h
index fc4f9c2..d92532c 100644
--- a/util/include/chre/util/array_queue.h
+++ b/util/include/chre/util/array_queue.h
@@ -97,14 +97,15 @@
   const ElementType& operator[](size_t index) const;
 
   /**
-   * Pushes an element onto the back of the array queue. It returns false if
-   * the array queue is full already and there is no room for the elements. All
-   * iterators and references are unaffected.
+   * Pushes an element onto the back of the array queue via copy or move
+   * construction. It returns false if the array queue is full already and there
+   * is no room for the elements. All iterators and references are unaffected.
    *
    * @param element The element to push onto the array queue.
    * @return true if the element is pushed successfully.
    */
   bool push(const ElementType& element);
+  bool push(ElementType&& element);
 
   /**
    * Removes the front element from the array queue if the array queue is not
diff --git a/util/include/chre/util/array_queue_impl.h b/util/include/chre/util/array_queue_impl.h
index e5ede30..2d3061e 100644
--- a/util/include/chre/util/array_queue_impl.h
+++ b/util/include/chre/util/array_queue_impl.h
@@ -17,6 +17,7 @@
 #ifndef CHRE_UTIL_ARRAY_QUEUE_IMPL_H_
 #define CHRE_UTIL_ARRAY_QUEUE_IMPL_H_
 
+#include <new>
 #include <utility>
 
 #include "chre/platform/assert.h"
@@ -75,7 +76,16 @@
 bool ArrayQueue<ElementType, kCapacity>::push(const ElementType& element) {
   bool success = pushTail();
   if (success) {
-    data()[mTail] = element;
+    new (&data()[mTail]) ElementType(element);
+  }
+  return success;
+}
+
+template<typename ElementType, size_t kCapacity>
+bool ArrayQueue<ElementType, kCapacity>::push(ElementType&& element) {
+  bool success = pushTail();
+  if (success) {
+    new (&data()[mTail]) ElementType(std::move(element));
   }
   return success;
 }
diff --git a/util/include/chre/util/dynamic_vector.h b/util/include/chre/util/dynamic_vector.h
index ce0d770..2e8bb0f 100644
--- a/util/include/chre/util/dynamic_vector.h
+++ b/util/include/chre/util/dynamic_vector.h
@@ -95,25 +95,15 @@
   bool empty() const;
 
   /**
-   * Pushes 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.
+   * 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);
-
-  /**
-   * Moves 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 move onto the vector.
-   * @return true if the element was moved successfully.
-   */
   bool push_back(ElementType&& element);
 
   /**
@@ -178,6 +168,7 @@
    * @return Whether or not the insert operation was successful.
    */
   bool insert(size_t index, const ElementType& element);
+  bool insert(size_t index, ElementType&& element);
 
   /**
    * Similar to wrap(), except makes a copy of the supplied C-style array,
@@ -348,6 +339,15 @@
 
   //! Set to true when the buffer (mData) was supplied via wrap()
   bool mDataIsWrapped = false;
+
+  /**
+   * 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);
 };
 
 }  // namespace chre
diff --git a/util/include/chre/util/dynamic_vector_impl.h b/util/include/chre/util/dynamic_vector_impl.h
index 2585650..633a459 100644
--- a/util/include/chre/util/dynamic_vector_impl.h
+++ b/util/include/chre/util/dynamic_vector_impl.h
@@ -17,12 +17,13 @@
 #ifndef CHRE_UTIL_DYNAMIC_VECTOR_IMPL_H_
 #define CHRE_UTIL_DYNAMIC_VECTOR_IMPL_H_
 
-#include <algorithm>
+#include <memory>
+#include <new>
 #include <utility>
 
 #include "chre/platform/assert.h"
 #include "chre/platform/memory.h"
-#include "chre/util/dynamic_vector.h"
+#include "chre/util/memory.h"
 
 namespace chre {
 
@@ -51,9 +52,7 @@
 
 template <typename ElementType>
 void DynamicVector<ElementType>::clear() {
-  for (size_t i = 0; i < mSize; i++) {
-    mData[i].~ElementType();
-  }
+  destroy(mData, mSize);
   mSize = 0;
 }
 
@@ -86,7 +85,7 @@
 bool DynamicVector<ElementType>::push_back(const ElementType& element) {
   bool spaceAvailable = prepareForPush();
   if (spaceAvailable) {
-    mData[mSize++] = element;
+    new (&mData[mSize++]) ElementType(element);
   }
 
   return spaceAvailable;
@@ -96,7 +95,7 @@
 bool DynamicVector<ElementType>::push_back(ElementType&& element) {
   bool spaceAvailable = prepareForPush();
   if (spaceAvailable) {
-    mData[mSize++] = std::move(element);
+    new (&mData[mSize++]) ElementType(std::move(element));
   }
 
   return spaceAvailable;
@@ -133,34 +132,6 @@
   return data()[index];
 }
 
-/**
- *  Moves a range of data items. This is part of the template specialization for
- *  when the underlying type is move-assignable.
- *
- *  @param data The beginning of the data to move.
- *  @param count The number of data items to move.
- *  @param newData The location to move these items to.
- */
-template<typename ElementType>
-void moveOrCopy(ElementType *data, size_t count, ElementType *newData,
-                std::true_type) {
-  std::move(data, data + count, newData);
-}
-
-/**
- * Copies a range of data items. This is part of the template specialization
- * for when the underlying type is not move-assignable.
- *
- * @param data The beginning of the data to copy.
- * @param count The number of data items to copy.
- * @param newData The location to copy these items to.
- */
-template<typename ElementType>
-void moveOrCopy(ElementType *data, size_t count, ElementType *newData,
-                std::false_type) {
-  std::copy(data, data + count, newData);
-}
-
 template<typename ElementType>
 bool DynamicVector<ElementType>::reserve(size_t newCapacity) {
   bool success = false;
@@ -172,9 +143,8 @@
     ElementType *newData = static_cast<ElementType *>(
         memoryAlloc(newCapacity * sizeof(ElementType)));
     if (newData != nullptr) {
-      moveOrCopy(mData, mSize, newData,
-                 typename std::is_move_assignable<ElementType>::type());
-
+      uninitializedMoveOrCopy(mData, mSize, newData);
+      destroy(mData, mSize);
       memoryFree(mData);
       mData = newData;
       mCapacity = newCapacity;
@@ -188,24 +158,48 @@
 template<typename ElementType>
 bool DynamicVector<ElementType>::insert(size_t index,
                                         const ElementType& element) {
+  bool inserted = prepareInsert(index);
+  if (inserted) {
+    new (&mData[index]) ElementType(element);
+  }
+  return inserted;
+}
+
+template<typename ElementType>
+bool DynamicVector<ElementType>::insert(size_t index, ElementType&& element) {
+  bool inserted = prepareInsert(index);
+  if (inserted) {
+    new (&mData[index]) ElementType(std::move(element));
+  }
+  return inserted;
+}
+
+template<typename ElementType>
+bool DynamicVector<ElementType>::prepareInsert(size_t index) {
   // Insertions are not allowed to create a sparse array.
   CHRE_ASSERT(index <= mSize);
 
-  bool inserted = false;
-  if (index <= mSize && prepareForPush()) {
-    // Shift all elements starting at the given index backward one position.
-    for (size_t i = mSize; i > index; i--) {
-      mData[i] = std::move(mData[i - 1]);
+  // TODO: 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(&mData[mSize - 1], 1, &mData[mSize]);
+      // Shift all elements starting at index towards the end
+      for (size_t i = mSize - 1; i > index; i--) {
+        moveOrCopyAssign(mData[i], mData[i - 1]);
+      }
+
+      mData[index].~ElementType();
     }
 
-    mData[index].~ElementType();
-    mData[index] = element;
     mSize++;
-
-    inserted = true;
   }
 
-  return inserted;
+  return readyForInsert;
 }
 
 template<typename ElementType>
@@ -230,7 +224,7 @@
   if (index < mSize) {
     mSize--;
     for (size_t i = index; i < mSize; i++) {
-      mData[i] = std::move(mData[i + 1]);
+      moveOrCopyAssign(mData[i], mData[i + 1]);
     }
 
     mData[mSize].~ElementType();
@@ -253,10 +247,13 @@
 template<typename ElementType>
 void DynamicVector<ElementType>::swap(size_t index0, size_t index1) {
   CHRE_ASSERT(index0 < mSize && index1 < mSize);
-  if (index0 < mSize && index1 < mSize) {
-    ElementType temp = std::move(mData[index0]);
-    mData[index0] = std::move(mData[index1]);
-    mData[index1] = std::move(temp);
+  if (index0 < mSize && index1 < mSize && index0 != index1) {
+    typename std::aligned_storage<sizeof(ElementType),
+        alignof(ElementType)>::type tempStorage;
+    ElementType& temp = *reinterpret_cast<ElementType *>(&tempStorage);
+    uninitializedMoveOrCopy(&mData[index0], 1, &temp);
+    moveOrCopyAssign(mData[index0], mData[index1]);
+    moveOrCopyAssign(mData[index1], temp);
   }
 }
 
diff --git a/util/include/chre/util/entry_points.h b/util/include/chre/util/entry_points.h
index cd794a7..e5350d5 100644
--- a/util/include/chre/util/entry_points.h
+++ b/util/include/chre/util/entry_points.h
@@ -17,19 +17,18 @@
 #ifndef CHRE_UTIL_ENTRY_POINTS_H_
 #define CHRE_UTIL_ENTRY_POINTS_H_
 
-namespace chre {
+#include <stdbool.h>
+#include <stdint.h>
 
-//! The type for the Nanoapp start function pointer.
-typedef bool (NanoappStartFunction)();
+//! @see nanoappStart()
+typedef bool (chreNanoappStartFunction)();
 
-//! The type of the Nanoapp handle event function pointer.
-typedef void (NanoappHandleEventFunction)(uint32_t senderInstanceId,
-                                          uint16_t eventType,
-                                          const void *eventData);
+//! @see nanoappHandleEvent()
+typedef void (chreNanoappHandleEventFunction)(uint32_t senderInstanceId,
+                                              uint16_t eventType,
+                                              const void *eventData);
 
-//! The type of the Nanoapp stop function pointer.
-typedef void (NanoappStopFunction)();
-
-}  // namespace chre
+//! @see nanoappEnd()
+typedef void (chreNanoappEndFunction)();
 
 #endif  // CHRE_UTIL_ENTRY_POINTS_H_
diff --git a/util/include/chre/util/fixed_size_vector_impl.h b/util/include/chre/util/fixed_size_vector_impl.h
index 8e8fe55..90f131a 100644
--- a/util/include/chre/util/fixed_size_vector_impl.h
+++ b/util/include/chre/util/fixed_size_vector_impl.h
@@ -17,10 +17,10 @@
 #ifndef CHRE_UTIL_FIXED_SIZE_VECTOR_IMPL_H_
 #define CHRE_UTIL_FIXED_SIZE_VECTOR_IMPL_H_
 
-#include <utility>
+#include <new>
 
 #include "chre/platform/assert.h"
-#include "chre/util/fixed_size_vector.h"
+#include "chre/util/memory.h"
 
 namespace chre {
 
@@ -59,7 +59,7 @@
     const ElementType& element) {
   CHRE_ASSERT(!full());
   if (!full()) {
-    data()[mSize++] = element;
+    new (&data()[mSize++]) ElementType(element);
   }
 }
 
@@ -100,7 +100,7 @@
   if (index < mSize) {
     mSize--;
     for (size_t i = index; i < mSize; i++) {
-      data()[i] = std::move(data()[i + 1]);
+      moveOrCopyAssign(data()[i], data()[i + 1]);
     }
 
     data()[mSize].~ElementType();
@@ -111,10 +111,13 @@
 void FixedSizeVector<ElementType, kCapacity>::swap(size_t index0,
                                                    size_t index1) {
   CHRE_ASSERT(index0 < mSize && index1 < mSize);
-  if (index0 < mSize && index1 < mSize) {
-    ElementType temp = std::move(data()[index0]);
-    data()[index0] = std::move(data()[index1]);
-    data()[index1] = std::move(temp);
+  if (index0 < mSize && index1 < mSize && 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);
   }
 }
 
diff --git a/util/include/chre/util/memory.h b/util/include/chre/util/memory.h
new file mode 100644
index 0000000..ccfb5ce
--- /dev/null
+++ b/util/include/chre/util/memory.h
@@ -0,0 +1,57 @@
+/*
+ * 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_MEMORY_H_
+#define CHRE_UTIL_MEMORY_H_
+
+namespace chre {
+
+/**
+ * Destroys count objects starting at first. This function is similar to
+ * std::destroy_n.
+ *
+ * @param first Starting address of count objects to destroy
+ * @param count The number of objects to destroy
+ */
+template<typename ElementType>
+void destroy(ElementType *first, size_t count);
+
+/**
+ * Performs move assignment (dest = std::move(source)) if supported by
+ * ElementType, otherwise copy assignment (dest = source).
+ */
+template<typename ElementType>
+void moveOrCopyAssign(ElementType& dest, ElementType& source);
+
+/**
+ * Initializes a new block of memory by transferring objects from another block,
+ * using memcpy if valid for the underlying type, or the move constructor if
+ * available, or the copy constructor. This function is similar to
+ * std::uninitialized_move_n.
+ *
+ * @param source The beginning of the data to transfer
+ * @param count The number of elements to transfer
+ * @param dest An uninitialized buffer to be populated with count elements
+ */
+template<typename ElementType>
+void uninitializedMoveOrCopy(ElementType *source, size_t count,
+                             ElementType *dest);
+
+}  // namespace chre
+
+#include "chre/util/memory_impl.h"
+
+#endif  // CHRE_UTIL_MEMORY_H
diff --git a/util/include/chre/util/memory_impl.h b/util/include/chre/util/memory_impl.h
new file mode 100644
index 0000000..cf9b712
--- /dev/null
+++ b/util/include/chre/util/memory_impl.h
@@ -0,0 +1,112 @@
+/*
+ * 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_MEMORY_IMPL_H_
+#define CHRE_UTIL_MEMORY_IMPL_H_
+
+#include <cstring>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+namespace chre {
+
+template<typename ElementType>
+inline void destroy(ElementType *first, size_t count) {
+  for (size_t i = 0; i < count; i++) {
+    first[i].~ElementType();
+  }
+}
+
+//! Overload used when the type is move assignable
+template<typename ElementType>
+inline void moveOrCopyAssign(ElementType& dest, ElementType& source,
+                             std::true_type) {
+  dest = std::move(source);
+}
+
+//! Overload used when the type is not move assignable
+template<typename ElementType>
+inline void moveOrCopyAssign(ElementType& dest, ElementType& source,
+                             std::false_type) {
+  dest = source;
+}
+
+template<typename ElementType>
+inline void moveOrCopyAssign(ElementType& dest, ElementType& source) {
+  moveOrCopyAssign(dest, source,
+                   typename std::is_move_assignable<ElementType>::type());
+}
+
+//! Overload used when type is trivially copy constructible
+template<typename ElementType>
+inline void uninitializedMoveOrCopy(ElementType *source, size_t count,
+                                    ElementType *dest, std::true_type) {
+  std::memcpy(dest, source, count * sizeof(ElementType));
+}
+
+//! Overload used when type is not trivially copy constructible, but is move
+//! constructible
+template<typename ElementType>
+inline void uninitializedMoveOrCopy(ElementType *source, size_t count,
+                                    ElementType *dest, std::false_type,
+                                    std::true_type) {
+  for (size_t i = 0; i < count; i++) {
+    new (&dest[i]) ElementType(std::move(source[i]));
+  }
+}
+
+//! Overload used when type is not trivially copy constructible or move
+//! constructible
+template<typename ElementType>
+inline void uninitializedMoveOrCopy(ElementType *source, size_t count,
+                                    ElementType *dest, std::false_type,
+                                    std::false_type) {
+  for (size_t i = 0; i < count; i++) {
+    new (&dest[i]) ElementType(source[i]);
+  }
+}
+
+//! Overload used when type is not trivially copy constructible
+template<typename ElementType>
+inline void uninitializedMoveOrCopy(
+    ElementType *source, size_t count, ElementType *dest, std::false_type) {
+  // Check the assumption that if is_move_constructible is false, then
+  // is_copy_constructible is true
+  static_assert(std::is_move_constructible<ElementType>()
+                || std::is_copy_constructible<ElementType>(),
+                "Object must be copy- or move- constructible to use "
+                "unintializedMoveOrCopy");
+  uninitializedMoveOrCopy(
+      source, count, dest, std::false_type(),
+      typename std::is_move_constructible<ElementType>::type());
+}
+
+template<typename ElementType>
+inline void uninitializedMoveOrCopy(ElementType *source, size_t count,
+                                    ElementType *dest) {
+  // TODO: we should be able to use std::is_trivially_copy_constructible here,
+  // but it's not found in the linux x86 build, because our build uses GCC 4.8's
+  // C++ standard library, which doesn't support it. Works in the SLPI build,
+  // though...
+  uninitializedMoveOrCopy(
+      source, count, dest, typename std::is_trivial<ElementType>::type());
+      //typename std::is_trivially_copy_constructible<ElementType>::type());
+}
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_MEMORY_IMPL_H_
diff --git a/util/include/chre/util/nanoapp/app_id.h b/util/include/chre/util/nanoapp/app_id.h
new file mode 100644
index 0000000..fca6257
--- /dev/null
+++ b/util/include/chre/util/nanoapp/app_id.h
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CHRE_UTIL_NANOAPP_APP_ID_H_
+#define CHRE_UTIL_NANOAPP_APP_ID_H_
+
+#include <chre/common.h>
+#include <stdint.h>
+
+/**
+ * @file
+ * Helpers for defining static nanoapp IDs, e.g. for use with
+ * CHRE_STATIC_NANOAPP_INIT.
+ */
+
+//! Sample Vendor ID for example nanoapps. The vendor ID portion is the 5 most
+//! significant bytes of the app ID. This vendor ID should not appear in
+//! production.
+#define CHRE_VENDOR_ID_EXAMPLE  UINT64_C(0x0123456789000000)
+
+namespace chre {
+
+/**
+ * Constructs a fully qualified nanoapp ID from a vendor portion (most
+ * significant 5 bytes) and app number (least significant 3 bytes)
+ *
+ * @param vendor Value with vendor unique identifier in the most significant 5
+ *        bytes
+ * @param appNumber Value with vendor-scoped app number in the least significant
+ *        3 bytes
+ *
+ * @return app ID combining vendor and app number
+ */
+constexpr uint64_t makeNanoappId(uint64_t vendor, uint32_t appNumber) {
+  return ((vendor & CHRE_VENDOR_ID_MASK) | (appNumber & ~CHRE_VENDOR_ID_MASK));
+}
+
+/**
+ * @return App ID combining the given 3-byte app number and Google's 5-byte
+ *         vendor ID
+ */
+constexpr uint64_t makeExampleNanoappId(uint32_t appNumber) {
+  return makeNanoappId(CHRE_VENDOR_ID_EXAMPLE, appNumber);
+}
+
+/**
+ * @return App ID combining the given 3-byte app number and Google's 5-byte
+ *         vendor ID
+ */
+constexpr uint64_t makeGoogleNanoappId(uint32_t appNumber) {
+  return makeNanoappId(CHRE_VENDOR_ID_GOOGLE, appNumber);
+}
+
+constexpr uint64_t kHelloWorldAppId   = makeExampleNanoappId(1);
+constexpr uint64_t kMessageWorldAppId = makeExampleNanoappId(2);
+constexpr uint64_t kTimerWorldAppId   = makeExampleNanoappId(3);
+constexpr uint64_t kSensorWorldAppId  = makeExampleNanoappId(4);
+constexpr uint64_t kGnssWorldAppId    = makeExampleNanoappId(5);
+constexpr uint64_t kWifiWorldAppId    = makeExampleNanoappId(6);
+constexpr uint64_t kWwanWorldAppId    = makeExampleNanoappId(7);
+constexpr uint64_t kImuCalAppId       = makeExampleNanoappId(8);
+
+}  // namespace chre
+
+#endif  // CHRE_UTIL_NANOAPP_APP_ID_H_
diff --git a/util/include/chre/util/unique_ptr.h b/util/include/chre/util/unique_ptr.h
index 2fbeff1..78feb53 100644
--- a/util/include/chre/util/unique_ptr.h
+++ b/util/include/chre/util/unique_ptr.h
@@ -23,20 +23,33 @@
 
 /**
  * Wraps a pointer to a dynamically allocated object and manages the underlying
- * memory. The goal is to be similar to std::unique_ptr.
+ * memory. The goal is to be similar to std::unique_ptr, but we do not support
+ * custom deleters - deletion is always done via memoryFree().
  */
 template<typename ObjectType>
 class UniquePtr : public NonCopyable {
  public:
   /**
-   * Allocates and constructs a new object of type ObjectType on the heap. If
-   * memory allocation fails the constructor is not invoked. This constructor is
-   * similar to std::make_unique.
-   *
-   * @param args The arguments to pass to the object's constructor.
+   * Construct a UniquePtr instance that does not own any object.
    */
-  template<typename... Args>
-  UniquePtr(Args&&... args);
+  UniquePtr();
+
+  /**
+   * Constructs a UniquePtr instance that owns the given object, and will free
+   * its memory when the UniquePtr is destructed.
+   *
+   * @param object Pointer to an object allocated via memoryAlloc. It is not
+   *        valid for this object's memory to come from any other source,
+   *        including the stack, or static allocation on the heap.
+   */
+  UniquePtr(ObjectType *object);
+
+  /**
+   * Constructs a new UniquePtr via moving the Object from another UniquePtr.
+   *
+   * @param other UniquePtr instance to move into this object
+   */
+  UniquePtr(UniquePtr<ObjectType>&& other);
 
   /**
    * Deconstructs the object (if necessary) and releases associated memory.
@@ -44,9 +57,9 @@
   ~UniquePtr();
 
   /**
-   * Determines if the object was constructed correctly.
+   * Determines if this UniquePtr owns an object, or references null.
    *
-   * @return true if the object is null and not constructed.
+   * @return true if get() returns nullptr
    */
   bool isNull() const;
 
@@ -59,7 +72,7 @@
   /**
    * Releases ownership of the underlying object, so it will not be freed when
    * this object is destructed. After this function returns, get() will return
-   * null.
+   * nullptr.
    *
    * @return A pointer to the underlying object (i.e. what get() would return
    *         prior to this function call)
@@ -96,6 +109,16 @@
   ObjectType *mObject;
 };
 
+/**
+ * Allocates and constructs a new object of type ObjectType on the heap, and
+ * returns a UniquePtr that owns the object. This function is similar to
+ * std::make_unique.
+ *
+ * @param args The arguments to pass to the object's constructor.
+ */
+template<typename ObjectType, typename... Args>
+UniquePtr<ObjectType> MakeUnique(Args&&... args);
+
 }  // namespace chre
 
 #include "chre/util/unique_ptr_impl.h"
diff --git a/util/include/chre/util/unique_ptr_impl.h b/util/include/chre/util/unique_ptr_impl.h
index 3059abc..aac1411 100644
--- a/util/include/chre/util/unique_ptr_impl.h
+++ b/util/include/chre/util/unique_ptr_impl.h
@@ -24,12 +24,15 @@
 namespace chre {
 
 template<typename ObjectType>
-template<typename... Args>
-UniquePtr<ObjectType>::UniquePtr(Args&&... args) {
-  mObject = static_cast<ObjectType *>(memoryAlloc(sizeof(ObjectType)));
-  if (mObject != nullptr) {
-    new (mObject) ObjectType(std::forward<Args>(args)...);
-  }
+UniquePtr<ObjectType>::UniquePtr() : mObject(nullptr) {}
+
+template<typename ObjectType>
+UniquePtr<ObjectType>::UniquePtr(ObjectType *object) : mObject(object) {}
+
+template<typename ObjectType>
+UniquePtr<ObjectType>::UniquePtr(UniquePtr<ObjectType>&& other) {
+  mObject = other.mObject;
+  other.mObject = nullptr;
 }
 
 template<typename ObjectType>
@@ -82,6 +85,12 @@
   return *this;
 }
 
+template<typename ObjectType, typename... Args>
+inline UniquePtr<ObjectType> MakeUnique(Args&&... args) {
+  return UniquePtr<ObjectType>(memoryAlloc<ObjectType>(
+      std::forward<Args>(args)...));
+}
+
 }  // namespace chre
 
 #endif  // CHRE_UTIL_UNIQUE_PTR_IMPL_H_
diff --git a/util/tests/dynamic_vector_test.cc b/util/tests/dynamic_vector_test.cc
index 5b167e9..a660036 100644
--- a/util/tests/dynamic_vector_test.cc
+++ b/util/tests/dynamic_vector_test.cc
@@ -19,6 +19,8 @@
 #include "chre/util/dynamic_vector.h"
 #include "chre/util/macros.h"
 
+#include <stdint.h>
+
 using chre::DynamicVector;
 
 namespace {
@@ -52,39 +54,61 @@
 
 TEST(DynamicVector, EmptyByDefault) {
   DynamicVector<int> vector;
-  ASSERT_EQ(vector.data(), nullptr);
-  ASSERT_EQ(vector.size(), 0);
-  ASSERT_EQ(vector.capacity(), 0);
+  EXPECT_EQ(vector.data(), nullptr);
+  EXPECT_TRUE(vector.empty());
+  EXPECT_EQ(vector.size(), 0);
+  EXPECT_EQ(vector.capacity(), 0);
+  vector.clear();
 }
 
 TEST(DynamicVector, PushBackAndRead) {
   DynamicVector<int> vector;
   ASSERT_TRUE(vector.push_back(0x1337));
-  ASSERT_EQ(vector[0], 0x1337);
-  ASSERT_EQ(vector.data()[0], 0x1337);
+  EXPECT_EQ(vector.size(), 1);
+  EXPECT_EQ(vector.capacity(), 1);
+  EXPECT_EQ(vector.data(), &vector[0]);
+  EXPECT_FALSE(vector.empty());
+  EXPECT_EQ(vector[0], 0x1337);
 }
 
-TEST(DynamicVector, PushBackReserveAndRead) {
+TEST(DynamicVector, PushBackReserveAndReadTrivialType) {
   DynamicVector<int> vector;
-  ASSERT_TRUE(vector.push_back(0x1337));
+  ASSERT_TRUE(vector.emplace_back(0x1337));
   ASSERT_TRUE(vector.push_back(0xface));
-  ASSERT_TRUE(vector.reserve(4));
-  ASSERT_EQ(vector[0], 0x1337);
-  ASSERT_EQ(vector.data()[0], 0x1337);
-  ASSERT_EQ(vector[1], 0xface);
-  ASSERT_EQ(vector.data()[1], 0xface);
+  int x = 0xcafe;
+  ASSERT_TRUE(vector.push_back(std::move(x)));
+  ASSERT_TRUE(vector.insert(vector.size(), 0xd00d));
+  EXPECT_EQ(vector.size(), 4);
+  EXPECT_EQ(vector.capacity(), 4);
+  EXPECT_EQ(vector[0], 0x1337);
+  EXPECT_EQ(vector[1], 0xface);
+  EXPECT_EQ(vector[2], 0xcafe);
+  EXPECT_EQ(vector[3], 0xd00d);
+
+  ASSERT_TRUE(vector.reserve(8));
+  EXPECT_EQ(vector.size(), 4);
+  EXPECT_EQ(vector.capacity(), 8);
+  EXPECT_EQ(vector[0], 0x1337);
+  EXPECT_EQ(vector[1], 0xface);
+  EXPECT_EQ(vector[2], 0xcafe);
+  EXPECT_EQ(vector[3], 0xd00d);
 }
 
+constexpr int kConstructedMagic = 0xdeadbeef;
+
 class MovableButNonCopyable : public chre::NonCopyable {
  public:
   MovableButNonCopyable(int value) : mValue(value) {}
 
   MovableButNonCopyable(MovableButNonCopyable&& other) {
     mValue = other.mValue;
+    other.mValue = -1;
   }
 
   MovableButNonCopyable& operator=(MovableButNonCopyable&& other) {
+    assert(mMagic == kConstructedMagic);
     mValue = other.mValue;
+    other.mValue = -1;
     return *this;
   }
 
@@ -93,6 +117,7 @@
   }
 
  private:
+  int mMagic = kConstructedMagic;
   int mValue;
 };
 
@@ -100,11 +125,20 @@
   DynamicVector<MovableButNonCopyable> vector;
   ASSERT_TRUE(vector.emplace_back(0x1337));
   ASSERT_TRUE(vector.emplace_back(0xface));
-  ASSERT_TRUE(vector.reserve(4));
+  MovableButNonCopyable mbnc(0xcafe);
+  ASSERT_TRUE(vector.push_back(std::move(mbnc)));
+  EXPECT_EQ(mbnc.getValue(), -1);
+  MovableButNonCopyable mbnc2(0xd00d);
+  ASSERT_TRUE(vector.insert(vector.size(), std::move(mbnc2)));
+  EXPECT_EQ(mbnc2.getValue(), -1);
+
+  ASSERT_TRUE(vector.reserve(8));
   EXPECT_EQ(vector[0].getValue(), 0x1337);
-  EXPECT_EQ(vector.data()[0].getValue(), 0x1337);
   EXPECT_EQ(vector[1].getValue(), 0xface);
-  EXPECT_EQ(vector.data()[1].getValue(), 0xface);
+  EXPECT_EQ(vector[2].getValue(), 0xcafe);
+  EXPECT_EQ(vector[3].getValue(), 0xd00d);
+  EXPECT_EQ(vector.size(), 4);
+  EXPECT_EQ(vector.capacity(), 8);
 }
 
 class CopyableButNonMovable {
@@ -115,13 +149,13 @@
     mValue = other.mValue;
   }
 
-  CopyableButNonMovable(CopyableButNonMovable&& other) = delete;
-
   CopyableButNonMovable& operator=(const CopyableButNonMovable& other) {
+    assert(mMagic == kConstructedMagic);
     mValue = other.mValue;
     return *this;
   }
 
+  CopyableButNonMovable(CopyableButNonMovable&& other) = delete;
   CopyableButNonMovable& operator=(CopyableButNonMovable&& other) = delete;
 
   int getValue() const {
@@ -129,18 +163,26 @@
   }
 
  private:
+  int mMagic = kConstructedMagic;
   int mValue;
 };
 
 TEST(DynamicVector, PushBackReserveAndReadCopyableButNonMovable) {
   DynamicVector<CopyableButNonMovable> vector;
-  ASSERT_TRUE(vector.emplace_back(0xcafe));
+  ASSERT_TRUE(vector.emplace_back(0x1337));
   ASSERT_TRUE(vector.emplace_back(0xface));
-  ASSERT_TRUE(vector.reserve(4));
-  EXPECT_EQ(vector[0].getValue(), 0xcafe);
-  EXPECT_EQ(vector.data()[0].getValue(), 0xcafe);
+  CopyableButNonMovable cbnm(0xcafe);
+  ASSERT_TRUE(vector.push_back(cbnm));
+  CopyableButNonMovable cbnm2(0xd00d);
+  ASSERT_TRUE(vector.insert(vector.size(), cbnm2));
+
+  ASSERT_TRUE(vector.reserve(8));
+  EXPECT_EQ(vector[0].getValue(), 0x1337);
   EXPECT_EQ(vector[1].getValue(), 0xface);
-  EXPECT_EQ(vector.data()[1].getValue(), 0xface);
+  EXPECT_EQ(vector[2].getValue(), 0xcafe);
+  EXPECT_EQ(vector[3].getValue(), 0xd00d);
+  EXPECT_EQ(vector.size(), 4);
+  EXPECT_EQ(vector.capacity(), 8);
 }
 
 class MovableAndCopyable {
@@ -152,18 +194,21 @@
   }
 
   MovableAndCopyable(MovableAndCopyable&& other) {
-    mValue = other.mValue;
+    // The move constructor multiplies the value by 2 so that we can see that it
+    // was used
+    mValue = other.mValue * 2;
   }
 
   MovableAndCopyable& operator=(const MovableAndCopyable& other) {
+    assert(mMagic == kConstructedMagic);
     mValue = other.mValue;
     return *this;
   }
 
   MovableAndCopyable& operator=(MovableAndCopyable&& other) {
-    // The move operation multiplies the value by 2 so that we can see that the
-    // move assignment operator was used.
+    assert(mMagic == kConstructedMagic);
     mValue = other.mValue * 2;
+    other.mValue = -1;
     return *this;
   }
 
@@ -172,11 +217,12 @@
   }
 
  private:
+  int mMagic = kConstructedMagic;
   int mValue;
 };
 
-TEST(DynamicVector, PushBackReserveAndReadMovableAndCopyable) {
-  // Ensure that preference is given to std::move.
+TEST(DynamicVector, ReservePrefersMove) {
+  // Ensure that preference is given to std::move in reserve()
   DynamicVector<MovableAndCopyable> vector;
 
   // Reserve enough space for the first two elements.
@@ -190,9 +236,7 @@
   // Move on this type results in a multiplication by 2. Verify that all
   // elements have been multiplied by 2.
   EXPECT_EQ(vector[0].getValue(), 2000);
-  EXPECT_EQ(vector.data()[0].getValue(), 2000);
   EXPECT_EQ(vector[1].getValue(), 4000);
-  EXPECT_EQ(vector.data()[1].getValue(), 4000);
 }
 
 /**
@@ -208,6 +252,13 @@
     sConstructedCounter++;
   }
 
+  Foo(const Foo& other) {
+    value = other.value;
+    sConstructedCounter++;
+  }
+
+  Foo(Foo&& other) = delete;
+
   /**
    * Tear down the object, decrementing the number of objects that have been
    * constructed of this type.
@@ -217,7 +268,7 @@
   }
 
   //! The number of objects of this type that have been constructed.
-  static size_t sConstructedCounter;
+  static ssize_t sConstructedCounter;
 
   //! The value stored in the object to verify the contents of this object after
   //! construction.
@@ -225,9 +276,10 @@
 };
 
 //! Storage for the Foo reference counter.
-size_t Foo::sConstructedCounter = 0;
+ssize_t Foo::sConstructedCounter = 0;
 
 TEST(DynamicVector, EmplaceBackAndDestruct) {
+  Foo::sConstructedCounter = 0;
   {
     DynamicVector<Foo> vector;
     ASSERT_TRUE(vector.emplace_back(1000));
@@ -236,26 +288,75 @@
     ASSERT_TRUE(vector.emplace_back(4000));
 
     ASSERT_EQ(vector[0].value, 1000);
-    ASSERT_EQ(vector.data()[0].value, 1000);
     ASSERT_EQ(vector[1].value, 2000);
-    ASSERT_EQ(vector.data()[1].value, 2000);
     ASSERT_EQ(vector[2].value, 3000);
-    ASSERT_EQ(vector.data()[2].value, 3000);
     ASSERT_EQ(vector[3].value, 4000);
-    ASSERT_EQ(vector.data()[3].value, 4000);
 
-    ASSERT_EQ(Foo::sConstructedCounter, 4);
+    EXPECT_EQ(Foo::sConstructedCounter, 4);
   }
 
-  ASSERT_EQ(Foo::sConstructedCounter, 0);
+  EXPECT_EQ(Foo::sConstructedCounter, 0);
 }
 
 TEST(DynamicVector, InsertEmpty) {
   DynamicVector<int> vector;
   EXPECT_CHRE_ASSERT(EXPECT_FALSE(vector.insert(1, 0x1337)));
+
+  // Insert to empty vector
   ASSERT_TRUE(vector.insert(0, 0x1337));
   EXPECT_EQ(vector[0], 0x1337);
-  EXPECT_EQ(vector.data()[0], 0x1337);
+
+  // Insert at end triggering grow
+  ASSERT_EQ(vector.capacity(), 1);
+  EXPECT_TRUE(vector.insert(1, 0xface));
+  EXPECT_EQ(vector[0], 0x1337);
+  EXPECT_EQ(vector[1], 0xface);
+
+  // Insert at beginning triggering grow
+  ASSERT_EQ(vector.capacity(), 2);
+  EXPECT_TRUE(vector.insert(0, 0xcafe));
+  EXPECT_EQ(vector[0], 0xcafe);
+  EXPECT_EQ(vector[1], 0x1337);
+  EXPECT_EQ(vector[2], 0xface);
+
+  // Insert at middle with spare capacity
+  ASSERT_EQ(vector.capacity(), 4);
+  EXPECT_TRUE(vector.insert(1, 0xdead));
+  EXPECT_EQ(vector[0], 0xcafe);
+  EXPECT_EQ(vector[1], 0xdead);
+  EXPECT_EQ(vector[2], 0x1337);
+  EXPECT_EQ(vector[3], 0xface);
+
+  // Insert at middle triggering grow
+  ASSERT_EQ(vector.capacity(), 4);
+  EXPECT_TRUE(vector.insert(2, 0xbeef));
+  EXPECT_EQ(vector[0], 0xcafe);
+  EXPECT_EQ(vector[1], 0xdead);
+  EXPECT_EQ(vector[2], 0xbeef);
+  EXPECT_EQ(vector[3], 0x1337);
+  EXPECT_EQ(vector[4], 0xface);
+
+  // Insert at beginning with spare capacity
+  ASSERT_EQ(vector.capacity(), 8);
+  ASSERT_EQ(vector.size(), 5);
+  EXPECT_TRUE(vector.insert(0, 0xabad));
+  EXPECT_EQ(vector[0], 0xabad);
+  EXPECT_EQ(vector[1], 0xcafe);
+  EXPECT_EQ(vector[2], 0xdead);
+  EXPECT_EQ(vector[3], 0xbeef);
+  EXPECT_EQ(vector[4], 0x1337);
+  EXPECT_EQ(vector[5], 0xface);
+
+  // Insert at end with spare capacity
+  ASSERT_EQ(vector.size(), 6);
+  EXPECT_TRUE(vector.insert(vector.size(), 0xc0de));
+  EXPECT_EQ(vector[0], 0xabad);
+  EXPECT_EQ(vector[1], 0xcafe);
+  EXPECT_EQ(vector[2], 0xdead);
+  EXPECT_EQ(vector[3], 0xbeef);
+  EXPECT_EQ(vector[4], 0x1337);
+  EXPECT_EQ(vector[5], 0xface);
+  EXPECT_EQ(vector[6], 0xc0de);
 }
 
 TEST(DynamicVector, PushBackInsertInMiddleAndRead) {
@@ -266,13 +367,9 @@
   ASSERT_TRUE(vector.insert(1, 0xbeef));
 
   ASSERT_EQ(vector[0], 0x1337);
-  ASSERT_EQ(vector.data()[0], 0x1337);
   ASSERT_EQ(vector[1], 0xbeef);
-  ASSERT_EQ(vector.data()[1], 0xbeef);
   ASSERT_EQ(vector[2], 0xface);
-  ASSERT_EQ(vector.data()[2], 0xface);
   ASSERT_EQ(vector[3], 0xcafe);
-  ASSERT_EQ(vector.data()[3], 0xcafe);
 }
 
 TEST(DynamicVector, PushBackAndErase) {
@@ -285,11 +382,8 @@
   vector.erase(1);
 
   ASSERT_EQ(vector[0], 0x1337);
-  ASSERT_EQ(vector.data()[0], 0x1337);
   ASSERT_EQ(vector[1], 0xbeef);
-  ASSERT_EQ(vector.data()[1], 0xbeef);
   ASSERT_EQ(vector[2], 0xface);
-  ASSERT_EQ(vector.data()[2], 0xface);
   ASSERT_EQ(vector.size(), 3);
 }
 
@@ -314,8 +408,9 @@
   resetDestructorCounts();
 
   DynamicVector<Dummy> vector;
+  vector.reserve(4);
   for (size_t i = 0; i < 4; ++i) {
-    vector.push_back(Dummy());
+    vector.emplace_back();
     vector[i].setValue(i);
   }
 
@@ -345,8 +440,9 @@
   resetDestructorCounts();
 
   DynamicVector<Dummy> vector;
+  vector.reserve(4);
   for (size_t i = 0; i < 4; ++i) {
-    vector.push_back(Dummy());
+    vector.emplace_back();
     vector[i].setValue(i);
   }
 
@@ -774,3 +870,8 @@
   EXPECT_EQ(vector.size(), 1);
   EXPECT_EQ(vector.capacity(), 2);
 }
+
+TEST(DynamicVector, RidiculouslyHugeReserveFails) {
+  DynamicVector<int> vector;
+  ASSERT_FALSE(vector.reserve(SIZE_MAX));
+}
diff --git a/util/tests/unique_ptr_test.cc b/util/tests/unique_ptr_test.cc
index 0f1dc6d..5d53901 100644
--- a/util/tests/unique_ptr_test.cc
+++ b/util/tests/unique_ptr_test.cc
@@ -3,6 +3,7 @@
 #include "chre/util/unique_ptr.h"
 
 using chre::UniquePtr;
+using chre::MakeUnique;
 
 struct Value {
   Value(int value) : value(value) {
@@ -25,7 +26,7 @@
 int Value::constructionCounter = 0;
 
 TEST(UniquePtr, Construct) {
-  UniquePtr<Value> myInt(0xcafe);
+  UniquePtr<Value> myInt = MakeUnique<Value>(0xcafe);
   ASSERT_FALSE(myInt.isNull());
   EXPECT_EQ(myInt.get()->value, 0xcafe);
   EXPECT_EQ(myInt->value, 0xcafe);
@@ -33,15 +34,25 @@
   EXPECT_EQ(myInt[0].value, 0xcafe);
 }
 
+TEST(UniquePtr, MoveConstruct) {
+  UniquePtr<Value> myInt = MakeUnique<Value>(0xcafe);
+  ASSERT_FALSE(myInt.isNull());
+  Value *value = myInt.get();
+
+  UniquePtr<Value> moved(std::move(myInt));
+  EXPECT_EQ(moved.get(), value);
+  EXPECT_EQ(myInt.get(), nullptr);
+}
+
 TEST(UniquePtr, Move) {
   Value::constructionCounter = 0;
 
   {
-    UniquePtr<Value> myInt(0xcafe);
+    UniquePtr<Value> myInt = MakeUnique<Value>(0xcafe);
     ASSERT_FALSE(myInt.isNull());
     EXPECT_EQ(Value::constructionCounter, 1);
 
-    UniquePtr<Value> myMovedInt(0);
+    UniquePtr<Value> myMovedInt = MakeUnique<Value>(0);
     ASSERT_FALSE(myMovedInt.isNull());
     EXPECT_EQ(Value::constructionCounter, 2);
     myMovedInt = std::move(myInt);
@@ -58,7 +69,7 @@
 
   Value *value1, *value2;
   {
-    UniquePtr<Value> myInt(0xcafe);
+    UniquePtr<Value> myInt = MakeUnique<Value>(0xcafe);
     ASSERT_FALSE(myInt.isNull());
     EXPECT_EQ(Value::constructionCounter, 1);
     value1 = myInt.get();