ART: TI Agent test library refactor

More refactoring, lowering the reliance on ART-provided functionality
and adding a library that includes all the code that can run without
ART.

Bug: 32072923
Test: m test-art-host
Change-Id: I67d84056a6fd7722c58855fccbdea3f6869b2efb
(cherry picked from commit 027444b64dd52e1d2beea7aa525fbb8146a516bc)
diff --git a/test/901-hello-ti-agent/basics.cc b/test/901-hello-ti-agent/basics.cc
index 00776ca..20b227a 100644
--- a/test/901-hello-ti-agent/basics.cc
+++ b/test/901-hello-ti-agent/basics.cc
@@ -21,7 +21,7 @@
 #include <jni.h>
 #include <stdio.h>
 #include <string.h>
-#include "base/macros.h"
+#include "android-base/macros.h"
 #include "jvmti.h"
 
 // Test infrastructure
diff --git a/test/903-hello-tagging/tagging.cc b/test/903-hello-tagging/tagging.cc
index 7f079a2..701b0c3 100644
--- a/test/903-hello-tagging/tagging.cc
+++ b/test/903-hello-tagging/tagging.cc
@@ -19,14 +19,12 @@
 #include <stdio.h>
 #include <vector>
 
+#include "android-base/logging.h"
 #include "jni.h"
-#include "ScopedLocalRef.h"
-#include "ScopedPrimitiveArray.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
 
-#include "art_method-inl.h"
-#include "base/logging.h"
 #include "jvmti.h"
-#include "utils.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
diff --git a/test/904-object-allocation/tracking.cc b/test/904-object-allocation/tracking.cc
index 303f954..c829496 100644
--- a/test/904-object-allocation/tracking.cc
+++ b/test/904-object-allocation/tracking.cc
@@ -19,12 +19,11 @@
 #include <stdio.h>
 #include <vector>
 
-#include "base/logging.h"
+#include "android-base/logging.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
-#include "utils.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
diff --git a/test/905-object-free/tracking_free.cc b/test/905-object-free/tracking_free.cc
index 68ce38d..59b429c 100644
--- a/test/905-object-free/tracking_free.cc
+++ b/test/905-object-free/tracking_free.cc
@@ -19,12 +19,11 @@
 #include <stdio.h>
 #include <vector>
 
-#include "base/logging.h"
+#include "android-base/logging.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
-#include "utils.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc
index 74cb1e9..bb300749 100644
--- a/test/906-iterate-heap/iterate_heap.cc
+++ b/test/906-iterate-heap/iterate_heap.cc
@@ -23,16 +23,18 @@
 #include <stdio.h>
 #include <vector>
 
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
-#include "base/logging.h"
+
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedPrimitiveArray.h"
-#include "utf.h"
+#include "scoped_primitive_array.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
 #include "test_env.h"
+#include "ti_macros.h"
+#include "ti_utf.h"
 
 namespace art {
 namespace Test906IterateHeap {
@@ -197,10 +199,10 @@
                                             void* user_data) {
       FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
       if (*tag_ptr == p->tag_to_find) {
-        size_t utf_byte_count = CountUtf8Bytes(value, value_length);
+        size_t utf_byte_count = ti::CountUtf8Bytes(value, value_length);
         std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
         memset(mod_utf.get(), 0, utf_byte_count + 1);
-        ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
+        ti::ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
         if (!p->data.empty()) {
           p->data += "\n";
         }
diff --git a/test/907-get-loaded-classes/get_loaded_classes.cc b/test/907-get-loaded-classes/get_loaded_classes.cc
index 1b973bf..5ec56c4 100644
--- a/test/907-get-loaded-classes/get_loaded_classes.cc
+++ b/test/907-get-loaded-classes/get_loaded_classes.cc
@@ -19,11 +19,12 @@
 #include <stdio.h>
 #include <vector>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
+
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jni_helper.h"
diff --git a/test/908-gc-start-finish/gc_callbacks.cc b/test/908-gc-start-finish/gc_callbacks.cc
index 4b9a23c..f186895 100644
--- a/test/908-gc-start-finish/gc_callbacks.cc
+++ b/test/908-gc-start-finish/gc_callbacks.cc
@@ -17,7 +17,8 @@
 #include <stdio.h>
 #include <string.h>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
+
 #include "jni.h"
 #include "jvmti.h"
 
diff --git a/test/909-attach-agent/attach.cc b/test/909-attach-agent/attach.cc
index 67c7567..0150e09 100644
--- a/test/909-attach-agent/attach.cc
+++ b/test/909-attach-agent/attach.cc
@@ -19,7 +19,9 @@
 #include <jni.h>
 #include <stdio.h>
 #include <string.h>
-#include "base/macros.h"
+
+#include "android-base/macros.h"
+
 #include "jvmti.h"
 
 namespace art {
diff --git a/test/910-methods/methods.cc b/test/910-methods/methods.cc
index 429076c..ded4f09 100644
--- a/test/910-methods/methods.cc
+++ b/test/910-methods/methods.cc
@@ -16,10 +16,11 @@
 
 #include <stdio.h>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
+
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "jni_helper.h"
diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc
index 49cbb7e..a499e90 100644
--- a/test/911-get-stack-trace/stack_trace.cc
+++ b/test/911-get-stack-trace/stack_trace.cc
@@ -18,20 +18,19 @@
 #include <memory>
 #include <stdio.h>
 
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
 
-#include "android-base/stringprintf.h"
-#include "base/logging.h"
-#include "base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "jni_binder.h"
 #include "jni_helper.h"
 #include "jvmti_helper.h"
 #include "test_env.h"
+#include "ti_macros.h"
 
 namespace art {
 namespace Test911GetStackTrace {
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index 4d84e39..2636367 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -16,14 +16,15 @@
 
 #include <stdio.h>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
+
 #include "class_linker.h"
 #include "jni.h"
 #include "mirror/class_loader.h"
 #include "jvmti.h"
 #include "runtime.h"
-#include "ScopedLocalRef.h"
-#include "ScopedUtfChars.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
 
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 999467f..6a47ca1 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -21,10 +21,10 @@
 #include <iostream>
 #include <vector>
 
+#include "android-base/macros.h"
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
 
-#include "base/logging.h"
-#include "base/macros.h"
 #include "jit/jit.h"
 #include "jni.h"
 #include "native_stack_dump.h"
diff --git a/test/918-fields/fields.cc b/test/918-fields/fields.cc
index 0c019e3..726c5cf 100644
--- a/test/918-fields/fields.cc
+++ b/test/918-fields/fields.cc
@@ -16,10 +16,10 @@
 
 #include <stdio.h>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "jni_helper.h"
diff --git a/test/920-objects/objects.cc b/test/920-objects/objects.cc
index 1dfb516..5263e75 100644
--- a/test/920-objects/objects.cc
+++ b/test/920-objects/objects.cc
@@ -16,10 +16,10 @@
 
 #include <stdio.h>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "test_env.h"
diff --git a/test/922-properties/properties.cc b/test/922-properties/properties.cc
index 948da6a..896e4c3 100644
--- a/test/922-properties/properties.cc
+++ b/test/922-properties/properties.cc
@@ -16,10 +16,10 @@
 
 #include <stdio.h>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedUtfChars.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jni_helper.h"
diff --git a/test/923-monitors/monitors.cc b/test/923-monitors/monitors.cc
index 60d5b5a..6369a74 100644
--- a/test/923-monitors/monitors.cc
+++ b/test/923-monitors/monitors.cc
@@ -16,10 +16,10 @@
 
 #include <stdio.h>
 
-#include "base/macros.h"
+#include "android-base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedUtfChars.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
diff --git a/test/924-threads/threads.cc b/test/924-threads/threads.cc
index bb040bd..a8b37ec 100644
--- a/test/924-threads/threads.cc
+++ b/test/924-threads/threads.cc
@@ -16,17 +16,17 @@
 
 #include <stdio.h>
 
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
-#include "base/macros.h"
-#include "base/logging.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "jni_helper.h"
 #include "jvmti_helper.h"
 #include "test_env.h"
+#include "ti_macros.h"
 
 namespace art {
 namespace Test924Threads {
diff --git a/test/925-threadgroups/threadgroups.cc b/test/925-threadgroups/threadgroups.cc
index 1cd93be..d555553 100644
--- a/test/925-threadgroups/threadgroups.cc
+++ b/test/925-threadgroups/threadgroups.cc
@@ -16,17 +16,17 @@
 
 #include <stdio.h>
 
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
-#include "base/macros.h"
-#include "base/logging.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "jni_helper.h"
 #include "jvmti_helper.h"
 #include "test_env.h"
+#include "ti_macros.h"
 
 namespace art {
 namespace Test925ThreadGroups {
diff --git a/test/927-timers/timers.cc b/test/927-timers/timers.cc
index a67f5b4..55d3921 100644
--- a/test/927-timers/timers.cc
+++ b/test/927-timers/timers.cc
@@ -16,9 +16,9 @@
 
 #include <inttypes.h>
 
+#include "android-base/logging.h"
 #include "android-base/stringprintf.h"
-#include "base/logging.h"
-#include "base/macros.h"
+
 #include "jni.h"
 #include "jvmti.h"
 
@@ -26,6 +26,7 @@
 #include "jni_helper.h"
 #include "jvmti_helper.h"
 #include "test_env.h"
+#include "ti_macros.h"
 
 namespace art {
 namespace Test926Timers {
diff --git a/test/928-jni-table/jni_table.cc b/test/928-jni-table/jni_table.cc
index 3f4a93e..26a6707 100644
--- a/test/928-jni-table/jni_table.cc
+++ b/test/928-jni-table/jni_table.cc
@@ -19,8 +19,8 @@
 #include "jni.h"
 #include "jvmti.h"
 
-#include "base/logging.h"
-#include "base/macros.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
diff --git a/test/929-search/search.cc b/test/929-search/search.cc
index bed4dfe..5516105 100644
--- a/test/929-search/search.cc
+++ b/test/929-search/search.cc
@@ -16,12 +16,12 @@
 
 #include <inttypes.h>
 
+#include "android-base/logging.h"
+#include "android-base/macros.h"
 #include "android-base/stringprintf.h"
-#include "base/logging.h"
-#include "base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedUtfChars.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
diff --git a/test/931-agent-thread/agent_thread.cc b/test/931-agent-thread/agent_thread.cc
index 3ec8793..f9af8cf 100644
--- a/test/931-agent-thread/agent_thread.cc
+++ b/test/931-agent-thread/agent_thread.cc
@@ -15,17 +15,14 @@
  */
 
 #include <inttypes.h>
+#include <pthread.h>
 #include <sched.h>
 
-#include "barrier.h"
-#include "base/logging.h"
-#include "base/macros.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "runtime.h"
-#include "ScopedLocalRef.h"
-#include "thread-inl.h"
-#include "well_known_classes.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
@@ -37,13 +34,12 @@
 struct AgentData {
   AgentData() : main_thread(nullptr),
                 jvmti_env(nullptr),
-                b(2),
                 priority(0) {
   }
 
   jthread main_thread;
   jvmtiEnv* jvmti_env;
-  Barrier b;
+  pthread_barrier_t b;
   jint priority;
 };
 
@@ -54,14 +50,21 @@
   // This thread is not the main thread.
   jthread this_thread;
   jvmtiError this_thread_result = jenv->GetCurrentThread(&this_thread);
-  CHECK(!JvmtiErrorToException(env, jenv, this_thread_result));
+  CheckJvmtiError(jenv, this_thread_result);
   CHECK(!env->IsSameObject(this_thread, data->main_thread));
 
   // The thread is a daemon.
   jvmtiThreadInfo info;
   jvmtiError info_result = jenv->GetThreadInfo(this_thread, &info);
-  CHECK(!JvmtiErrorToException(env, jenv, info_result));
+  CheckJvmtiError(jenv, info_result);
   CHECK(info.is_daemon);
+  CheckJvmtiError(jenv, jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name)));
+  if (info.thread_group != nullptr) {
+    env->DeleteLocalRef(info.thread_group);
+  }
+  if (info.context_class_loader != nullptr) {
+    env->DeleteLocalRef(info.context_class_loader);
+  }
 
   // The thread has the requested priority.
   // TODO: Our thread priorities do not work on the host.
@@ -71,7 +74,7 @@
   jint thread_count;
   jthread* threads;
   jvmtiError threads_result = jenv->GetAllThreads(&thread_count, &threads);
-  CHECK(!JvmtiErrorToException(env, jenv, threads_result));
+  CheckJvmtiError(jenv, threads_result);
   bool found = false;
   for (jint i = 0; i != thread_count; ++i) {
     if (env->IsSameObject(threads[i], this_thread)) {
@@ -82,29 +85,53 @@
   CHECK(found);
 
   // Done, let the main thread progress.
-  data->b.Pass(Thread::Current());
+  int wait_result = pthread_barrier_wait(&data->b);
+  CHECK(wait_result == PTHREAD_BARRIER_SERIAL_THREAD || wait_result == 0);
 }
 
 extern "C" JNIEXPORT void JNICALL Java_Main_testAgentThread(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
   // Create a Thread object.
-  ScopedLocalRef<jobject> thread_name(env,
-                                      env->NewStringUTF("Agent Thread"));
+  ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF("Agent Thread"));
   if (thread_name.get() == nullptr) {
     return;
   }
 
-  ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread));
+  ScopedLocalRef<jclass> thread_klass(env, env->FindClass("java/lang/Thread"));
+  if (thread_klass.get() == nullptr) {
+    return;
+  }
+  ScopedLocalRef<jobject> thread(env, env->AllocObject(thread_klass.get()));
   if (thread.get() == nullptr) {
     return;
   }
 
+  // Get a ThreadGroup from the current thread. We need a non-null one as we're gonna call a
+  // runtime-only constructor (so we can set priority and daemon state).
+  jvmtiThreadInfo cur_thread_info;
+  jvmtiError info_result = jvmti_env->GetThreadInfo(nullptr, &cur_thread_info);
+  if (JvmtiErrorToException(env, jvmti_env, info_result)) {
+    return;
+  }
+  CheckJvmtiError(jvmti_env,
+                  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(cur_thread_info.name)));
+  ScopedLocalRef<jobject> thread_group(env, cur_thread_info.thread_group);
+  if (cur_thread_info.context_class_loader != nullptr) {
+    env->DeleteLocalRef(cur_thread_info.context_class_loader);
+  }
+
+  jmethodID initID = env->GetMethodID(thread_klass.get(),
+                                      "<init>",
+                                      "(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V");
+  if (initID == nullptr) {
+    return;
+  }
   env->CallNonvirtualVoidMethod(thread.get(),
-                                WellKnownClasses::java_lang_Thread,
-                                WellKnownClasses::java_lang_Thread_init,
-                                Runtime::Current()->GetMainThreadGroup(),
+                                thread_klass.get(),
+                                initID,
+                                thread_group.get(),
                                 thread_name.get(),
-                                kMinThreadPriority,
+                                0,
                                 JNI_FALSE);
   if (env->ExceptionCheck()) {
     return;
@@ -120,18 +147,20 @@
   data.main_thread = env->NewGlobalRef(main_thread);
   data.jvmti_env = jvmti_env;
   data.priority = JVMTI_THREAD_MIN_PRIORITY;
+  CHECK_EQ(0, pthread_barrier_init(&data.b, nullptr, 2));
 
   jvmtiError result = jvmti_env->RunAgentThread(thread.get(), AgentMain, &data, data.priority);
   if (JvmtiErrorToException(env, jvmti_env, result)) {
     return;
   }
 
-  data.b.Wait(Thread::Current());
+  int wait_result = pthread_barrier_wait(&data.b);
+  CHECK(wait_result == PTHREAD_BARRIER_SERIAL_THREAD || wait_result == 0);
 
   // Scheduling may mean that the agent thread is put to sleep. Wait until it's dead in an effort
   // to not unload the plugin and crash.
   for (;;) {
-    NanoSleep(1000 * 1000);
+    sleep(1);
     jint thread_state;
     jvmtiError state_result = jvmti_env->GetThreadState(thread.get(), &thread_state);
     if (JvmtiErrorToException(env, jvmti_env, state_result)) {
@@ -144,9 +173,11 @@
   }
   // Yield and sleep a bit more, to give the plugin time to tear down the native thread structure.
   sched_yield();
-  NanoSleep(100 * 1000 * 1000);
+  sleep(1);
 
   env->DeleteGlobalRef(data.main_thread);
+
+  pthread_barrier_destroy(&data.b);
 }
 
 }  // namespace Test930AgentThread
diff --git a/test/933-misc-events/misc_events.cc b/test/933-misc-events/misc_events.cc
index 7b6c64d..2b74c40 100644
--- a/test/933-misc-events/misc_events.cc
+++ b/test/933-misc-events/misc_events.cc
@@ -18,8 +18,8 @@
 #include <signal.h>
 #include <sys/types.h>
 
-#include "base/logging.h"
-#include "base/macros.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
 
diff --git a/test/936-search-onload/search_onload.cc b/test/936-search-onload/search_onload.cc
index 72987eb..b2ef056 100644
--- a/test/936-search-onload/search_onload.cc
+++ b/test/936-search-onload/search_onload.cc
@@ -23,7 +23,7 @@
 #include "base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedUtfChars.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jvmti_helper.h"
diff --git a/test/944-transform-classloaders/classloader.cc b/test/944-transform-classloaders/classloader.cc
index f46763c..698e023 100644
--- a/test/944-transform-classloaders/classloader.cc
+++ b/test/944-transform-classloaders/classloader.cc
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include "base/macros.h"
+#include "android-base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
 #include "mirror/class-inl.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "test_env.h"
@@ -26,7 +26,6 @@
 namespace art {
 namespace Test944TransformClassloaders {
 
-
 extern "C" JNIEXPORT jlong JNICALL Java_Main_getDexFilePointer(JNIEnv* env, jclass, jclass klass) {
   if (Runtime::Current() == nullptr) {
     env->ThrowNew(env->FindClass("java/lang/Exception"),
diff --git a/test/945-obsolete-native/obsolete_native.cc b/test/945-obsolete-native/obsolete_native.cc
index b9303dd..ee653a4 100644
--- a/test/945-obsolete-native/obsolete_native.cc
+++ b/test/945-obsolete-native/obsolete_native.cc
@@ -25,7 +25,7 @@
 #include "base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedLocalRef.h"
+#include "scoped_local_ref.h"
 
 // Test infrastructure
 #include "jni_binder.h"
diff --git a/test/980-redefine-object/redefine_object.cc b/test/980-redefine-object/redefine_object.cc
index 6c8c4bd..1faf1a1 100644
--- a/test/980-redefine-object/redefine_object.cc
+++ b/test/980-redefine-object/redefine_object.cc
@@ -22,7 +22,7 @@
 #include "base/macros.h"
 #include "jni.h"
 #include "jvmti.h"
-#include "ScopedUtfChars.h"
+#include "scoped_utf_chars.h"
 
 // Test infrastructure
 #include "jni_binder.h"
diff --git a/test/Android.bp b/test/Android.bp
index 2e8f5bb..40f7edd 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -241,29 +241,24 @@
 }
 
 art_cc_defaults {
-    name: "libtiagent-defaults",
+   name: "libtiagent-base-defaults",
     defaults: ["libartagent-defaults"],
     srcs: [
-        // This is to get the IsInterpreted native method.
-        "common/stack_inspect.cc",
-        "common/runtime_state.cc",
-        "ti-agent/common_load.cc",
-        "ti-agent/common_helper.cc",
+        // These are the ART-independent parts.
+        "ti-agent/agent_startup.cc",
         "ti-agent/jni_binder.cc",
         "ti-agent/jvmti_helper.cc",
         "ti-agent/test_env.cc",
-        "901-hello-ti-agent/basics.cc",
+        // This is the list of non-special OnLoad things and excludes BCI and anything that depends
+        // on ART internals.
         "903-hello-tagging/tagging.cc",
         "904-object-allocation/tracking.cc",
         "905-object-free/tracking_free.cc",
         "906-iterate-heap/iterate_heap.cc",
         "907-get-loaded-classes/get_loaded_classes.cc",
         "908-gc-start-finish/gc_callbacks.cc",
-        "909-attach-agent/attach.cc",
         "910-methods/methods.cc",
         "911-get-stack-trace/stack_trace.cc",
-        "912-classes/classes.cc",
-        "913-heaps/heaps.cc",
         "918-fields/fields.cc",
         "920-objects/objects.cc",
         "922-properties/properties.cc",
@@ -275,11 +270,6 @@
         "929-search/search.cc",
         "931-agent-thread/agent_thread.cc",
         "933-misc-events/misc_events.cc",
-        "936-search-onload/search_onload.cc",
-        "944-transform-classloaders/classloader.cc",
-        "945-obsolete-native/obsolete_native.cc",
-        "980-redefine-object/redefine_object.cc",
-        "983-source-transform-verify/source_transform.cc",
     ],
     shared_libs: [
         "libbase",
@@ -288,6 +278,29 @@
     include_dirs: ["art/test/ti-agent"],
 }
 
+art_cc_defaults {
+    name: "libtiagent-defaults",
+    defaults: ["libtiagent-base-defaults"],
+    srcs: [
+        // This is to get the IsInterpreted native method.
+        "common/stack_inspect.cc",
+        "common/runtime_state.cc",
+        // This includes the remaining test functions. We should try to refactor things to
+        // make this list smaller.
+        "ti-agent/common_helper.cc",
+        "ti-agent/common_load.cc",
+        "901-hello-ti-agent/basics.cc",
+        "909-attach-agent/attach.cc",
+        "912-classes/classes.cc",
+        "913-heaps/heaps.cc",
+        "936-search-onload/search_onload.cc",
+        "944-transform-classloaders/classloader.cc",
+        "945-obsolete-native/obsolete_native.cc",
+        "980-redefine-object/redefine_object.cc",
+        "983-source-transform-verify/source_transform.cc",
+    ],
+}
+
 art_cc_test_library {
     name: "libtiagent",
     defaults: ["libtiagent-defaults"],
@@ -303,6 +316,12 @@
     shared_libs: ["libartd"],
 }
 
+art_cc_test_library {
+    name: "libctstiagent",
+    defaults: ["libtiagent-base-defaults"],
+    export_include_dirs: ["ti-agent"],
+}
+
 cc_defaults {
     name: "libarttest-defaults",
     defaults: [
diff --git a/test/ti-agent/agent_startup.cc b/test/ti-agent/agent_startup.cc
new file mode 100644
index 0000000..b55db7b
--- /dev/null
+++ b/test/ti-agent/agent_startup.cc
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "agent_startup.h"
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
+#include "jni_binder.h"
+#include "jvmti_helper.h"
+
+namespace art {
+
+static constexpr const char* kMainClass = "Main";
+
+static StartCallback gCallback = nullptr;
+
+// TODO: Check this. This may not work on device. The classloader containing the app's classes
+//       may not have been created at this point (i.e., if it's not the system classloader).
+static void JNICALL VMInitCallback(jvmtiEnv* jvmti_env,
+                                   JNIEnv* jni_env,
+                                   jthread thread ATTRIBUTE_UNUSED) {
+  // Bind kMainClass native methods.
+  BindFunctions(jvmti_env, jni_env, kMainClass);
+
+  if (gCallback != nullptr) {
+    gCallback(jvmti_env, jni_env);
+    gCallback = nullptr;
+  }
+
+  // And delete the jvmtiEnv.
+  jvmti_env->DisposeEnvironment();
+}
+
+// Install a phase callback that will bind JNI functions on VMInit.
+void BindOnLoad(JavaVM* vm, StartCallback callback) {
+  // Use a new jvmtiEnv. Otherwise we might collide with table changes.
+  jvmtiEnv* install_env;
+  if (vm->GetEnv(reinterpret_cast<void**>(&install_env), JVMTI_VERSION_1_0) != 0) {
+    LOG(FATAL) << "Could not get jvmtiEnv";
+  }
+  SetAllCapabilities(install_env);
+
+  {
+    jvmtiEventCallbacks callbacks;
+    memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+    callbacks.VMInit = VMInitCallback;
+
+    CheckJvmtiError(install_env, install_env->SetEventCallbacks(&callbacks, sizeof(callbacks)));
+  }
+
+  CheckJvmtiError(install_env, install_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                     JVMTI_EVENT_VM_INIT,
+                                                                     nullptr));
+
+  gCallback = callback;
+}
+
+// Ensure binding of the Main class when the agent is started through OnAttach.
+void BindOnAttach(JavaVM* vm, StartCallback callback) {
+  // Get a JNIEnv. As the thread is attached, we must not destroy it.
+  JNIEnv* env;
+  CHECK_EQ(0, vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6))
+      << "Could not get JNIEnv";
+
+  jvmtiEnv* jvmti_env;
+  CHECK_EQ(0, vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0))
+      << "Could not get jvmtiEnv";
+  SetAllCapabilities(jvmti_env);
+
+  BindFunctions(jvmti_env, env, kMainClass);
+
+  if (callback != nullptr) {
+    callback(jvmti_env, env);
+  }
+
+  if (jvmti_env->DisposeEnvironment() != JVMTI_ERROR_NONE) {
+    LOG(FATAL) << "Could not dispose temporary jvmtiEnv";
+  }
+}
+
+}  // namespace art
diff --git a/test/ti-agent/agent_startup.h b/test/ti-agent/agent_startup.h
new file mode 100644
index 0000000..4963320
--- /dev/null
+++ b/test/ti-agent/agent_startup.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_TEST_TI_AGENT_AGENT_STARTUP_H_
+#define ART_TEST_TI_AGENT_AGENT_STARTUP_H_
+
+#include <functional>
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace art {
+
+using StartCallback = void(*)(jvmtiEnv*, JNIEnv*);
+
+// Ensure binding of the Main class when the agent is started through OnLoad.
+void BindOnLoad(JavaVM* vm, StartCallback callback);
+
+// Ensure binding of the Main class when the agent is started through OnAttach.
+void BindOnAttach(JavaVM* vm, StartCallback callback);
+
+}  // namespace art
+
+#endif  // ART_TEST_TI_AGENT_AGENT_STARTUP_H_
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index c5d75c9..9e7b75d 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -19,6 +19,8 @@
 
 #include "base/logging.h"
 #include "base/macros.h"
+
+#include "agent_startup.h"
 #include "common_helper.h"
 #include "jni_binder.h"
 #include "jvmti_helper.h"
@@ -42,45 +44,6 @@
   OnAttach attach;
 };
 
-static void JNICALL VMInitCallback(jvmtiEnv *jvmti_env,
-                                   JNIEnv* jni_env,
-                                   jthread thread ATTRIBUTE_UNUSED) {
-  // Bind Main native methods.
-  BindFunctions(jvmti_env, jni_env, "Main");
-}
-
-// Install a phase callback that will bind JNI functions on VMInit.
-bool InstallBindCallback(JavaVM* vm) {
-  // Use a new jvmtiEnv. Otherwise we might collide with table changes.
-  jvmtiEnv* install_env;
-  if (vm->GetEnv(reinterpret_cast<void**>(&install_env), JVMTI_VERSION_1_0) != 0) {
-    return false;
-  }
-  SetAllCapabilities(install_env);
-
-  {
-    jvmtiEventCallbacks callbacks;
-    memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
-    callbacks.VMInit = VMInitCallback;
-
-    jvmtiError install_error = install_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
-    if (install_error != JVMTI_ERROR_NONE) {
-      return false;
-    }
-  }
-
-  {
-    jvmtiError enable_error = install_env->SetEventNotificationMode(JVMTI_ENABLE,
-                                                                    JVMTI_EVENT_VM_INIT,
-                                                                    nullptr);
-    if (enable_error != JVMTI_ERROR_NONE) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
 // A trivial OnLoad implementation that only initializes the global jvmti_env.
 static jint MinimalOnLoad(JavaVM* vm,
                           char* options ATTRIBUTE_UNUSED,
@@ -156,26 +119,6 @@
   SetJVM(strncmp(options, "jvm", 3) == 0);
 }
 
-static bool BindFunctionsAttached(JavaVM* vm, const char* class_name) {
-  // Get a JNIEnv. As the thread is attached, we must not destroy it.
-  JNIEnv* env;
-  if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != 0) {
-    printf("Unable to get JNI env!\n");
-    return false;
-  }
-
-  jvmtiEnv* jenv;
-  if (vm->GetEnv(reinterpret_cast<void**>(&jenv), JVMTI_VERSION_1_0) != 0) {
-    printf("Unable to get jvmti env!\n");
-    return false;
-  }
-  SetAllCapabilities(jenv);
-
-  BindFunctions(jenv, env, class_name);
-
-  return true;
-}
-
 }  // namespace
 
 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
@@ -188,9 +131,7 @@
 
   SetIsJVM(remaining_options);
 
-  if (!InstallBindCallback(vm)) {
-    return 1;
-  }
+  BindOnLoad(vm, nullptr);
 
   AgentLib* lib = FindAgent(name_option);
   OnLoad fn = nullptr;
@@ -214,7 +155,7 @@
     return -1;
   }
 
-  BindFunctionsAttached(vm, "Main");
+  BindOnAttach(vm, nullptr);
 
   AgentLib* lib = FindAgent(name_option);
   if (lib == nullptr) {
diff --git a/test/ti-agent/jni_binder.cc b/test/ti-agent/jni_binder.cc
index efc2af8..b66c2c7 100644
--- a/test/ti-agent/jni_binder.cc
+++ b/test/ti-agent/jni_binder.cc
@@ -26,94 +26,16 @@
 #include "jvmti_helper.h"
 #include "scoped_local_ref.h"
 #include "scoped_utf_chars.h"
+#include "ti_utf.h"
 
 namespace art {
 
-size_t CountModifiedUtf8Chars(const char* utf8, size_t byte_count) {
-  DCHECK_LE(byte_count, strlen(utf8));
-  size_t len = 0;
-  const char* end = utf8 + byte_count;
-  for (; utf8 < end; ++utf8) {
-    int ic = *utf8;
-    len++;
-    if (LIKELY((ic & 0x80) == 0)) {
-      // One-byte encoding.
-      continue;
-    }
-    // Two- or three-byte encoding.
-    utf8++;
-    if ((ic & 0x20) == 0) {
-      // Two-byte encoding.
-      continue;
-    }
-    utf8++;
-    if ((ic & 0x10) == 0) {
-      // Three-byte encoding.
-      continue;
-    }
-
-    // Four-byte encoding: needs to be converted into a surrogate
-    // pair.
-    utf8++;
-    len++;
-  }
-  return len;
-}
-
-static uint16_t GetTrailingUtf16Char(uint32_t maybe_pair) {
-  return static_cast<uint16_t>(maybe_pair >> 16);
-}
-
-static uint16_t GetLeadingUtf16Char(uint32_t maybe_pair) {
-  return static_cast<uint16_t>(maybe_pair & 0x0000FFFF);
-}
-
-static uint32_t GetUtf16FromUtf8(const char** utf8_data_in) {
-  const uint8_t one = *(*utf8_data_in)++;
-  if ((one & 0x80) == 0) {
-    // one-byte encoding
-    return one;
-  }
-
-  const uint8_t two = *(*utf8_data_in)++;
-  if ((one & 0x20) == 0) {
-    // two-byte encoding
-    return ((one & 0x1f) << 6) | (two & 0x3f);
-  }
-
-  const uint8_t three = *(*utf8_data_in)++;
-  if ((one & 0x10) == 0) {
-    return ((one & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
-  }
-
-  // Four byte encodings need special handling. We'll have
-  // to convert them into a surrogate pair.
-  const uint8_t four = *(*utf8_data_in)++;
-
-  // Since this is a 4 byte UTF-8 sequence, it will lie between
-  // U+10000 and U+1FFFFF.
-  //
-  // TODO: What do we do about values in (U+10FFFF, U+1FFFFF) ? The
-  // spec says they're invalid but nobody appears to check for them.
-  const uint32_t code_point = ((one & 0x0f) << 18) | ((two & 0x3f) << 12)
-      | ((three & 0x3f) << 6) | (four & 0x3f);
-
-  uint32_t surrogate_pair = 0;
-  // Step two: Write out the high (leading) surrogate to the bottom 16 bits
-  // of the of the 32 bit type.
-  surrogate_pair |= ((code_point >> 10) + 0xd7c0) & 0xffff;
-  // Step three : Write out the low (trailing) surrogate to the top 16 bits.
-  surrogate_pair |= ((code_point & 0x03ff) + 0xdc00) << 16;
-
-  return surrogate_pair;
-}
-
 static std::string MangleForJni(const std::string& s) {
   std::string result;
-  size_t char_count = CountModifiedUtf8Chars(s.c_str(), s.length());
+  size_t char_count = ti::CountModifiedUtf8Chars(s.c_str(), s.length());
   const char* cp = &s[0];
   for (size_t i = 0; i < char_count; ++i) {
-    uint32_t ch = GetUtf16FromUtf8(&cp);
+    uint32_t ch = ti::GetUtf16FromUtf8(&cp);
     if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) {
       result.push_back(ch);
     } else if (ch == '.' || ch == '/') {
@@ -125,8 +47,8 @@
     } else if (ch == '[') {
       result += "_3";
     } else {
-      const uint16_t leading = GetLeadingUtf16Char(ch);
-      const uint32_t trailing = GetTrailingUtf16Char(ch);
+      const uint16_t leading = ti::GetLeadingUtf16Char(ch);
+      const uint32_t trailing = ti::GetTrailingUtf16Char(ch);
 
       android::base::StringAppendF(&result, "_0%04x", leading);
       if (trailing != 0) {
diff --git a/test/ti-agent/jni_helper.h b/test/ti-agent/jni_helper.h
index c48b0c0..0cbc634 100644
--- a/test/ti-agent/jni_helper.h
+++ b/test/ti-agent/jni_helper.h
@@ -54,6 +54,19 @@
   return ret.release();
 }
 
+inline bool JniThrowNullPointerException(JNIEnv* env, const char* msg) {
+  if (env->ExceptionCheck()) {
+    env->ExceptionClear();
+  }
+
+  ScopedLocalRef<jclass> exc_class(env, env->FindClass("java/lang/NullPointerException"));
+  if (exc_class.get() == nullptr) {
+    return -1;
+  }
+
+  return env->ThrowNew(exc_class.get(), msg) == JNI_OK;
+}
+
 }  // namespace art
 
 #endif  // ART_TEST_TI_AGENT_JNI_HELPER_H_
diff --git a/test/ti-agent/scoped_local_ref.h b/test/ti-agent/scoped_local_ref.h
index 0cd9891..daa1583 100644
--- a/test/ti-agent/scoped_local_ref.h
+++ b/test/ti-agent/scoped_local_ref.h
@@ -35,9 +35,9 @@
     reset();
   }
 
-  void reset(T ptr = NULL) {
+  void reset(T ptr = nullptr) {
     if (ptr != mLocalRef) {
-      if (mLocalRef != NULL) {
+      if (mLocalRef != nullptr) {
         mEnv->DeleteLocalRef(mLocalRef);
       }
       mLocalRef = ptr;
@@ -46,7 +46,7 @@
 
   T release() __attribute__((warn_unused_result)) {
     T localRef = mLocalRef;
-    mLocalRef = NULL;
+    mLocalRef = nullptr;
     return localRef;
   }
 
diff --git a/test/ti-agent/scoped_primitive_array.h b/test/ti-agent/scoped_primitive_array.h
new file mode 100644
index 0000000..1649ed9
--- /dev/null
+++ b/test/ti-agent/scoped_primitive_array.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 ART_TEST_TI_AGENT_SCOPED_PRIMITIVE_ARRAY_H_
+#define ART_TEST_TI_AGENT_SCOPED_PRIMITIVE_ARRAY_H_
+
+#include "jni.h"
+
+#include "android-base/macros.h"
+
+#include "jni_helper.h"
+
+namespace art {
+
+#ifdef POINTER_TYPE
+#error POINTER_TYPE is defined.
+#else
+#define POINTER_TYPE(T) T*  /* NOLINT */
+#endif
+
+#ifdef REFERENCE_TYPE
+#error REFERENCE_TYPE is defined.
+#else
+#define REFERENCE_TYPE(T) T&  /* NOLINT */
+#endif
+
+// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, ScopedDoubleArrayRO,
+// ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, and ScopedShortArrayRO provide
+// convenient read-only access to Java arrays from JNI code. This is cheaper than read-write
+// access and should be used by default.
+#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(PRIMITIVE_TYPE, NAME) \
+    class Scoped ## NAME ## ArrayRO { \
+    public: \
+        explicit Scoped ## NAME ## ArrayRO(JNIEnv* env) \
+        : mEnv(env), mJavaArray(nullptr), mRawArray(nullptr), mSize(0) {} \
+        Scoped ## NAME ## ArrayRO(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
+        : mEnv(env) { \
+            if (javaArray == nullptr) { \
+                mJavaArray = nullptr; \
+                mSize = 0; \
+                mRawArray = nullptr; \
+                JniThrowNullPointerException(env, nullptr); \
+            } else { \
+                reset(javaArray); \
+            } \
+        } \
+        ~Scoped ## NAME ## ArrayRO() { \
+            if (mRawArray != nullptr && mRawArray != mBuffer) { \
+                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, JNI_ABORT); \
+            } \
+        } \
+        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
+            mJavaArray = javaArray; \
+            mSize = mEnv->GetArrayLength(mJavaArray); \
+            if (mSize <= kBufferSize) { \
+                mEnv->Get ## NAME ## ArrayRegion(mJavaArray, 0, mSize, mBuffer); \
+                mRawArray = mBuffer; \
+            } else { \
+                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \
+            } \
+        } \
+        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
+        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
+        size_t size() const { return mSize; } \
+    private: \
+        static constexpr jsize kBufferSize = 1024; \
+        JNIEnv* const mEnv; \
+        PRIMITIVE_TYPE ## Array mJavaArray; \
+        POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \
+        jsize mSize; \
+        PRIMITIVE_TYPE mBuffer[kBufferSize]; \
+        DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRO); \
+    }
+
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jboolean, Boolean);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jbyte, Byte);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jchar, Char);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jdouble, Double);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jfloat, Float);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jint, Int);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jlong, Long);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jshort, Short);
+
+#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO
+
+// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, ScopedDoubleArrayRW,
+// ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, and ScopedShortArrayRW provide
+// convenient read-write access to Java arrays from JNI code. These are more expensive,
+// since they entail a copy back onto the Java heap, and should only be used when necessary.
+#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(PRIMITIVE_TYPE, NAME) \
+    class Scoped ## NAME ## ArrayRW { \
+    public: \
+        explicit Scoped ## NAME ## ArrayRW(JNIEnv* env) \
+        : mEnv(env), mJavaArray(nullptr), mRawArray(nullptr) {} \
+        Scoped ## NAME ## ArrayRW(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
+        : mEnv(env), mJavaArray(javaArray), mRawArray(nullptr) { \
+            if (mJavaArray == nullptr) { \
+                JniThrowNullPointerException(env, nullptr); \
+            } else { \
+                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \
+            } \
+        } \
+        ~Scoped ## NAME ## ArrayRW() { \
+            if (mRawArray) { \
+                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, 0); \
+            } \
+        } \
+        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
+            mJavaArray = javaArray; \
+            mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \
+        } \
+        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
+        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
+        POINTER_TYPE(PRIMITIVE_TYPE) get() { return mRawArray; }  \
+        REFERENCE_TYPE(PRIMITIVE_TYPE) operator[](size_t n) { return mRawArray[n]; } \
+        size_t size() const { return mEnv->GetArrayLength(mJavaArray); } \
+    private: \
+        JNIEnv* const mEnv; \
+        PRIMITIVE_TYPE ## Array mJavaArray; \
+        POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \
+        DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRW); \
+    }
+
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jboolean, Boolean);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jbyte, Byte);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jchar, Char);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jdouble, Double);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jfloat, Float);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jint, Int);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jlong, Long);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jshort, Short);
+
+#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW
+#undef POINTER_TYPE
+#undef REFERENCE_TYPE
+
+}  // namespace art
+
+#endif  // ART_TEST_TI_AGENT_SCOPED_PRIMITIVE_ARRAY_H_
diff --git a/test/ti-agent/scoped_utf_chars.h b/test/ti-agent/scoped_utf_chars.h
index e8c9a11..422caaf 100644
--- a/test/ti-agent/scoped_utf_chars.h
+++ b/test/ti-agent/scoped_utf_chars.h
@@ -23,6 +23,8 @@
 
 #include "android-base/macros.h"
 
+#include "jni_helper.h"
+
 namespace art {
 
 class ScopedUtfChars {
@@ -30,7 +32,7 @@
   ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
     if (s == nullptr) {
       utf_chars_ = nullptr;
-      // TODO: JniThrowNullPointerException(env, nullptr);
+      JniThrowNullPointerException(env, nullptr);
     } else {
       utf_chars_ = env->GetStringUTFChars(s, nullptr);
     }
diff --git a/test/ti-agent/ti_macros.h b/test/ti-agent/ti_macros.h
new file mode 100644
index 0000000..d913383
--- /dev/null
+++ b/test/ti-agent/ti_macros.h
@@ -0,0 +1,26 @@
+/*
+ * 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 ART_TEST_TI_AGENT_TI_MACROS_H_
+#define ART_TEST_TI_AGENT_TI_MACROS_H_
+
+#include "android-base/macros.h"
+
+#define FINAL final
+#define OVERRIDE override
+#define UNREACHABLE  __builtin_unreachable
+
+#endif  // ART_TEST_TI_AGENT_TI_MACROS_H_
diff --git a/test/ti-agent/ti_utf.h b/test/ti-agent/ti_utf.h
new file mode 100644
index 0000000..341e106
--- /dev/null
+++ b/test/ti-agent/ti_utf.h
@@ -0,0 +1,196 @@
+/*
+ * 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 ART_TEST_TI_AGENT_TI_UTF_H_
+#define ART_TEST_TI_AGENT_TI_UTF_H_
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "android-base/logging.h"
+
+namespace art {
+namespace ti {
+
+inline size_t CountModifiedUtf8Chars(const char* utf8, size_t byte_count) {
+  DCHECK_LE(byte_count, strlen(utf8));
+  size_t len = 0;
+  const char* end = utf8 + byte_count;
+  for (; utf8 < end; ++utf8) {
+    int ic = *utf8;
+    len++;
+    if (LIKELY((ic & 0x80) == 0)) {
+      // One-byte encoding.
+      continue;
+    }
+    // Two- or three-byte encoding.
+    utf8++;
+    if ((ic & 0x20) == 0) {
+      // Two-byte encoding.
+      continue;
+    }
+    utf8++;
+    if ((ic & 0x10) == 0) {
+      // Three-byte encoding.
+      continue;
+    }
+
+    // Four-byte encoding: needs to be converted into a surrogate
+    // pair.
+    utf8++;
+    len++;
+  }
+  return len;
+}
+
+inline uint16_t GetTrailingUtf16Char(uint32_t maybe_pair) {
+  return static_cast<uint16_t>(maybe_pair >> 16);
+}
+
+inline uint16_t GetLeadingUtf16Char(uint32_t maybe_pair) {
+  return static_cast<uint16_t>(maybe_pair & 0x0000FFFF);
+}
+
+inline uint32_t GetUtf16FromUtf8(const char** utf8_data_in) {
+  const uint8_t one = *(*utf8_data_in)++;
+  if ((one & 0x80) == 0) {
+    // one-byte encoding
+    return one;
+  }
+
+  const uint8_t two = *(*utf8_data_in)++;
+  if ((one & 0x20) == 0) {
+    // two-byte encoding
+    return ((one & 0x1f) << 6) | (two & 0x3f);
+  }
+
+  const uint8_t three = *(*utf8_data_in)++;
+  if ((one & 0x10) == 0) {
+    return ((one & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
+  }
+
+  // Four byte encodings need special handling. We'll have
+  // to convert them into a surrogate pair.
+  const uint8_t four = *(*utf8_data_in)++;
+
+  // Since this is a 4 byte UTF-8 sequence, it will lie between
+  // U+10000 and U+1FFFFF.
+  //
+  // TODO: What do we do about values in (U+10FFFF, U+1FFFFF) ? The
+  // spec says they're invalid but nobody appears to check for them.
+  const uint32_t code_point = ((one & 0x0f) << 18) | ((two & 0x3f) << 12)
+      | ((three & 0x3f) << 6) | (four & 0x3f);
+
+  uint32_t surrogate_pair = 0;
+  // Step two: Write out the high (leading) surrogate to the bottom 16 bits
+  // of the of the 32 bit type.
+  surrogate_pair |= ((code_point >> 10) + 0xd7c0) & 0xffff;
+  // Step three : Write out the low (trailing) surrogate to the top 16 bits.
+  surrogate_pair |= ((code_point & 0x03ff) + 0xdc00) << 16;
+
+  return surrogate_pair;
+}
+
+inline void ConvertUtf16ToModifiedUtf8(char* utf8_out,
+                                       size_t byte_count,
+                                       const uint16_t* utf16_in,
+                                       size_t char_count) {
+  if (LIKELY(byte_count == char_count)) {
+    // Common case where all characters are ASCII.
+    const uint16_t *utf16_end = utf16_in + char_count;
+    for (const uint16_t *p = utf16_in; p < utf16_end;) {
+      *utf8_out++ = static_cast<char>(*p++);
+    }
+    return;
+  }
+
+  // String contains non-ASCII characters.
+  while (char_count--) {
+    const uint16_t ch = *utf16_in++;
+    if (ch > 0 && ch <= 0x7f) {
+      *utf8_out++ = ch;
+    } else {
+      // Char_count == 0 here implies we've encountered an unpaired
+      // surrogate and we have no choice but to encode it as 3-byte UTF
+      // sequence. Note that unpaired surrogates can occur as a part of
+      // "normal" operation.
+      if ((ch >= 0xd800 && ch <= 0xdbff) && (char_count > 0)) {
+        const uint16_t ch2 = *utf16_in;
+
+        // Check if the other half of the pair is within the expected
+        // range. If it isn't, we will have to emit both "halves" as
+        // separate 3 byte sequences.
+        if (ch2 >= 0xdc00 && ch2 <= 0xdfff) {
+          utf16_in++;
+          char_count--;
+          const uint32_t code_point = (ch << 10) + ch2 - 0x035fdc00;
+          *utf8_out++ = (code_point >> 18) | 0xf0;
+          *utf8_out++ = ((code_point >> 12) & 0x3f) | 0x80;
+          *utf8_out++ = ((code_point >> 6) & 0x3f) | 0x80;
+          *utf8_out++ = (code_point & 0x3f) | 0x80;
+          continue;
+        }
+      }
+
+      if (ch > 0x07ff) {
+        // Three byte encoding.
+        *utf8_out++ = (ch >> 12) | 0xe0;
+        *utf8_out++ = ((ch >> 6) & 0x3f) | 0x80;
+        *utf8_out++ = (ch & 0x3f) | 0x80;
+      } else /*(ch > 0x7f || ch == 0)*/ {
+        // Two byte encoding.
+        *utf8_out++ = (ch >> 6) | 0xc0;
+        *utf8_out++ = (ch & 0x3f) | 0x80;
+      }
+    }
+  }
+}
+
+inline size_t CountUtf8Bytes(const uint16_t* chars, size_t char_count) {
+  size_t result = 0;
+  const uint16_t *end = chars + char_count;
+  while (chars < end) {
+    const uint16_t ch = *chars++;
+    if (LIKELY(ch != 0 && ch < 0x80)) {
+      result++;
+      continue;
+    }
+    if (ch < 0x800) {
+      result += 2;
+      continue;
+    }
+    if (ch >= 0xd800 && ch < 0xdc00) {
+      if (chars < end) {
+        const uint16_t ch2 = *chars;
+        // If we find a properly paired surrogate, we emit it as a 4 byte
+        // UTF sequence. If we find an unpaired leading or trailing surrogate,
+        // we emit it as a 3 byte sequence like would have done earlier.
+        if (ch2 >= 0xdc00 && ch2 < 0xe000) {
+          chars++;
+          result += 4;
+          continue;
+        }
+      }
+    }
+    result += 3;
+  }
+  return result;
+}
+
+}  // namespace ti
+}  // namespace art
+
+#endif  // ART_TEST_TI_AGENT_TI_UTF_H_