ART: Add runtime phase callback

Add callback that describes the current runtime phase, one of
starting, initialized and death. Add test.

Bug: 31684920
Test: m test-art-host-gtest-runtime_callbacks_test
Change-Id: I08ae7c45851572f8b3e446c07f0498f66bb032d3
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 743fcc8..fc82264 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -133,7 +133,9 @@
 
 static bool unstarted_initialized_ = false;
 
-CommonRuntimeTestImpl::CommonRuntimeTestImpl() {}
+CommonRuntimeTestImpl::CommonRuntimeTestImpl()
+    : class_linker_(nullptr), java_lang_dex_file_(nullptr) {
+}
 
 CommonRuntimeTestImpl::~CommonRuntimeTestImpl() {
   // Ensure the dex files are cleaned up before the runtime.
@@ -425,7 +427,9 @@
   TearDownAndroidData(android_data_, true);
   dalvik_cache_.clear();
 
-  Runtime::Current()->GetHeap()->VerifyHeap();  // Check for heap corruption after the test
+  if (runtime_ != nullptr) {
+    runtime_->GetHeap()->VerifyHeap();  // Check for heap corruption after the test
+  }
 }
 
 static std::string GetDexFileName(const std::string& jar_prefix, bool host) {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 0246649..8b355c8 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -304,6 +304,13 @@
 
   Trace::Shutdown();
 
+  // Report death. Clients me require a working thread, still, so do it before GC completes and
+  // all non-daemon threads are done.
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kDeath);
+  }
+
   if (attach_shutdown_thread) {
     DetachCurrentThread();
     self = nullptr;
@@ -706,6 +713,13 @@
 
   Thread::FinishStartup();
 
+  // Send the start phase event. We have to wait till here as this is when the main thread peer
+  // has just been generated, important root clinits have been run and JNI is completely functional.
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kStart);
+  }
+
   system_class_loader_ = CreateSystemClassLoader(this);
 
   if (!is_zygote_) {
@@ -742,6 +756,12 @@
                  0);
   }
 
+  // Send the initialized phase event.
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit);
+  }
+
   return true;
 }
 
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index cd38ead..7b15a4f 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -87,4 +87,18 @@
   }
 }
 
+void RuntimeCallbacks::AddRuntimePhaseCallback(RuntimePhaseCallback* cb) {
+  phase_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveRuntimePhaseCallback(RuntimePhaseCallback* cb) {
+  Remove(cb, &phase_callbacks_);
+}
+
+void RuntimeCallbacks::NextRuntimePhase(RuntimePhaseCallback::RuntimePhase phase) {
+  for (RuntimePhaseCallback* cb : phase_callbacks_) {
+    cb->NextRuntimePhase(phase);
+  }
+}
+
 }  // namespace art
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index d700cf2..6344c69 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -55,6 +55,19 @@
   virtual void SigQuit() REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 };
 
+class RuntimePhaseCallback {
+ public:
+  enum RuntimePhase {
+    kStart,  // The runtime is started.
+    kInit,   // The runtime is initialized (and will run user code soon).
+    kDeath,  // The runtime just died.
+  };
+
+  virtual ~RuntimePhaseCallback() {}
+
+  virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
 class RuntimeCallbacks {
  public:
   void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
@@ -77,6 +90,14 @@
 
   void SigQuit() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void AddRuntimePhaseCallback(RuntimePhaseCallback* cb)
+      REQUIRES(Locks::mutator_lock_);
+  void RemoveRuntimePhaseCallback(RuntimePhaseCallback* cb)
+      REQUIRES(Locks::mutator_lock_);
+
+  void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase phase)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   std::vector<ThreadLifecycleCallback*> thread_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
@@ -84,6 +105,8 @@
       GUARDED_BY(Locks::mutator_lock_);
   std::vector<RuntimeSigQuitCallback*> sigquit_callbacks_
       GUARDED_BY(Locks::mutator_lock_);
+  std::vector<RuntimePhaseCallback*> phase_callbacks_
+        GUARDED_BY(Locks::mutator_lock_);
 };
 
 }  // namespace art
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index c96bfd4..c379b5c 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -62,7 +62,6 @@
       ScopedObjectAccess soa(self);
       ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach);
       ScopedSuspendAll ssa("RuntimeCallbacksTest TearDown");
-      AddListener();
       RemoveListener();
     }
 
@@ -336,4 +335,67 @@
   EXPECT_EQ(1u, cb_.sigquit_count);
 }
 
+class RuntimePhaseCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+  void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&cb_);
+  }
+  void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&cb_);
+  }
+
+  void TearDown() OVERRIDE {
+    // Bypass RuntimeCallbacksTest::TearDown, as the runtime is already gone.
+    CommonRuntimeTest::TearDown();
+  }
+
+  struct Callback : public RuntimePhaseCallback {
+    void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase p) OVERRIDE {
+      if (p == RuntimePhaseCallback::RuntimePhase::kStart) {
+        if (init_seen > 0) {
+          LOG(FATAL) << "Init seen before start.";
+        }
+        ++start_seen;
+      } else if (p == RuntimePhaseCallback::RuntimePhase::kInit) {
+        ++init_seen;
+      } else if (p == RuntimePhaseCallback::RuntimePhase::kDeath) {
+        ++death_seen;
+      } else {
+        LOG(FATAL) << "Unknown phase " << static_cast<uint32_t>(p);
+      }
+    }
+
+    size_t start_seen = 0;
+    size_t init_seen = 0;
+    size_t death_seen = 0;
+  };
+
+  Callback cb_;
+};
+
+TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) {
+  ASSERT_EQ(0u, cb_.start_seen);
+  ASSERT_EQ(0u, cb_.init_seen);
+  ASSERT_EQ(0u, cb_.death_seen);
+
+  // Start the runtime.
+  {
+    Thread* self = Thread::Current();
+    self->TransitionFromSuspendedToRunnable();
+    bool started = runtime_->Start();
+    ASSERT_TRUE(started);
+  }
+
+  ASSERT_EQ(1u, cb_.start_seen);
+  ASSERT_EQ(1u, cb_.init_seen);
+  ASSERT_EQ(0u, cb_.death_seen);
+
+  // Delete the runtime.
+  runtime_.reset();
+
+  ASSERT_EQ(1u, cb_.start_seen);
+  ASSERT_EQ(1u, cb_.init_seen);
+  ASSERT_EQ(1u, cb_.death_seen);
+}
+
 }  // namespace art