Add support for JVMTI monitor events.

Adds support for the JVMTI can_generate_monitor_events capability and
all associated events. This adds support for the
JVMTI_EVENT_MONITOR_WAIT, JVMTI_EVENT_MONITOR_WAITED,
JVMTI_EVENT_MONITOR_CONTENDED_ENTER, and
JVMTI_EVENT_MONITOR_CONTENDED_ENTERED events.

Bug: 65558434
Bug: 62821960
Bug: 34415266

Test: ./test.py --host -j50

Change-Id: I0fe8038e6c4249e77d37a67e5056b5d2a94b6f48
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index d3f52f6..8e3cf21 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -247,7 +247,7 @@
     .can_generate_method_exit_events                 = 1,
     .can_generate_all_class_hook_events              = 0,
     .can_generate_compiled_method_load_events        = 0,
-    .can_generate_monitor_events                     = 0,
+    .can_generate_monitor_events                     = 1,
     .can_generate_vm_object_alloc_events             = 1,
     .can_generate_native_method_bind_events          = 1,
     .can_generate_garbage_collection_events          = 1,
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index c41e15e..5911a03 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -31,6 +31,8 @@
 
 #include "events-inl.h"
 
+#include <array>
+
 #include "art_field-inl.h"
 #include "art_jvmti.h"
 #include "art_method-inl.h"
@@ -45,6 +47,7 @@
 #include "jni_internal.h"
 #include "mirror/class.h"
 #include "mirror/object-inl.h"
+#include "monitor.h"
 #include "nativehelper/ScopedLocalRef.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
@@ -247,6 +250,127 @@
   }
 }
 
+template<typename Type>
+static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
+}
+
+template<ArtJvmtiEvent kEvent, typename ...Args>
+static void RunEventCallback(EventHandler* handler,
+                             art::Thread* self,
+                             art::JNIEnvExt* jnienv,
+                             Args... args)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
+  art::StackHandleScope<1> hs(self);
+  art::Handle<art::mirror::Throwable> old_exception(hs.NewHandle(self->GetException()));
+  self->ClearException();
+  // Just give the event a good sized JNI frame. 100 should be fine.
+  jnienv->PushFrame(100);
+  {
+    // Need to do trampoline! :(
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+    handler->DispatchEvent<kEvent>(self,
+                                   static_cast<JNIEnv*>(jnienv),
+                                   thread_jni.get(),
+                                   args...);
+  }
+  jnienv->PopFrame();
+  if (!self->IsExceptionPending() && !old_exception.IsNull()) {
+    self->SetException(old_exception.Get());
+  }
+}
+
+class JvmtiMonitorListener : public art::MonitorCallback {
+ public:
+  explicit JvmtiMonitorListener(EventHandler* handler) : handler_(handler) {}
+
+  void MonitorContendedLocking(art::Monitor* m)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEnter)) {
+      art::Thread* self = art::Thread::Current();
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+      RunEventCallback<ArtJvmtiEvent::kMonitorContendedEnter>(
+          handler_,
+          self,
+          jnienv,
+          mon.get());
+    }
+  }
+
+  void MonitorContendedLocked(art::Monitor* m)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEntered)) {
+      art::Thread* self = art::Thread::Current();
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+      RunEventCallback<ArtJvmtiEvent::kMonitorContendedEntered>(
+          handler_,
+          self,
+          jnienv,
+          mon.get());
+    }
+  }
+
+  void ObjectWaitStart(art::Handle<art::mirror::Object> obj, int64_t timeout)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWait)) {
+      art::Thread* self = art::Thread::Current();
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, obj.Get()));
+      RunEventCallback<ArtJvmtiEvent::kMonitorWait>(
+          handler_,
+          self,
+          jnienv,
+          mon.get(),
+          static_cast<jlong>(timeout));
+    }
+  }
+
+
+  // Our interpretation of the spec is that the JVMTI_EVENT_MONITOR_WAITED will be sent immediately
+  // after a thread has woken up from a sleep caused by a call to Object#wait. If the thread will
+  // never go to sleep (due to not having the lock, having bad arguments, or having an exception
+  // propogated from JVMTI_EVENT_MONITOR_WAIT) we will not send this event.
+  //
+  // This does not fully match the RI semantics. Specifically, we will not send the
+  // JVMTI_EVENT_MONITOR_WAITED event in one situation where the RI would, there was an exception in
+  // the JVMTI_EVENT_MONITOR_WAIT event but otherwise the call was fine. In that case the RI would
+  // send this event and return without going to sleep.
+  //
+  // See b/65558434 for more discussion.
+  void MonitorWaitFinished(art::Monitor* m, bool timeout)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWaited)) {
+      art::Thread* self = art::Thread::Current();
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+      RunEventCallback<ArtJvmtiEvent::kMonitorWaited>(
+          handler_,
+          self,
+          jnienv,
+          mon.get(),
+          static_cast<jboolean>(timeout));
+    }
+  }
+
+ private:
+  EventHandler* handler_;
+};
+
+static void SetupMonitorListener(art::MonitorCallback* listener, bool enable) {
+  // We must not hold the mutator lock here, but if we're in FastJNI, for example, we might. For
+  // now, do a workaround: (possibly) acquire and release.
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (enable) {
+    art::Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(listener);
+  } else {
+    art::Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(listener);
+  }
+}
+
 // Report GC pauses (see spec) as GARBAGE_COLLECTION_START and GARBAGE_COLLECTION_END.
 class JvmtiGcPauseListener : public art::gc::GcPauseListener {
  public:
@@ -301,33 +425,10 @@
   }
 }
 
-template<typename Type>
-static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
-    REQUIRES_SHARED(art::Locks::mutator_lock_) {
-  return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
-}
-
 class JvmtiMethodTraceListener FINAL : public art::instrumentation::InstrumentationListener {
  public:
   explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {}
 
-  template<ArtJvmtiEvent kEvent, typename ...Args>
-  void RunEventCallback(art::Thread* self, art::JNIEnvExt* jnienv, Args... args)
-      REQUIRES_SHARED(art::Locks::mutator_lock_) {
-    ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
-    // Just give the event a good sized JNI frame. 100 should be fine.
-    jnienv->PushFrame(100);
-    {
-      // Need to do trampoline! :(
-      art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
-      event_handler_->DispatchEvent<kEvent>(self,
-                                            static_cast<JNIEnv*>(jnienv),
-                                            thread_jni.get(),
-                                            args...);
-    }
-    jnienv->PopFrame();
-  }
-
   // Call-back for when a method is entered.
   void MethodEntered(art::Thread* self,
                      art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
@@ -337,7 +438,8 @@
     if (!method->IsRuntimeMethod() &&
         event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodEntry)) {
       art::JNIEnvExt* jnienv = self->GetJniEnv();
-      RunEventCallback<ArtJvmtiEvent::kMethodEntry>(self,
+      RunEventCallback<ArtJvmtiEvent::kMethodEntry>(event_handler_,
+                                                    self,
                                                     jnienv,
                                                     art::jni::EncodeArtMethod(method));
     }
@@ -360,6 +462,7 @@
       ScopedLocalRef<jobject> return_jobj(jnienv, AddLocalRef<jobject>(jnienv, return_value.Get()));
       val.l = return_jobj.get();
       RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(method),
@@ -386,6 +489,7 @@
       // the union.
       val.j = return_value.GetJ();
       RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(method),
@@ -412,6 +516,7 @@
       CHECK(!old_exception.IsNull());
       self->ClearException();
       RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(method),
@@ -442,11 +547,11 @@
     jlocation location = static_cast<jlocation>(new_dex_pc);
     // Step event is reported first according to the spec.
     if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) {
-      RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location);
+      RunEventCallback<ArtJvmtiEvent::kSingleStep>(event_handler_, self, jnienv, jmethod, location);
     }
     // Next we do the Breakpoint events. The Dispatch code will filter the individual
     if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) {
-      RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location);
+      RunEventCallback<ArtJvmtiEvent::kBreakpoint>(event_handler_, self, jnienv, jmethod, location);
     }
   }
 
@@ -464,7 +569,8 @@
       ScopedLocalRef<jobject> fklass(jnienv,
                                      AddLocalRef<jobject>(jnienv,
                                                           field->GetDeclaringClass().Ptr()));
-      RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self,
+      RunEventCallback<ArtJvmtiEvent::kFieldAccess>(event_handler_,
+                                                    self,
                                                     jnienv,
                                                     art::jni::EncodeArtMethod(method),
                                                     static_cast<jlocation>(dex_pc),
@@ -492,6 +598,7 @@
       jvalue val;
       val.l = fval.get();
       RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(method),
@@ -525,6 +632,7 @@
       // the union.
       val.j = field_value.GetJ();
       RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(method),
@@ -545,6 +653,7 @@
       art::JNIEnvExt* jnienv = self->GetJniEnv();
       jboolean is_exception_pending = self->IsExceptionPending();
       RunEventCallback<ArtJvmtiEvent::kFramePop>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(frame.GetMethod()),
@@ -639,6 +748,7 @@
       ScopedLocalRef<jobject> exception(jnienv,
                                         AddLocalRef<jobject>(jnienv, exception_object.Get()));
       RunEventCallback<ArtJvmtiEvent::kException>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(method),
@@ -664,6 +774,7 @@
       ScopedLocalRef<jobject> exception(jnienv,
                                         AddLocalRef<jobject>(jnienv, exception_object.Get()));
       RunEventCallback<ArtJvmtiEvent::kExceptionCatch>(
+          event_handler_,
           self,
           jnienv,
           art::jni::EncodeArtMethod(method),
@@ -759,6 +870,23 @@
   instr->DeoptimizeEverything("jvmti-local-variable-access");
 }
 
+bool EventHandler::OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event) {
+  std::array<ArtJvmtiEvent, 4> events {
+    {
+      ArtJvmtiEvent::kMonitorContendedEnter,
+      ArtJvmtiEvent::kMonitorContendedEntered,
+      ArtJvmtiEvent::kMonitorWait,
+      ArtJvmtiEvent::kMonitorWaited
+    }
+  };
+  for (ArtJvmtiEvent e : events) {
+    if (e != event && IsEventEnabledAnywhere(e)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 // Handle special work for the given event type, if necessary.
 void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
   switch (event) {
@@ -799,7 +927,14 @@
     case ArtJvmtiEvent::kExceptionCatch:
       SetupTraceListener(method_trace_listener_.get(), event, enable);
       return;
-
+    case ArtJvmtiEvent::kMonitorContendedEnter:
+    case ArtJvmtiEvent::kMonitorContendedEntered:
+    case ArtJvmtiEvent::kMonitorWait:
+    case ArtJvmtiEvent::kMonitorWaited:
+      if (!OtherMonitorEventsEnabledAnywhere(event)) {
+        SetupMonitorListener(monitor_listener_.get(), enable);
+      }
+      return;
     default:
       break;
   }
@@ -928,6 +1063,7 @@
   alloc_listener_.reset(new JvmtiAllocationListener(this));
   gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
   method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
+  monitor_listener_.reset(new JvmtiMonitorListener(this));
 }
 
 EventHandler::~EventHandler() {
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index b49e80c..0d36c46 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -30,6 +30,7 @@
 class JvmtiAllocationListener;
 class JvmtiGcPauseListener;
 class JvmtiMethodTraceListener;
+class JvmtiMonitorListener;
 
 // an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between
 // retransformation capable and incapable loading
@@ -212,6 +213,8 @@
   void HandleEventType(ArtJvmtiEvent event, bool enable);
   void HandleLocalAccessCapabilityAdded();
 
+  bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event);
+
   // List of all JvmTiEnv objects that have been created, in their creation order.
   // NB Some elements might be null representing envs that have been deleted. They should be skipped
   // anytime this list is used.
@@ -223,6 +226,7 @@
   std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
   std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
   std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
+  std::unique_ptr<JvmtiMonitorListener> monitor_listener_;
 
   // True if frame pop has ever been enabled. Since we store pointers to stack frames we need to
   // continue to listen to this event even if it has been disabled.
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index 27d01ea..d437e52 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -392,6 +392,8 @@
       return JVMTI_JAVA_LANG_THREAD_STATE_NEW;
 
     case art::ThreadState::kWaiting:
+    case art::ThreadState::kWaitingForTaskProcessor:
+    case art::ThreadState::kWaitingForLockInflation:
     case art::ThreadState::kWaitingForGcToComplete:
     case art::ThreadState::kWaitingPerformingGc:
     case art::ThreadState::kWaitingForCheckPointsToRun:
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index af56810..2868180 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -2212,6 +2212,8 @@
     case kTerminated:
       return JDWP::TS_ZOMBIE;
     case kTimedWaiting:
+    case kWaitingForTaskProcessor:
+    case kWaitingForLockInflation:
     case kWaitingForCheckPointsToRun:
     case kWaitingForDebuggerSend:
     case kWaitingForDebuggerSuspension:
diff --git a/runtime/entrypoints/quick/quick_lock_entrypoints.cc b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
index b4f945a..4bcce21 100644
--- a/runtime/entrypoints/quick/quick_lock_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
@@ -29,15 +29,22 @@
     ThrowNullPointerException("Null reference used for synchronization (monitor-enter)");
     return -1;  // Failure.
   } else {
-    if (kIsDebugBuild) {
-      obj = obj->MonitorEnter(self);  // May block
-      CHECK(self->HoldsLock(obj));
-      CHECK(!self->IsExceptionPending());
+    obj = obj->MonitorEnter(self);  // May block
+    DCHECK(self->HoldsLock(obj));
+    // Exceptions can be thrown by monitor event listeners. This is expected to be rare however.
+    if (UNLIKELY(self->IsExceptionPending())) {
+      // TODO Remove this DCHECK if we expand the use of monitor callbacks.
+      DCHECK(Runtime::Current()->HasLoadedPlugins())
+          << "Exceptions are only expected to be thrown by plugin code which doesn't seem to be "
+          << "loaded.";
+      // We need to get rid of the lock
+      bool unlocked = obj->MonitorExit(self);
+      DCHECK(unlocked);
+      return -1;  // Failure.
     } else {
-      obj->MonitorEnter(self);  // May block
+      DCHECK(self->HoldsLock(obj));
+      return 0;  // Success.
     }
-    return 0;  // Success.
-    // Only possible exception is NPE and is handled before entry
   }
 }
 
diff --git a/runtime/gc/task_processor.cc b/runtime/gc/task_processor.cc
index 0704a68..e928644 100644
--- a/runtime/gc/task_processor.cc
+++ b/runtime/gc/task_processor.cc
@@ -34,14 +34,14 @@
 }
 
 void TaskProcessor::AddTask(Thread* self, HeapTask* task) {
-  ScopedThreadStateChange tsc(self, kBlocked);
+  ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
   MutexLock mu(self, *lock_);
   tasks_.insert(task);
   cond_->Signal(self);
 }
 
 HeapTask* TaskProcessor::GetTask(Thread* self) {
-  ScopedThreadStateChange tsc(self, kBlocked);
+  ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
   MutexLock mu(self, *lock_);
   while (true) {
     if (tasks_.empty()) {
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 3ccab85..50bd7e7 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -65,9 +65,16 @@
 static inline void DoMonitorEnter(Thread* self, ShadowFrame* frame, ObjPtr<mirror::Object> ref)
     NO_THREAD_SAFETY_ANALYSIS
     REQUIRES(!Roles::uninterruptible_) {
+  DCHECK(!ref.IsNull());
   StackHandleScope<1> hs(self);
   Handle<mirror::Object> h_ref(hs.NewHandle(ref));
   h_ref->MonitorEnter(self);
+  DCHECK(self->HoldsLock(h_ref.Get()));
+  if (UNLIKELY(self->IsExceptionPending())) {
+    bool unlocked = h_ref->MonitorExit(self);
+    DCHECK(unlocked);
+    return;
+  }
   if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
     frame->GetLockCountData().AddMonitor(self, h_ref.Get());
   }
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index f96792d..d74cec3 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -2398,10 +2398,12 @@
     ScopedObjectAccess soa(env);
     ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
     o = o->MonitorEnter(soa.Self());
+    if (soa.Self()->HoldsLock(o)) {
+      soa.Env()->monitors.Add(o);
+    }
     if (soa.Self()->IsExceptionPending()) {
       return JNI_ERR;
     }
-    soa.Env()->monitors.Add(o);
     return JNI_OK;
   }
 
@@ -2409,11 +2411,14 @@
     CHECK_NON_NULL_ARGUMENT_RETURN(java_object, JNI_ERR);
     ScopedObjectAccess soa(env);
     ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
+    bool remove_mon = soa.Self()->HoldsLock(o);
     o->MonitorExit(soa.Self());
+    if (remove_mon) {
+      soa.Env()->monitors.Remove(o);
+    }
     if (soa.Self()->IsExceptionPending()) {
       return JNI_ERR;
     }
-    soa.Env()->monitors.Remove(o);
     return JNI_OK;
   }
 
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 80e6ad3..051e015 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -346,11 +346,31 @@
   return TryLockLocked(self);
 }
 
+// Asserts that a mutex isn't held when the class comes into and out of scope.
+class ScopedAssertNotHeld {
+ public:
+  ScopedAssertNotHeld(Thread* self, Mutex& mu) : self_(self), mu_(mu) {
+    mu_.AssertNotHeld(self_);
+  }
+
+  ~ScopedAssertNotHeld() {
+    mu_.AssertNotHeld(self_);
+  }
+
+ private:
+  Thread* const self_;
+  Mutex& mu_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedAssertNotHeld);
+};
+
+template <LockReason reason>
 void Monitor::Lock(Thread* self) {
-  MutexLock mu(self, monitor_lock_);
+  ScopedAssertNotHeld sanh(self, monitor_lock_);
+  bool called_monitors_callback = false;
+  monitor_lock_.Lock(self);
   while (true) {
     if (TryLockLocked(self)) {
-      return;
+      break;
     }
     // Contended.
     const bool log_contention = (lock_profiling_threshold_ != 0);
@@ -389,6 +409,12 @@
     }
 
     monitor_lock_.Unlock(self);  // Let go of locks in order.
+    // Call the contended locking cb once and only once. Also only call it if we are locking for
+    // the first time, not during a Wait wakeup.
+    if (reason == LockReason::kForLock && !called_monitors_callback) {
+      called_monitors_callback = true;
+      Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocking(this);
+    }
     self->SetMonitorEnterObject(GetObject());
     {
       ScopedThreadSuspension tsc(self, kBlocked);  // Change to blocked and give up mutator_lock_.
@@ -492,10 +518,10 @@
                     << PrettyDuration(MsToNs(wait_ms));
               }
               LogContentionEvent(self,
-                                 wait_ms,
-                                 sample_percent,
-                                 owners_method,
-                                 owners_dex_pc);
+                                wait_ms,
+                                sample_percent,
+                                owners_method,
+                                owners_dex_pc);
             }
           }
         }
@@ -508,8 +534,18 @@
     monitor_lock_.Lock(self);  // Reacquire locks in order.
     --num_waiters_;
   }
+  monitor_lock_.Unlock(self);
+  // We need to pair this with a single contended locking call. NB we match the RI behavior and call
+  // this even if MonitorEnter failed.
+  if (called_monitors_callback) {
+    CHECK(reason == LockReason::kForLock);
+    Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocked(this);
+  }
 }
 
+template void Monitor::Lock<LockReason::kForLock>(Thread* self);
+template void Monitor::Lock<LockReason::kForWait>(Thread* self);
+
 static void ThrowIllegalMonitorStateExceptionF(const char* fmt, ...)
                                               __attribute__((format(printf, 1, 2)));
 
@@ -690,6 +726,7 @@
   AtraceMonitorLock(self, GetObject(), true /* is_wait */);
 
   bool was_interrupted = false;
+  bool timed_out = false;
   {
     // Update thread state. If the GC wakes up, it'll ignore us, knowing
     // that we won't touch any references in this state, and we'll check
@@ -718,7 +755,7 @@
         self->GetWaitConditionVariable()->Wait(self);
       } else {
         DCHECK(why == kTimedWaiting || why == kSleeping) << why;
-        self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
+        timed_out = self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
       }
       was_interrupted = self->IsInterrupted();
     }
@@ -751,8 +788,11 @@
 
   AtraceMonitorUnlock();  // End Wait().
 
+  // We just slept, tell the runtime callbacks about this.
+  Runtime::Current()->GetRuntimeCallbacks()->MonitorWaitFinished(this, timed_out);
+
   // Re-acquire the monitor and lock.
-  Lock(self);
+  Lock<LockReason::kForWait>(self);
   monitor_lock_.Lock(self);
   self->GetWaitMutex()->AssertNotHeld(self);
 
@@ -897,7 +937,7 @@
     bool timed_out;
     Thread* owner;
     {
-      ScopedThreadSuspension sts(self, kBlocked);
+      ScopedThreadSuspension sts(self, kWaitingForLockInflation);
       owner = thread_list->SuspendThreadByThreadId(owner_thread_id,
                                                    SuspendReason::kInternal,
                                                    &timed_out);
@@ -989,10 +1029,10 @@
           contention_count++;
           Runtime* runtime = Runtime::Current();
           if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) {
-            // TODO: Consider switching the thread state to kBlocked when we are yielding.
-            // Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the
-            // parameter you pass in. This can cause thread suspension to take excessively long
-            // and make long pauses. See b/16307460.
+            // TODO: Consider switching the thread state to kWaitingForLockInflation when we are
+            // yielding.  Use sched_yield instead of NanoSleep since NanoSleep can wait much longer
+            // than the parameter you pass in. This can cause thread suspension to take excessively
+            // long and make long pauses. See b/16307460.
             // TODO: We should literally spin first, without sched_yield. Sched_yield either does
             // nothing (at significant expense), or guarantees that we wait at least microseconds.
             // If the owner is running, I would expect the median lock hold time to be hundreds
@@ -1098,7 +1138,16 @@
                    bool interruptShouldThrow, ThreadState why) {
   DCHECK(self != nullptr);
   DCHECK(obj != nullptr);
-  LockWord lock_word = obj->GetLockWord(true);
+  StackHandleScope<1> hs(self);
+  Handle<mirror::Object> h_obj(hs.NewHandle(obj));
+
+  Runtime::Current()->GetRuntimeCallbacks()->ObjectWaitStart(h_obj, ms);
+  if (UNLIKELY(self->IsExceptionPending())) {
+    // See b/65558434 for information on handling of exceptions here.
+    return;
+  }
+
+  LockWord lock_word = h_obj->GetLockWord(true);
   while (lock_word.GetState() != LockWord::kFatLocked) {
     switch (lock_word.GetState()) {
       case LockWord::kHashCode:
@@ -1115,8 +1164,8 @@
         } else {
           // We own the lock, inflate to enqueue ourself on the Monitor. May fail spuriously so
           // re-load.
-          Inflate(self, self, obj, 0);
-          lock_word = obj->GetLockWord(true);
+          Inflate(self, self, h_obj.Get(), 0);
+          lock_word = h_obj->GetLockWord(true);
         }
         break;
       }
@@ -1203,8 +1252,9 @@
     if (monitor != nullptr) {
       pretty_object = monitor->GetObject();
     }
-  } else if (state == kBlocked) {
-    wait_message = "  - waiting to lock ";
+  } else if (state == kBlocked || state == kWaitingForLockInflation) {
+    wait_message = (state == kBlocked) ? "  - waiting to lock "
+                                       : "  - waiting for lock inflation of ";
     pretty_object = thread->GetMonitorEnterObject();
     if (pretty_object != nullptr) {
       if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
diff --git a/runtime/monitor.h b/runtime/monitor.h
index 09faeba..d7aef34 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -31,6 +31,7 @@
 #include "gc_root.h"
 #include "lock_word.h"
 #include "read_barrier_option.h"
+#include "runtime_callbacks.h"
 #include "thread_state.h"
 
 namespace art {
@@ -47,6 +48,11 @@
   class Object;
 }  // namespace mirror
 
+enum class LockReason {
+  kForWait,
+  kForLock,
+};
+
 class Monitor {
  public:
   // The default number of spins that are done before thread suspension is used to forcibly inflate
@@ -205,9 +211,11 @@
       REQUIRES(monitor_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  template<LockReason reason = LockReason::kForLock>
   void Lock(Thread* self)
       REQUIRES(!monitor_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
+
   bool Unlock(Thread* thread)
       REQUIRES(!monitor_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 4fbbb72..94007ff 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -86,6 +86,8 @@
     case kWaiting:                        return kJavaWaiting;
     case kStarting:                       return kJavaNew;
     case kNative:                         return kJavaRunnable;
+    case kWaitingForTaskProcessor:        return kJavaWaiting;
+    case kWaitingForLockInflation:        return kJavaWaiting;
     case kWaitingForGcToComplete:         return kJavaWaiting;
     case kWaitingPerformingGc:            return kJavaWaiting;
     case kWaitingForCheckPointsToRun:     return kJavaWaiting;
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 4e84468..2eb4411 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -658,6 +658,10 @@
 
   RuntimeCallbacks* GetRuntimeCallbacks();
 
+  bool HasLoadedPlugins() const {
+    return !plugins_.empty();
+  }
+
   void InitThreadGroups(Thread* self);
 
   void SetDumpGCPerformanceOnShutdown(bool value) {
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 16d6c13..88d3f28 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -21,6 +21,7 @@
 #include "art_method.h"
 #include "base/macros.h"
 #include "class_linker.h"
+#include "monitor.h"
 #include "thread.h"
 
 namespace art {
@@ -38,6 +39,38 @@
   }
 }
 
+void RuntimeCallbacks::MonitorContendedLocking(Monitor* m) {
+  for (MonitorCallback* cb : monitor_callbacks_) {
+    cb->MonitorContendedLocking(m);
+  }
+}
+
+void RuntimeCallbacks::MonitorContendedLocked(Monitor* m) {
+  for (MonitorCallback* cb : monitor_callbacks_) {
+    cb->MonitorContendedLocked(m);
+  }
+}
+
+void RuntimeCallbacks::ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout) {
+  for (MonitorCallback* cb : monitor_callbacks_) {
+    cb->ObjectWaitStart(m, timeout);
+  }
+}
+
+void RuntimeCallbacks::MonitorWaitFinished(Monitor* m, bool timeout) {
+  for (MonitorCallback* cb : monitor_callbacks_) {
+    cb->MonitorWaitFinished(m, timeout);
+  }
+}
+
+void RuntimeCallbacks::AddMonitorCallback(MonitorCallback* cb) {
+  monitor_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveMonitorCallback(MonitorCallback* cb) {
+  Remove(cb, &monitor_callbacks_);
+}
+
 void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
   Remove(cb, &thread_callbacks_);
 }
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index e8f1824..fa686d3 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -29,12 +29,14 @@
 namespace mirror {
 class Class;
 class ClassLoader;
+class Object;
 }  // namespace mirror
 
 class ArtMethod;
 class ClassLoadCallback;
 class Thread;
 class MethodCallback;
+class Monitor;
 class ThreadLifecycleCallback;
 
 // Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must
@@ -73,6 +75,25 @@
   virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 };
 
+class MonitorCallback {
+ public:
+  // Called just before the thread goes to sleep to wait for the monitor to become unlocked.
+  virtual void MonitorContendedLocking(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+  // Called just after the monitor has been successfully acquired when it was already locked.
+  virtual void MonitorContendedLocked(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+  // Called on entry to the Object#wait method regardless of whether or not the call is valid.
+  virtual void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis_timeout)
+      REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+  // Called just after the monitor has woken up from going to sleep for a wait(). At this point the
+  // thread does not possess a lock on the monitor. This will only be called for threads wait calls
+  // where the thread did (or at least could have) gone to sleep.
+  virtual void MonitorWaitFinished(Monitor* m, bool timed_out)
+      REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+  virtual ~MonitorCallback() {}
+};
+
 class RuntimeCallbacks {
  public:
   void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
@@ -120,6 +141,16 @@
                             /*out*/void** new_implementation)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void MonitorContendedLocking(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+  void MonitorContendedLocked(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+  void ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  void MonitorWaitFinished(Monitor* m, bool timed_out)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void AddMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+  void RemoveMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   std::vector<ThreadLifecycleCallback*> thread_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
@@ -131,6 +162,8 @@
       GUARDED_BY(Locks::mutator_lock_);
   std::vector<MethodCallback*> method_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
+  std::vector<MonitorCallback*> monitor_callbacks_
+      GUARDED_BY(Locks::mutator_lock_);
 };
 
 }  // namespace art
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index ac2ed9e..ef17258 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -22,6 +22,7 @@
 
 #include <initializer_list>
 #include <memory>
+#include <mutex>
 #include <string>
 
 #include "jni.h"
@@ -29,12 +30,14 @@
 #include "art_method-inl.h"
 #include "base/mutex.h"
 #include "class_linker.h"
+#include "class_reference.h"
 #include "common_runtime_test.h"
 #include "handle.h"
 #include "handle_scope-inl.h"
 #include "mem_map.h"
 #include "mirror/class-inl.h"
 #include "mirror/class_loader.h"
+#include "monitor.h"
 #include "nativehelper/ScopedLocalRef.h"
 #include "obj_ptr.h"
 #include "runtime.h"
@@ -433,4 +436,87 @@
   ASSERT_EQ(1u, cb_.death_seen);
 }
 
+class MonitorWaitCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+  void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_);
+  }
+  void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_);
+  }
+
+  struct Callback : public MonitorCallback {
+    bool IsInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      if (!obj->IsClass()) {
+        return false;
+      }
+      std::lock_guard<std::mutex> lock(ref_guard_);
+      mirror::Class* k = obj->AsClass();
+      ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+      return ref_ == test;
+    }
+
+    void SetInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      std::lock_guard<std::mutex> lock(ref_guard_);
+      mirror::Class* k = obj->AsClass();
+      ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+    }
+
+    void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED)
+        REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+    void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED)
+        REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+    void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis ATTRIBUTE_UNUSED)
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      if (IsInterestingObject(obj.Get())) {
+        saw_wait_start_ = true;
+      }
+    }
+
+    void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED)
+        REQUIRES_SHARED(Locks::mutator_lock_) {
+      if (IsInterestingObject(m->GetObject())) {
+        saw_wait_finished_ = true;
+      }
+    }
+
+    std::mutex ref_guard_;
+    ClassReference ref_ = {nullptr, 0};
+    bool saw_wait_start_ = false;
+    bool saw_wait_finished_ = false;
+  };
+
+  Callback cb_;
+};
+
+// TODO It would be good to have more tests for this but due to the multi-threaded nature of the
+// callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient.
+TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) {
+  ASSERT_FALSE(cb_.saw_wait_finished_);
+  ASSERT_FALSE(cb_.saw_wait_start_);
+  {
+    Thread* self = Thread::Current();
+    self->TransitionFromSuspendedToRunnable();
+    bool started = runtime_->Start();
+    ASSERT_TRUE(started);
+    {
+      ScopedObjectAccess soa(self);
+      cb_.SetInterestingObject(
+          soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr());
+      Monitor::Wait(
+          self,
+          // Just a random class
+          soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr(),
+          /*ms*/0,
+          /*ns*/0,
+          /*interruptShouldThrow*/false,
+          /*why*/kWaiting);
+    }
+  }
+  ASSERT_TRUE(cb_.saw_wait_start_);
+  ASSERT_FALSE(cb_.saw_wait_finished_);
+}
+
 }  // namespace art
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index 8f2f70f..8edfeec 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -29,6 +29,8 @@
   kSleeping,                        // TIMED_WAITING  TS_SLEEPING  in Thread.sleep()
   kBlocked,                         // BLOCKED        TS_MONITOR   blocked on a monitor
   kWaiting,                         // WAITING        TS_WAIT      in Object.wait()
+  kWaitingForLockInflation,         // WAITING        TS_WAIT      blocked inflating a thin-lock
+  kWaitingForTaskProcessor,         // WAITING        TS_WAIT      blocked waiting for taskProcessor
   kWaitingForGcToComplete,          // WAITING        TS_WAIT      blocked waiting for GC
   kWaitingForCheckPointsToRun,      // WAITING        TS_WAIT      GC waiting for checkpoints to run
   kWaitingPerformingGc,             // WAITING        TS_WAIT      performing GC
diff --git a/test/1930-monitor-info/src/art/Monitors.java b/test/1930-monitor-info/src/art/Monitors.java
index 26f7718..f6a99fd 100644
--- a/test/1930-monitor-info/src/art/Monitors.java
+++ b/test/1930-monitor-info/src/art/Monitors.java
@@ -16,12 +16,24 @@
 
 package art;
 
-import java.util.Arrays;
-import java.util.Objects;
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
 import java.util.function.Function;
 import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
 
 public class Monitors {
+  public native static void setupMonitorEvents(
+      Class<?> method_klass,
+      Method monitor_contended_enter_event,
+      Method monitor_contended_entered_event,
+      Method monitor_wait_event,
+      Method monitor_waited_event,
+      Class<?> lock_klass,
+      Thread thr);
+  public native static void stopMonitorEvents();
+
   public static class NamedLock {
     public final String name;
     public NamedLock(String name) {
@@ -68,5 +80,220 @@
   }
 
   public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+
+  public static class TestException extends Error {
+    public TestException() { super(); }
+    public TestException(String s) { super(s); }
+    public TestException(String s, Throwable c) { super(s, c); }
+  }
+
+  public static class LockController {
+    private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+    public final Object lock;
+    public final long timeout;
+    private final AtomicStampedReference<Action> action;
+    private volatile Thread runner = null;
+    private volatile boolean started = false;
+    private volatile boolean held = false;
+    private static final AtomicInteger cnt = new AtomicInteger(0);
+    private volatile Throwable exe;
+
+    public LockController(Object lock) {
+      this(lock, 10 * 1000);
+    }
+    public LockController(Object lock, long timeout) {
+      this.lock = lock;
+      this.timeout = timeout;
+      this.action = new AtomicStampedReference(Action.HOLD, 0);
+      this.exe = null;
+    }
+
+    public boolean IsWorkerThread(Thread thd) {
+      return Objects.equals(runner, thd);
+    }
+
+    public boolean IsLocked() {
+      checkException();
+      return held;
+    }
+
+    public void checkException() {
+      if (exe != null) {
+        throw new TestException("Exception thrown by other thread!", exe);
+      }
+    }
+
+    private void setAction(Action a) {
+      int stamp = action.getStamp();
+      // Wait for it to be HOLD before updating.
+      while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+        stamp = action.getStamp();
+      }
+    }
+
+    public synchronized void DoLock() {
+      if (IsLocked()) {
+        throw new Error("lock is already acquired or being acquired.");
+      }
+      if (runner != null) {
+        throw new Error("Already have thread!");
+      }
+      runner = new Thread(() -> {
+        started = true;
+        try {
+          synchronized (lock) {
+            held = true;
+            int[] stamp_h = new int[] { -1 };
+            Action cur_action = Action.HOLD;
+            try {
+              while (true) {
+                cur_action = action.get(stamp_h);
+                int stamp = stamp_h[0];
+                if (cur_action == Action.RELEASE) {
+                  // The other thread will deal with reseting action.
+                  break;
+                }
+                try {
+                  switch (cur_action) {
+                    case HOLD:
+                      Thread.yield();
+                      break;
+                    case NOTIFY:
+                      lock.notify();
+                      break;
+                    case NOTIFY_ALL:
+                      lock.notifyAll();
+                      break;
+                    case TIMED_WAIT:
+                      lock.wait(timeout);
+                      break;
+                    case WAIT:
+                      lock.wait();
+                      break;
+                    default:
+                      throw new Error("Unknown action " + action);
+                  }
+                } finally {
+                  // reset action back to hold if it isn't something else.
+                  action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+                }
+              }
+            } catch (Exception e) {
+              throw new TestException("Got an error while performing action " + cur_action, e);
+            }
+          }
+        } finally {
+          held = false;
+          started = false;
+        }
+      }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+      // Make sure we can get any exceptions this throws.
+      runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+      runner.start();
+    }
+
+    public void waitForLockToBeHeld() throws Exception {
+      while (true) {
+        if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+          return;
+        }
+      }
+    }
+
+    public synchronized void waitForNotifySleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+    }
+
+    public synchronized void waitForContendedSleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          runner.getState() != Thread.State.BLOCKED ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+    }
+
+    public synchronized void DoNotify() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY);
+    }
+
+    public synchronized void DoNotifyAll() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY_ALL);
+    }
+
+    public synchronized void DoTimedWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.TIMED_WAIT);
+    }
+
+    public synchronized void DoWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.WAIT);
+    }
+
+    public synchronized void interruptWorker() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      runner.interrupt();
+    }
+
+    public synchronized void waitForActionToFinish() throws Exception {
+      checkException();
+      while (action.getReference() != Action.HOLD) { checkException(); }
+    }
+
+    public synchronized void DoUnlock() throws Exception {
+      Error throwing = null;
+      if (!IsLocked()) {
+        // We might just be racing some exception that was thrown by the worker thread. Cache the
+        // exception, we will throw one from the worker before this one.
+        throwing = new Error("Not locked!");
+      }
+      setAction(Action.RELEASE);
+      Thread run = runner;
+      runner = null;
+      while (held) {}
+      run.join();
+      action.set(Action.HOLD, 0);
+      // Make sure to throw any exception that occurred since it might not have unlocked due to our
+      // request.
+      checkException();
+      DoCleanup();
+      if (throwing != null) {
+        throw throwing;
+      }
+    }
+
+    public synchronized void DoCleanup() throws Exception {
+      if (runner != null) {
+        Thread run = runner;
+        runner = null;
+        while (held) {}
+        run.join();
+      }
+      action.set(Action.HOLD, 0);
+      exe = null;
+    }
+  }
 }
 
diff --git a/test/1931-monitor-events/expected.txt b/test/1931-monitor-events/expected.txt
new file mode 100644
index 0000000..33a9bd3
--- /dev/null
+++ b/test/1931-monitor-events/expected.txt
@@ -0,0 +1,29 @@
+Testing contended locking.
+Locker thread 1 for NamedLock[Lock testLock] contended-LOCKING NamedLock[Lock testLock]
+Locker thread 1 for NamedLock[Lock testLock] LOCKED NamedLock[Lock testLock]
+Testing monitor wait.
+Locker thread 2 for NamedLock[Lock testWait] start-monitor-wait NamedLock[Lock testWait] timeout: 0
+Locker thread 2 for NamedLock[Lock testWait] monitor-waited NamedLock[Lock testWait] timed_out: false
+Testing monitor timed wait.
+Locker thread 4 for NamedLock[Lock testTimedWait] start-monitor-wait NamedLock[Lock testTimedWait] timeout: 3600000
+Locker thread 4 for NamedLock[Lock testTimedWait] monitor-waited NamedLock[Lock testTimedWait] timed_out: false
+Testing monitor timed with timeout.
+Waiting for 10 seconds.
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] start-monitor-wait NamedLock[Lock testTimedWaitTimeout] timeout: 10000
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] monitor-waited NamedLock[Lock testTimedWaitTimeout] timed_out: true
+Wait finished with timeout.
+Waiting on an unlocked monitor.
+Unlocked wait thread: start-monitor-wait NamedLock[Lock testUnlockedWait] timeout: 0
+Caught exception: java.lang.reflect.InvocationTargetException
+	Caused by: class java.lang.IllegalMonitorStateException
+Waiting with an illegal argument (negative timeout)
+Locker thread 7 for NamedLock[Lock testIllegalWait] start-monitor-wait NamedLock[Lock testIllegalWait] timeout: -100
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: Got an error while performing action TIMED_WAIT
+	Caused by: class java.lang.IllegalArgumentException
+Interrupt a monitor being waited on.
+Locker thread 8 for NamedLock[Lock testInteruptWait] start-monitor-wait NamedLock[Lock testInteruptWait] timeout: 0
+Locker thread 8 for NamedLock[Lock testInteruptWait] monitor-waited NamedLock[Lock testInteruptWait] timed_out: false
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: Got an error while performing action WAIT
+	Caused by: class java.lang.InterruptedException
diff --git a/test/1931-monitor-events/info.txt b/test/1931-monitor-events/info.txt
new file mode 100644
index 0000000..ae07c53
--- /dev/null
+++ b/test/1931-monitor-events/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that the basic monitor-events work as we expect them to.
diff --git a/test/1931-monitor-events/run b/test/1931-monitor-events/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1931-monitor-events/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1931-monitor-events/src/Main.java b/test/1931-monitor-events/src/Main.java
new file mode 100644
index 0000000..81c9d2c
--- /dev/null
+++ b/test/1931-monitor-events/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1931.run();
+  }
+}
diff --git a/test/1931-monitor-events/src/art/Monitors.java b/test/1931-monitor-events/src/art/Monitors.java
new file mode 100644
index 0000000..f6a99fd
--- /dev/null
+++ b/test/1931-monitor-events/src/art/Monitors.java
@@ -0,0 +1,299 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+  public native static void setupMonitorEvents(
+      Class<?> method_klass,
+      Method monitor_contended_enter_event,
+      Method monitor_contended_entered_event,
+      Method monitor_wait_event,
+      Method monitor_waited_event,
+      Class<?> lock_klass,
+      Thread thr);
+  public native static void stopMonitorEvents();
+
+  public static class NamedLock {
+    public final String name;
+    public NamedLock(String name) {
+      this.name = name;
+    }
+    public String toString() {
+      return String.format("NamedLock[%s]", name);
+    }
+  }
+
+  public static final class MonitorUsage {
+    public final Object monitor;
+    public final Thread owner;
+    public final int entryCount;
+    public final Thread[] waiters;
+    public final Thread[] notifyWaiters;
+
+    public MonitorUsage(
+        Object monitor,
+        Thread owner,
+        int entryCount,
+        Thread[] waiters,
+        Thread[] notifyWaiters) {
+      this.monitor = monitor;
+      this.entryCount = entryCount;
+      this.owner = owner;
+      this.waiters = waiters;
+      this.notifyWaiters = notifyWaiters;
+    }
+
+    private static String toNameList(Thread[] ts) {
+      return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+    }
+
+    public String toString() {
+      return String.format(
+          "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+          monitor,
+          (owner != null) ? owner.getName() : "<NULL>",
+          entryCount,
+          toNameList(waiters),
+          toNameList(notifyWaiters));
+    }
+  }
+
+  public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+
+  public static class TestException extends Error {
+    public TestException() { super(); }
+    public TestException(String s) { super(s); }
+    public TestException(String s, Throwable c) { super(s, c); }
+  }
+
+  public static class LockController {
+    private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+    public final Object lock;
+    public final long timeout;
+    private final AtomicStampedReference<Action> action;
+    private volatile Thread runner = null;
+    private volatile boolean started = false;
+    private volatile boolean held = false;
+    private static final AtomicInteger cnt = new AtomicInteger(0);
+    private volatile Throwable exe;
+
+    public LockController(Object lock) {
+      this(lock, 10 * 1000);
+    }
+    public LockController(Object lock, long timeout) {
+      this.lock = lock;
+      this.timeout = timeout;
+      this.action = new AtomicStampedReference(Action.HOLD, 0);
+      this.exe = null;
+    }
+
+    public boolean IsWorkerThread(Thread thd) {
+      return Objects.equals(runner, thd);
+    }
+
+    public boolean IsLocked() {
+      checkException();
+      return held;
+    }
+
+    public void checkException() {
+      if (exe != null) {
+        throw new TestException("Exception thrown by other thread!", exe);
+      }
+    }
+
+    private void setAction(Action a) {
+      int stamp = action.getStamp();
+      // Wait for it to be HOLD before updating.
+      while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+        stamp = action.getStamp();
+      }
+    }
+
+    public synchronized void DoLock() {
+      if (IsLocked()) {
+        throw new Error("lock is already acquired or being acquired.");
+      }
+      if (runner != null) {
+        throw new Error("Already have thread!");
+      }
+      runner = new Thread(() -> {
+        started = true;
+        try {
+          synchronized (lock) {
+            held = true;
+            int[] stamp_h = new int[] { -1 };
+            Action cur_action = Action.HOLD;
+            try {
+              while (true) {
+                cur_action = action.get(stamp_h);
+                int stamp = stamp_h[0];
+                if (cur_action == Action.RELEASE) {
+                  // The other thread will deal with reseting action.
+                  break;
+                }
+                try {
+                  switch (cur_action) {
+                    case HOLD:
+                      Thread.yield();
+                      break;
+                    case NOTIFY:
+                      lock.notify();
+                      break;
+                    case NOTIFY_ALL:
+                      lock.notifyAll();
+                      break;
+                    case TIMED_WAIT:
+                      lock.wait(timeout);
+                      break;
+                    case WAIT:
+                      lock.wait();
+                      break;
+                    default:
+                      throw new Error("Unknown action " + action);
+                  }
+                } finally {
+                  // reset action back to hold if it isn't something else.
+                  action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+                }
+              }
+            } catch (Exception e) {
+              throw new TestException("Got an error while performing action " + cur_action, e);
+            }
+          }
+        } finally {
+          held = false;
+          started = false;
+        }
+      }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+      // Make sure we can get any exceptions this throws.
+      runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+      runner.start();
+    }
+
+    public void waitForLockToBeHeld() throws Exception {
+      while (true) {
+        if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+          return;
+        }
+      }
+    }
+
+    public synchronized void waitForNotifySleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+    }
+
+    public synchronized void waitForContendedSleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          runner.getState() != Thread.State.BLOCKED ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+    }
+
+    public synchronized void DoNotify() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY);
+    }
+
+    public synchronized void DoNotifyAll() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY_ALL);
+    }
+
+    public synchronized void DoTimedWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.TIMED_WAIT);
+    }
+
+    public synchronized void DoWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.WAIT);
+    }
+
+    public synchronized void interruptWorker() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      runner.interrupt();
+    }
+
+    public synchronized void waitForActionToFinish() throws Exception {
+      checkException();
+      while (action.getReference() != Action.HOLD) { checkException(); }
+    }
+
+    public synchronized void DoUnlock() throws Exception {
+      Error throwing = null;
+      if (!IsLocked()) {
+        // We might just be racing some exception that was thrown by the worker thread. Cache the
+        // exception, we will throw one from the worker before this one.
+        throwing = new Error("Not locked!");
+      }
+      setAction(Action.RELEASE);
+      Thread run = runner;
+      runner = null;
+      while (held) {}
+      run.join();
+      action.set(Action.HOLD, 0);
+      // Make sure to throw any exception that occurred since it might not have unlocked due to our
+      // request.
+      checkException();
+      DoCleanup();
+      if (throwing != null) {
+        throw throwing;
+      }
+    }
+
+    public synchronized void DoCleanup() throws Exception {
+      if (runner != null) {
+        Thread run = runner;
+        runner = null;
+        while (held) {}
+        run.join();
+      }
+      action.set(Action.HOLD, 0);
+      exe = null;
+    }
+  }
+}
+
diff --git a/test/1931-monitor-events/src/art/Test1931.java b/test/1931-monitor-events/src/art/Test1931.java
new file mode 100644
index 0000000..ccefede
--- /dev/null
+++ b/test/1931-monitor-events/src/art/Test1931.java
@@ -0,0 +1,198 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.*;
+import java.util.ListIterator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class Test1931 {
+  public static void printStackTrace(Throwable t) {
+    System.out.println("Caught exception: " + t);
+    for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
+      System.out.println("\tCaused by: " +
+          (Test1931.class.getPackage().equals(c.getClass().getPackage())
+           ? c.toString() : c.getClass().toString()));
+    }
+  }
+
+  public static void handleMonitorEnter(Thread thd, Object lock) {
+    System.out.println(thd.getName() + " contended-LOCKING " + lock);
+  }
+
+  public static void handleMonitorEntered(Thread thd, Object lock) {
+    System.out.println(thd.getName() + " LOCKED " + lock);
+  }
+  public static void handleMonitorWait(Thread thd, Object lock, long timeout) {
+    System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout);
+  }
+
+  public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {
+    System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out);
+  }
+
+  public static void run() throws Exception {
+    Monitors.setupMonitorEvents(
+        Test1931.class,
+        Test1931.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class),
+        Test1931.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class),
+        Test1931.class.getDeclaredMethod("handleMonitorWait",
+          Thread.class, Object.class, Long.TYPE),
+        Test1931.class.getDeclaredMethod("handleMonitorWaited",
+          Thread.class, Object.class, Boolean.TYPE),
+        Monitors.NamedLock.class,
+        null);
+
+    System.out.println("Testing contended locking.");
+    testLock(new Monitors.NamedLock("Lock testLock"));
+
+    System.out.println("Testing monitor wait.");
+    testWait(new Monitors.NamedLock("Lock testWait"));
+
+    System.out.println("Testing monitor timed wait.");
+    testTimedWait(new Monitors.NamedLock("Lock testTimedWait"));
+
+    System.out.println("Testing monitor timed with timeout.");
+    testTimedWaitTimeout(new Monitors.NamedLock("Lock testTimedWaitTimeout"));
+
+    // TODO It would be good (but annoying) to do this with jasmin/smali in order to test if it's
+    // different without the reflection.
+    System.out.println("Waiting on an unlocked monitor.");
+    testUnlockedWait(new Monitors.NamedLock("Lock testUnlockedWait"));
+
+    System.out.println("Waiting with an illegal argument (negative timeout)");
+    testIllegalWait(new Monitors.NamedLock("Lock testIllegalWait"));
+
+    System.out.println("Interrupt a monitor being waited on.");
+    testInteruptWait(new Monitors.NamedLock("Lock testInteruptWait"));
+  }
+
+  public static void testInteruptWait(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoWait();
+    controller1.waitForNotifySleep();
+    try {
+      controller1.interruptWorker();
+      controller1.waitForLockToBeHeld();
+      controller1.DoUnlock();
+      System.out.println("No Exception thrown!");
+    } catch (Monitors.TestException e) {
+      printStackTrace(e);
+    }
+    controller1.DoCleanup();
+  }
+
+  public static void testIllegalWait(final Monitors.NamedLock lk) throws Exception {
+    Monitors.LockController controller1 = new Monitors.LockController(lk, /*timed_wait time*/-100);
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    try {
+      controller1.DoTimedWait();
+      controller1.waitForNotifySleep();
+      controller1.waitForLockToBeHeld();
+      controller1.DoUnlock();
+      System.out.println("No Exception thrown!");
+    } catch (Monitors.TestException e) {
+      printStackTrace(e);
+    }
+    controller1.DoCleanup();
+  }
+
+  public static void testUnlockedWait(final Monitors.NamedLock lk) throws Exception {
+    synchronized (lk) {
+      Thread thd = new Thread(() -> {
+        try {
+          Method m = Object.class.getDeclaredMethod("wait");
+          m.invoke(lk);
+        } catch (Exception e) {
+          printStackTrace(e);
+        }
+      }, "Unlocked wait thread:");
+      thd.start();
+      thd.join();
+    }
+  }
+
+  public static void testLock(Monitors.NamedLock lk) throws Exception {
+    Monitors.LockController controller1 = new Monitors.LockController(lk);
+    Monitors.LockController controller2 = new Monitors.LockController(lk);
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller2.DoLock();
+    if (controller2.IsLocked()) {
+      throw new Exception("c2 was able to gain lock while it was held by c1");
+    }
+    controller2.waitForContendedSleep();
+    controller1.DoUnlock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoUnlock();
+  }
+
+  public static void testWait(Monitors.NamedLock lk) throws Exception {
+    Monitors.LockController controller1 = new Monitors.LockController(lk);
+    Monitors.LockController controller2 = new Monitors.LockController(lk);
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoWait();
+    controller1.waitForNotifySleep();
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoNotifyAll();
+    controller2.DoUnlock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoUnlock();
+  }
+
+  public static void testTimedWait(Monitors.NamedLock lk) throws Exception {
+    // Time to wait (1 hour). We will wake it up before timeout.
+    final long millis =  60l * 60l * 1000l;
+    Monitors.LockController controller1 = new Monitors.LockController(lk, millis);
+    Monitors.LockController controller2 = new Monitors.LockController(lk);
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoTimedWait();
+    controller1.waitForNotifySleep();
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoNotifyAll();
+    controller2.DoUnlock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoUnlock();
+  }
+
+  public static void testTimedWaitTimeout(Monitors.NamedLock lk) throws Exception {
+    // Time to wait (10 seconds). We will wait for the timeout.
+    final long millis =  10l * 1000l;
+    Monitors.LockController controller1 = new Monitors.LockController(lk, millis);
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    System.out.println("Waiting for 10 seconds.");
+    controller1.DoTimedWait();
+    controller1.waitForNotifySleep();
+    controller1.DoUnlock();
+    System.out.println("Wait finished with timeout.");
+  }
+}
diff --git a/test/1932-monitor-events-misc/check b/test/1932-monitor-events-misc/check
new file mode 100644
index 0000000..8a84388
--- /dev/null
+++ b/test/1932-monitor-events-misc/check
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+# The RI sends an extra event that art doesn't. Add it to the expected output.
+if [[ "$TEST_RUNTIME" == "jvm" ]]; then
+  patch -p0 expected.txt < jvm-expected.patch >/dev/null
+fi
+
+./default-check "$@"
diff --git a/test/1932-monitor-events-misc/expected.txt b/test/1932-monitor-events-misc/expected.txt
new file mode 100644
index 0000000..b33aa7d
--- /dev/null
+++ b/test/1932-monitor-events-misc/expected.txt
@@ -0,0 +1,104 @@
+Testing contended locking where lock is released before callback ends.
+Locker thread 1 for NamedLock[Lock testLockUncontend] contended-LOCKING NamedLock[Lock testLockUncontend]
+Releasing NamedLock[Lock testLockUncontend] during monitorEnter event.
+Locker thread 1 for NamedLock[Lock testLockUncontend] LOCKED NamedLock[Lock testLockUncontend]
+Testing throwing exceptions in monitor_enter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] contended-LOCKING NamedLock[Lock testLockThrowEnter]
+Throwing exception in MonitorEnter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] LOCKED NamedLock[Lock testLockThrowEnter]
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[Lock testLockThrowEnter]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in monitor_entered
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] contended-LOCKING NamedLock[Lock testLockThrowEntered]
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] LOCKED NamedLock[Lock testLockThrowEntered]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowEntered]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEntered], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in both monitorEnter & MonitorEntered
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] contended-LOCKING NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEnter
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] LOCKED NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowBoth]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowBoth], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event
+Locker thread 8 for NamedLock[Lock testThrowWait] start-monitor-wait NamedLock[Lock testThrowWait] timeout: 0
+Throwing exception in MonitorWait
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during MonitorWait of NamedLock[Lock testThrowWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event with illegal aruments
+Locker thread 9 for NamedLock[Lock testThrowIllegalWait] start-monitor-wait NamedLock[Lock testThrowIllegalWait] timeout: -100000
+Throwing exception in MonitorWait timeout = -100000
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWait of NamedLock[Lock testThrowIllegalWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowIllegalWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event
+Locker thread 10 for NamedLock[Lock testThrowWaited] start-monitor-wait NamedLock[Lock testThrowWaited] timeout: 0
+Locker thread 10 for NamedLock[Lock testThrowWaited] monitor-waited NamedLock[Lock testThrowWaited] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaited]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaited], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by timeout
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] start-monitor-wait NamedLock[Lock testThrowWaitedTimeout] timeout: 5000
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] monitor-waited NamedLock[Lock testThrowWaitedTimeout] timed_out: true
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedTimeout]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedTimeout], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by interrupt
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] start-monitor-wait NamedLock[Lock testThrowWaitedInterrupt] timeout: 0
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] monitor-waited NamedLock[Lock testThrowWaitedInterrupt] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+	Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedInterrupt]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedInterrupt], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing ObjectMonitorInfo inside of events
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] contended-LOCKING NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEnter: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 14 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] LOCKED NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEntered: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] start-monitor-wait NamedLock[Lock testMonitorInfoInEvents] timeout: 0
+Monitor usage in MonitorWait: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] monitor-waited NamedLock[Lock testMonitorInfoInEvents] timed_out: false
+Monitor usage in MonitorWaited: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing that the monitor can be stolen during the MonitorWaited event.
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] start-monitor-wait NamedLock[test testWaitEnterInterleaving] timeout: 0
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] monitor-waited NamedLock[test testWaitEnterInterleaving] timed_out: false
+locking controller3 in controller2 MonitorWaited event
+Controller3 now holds the lock the monitor wait will try to re-acquire
+Testing that we can lock and release the monitor in the MonitorWait event
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] start-monitor-wait NamedLock[test testWaitMonitorEnter] timeout: 0
+In wait monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+In wait monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 2, waiters: [], notify_waiters: [] }
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] monitor-waited NamedLock[test testWaitMonitorEnter] timed_out: false
+Testing that we can lock and release the monitor in the MonitorWaited event
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] start-monitor-wait NamedLock[test testWaitedMonitorEnter] timeout: 0
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] monitor-waited NamedLock[test testWaitedMonitorEnter] timed_out: false
+In waited monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+In waited monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: Locker thread 22 for NamedLock[test testWaitedMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+Testing we can perform recursive lock in MonitorEntered
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] contended-LOCKING NamedLock[test testRecursiveMontiorEnteredLock]
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] LOCKED NamedLock[test testRecursiveMontiorEnteredLock]
+In MonitorEntered usage: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 1, waiters: [], notify_waiters: [] }
+In MonitorEntered sync: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 2, waiters: [], notify_waiters: [] }
+Testing the lock state if MonitorEnter throws in a native method
+NativeLockStateThrowEnter thread contended-LOCKING NamedLock[test testNativeLockStateThrowEnter]
+Unlocking controller1 in MonitorEnter
+Throwing exception in MonitorEnter
+NativeLockStateThrowEnter thread LOCKED NamedLock[test testNativeLockStateThrowEnter]
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEnter], owner: NativeLockStateThrowEnter thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[test testNativeLockStateThrowEnter]
+Testing the lock state if MonitorEntered throws in a native method
+NativeLockStateThrowEntered thread contended-LOCKING NamedLock[test testNativeLockStateThrowEntered]
+Unlocking controller1 in MonitorEnter
+NativeLockStateThrowEntered thread LOCKED NamedLock[test testNativeLockStateThrowEntered]
+Throwing exception in MonitorEntered
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEntered], owner: NativeLockStateThrowEntered thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[test testNativeLockStateThrowEntered]
diff --git a/test/1932-monitor-events-misc/info.txt b/test/1932-monitor-events-misc/info.txt
new file mode 100644
index 0000000..674ef56
--- /dev/null
+++ b/test/1932-monitor-events-misc/info.txt
@@ -0,0 +1,4 @@
+Tests jvmti monitor events in odd situations.
+
+Checks that the JVMTI monitor events are correctly dispatched and handled for
+many odd situations.
diff --git a/test/1932-monitor-events-misc/jvm-expected.patch b/test/1932-monitor-events-misc/jvm-expected.patch
new file mode 100644
index 0000000..f6b2285
--- /dev/null
+++ b/test/1932-monitor-events-misc/jvm-expected.patch
@@ -0,0 +1,2 @@
+29a30
+> Locker thread 8 for NamedLock[Lock testThrowWait] monitor-waited NamedLock[Lock testThrowWait] timed_out: false
diff --git a/test/1932-monitor-events-misc/monitor_misc.cc b/test/1932-monitor-events-misc/monitor_misc.cc
new file mode 100644
index 0000000..842c550
--- /dev/null
+++ b/test/1932-monitor-events-misc/monitor_misc.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 <pthread.h>
+
+#include <cstdio>
+#include <iostream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "jvmti.h"
+
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1932MonitorEventsMisc {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1932_doNativeLockPrint(JNIEnv* env,
+                                                                      jclass klass,
+                                                                      jobject lock) {
+                                                                      // jobject atomic_boolean) {
+  // ScopedLocalRef<jclass> atomic_klass(env, env->FindClass("java/util/concurrent/AtomicBoolean"));
+  // if (env->ExceptionCheck()) {
+  //   return;
+  // }
+  // jmethodID atomic_set = env->GetMethodID(atomic_klass.get(), "set", "(z)V");
+  jmethodID print_state = env->GetStaticMethodID(
+      klass, "printLockState", "(Lart/Monitors$NamedLock;Ljava/lang/Object;I)V");
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  jint res = env->MonitorEnter(lock);
+  ScopedLocalRef<jobject> exc(env, env->ExceptionOccurred());
+  env->ExceptionClear();
+  env->CallStaticVoidMethod(klass, print_state, lock, exc.get(), res);
+  env->MonitorExit(lock);
+}
+
+}  // namespace Test1932MonitorEventsMisc
+}  // namespace art
diff --git a/test/1932-monitor-events-misc/run b/test/1932-monitor-events-misc/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1932-monitor-events-misc/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1932-monitor-events-misc/src/Main.java b/test/1932-monitor-events-misc/src/Main.java
new file mode 100644
index 0000000..0100074
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1932.run();
+  }
+}
diff --git a/test/1932-monitor-events-misc/src/art/Monitors.java b/test/1932-monitor-events-misc/src/art/Monitors.java
new file mode 100644
index 0000000..f6a99fd
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/art/Monitors.java
@@ -0,0 +1,299 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+  public native static void setupMonitorEvents(
+      Class<?> method_klass,
+      Method monitor_contended_enter_event,
+      Method monitor_contended_entered_event,
+      Method monitor_wait_event,
+      Method monitor_waited_event,
+      Class<?> lock_klass,
+      Thread thr);
+  public native static void stopMonitorEvents();
+
+  public static class NamedLock {
+    public final String name;
+    public NamedLock(String name) {
+      this.name = name;
+    }
+    public String toString() {
+      return String.format("NamedLock[%s]", name);
+    }
+  }
+
+  public static final class MonitorUsage {
+    public final Object monitor;
+    public final Thread owner;
+    public final int entryCount;
+    public final Thread[] waiters;
+    public final Thread[] notifyWaiters;
+
+    public MonitorUsage(
+        Object monitor,
+        Thread owner,
+        int entryCount,
+        Thread[] waiters,
+        Thread[] notifyWaiters) {
+      this.monitor = monitor;
+      this.entryCount = entryCount;
+      this.owner = owner;
+      this.waiters = waiters;
+      this.notifyWaiters = notifyWaiters;
+    }
+
+    private static String toNameList(Thread[] ts) {
+      return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+    }
+
+    public String toString() {
+      return String.format(
+          "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+          monitor,
+          (owner != null) ? owner.getName() : "<NULL>",
+          entryCount,
+          toNameList(waiters),
+          toNameList(notifyWaiters));
+    }
+  }
+
+  public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+
+  public static class TestException extends Error {
+    public TestException() { super(); }
+    public TestException(String s) { super(s); }
+    public TestException(String s, Throwable c) { super(s, c); }
+  }
+
+  public static class LockController {
+    private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+    public final Object lock;
+    public final long timeout;
+    private final AtomicStampedReference<Action> action;
+    private volatile Thread runner = null;
+    private volatile boolean started = false;
+    private volatile boolean held = false;
+    private static final AtomicInteger cnt = new AtomicInteger(0);
+    private volatile Throwable exe;
+
+    public LockController(Object lock) {
+      this(lock, 10 * 1000);
+    }
+    public LockController(Object lock, long timeout) {
+      this.lock = lock;
+      this.timeout = timeout;
+      this.action = new AtomicStampedReference(Action.HOLD, 0);
+      this.exe = null;
+    }
+
+    public boolean IsWorkerThread(Thread thd) {
+      return Objects.equals(runner, thd);
+    }
+
+    public boolean IsLocked() {
+      checkException();
+      return held;
+    }
+
+    public void checkException() {
+      if (exe != null) {
+        throw new TestException("Exception thrown by other thread!", exe);
+      }
+    }
+
+    private void setAction(Action a) {
+      int stamp = action.getStamp();
+      // Wait for it to be HOLD before updating.
+      while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+        stamp = action.getStamp();
+      }
+    }
+
+    public synchronized void DoLock() {
+      if (IsLocked()) {
+        throw new Error("lock is already acquired or being acquired.");
+      }
+      if (runner != null) {
+        throw new Error("Already have thread!");
+      }
+      runner = new Thread(() -> {
+        started = true;
+        try {
+          synchronized (lock) {
+            held = true;
+            int[] stamp_h = new int[] { -1 };
+            Action cur_action = Action.HOLD;
+            try {
+              while (true) {
+                cur_action = action.get(stamp_h);
+                int stamp = stamp_h[0];
+                if (cur_action == Action.RELEASE) {
+                  // The other thread will deal with reseting action.
+                  break;
+                }
+                try {
+                  switch (cur_action) {
+                    case HOLD:
+                      Thread.yield();
+                      break;
+                    case NOTIFY:
+                      lock.notify();
+                      break;
+                    case NOTIFY_ALL:
+                      lock.notifyAll();
+                      break;
+                    case TIMED_WAIT:
+                      lock.wait(timeout);
+                      break;
+                    case WAIT:
+                      lock.wait();
+                      break;
+                    default:
+                      throw new Error("Unknown action " + action);
+                  }
+                } finally {
+                  // reset action back to hold if it isn't something else.
+                  action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+                }
+              }
+            } catch (Exception e) {
+              throw new TestException("Got an error while performing action " + cur_action, e);
+            }
+          }
+        } finally {
+          held = false;
+          started = false;
+        }
+      }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+      // Make sure we can get any exceptions this throws.
+      runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+      runner.start();
+    }
+
+    public void waitForLockToBeHeld() throws Exception {
+      while (true) {
+        if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+          return;
+        }
+      }
+    }
+
+    public synchronized void waitForNotifySleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+    }
+
+    public synchronized void waitForContendedSleep() throws Exception {
+      if (runner == null) {
+        throw new Error("No thread trying to lock!");
+      }
+      do {
+        checkException();
+      } while (!started ||
+          runner.getState() != Thread.State.BLOCKED ||
+          !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+    }
+
+    public synchronized void DoNotify() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY);
+    }
+
+    public synchronized void DoNotifyAll() {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.NOTIFY_ALL);
+    }
+
+    public synchronized void DoTimedWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.TIMED_WAIT);
+    }
+
+    public synchronized void DoWait() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      setAction(Action.WAIT);
+    }
+
+    public synchronized void interruptWorker() throws Exception {
+      if (!IsLocked()) {
+        throw new Error("Not locked");
+      }
+      runner.interrupt();
+    }
+
+    public synchronized void waitForActionToFinish() throws Exception {
+      checkException();
+      while (action.getReference() != Action.HOLD) { checkException(); }
+    }
+
+    public synchronized void DoUnlock() throws Exception {
+      Error throwing = null;
+      if (!IsLocked()) {
+        // We might just be racing some exception that was thrown by the worker thread. Cache the
+        // exception, we will throw one from the worker before this one.
+        throwing = new Error("Not locked!");
+      }
+      setAction(Action.RELEASE);
+      Thread run = runner;
+      runner = null;
+      while (held) {}
+      run.join();
+      action.set(Action.HOLD, 0);
+      // Make sure to throw any exception that occurred since it might not have unlocked due to our
+      // request.
+      checkException();
+      DoCleanup();
+      if (throwing != null) {
+        throw throwing;
+      }
+    }
+
+    public synchronized void DoCleanup() throws Exception {
+      if (runner != null) {
+        Thread run = runner;
+        runner = null;
+        while (held) {}
+        run.join();
+      }
+      action.set(Action.HOLD, 0);
+      exe = null;
+    }
+  }
+}
+
diff --git a/test/1932-monitor-events-misc/src/art/Test1932.java b/test/1932-monitor-events-misc/src/art/Test1932.java
new file mode 100644
index 0000000..7f66884
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/art/Test1932.java
@@ -0,0 +1,623 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.concurrent.Semaphore;
+
+public class Test1932 {
+  public static final boolean PRINT_FULL_STACK_TRACE = false;
+  public static final boolean INCLUDE_ANDROID_ONLY_TESTS = false;
+
+  public static interface MonitorHandler {
+    public default void handleMonitorEnter(Thread thd, Object lock) {}
+    public default void handleMonitorEntered(Thread thd, Object lock) {}
+    public default void handleMonitorWait(Thread thd, Object lock, long timeout) {}
+    public default void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {}
+  }
+
+  public static volatile MonitorHandler HANDLER = null;
+
+  public static void run() throws Exception {
+    Monitors.setupMonitorEvents(
+        Test1932.class,
+        Test1932.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class),
+        Test1932.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class),
+        Test1932.class.getDeclaredMethod("handleMonitorWait",
+          Thread.class, Object.class, Long.TYPE),
+        Test1932.class.getDeclaredMethod("handleMonitorWaited",
+          Thread.class, Object.class, Boolean.TYPE),
+        Monitors.NamedLock.class,
+        null);
+
+    System.out.println("Testing contended locking where lock is released before callback ends.");
+    testLockUncontend(new Monitors.NamedLock("Lock testLockUncontend"));
+
+    System.out.println("Testing throwing exceptions in monitor_enter");
+    testLockThrowEnter(new Monitors.NamedLock("Lock testLockThrowEnter"));
+
+    System.out.println("Testing throwing exceptions in monitor_entered");
+    testLockThrowEntered(new Monitors.NamedLock("Lock testLockThrowEntered"));
+
+    System.out.println("Testing throwing exceptions in both monitorEnter & MonitorEntered");
+    testLockThrowBoth(new Monitors.NamedLock("Lock testLockThrowBoth"));
+
+    // This exposes a difference between the RI and ART. On the RI this test will cause a
+    // JVMTI_EVENT_MONITOR_WAITED event to be sent even though we threw an exception during the
+    // JVMTI_EVENT_MONITOR_WAIT. See b/65558434.
+    System.out.println("Testing throwing exception in MonitorWait event");
+    testThrowWait(new Monitors.NamedLock("Lock testThrowWait"));
+
+    System.out.println("Testing throwing exception in MonitorWait event with illegal aruments");
+    testThrowIllegalWait(new Monitors.NamedLock("Lock testThrowIllegalWait"));
+
+    System.out.println("Testing throwing exception in MonitorWaited event");
+    testThrowWaited(new Monitors.NamedLock("Lock testThrowWaited"));
+
+    System.out.println("Testing throwing exception in MonitorWaited event caused by timeout");
+    testThrowWaitedTimeout(new Monitors.NamedLock("Lock testThrowWaitedTimeout"));
+
+    System.out.println("Testing throwing exception in MonitorWaited event caused by interrupt");
+    testThrowWaitedInterrupt(new Monitors.NamedLock("Lock testThrowWaitedInterrupt"));
+
+    System.out.println("Testing ObjectMonitorInfo inside of events");
+    testMonitorInfoInEvents(new Monitors.NamedLock("Lock testMonitorInfoInEvents"));
+
+    System.out.println("Testing that the monitor can be stolen during the MonitorWaited event.");
+    testWaitEnterInterleaving(new Monitors.NamedLock("test testWaitEnterInterleaving"));
+
+    // TODO We keep this here since it works on android but it's not clear it's behavior we want to
+    // support long term or at all.
+    if (INCLUDE_ANDROID_ONLY_TESTS) {
+      System.out.println(
+          "Testing that the monitor can be still held by notifier during the MonitorWaited " +
+          "event. NB This doesn't work on the RI.");
+      testWaitExitInterleaving(new Monitors.NamedLock("test testWaitExitInterleaving"));
+    }
+
+    System.out.println(
+        "Testing that we can lock and release the monitor in the MonitorWait event");
+    testWaitMonitorEnter(new Monitors.NamedLock("test testWaitMonitorEnter"));
+
+    System.out.println(
+        "Testing that we can lock and release the monitor in the MonitorWaited event");
+    testWaitedMonitorEnter(new Monitors.NamedLock("test testWaitedMonitorEnter"));
+
+    System.out.println("Testing we can perform recursive lock in MonitorEntered");
+    testRecursiveMontiorEnteredLock(new Monitors.NamedLock("test testRecursiveMontiorEnteredLock"));
+
+    System.out.println("Testing the lock state if MonitorEnter throws in a native method");
+    testNativeLockStateThrowEnter(new Monitors.NamedLock("test testNativeLockStateThrowEnter"));
+
+    System.out.println("Testing the lock state if MonitorEntered throws in a native method");
+    testNativeLockStateThrowEntered(new Monitors.NamedLock("test testNativeLockStateThrowEntered"));
+  }
+
+  public static native void doNativeLockPrint(Monitors.NamedLock lk);
+  public static void printLockState(Monitors.NamedLock lk, Object exception, int res) {
+    System.out.println(
+        "MonitorEnter returned: " + res + "\n" +
+        "Lock state is: " + Monitors.getObjectMonitorUsage(lk));
+    printExceptions((Throwable)exception);
+  }
+
+  public static void testNativeLockStateThrowEnter(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEnter(Thread t, Object l) {
+        System.out.println("Unlocking controller1 in MonitorEnter");
+        try {
+          controller1.DoUnlock();
+        } catch (Exception e) {
+          throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e);
+        }
+        System.out.println("Throwing exception in MonitorEnter");
+        throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    Thread native_thd = new Thread(() -> {
+      try {
+        doNativeLockPrint(lk);
+      } catch (Throwable e) {
+        System.out.println("Unhandled exception: " + e);
+        e.printStackTrace();
+      }
+    }, "NativeLockStateThrowEnter thread");
+    native_thd.start();
+    native_thd.join();
+  }
+
+  public static void testNativeLockStateThrowEntered(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEnter(Thread t, Object l) {
+        System.out.println("Unlocking controller1 in MonitorEnter");
+        try {
+          controller1.DoUnlock();
+        } catch (Exception e) {
+          throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e);
+        }
+      }
+      @Override public void handleMonitorEntered(Thread t, Object l) {
+        System.out.println("Throwing exception in MonitorEntered");
+        throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    Thread native_thd = new Thread(() -> {
+      try {
+        doNativeLockPrint(lk);
+      } catch (Throwable e) {
+        System.out.println("Unhandled exception: " + e);
+        e.printStackTrace();
+      }
+    }, "NativeLockStateThrowEntered thread");
+    native_thd.start();
+    native_thd.join();
+  }
+
+  public static void testRecursiveMontiorEnteredLock(final Monitors.NamedLock lk) throws Exception {
+    Monitors.LockController controller1 = new Monitors.LockController(lk);
+    Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEntered(Thread thd, Object l) {
+        try {
+          System.out.println("In MonitorEntered usage: " + Monitors.getObjectMonitorUsage(lk));
+          synchronized (lk) {
+            System.out.println("In MonitorEntered sync: " + Monitors.getObjectMonitorUsage(lk));
+          }
+        } catch (Exception e) {
+          throw new Monitors.TestException("error while recursive locking!", e);
+        }
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller2.DoLock();
+    controller2.waitForContendedSleep();
+    controller1.DoUnlock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoUnlock();
+  }
+
+  public static void testWaitedMonitorEnter(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    final Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+        try {
+          // make sure that controller2 has acutally unlocked everything, we can be sent earlier
+          // than that on ART.
+          while (controller2.IsLocked()) {}
+          System.out.println("In waited monitor usage: " + Monitors.getObjectMonitorUsage(lk));
+          synchronized (lk) {
+            System.out.println(
+                "In waited monitor usage sync: " + Monitors.getObjectMonitorUsage(lk));
+          }
+        } catch (Exception e) {
+          throw new Monitors.TestException("error while doing unlock in other thread!", e);
+        }
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoWait();
+    controller1.waitForNotifySleep();
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoNotifyAll();
+    controller2.DoUnlock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoUnlock();
+  }
+
+  public static void testWaitMonitorEnter(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    final Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+        try {
+          System.out.println("In wait monitor usage: " + Monitors.getObjectMonitorUsage(lk));
+          synchronized (lk) {
+            System.out.println("In wait monitor usage sync: " + Monitors.getObjectMonitorUsage(lk));
+          }
+        } catch (Exception e) {
+          throw new Monitors.TestException("error while doing unlock in other thread!", e);
+        }
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoWait();
+    controller1.waitForNotifySleep();
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoNotifyAll();
+    controller2.DoUnlock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoUnlock();
+  }
+
+  // NB This test cannot be run on the RI. It deadlocks. Leaving for documentation.
+  public static void testWaitExitInterleaving(Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    final Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+        System.out.println("un-locking controller1 in controller2 MonitorWaited event");
+        try {
+          controller1.DoUnlock();
+        } catch (Exception e) {
+          throw new Monitors.TestException("error while doing unlock in other thread!", e);
+        }
+      }
+    };
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoWait();
+    controller2.waitForNotifySleep();
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoNotifyAll();
+    controller2.waitForLockToBeHeld();
+    controller2.DoUnlock();
+  }
+
+  public static void testWaitEnterInterleaving(Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    final Monitors.LockController controller2 = new Monitors.LockController(lk);
+    final Monitors.LockController controller3 = new Monitors.LockController(lk);
+    final Semaphore unlocked_sem = new Semaphore(0);
+    final Semaphore continue_sem = new Semaphore(0);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+        System.out.println("locking controller3 in controller2 MonitorWaited event");
+        try {
+          unlocked_sem.acquire();
+          controller3.DoLock();
+          controller3.waitForLockToBeHeld();
+          System.out.println(
+              "Controller3 now holds the lock the monitor wait will try to re-acquire");
+          continue_sem.release();
+        } catch (Exception e) {
+          throw new Monitors.TestException("error while doing unlock in other thread!", e);
+        }
+      }
+    };
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoWait();
+    controller2.waitForNotifySleep();
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoNotifyAll();
+    controller1.DoUnlock();
+    // Wait for controller3 to have locked.
+    // We cannot use waitForLockToBeHeld since we could race with the HANDLER waitForLockToBeHeld
+    // function.
+    unlocked_sem.release();
+    continue_sem.acquire();
+    controller3.DoUnlock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoUnlock();
+  }
+
+  public static void testMonitorInfoInEvents(Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    final Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEnter(Thread thd, Object l) {
+        System.out.println("Monitor usage in MonitorEnter: " + Monitors.getObjectMonitorUsage(l));
+      }
+      @Override public void handleMonitorEntered(Thread thd, Object l) {
+        System.out.println("Monitor usage in MonitorEntered: " + Monitors.getObjectMonitorUsage(l));
+      }
+      @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+        System.out.println("Monitor usage in MonitorWait: " + Monitors.getObjectMonitorUsage(l));
+      }
+      @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+        // make sure that controller1 has acutally unlocked everything, we can be sent earlier than
+        // that on ART.
+        while (controller1.IsLocked()) {}
+        System.out.println("Monitor usage in MonitorWaited: " + Monitors.getObjectMonitorUsage(l));
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller2.DoLock();
+    controller2.waitForContendedSleep();
+    controller1.DoUnlock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoWait();
+    controller2.waitForNotifySleep();
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoNotifyAll();
+    controller1.DoUnlock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoUnlock();
+  }
+
+  public static void testThrowWaitedInterrupt(Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+        System.out.println("Throwing exception in MonitorWaited");
+        throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    try {
+      controller1.DoWait();
+      controller1.waitForNotifySleep();
+      controller1.interruptWorker();
+      controller1.waitForLockToBeHeld();
+      controller1.DoUnlock();
+      System.out.println("No Exception thrown!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller1.DoCleanup();
+    }
+  }
+
+  public static void testThrowWaitedTimeout(Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk, 5 * 1000);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+        System.out.println("Throwing exception in MonitorWaited");
+        throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    try {
+      controller1.DoTimedWait();
+      controller1.waitForNotifySleep();
+      controller1.waitForLockToBeHeld();
+      controller1.DoUnlock();
+      System.out.println("No Exception thrown!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller1.DoCleanup();
+    }
+  }
+
+  public static void testThrowWaited(Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    final Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+        System.out.println("Throwing exception in MonitorWaited");
+        throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    controller1.DoWait();
+    controller1.waitForNotifySleep();
+
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    controller2.DoNotifyAll();
+    controller2.DoUnlock();
+    try {
+      controller1.waitForLockToBeHeld();
+      controller1.DoUnlock();
+      System.out.println("No Exception thrown!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller1.DoCleanup();
+    }
+  }
+
+  public static void testThrowWait(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+        System.out.println("Throwing exception in MonitorWait");
+        throw new Monitors.TestException("throwing exception during MonitorWait of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    try {
+      controller1.DoWait();
+      controller1.waitForNotifySleep();
+      controller1.waitForLockToBeHeld();
+      controller1.DoUnlock();
+      System.out.println("No Exception thrown!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller1.DoCleanup();
+    }
+  }
+
+  public static void testThrowIllegalWait(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk, -100000);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+        System.out.println("Throwing exception in MonitorWait timeout = " + timeout);
+        throw new Monitors.TestException("throwing exception during monitorWait of " + l);
+      }
+    };
+    try {
+      controller1.DoLock();
+      controller1.waitForLockToBeHeld();
+      controller1.DoTimedWait();
+      controller1.waitForLockToBeHeld();
+      controller1.DoUnlock();
+      System.out.println("No Exception thrown!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller1.DoCleanup();
+    }
+  }
+
+  public static void testLockUncontend(final Monitors.NamedLock lk) throws Exception {
+    final Monitors.LockController controller1 = new Monitors.LockController(lk);
+    final Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEnter(Thread thd, Object lock) {
+        if (controller1.IsLocked()) {
+          System.out.println("Releasing " + lk + " during monitorEnter event.");
+          try {
+            controller1.DoUnlock();
+          } catch (Exception e) {
+            throw new Error("Unable to unlock controller1", e);
+          }
+        } else {
+          throw new Error("controller1 does not seem to hold the lock!");
+        }
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    // This will call handleMonitorEnter but will release during the callback.
+    controller2.DoLock();
+    controller2.waitForLockToBeHeld();
+    if (controller1.IsLocked()) {
+      throw new Error("controller1 still holds the lock somehow!");
+    }
+    controller2.DoUnlock();
+  }
+
+  public static void testLockThrowEnter(Monitors.NamedLock lk) throws Exception {
+    Monitors.LockController controller1 = new Monitors.LockController(lk);
+    Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEnter(Thread t, Object l) {
+        System.out.println("Throwing exception in MonitorEnter");
+        throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    try {
+      controller2.DoLock();
+      controller2.waitForContendedSleep();
+      controller1.DoUnlock();
+      controller2.waitForLockToBeHeld();
+      controller2.DoUnlock();
+      System.out.println("Did not get an exception!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller2.DoCleanup();
+    }
+  }
+
+  public static void testLockThrowEntered(Monitors.NamedLock lk) throws Exception {
+    Monitors.LockController controller1 = new Monitors.LockController(lk);
+    Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEntered(Thread t, Object l) {
+        System.out.println("Throwing exception in MonitorEntered");
+        throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    try {
+      controller2.DoLock();
+      controller2.waitForContendedSleep();
+      controller1.DoUnlock();
+      controller2.waitForLockToBeHeld();
+      controller2.DoUnlock();
+      System.out.println("Did not get an exception!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller2.DoCleanup();
+    }
+  }
+
+  public static void testLockThrowBoth(Monitors.NamedLock lk) throws Exception {
+    Monitors.LockController controller1 = new Monitors.LockController(lk);
+    Monitors.LockController controller2 = new Monitors.LockController(lk);
+    HANDLER = new MonitorHandler() {
+      @Override public void handleMonitorEnter(Thread t, Object l) {
+        System.out.println("Throwing exception in MonitorEnter");
+        throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+      }
+      @Override public void handleMonitorEntered(Thread t, Object l) {
+        System.out.println("Throwing exception in MonitorEntered");
+        throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+      }
+    };
+    controller1.DoLock();
+    controller1.waitForLockToBeHeld();
+    try {
+      controller2.DoLock();
+      controller2.waitForContendedSleep();
+      controller1.DoUnlock();
+      controller2.waitForLockToBeHeld();
+      controller2.DoUnlock();
+      System.out.println("Did not get an exception!");
+    } catch (Monitors.TestException e) {
+      printExceptions(e);
+      System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+      controller2.DoCleanup();
+    }
+  }
+
+  public static void printExceptions(Throwable t) {
+    System.out.println("Caught exception: " + t);
+    for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
+      System.out.println("\tCaused by: " +
+          (Test1932.class.getPackage().equals(c.getClass().getPackage())
+           ? c.toString() : c.getClass().toString()));
+    }
+    if (PRINT_FULL_STACK_TRACE) {
+      t.printStackTrace();
+    }
+  }
+
+  public static void handleMonitorEnter(Thread thd, Object lock) {
+    System.out.println(thd.getName() + " contended-LOCKING " + lock);
+    if (HANDLER != null) {
+      HANDLER.handleMonitorEnter(thd, lock);
+    }
+  }
+
+  public static void handleMonitorEntered(Thread thd, Object lock) {
+    System.out.println(thd.getName() + " LOCKED " + lock);
+    if (HANDLER != null) {
+      HANDLER.handleMonitorEntered(thd, lock);
+    }
+  }
+  public static void handleMonitorWait(Thread thd, Object lock, long timeout) {
+    System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout);
+    if (HANDLER != null) {
+      HANDLER.handleMonitorWait(thd, lock, timeout);
+    }
+  }
+
+  public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {
+    System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out);
+    if (HANDLER != null) {
+      HANDLER.handleMonitorWaited(thd, lock, timed_out);
+    }
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 2f23056..d56c0b5 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -302,6 +302,7 @@
         "1926-missed-frame-pop/frame_pop_missed.cc",
         "1927-exception-event/exception_event.cc",
         "1930-monitor-info/monitor.cc",
+        "1932-monitor-events-misc/monitor_misc.cc"
     ],
     shared_libs: [
         "libbase",
diff --git a/test/run-test b/test/run-test
index 9996986..79f3d1e 100755
--- a/test/run-test
+++ b/test/run-test
@@ -810,6 +810,7 @@
 good="no"
 good_build="yes"
 good_run="yes"
+export TEST_RUNTIME="${runtime}"
 if [ "$dev_mode" = "yes" ]; then
     "./${build}" $build_args 2>&1
     build_exit="$?"
diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc
index 7c28ede..97d8427 100644
--- a/test/ti-agent/monitors_helper.cc
+++ b/test/ti-agent/monitors_helper.cc
@@ -13,13 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 #include "jni.h"
 #include "jvmti.h"
+
 #include <vector>
+
 #include "jvmti_helper.h"
 #include "jni_helper.h"
 #include "test_env.h"
 #include "scoped_local_ref.h"
+
 namespace art {
 namespace common_monitors {
 
@@ -58,5 +62,154 @@
                         obj, usage.owner, usage.entry_count, wait, notify_wait);
 }
 
+struct MonitorsData {
+  jclass test_klass;
+  jmethodID monitor_enter;
+  jmethodID monitor_entered;
+  jmethodID monitor_wait;
+  jmethodID monitor_waited;
+  jclass monitor_klass;
+};
+
+static void monitorEnterCB(jvmtiEnv* jvmti,
+                           JNIEnv* jnienv,
+                           jthread thr,
+                           jobject obj) {
+  MonitorsData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+    return;
+  }
+  jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_enter, thr, obj);
+}
+static void monitorEnteredCB(jvmtiEnv* jvmti,
+                             JNIEnv* jnienv,
+                             jthread thr,
+                             jobject obj) {
+  MonitorsData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+    return;
+  }
+  jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_entered, thr, obj);
+}
+static void monitorWaitCB(jvmtiEnv* jvmti,
+                          JNIEnv* jnienv,
+                          jthread thr,
+                          jobject obj,
+                          jlong timeout) {
+  MonitorsData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+    return;
+  }
+  jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_wait, thr, obj, timeout);
+}
+static void monitorWaitedCB(jvmtiEnv* jvmti,
+                            JNIEnv* jnienv,
+                            jthread thr,
+                            jobject obj,
+                            jboolean timed_out) {
+  MonitorsData* data = nullptr;
+  if (JvmtiErrorToException(jnienv, jvmti,
+                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+    return;
+  }
+  jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_waited, thr, obj, timed_out);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Monitors_setupMonitorEvents(
+    JNIEnv* env,
+    jclass,
+    jclass test_klass,
+    jobject monitor_enter,
+    jobject monitor_entered,
+    jobject monitor_wait,
+    jobject monitor_waited,
+    jclass monitor_klass,
+    jthread thr) {
+  MonitorsData* data = nullptr;
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->Allocate(sizeof(MonitorsData),
+                                                reinterpret_cast<unsigned char**>(&data)))) {
+    return;
+  }
+  jvmtiCapabilities caps;
+  memset(&caps, 0, sizeof(caps));
+  caps.can_generate_monitor_events = 1;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
+    return;
+  }
+
+  memset(data, 0, sizeof(MonitorsData));
+  data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(test_klass));
+  data->monitor_enter = env->FromReflectedMethod(monitor_enter);
+  data->monitor_entered = env->FromReflectedMethod(monitor_entered);
+  data->monitor_wait = env->FromReflectedMethod(monitor_wait);
+  data->monitor_waited = env->FromReflectedMethod(monitor_waited);
+  data->monitor_klass = reinterpret_cast<jclass>(env->NewGlobalRef(monitor_klass));
+  MonitorsData* old_data = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->GetEnvironmentLocalStorage(
+                                reinterpret_cast<void**>(&old_data)))) {
+    return;
+  } else if (old_data != nullptr && old_data->test_klass != nullptr) {
+    ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+    env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+    return;
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
+    return;
+  }
+
+  jvmtiEventCallbacks cb;
+  memset(&cb, 0, sizeof(cb));
+  cb.MonitorContendedEnter = monitorEnterCB;
+  cb.MonitorContendedEntered = monitorEnteredCB;
+  cb.MonitorWait = monitorWaitCB;
+  cb.MonitorWaited = monitorWaitedCB;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(
+                                JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(
+                                JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(
+                                JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAIT, thr))) {
+    return;
+  }
+  if (JvmtiErrorToException(env,
+                            jvmti_env,
+                            jvmti_env->SetEventNotificationMode(
+                                JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAITED, thr))) {
+    return;
+  }
+}
+
 }  // namespace common_monitors
 }  // namespace art
+