Revert^2 "hidden_api: Call back into libcore on hidden api detection""

This reverts commit bbe60d58496991c16e2943e174e26ab8a096b3d0.

This CL deviates from the approach of the original change. Instead of
calling back every time ShouldBlock.. was called, we explicitly call
back in cases where it's safe to do so.

Note that we only call back on reflective accesses for now, and not
link time accesses. Coverage for the latter will be added in a follow up
change.

Bug: 73896556
Test: test-art-host
Test: art/test.py --host -t test-art-host-run-test-debug-prebuild-\
   interpreter-no-relocate-ntrace-gcstress-checkjni-picimage-pictest-\
   ndebuggable-no-jvmti-cdex-fast-674-hiddenapi64

(cherry picked from commit e453a8dd87731f4b37b86a1284f7655d86c2a809)

Merged-In: Ie99ac268a083af167accbdf955639da068bea950
Change-Id: I76860519d40b87032dbb8db38b04fcf79ef09723
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 5e69efe..3f33f79 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -7860,8 +7860,8 @@
   }
   DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr);
   if (resolved != nullptr &&
-      hiddenapi::ShouldBlockAccessToMember(
-          resolved, class_loader, dex_cache, hiddenapi::kLinking)) {
+      hiddenapi::GetMemberAction(
+          resolved, class_loader, dex_cache, hiddenapi::kLinking) == hiddenapi::kDeny) {
     resolved = nullptr;
   }
   if (resolved != nullptr) {
@@ -8003,8 +8003,8 @@
     resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, image_pointer_size_);
   }
   if (resolved != nullptr &&
-      hiddenapi::ShouldBlockAccessToMember(
-          resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking)) {
+      hiddenapi::GetMemberAction(
+          resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking) == hiddenapi::kDeny) {
     resolved = nullptr;
   }
   return resolved;
@@ -8083,8 +8083,8 @@
   }
 
   if (resolved == nullptr ||
-      hiddenapi::ShouldBlockAccessToMember(
-          resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking)) {
+      hiddenapi::GetMemberAction(
+          resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking) == hiddenapi::kDeny) {
     const char* name = dex_file.GetFieldName(field_id);
     const char* type = dex_file.GetFieldTypeDescriptor(field_id);
     ThrowNoSuchFieldError(is_static ? "static " : "instance ", klass, type, name);
@@ -8117,8 +8117,8 @@
   StringPiece type(dex_file.GetFieldTypeDescriptor(field_id));
   resolved = mirror::Class::FindField(self, klass, name, type);
   if (resolved != nullptr &&
-      hiddenapi::ShouldBlockAccessToMember(
-          resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking)) {
+      hiddenapi::GetMemberAction(
+          resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking) == hiddenapi::kDeny) {
     resolved = nullptr;
   }
   if (resolved != nullptr) {
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index f0b36a0..0e72f27 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -16,7 +16,11 @@
 
 #include "hidden_api.h"
 
+#include <nativehelper/scoped_local_ref.h>
+
 #include "base/dumpable.h"
+#include "thread-current-inl.h"
+#include "well_known_classes.h"
 
 namespace art {
 namespace hiddenapi {
@@ -111,7 +115,7 @@
 }
 
 template<typename T>
-bool ShouldBlockAccessToMemberImpl(T* member, Action action, AccessMethod access_method) {
+Action GetMemberActionImpl(T* member, Action action, AccessMethod access_method) {
   // Get the signature, we need it later.
   MemberSignature member_signature(member);
 
@@ -138,7 +142,7 @@
 
   if (action == kDeny) {
     // Block access
-    return true;
+    return action;
   }
 
   // Allow access to this member but print a warning.
@@ -156,17 +160,49 @@
     runtime->SetPendingHiddenApiWarning(true);
   }
 
-  return false;
+  return action;
 }
 
 // Need to instantiate this.
-template bool ShouldBlockAccessToMemberImpl<ArtField>(ArtField* member,
-                                                      Action action,
-                                                      AccessMethod access_method);
-template bool ShouldBlockAccessToMemberImpl<ArtMethod>(ArtMethod* member,
-                                                       Action action,
-                                                       AccessMethod access_method);
-
+template Action GetMemberActionImpl<ArtField>(ArtField* member,
+                                              Action action,
+                                              AccessMethod access_method);
+template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member,
+                                               Action action,
+                                               AccessMethod access_method);
 }  // namespace detail
+
+template<typename T>
+void NotifyHiddenApiListener(T* member) {
+  Runtime* runtime = Runtime::Current();
+  if (!runtime->IsAotCompiler()) {
+    ScopedObjectAccessUnchecked soa(Thread::Current());
+
+    ScopedLocalRef<jobject> consumer_object(soa.Env(),
+        soa.Env()->GetStaticObjectField(
+            WellKnownClasses::dalvik_system_VMRuntime,
+            WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer));
+    // If the consumer is non-null, we call back to it to let it know that we
+    // have encountered an API that's in one of our lists.
+    if (consumer_object != nullptr) {
+      detail::MemberSignature member_signature(member);
+      std::ostringstream member_signature_str;
+      member_signature.Dump(member_signature_str);
+
+      ScopedLocalRef<jobject> signature_str(
+          soa.Env(),
+          soa.Env()->NewStringUTF(member_signature_str.str().c_str()));
+
+      // Call through to Consumer.accept(String memberSignature);
+      soa.Env()->CallVoidMethod(consumer_object.get(),
+                                WellKnownClasses::java_util_function_Consumer_accept,
+                                signature_str.get());
+    }
+  }
+}
+
+template void NotifyHiddenApiListener<ArtMethod>(ArtMethod* member);
+template void NotifyHiddenApiListener<ArtField>(ArtField* member);
+
 }  // namespace hiddenapi
 }  // namespace art
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
index cc6c146..ffdeacb 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -58,7 +58,7 @@
   kLinking,
 };
 
-inline Action GetMemberAction(uint32_t access_flags) {
+inline Action GetActionFromAccessFlags(uint32_t access_flags) {
   EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
   if (policy == EnforcementPolicy::kNoChecks) {
     // Exit early. Nothing to enforce.
@@ -108,9 +108,7 @@
 };
 
 template<typename T>
-bool ShouldBlockAccessToMemberImpl(T* member,
-                                   Action action,
-                                   AccessMethod access_method)
+Action GetMemberActionImpl(T* member, Action action, AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
 // Returns true if the caller is either loaded by the boot strap class loader or comes from
@@ -138,28 +136,28 @@
 // return true if the caller is located in the platform.
 // This function might print warnings into the log if the member is hidden.
 template<typename T>
-inline bool ShouldBlockAccessToMember(T* member,
-                                      Thread* self,
-                                      std::function<bool(Thread*)> fn_caller_in_platform,
-                                      AccessMethod access_method)
+inline Action GetMemberAction(T* member,
+                              Thread* self,
+                              std::function<bool(Thread*)> fn_caller_in_platform,
+                              AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(member != nullptr);
 
-  Action action = GetMemberAction(member->GetAccessFlags());
+  Action action = GetActionFromAccessFlags(member->GetAccessFlags());
   if (action == kAllow) {
     // Nothing to do.
-    return false;
+    return action;
   }
 
   // Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access.
   // This can be *very* expensive. Save it for last.
   if (fn_caller_in_platform(self)) {
     // Caller in the platform. Exit.
-    return false;
+    return kAllow;
   }
 
   // Member is hidden and caller is not in the platform.
-  return detail::ShouldBlockAccessToMemberImpl(member, action, access_method);
+  return detail::GetMemberActionImpl(member, action, access_method);
 }
 
 inline bool IsCallerInPlatformDex(ObjPtr<mirror::Class> caller)
@@ -172,18 +170,26 @@
 // `caller_class_loader`.
 // This function might print warnings into the log if the member is hidden.
 template<typename T>
-inline bool ShouldBlockAccessToMember(T* member,
-                                      ObjPtr<mirror::ClassLoader> caller_class_loader,
-                                      ObjPtr<mirror::DexCache> caller_dex_cache,
-                                      AccessMethod access_method)
+inline Action GetMemberAction(T* member,
+                              ObjPtr<mirror::ClassLoader> caller_class_loader,
+                              ObjPtr<mirror::DexCache> caller_dex_cache,
+                              AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   bool caller_in_platform = detail::IsCallerInPlatformDex(caller_class_loader, caller_dex_cache);
-  return ShouldBlockAccessToMember(member,
-                                   /* thread */ nullptr,
-                                   [caller_in_platform] (Thread*) { return caller_in_platform; },
-                                   access_method);
+  return GetMemberAction(member,
+                         /* thread */ nullptr,
+                         [caller_in_platform] (Thread*) { return caller_in_platform; },
+                         access_method);
 }
 
+// Calls back into managed code to notify VMRuntime.nonSdkApiUsageConsumer that
+// |member| was accessed. This is usually called when an API is on the black,
+// dark grey or light grey lists. Given that the callback can execute arbitrary
+// code, a call to this method can result in thread suspension.
+template<typename T> void NotifyHiddenApiListener(T* member)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+
 }  // namespace hiddenapi
 }  // namespace art
 
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 4c7a97d..dd8d7dd 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -181,11 +181,13 @@
 template<typename T>
 static ALWAYS_INLINE bool ShouldBlockAccessToMember(T* member, ShadowFrame* frame)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return hiddenapi::ShouldBlockAccessToMember(
+  // All uses in this file are from reflection
+  constexpr hiddenapi::AccessMethod access_method = hiddenapi::kReflection;
+  return hiddenapi::GetMemberAction(
       member,
       frame->GetMethod()->GetDeclaringClass()->GetClassLoader(),
       frame->GetMethod()->GetDeclaringClass()->GetDexCache(),
-      hiddenapi::kReflection);  // all uses in this file are from reflection
+      access_method) == hiddenapi::kDeny;
 }
 
 void UnstartedRuntime::UnstartedClassForNameCommon(Thread* self,
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index f309581..9dbcded 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -87,8 +87,13 @@
 template<typename T>
 ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return hiddenapi::ShouldBlockAccessToMember(
+  hiddenapi::Action action = hiddenapi::GetMemberAction(
       member, self, IsCallerInPlatformDex, hiddenapi::kJNI);
+  if (action != hiddenapi::kAllow) {
+    hiddenapi::NotifyHiddenApiListener(member);
+  }
+
+  return action == hiddenapi::kDeny;
 }
 
 // Helpers to call instrumentation functions for fields. These take jobjects so we don't need to set
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index ad05856..a8b203b 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -98,8 +98,13 @@
 template<typename T>
 ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return hiddenapi::ShouldBlockAccessToMember(
+  hiddenapi::Action action = hiddenapi::GetMemberAction(
       member, self, IsCallerInPlatformDex, hiddenapi::kReflection);
+  if (action != hiddenapi::kAllow) {
+    hiddenapi::NotifyHiddenApiListener(member);
+  }
+
+  return action == hiddenapi::kDeny;
 }
 
 // Returns true if a class member should be discoverable with reflection given
@@ -113,7 +118,8 @@
     return false;
   }
 
-  if (enforce_hidden_api && hiddenapi::GetMemberAction(access_flags) == hiddenapi::kDeny) {
+  if (enforce_hidden_api &&
+      hiddenapi::GetActionFromAccessFlags(access_flags) == hiddenapi::kDeny) {
     return false;
   }
 
@@ -433,12 +439,14 @@
     return nullptr;
   }
 
-  mirror::Field* field = GetPublicFieldRecursive(
-      soa.Self(), DecodeClass(soa, javaThis), name_string);
-  if (field == nullptr || ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) {
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Field> field = hs.NewHandle(GetPublicFieldRecursive(
+      soa.Self(), DecodeClass(soa, javaThis), name_string));
+  if (field.Get() == nullptr ||
+      ShouldBlockAccessToMember(field->GetArtField(), soa.Self())) {
     return nullptr;
   }
-  return soa.AddLocalReference<jobject>(field);
+  return soa.AddLocalReference<jobject>(field.Get());
 }
 
 static jobject Class_getDeclaredField(JNIEnv* env, jobject javaThis, jstring name) {
@@ -477,15 +485,17 @@
   ScopedFastNativeObjectAccess soa(env);
   DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
   DCHECK(!Runtime::Current()->IsActiveTransaction());
-  ObjPtr<mirror::Constructor> result =
+
+  StackHandleScope<1> hs(soa.Self());
+  Handle<mirror::Constructor> result = hs.NewHandle(
       mirror::Class::GetDeclaredConstructorInternal<kRuntimePointerSize, false>(
       soa.Self(),
       DecodeClass(soa, javaThis),
-      soa.Decode<mirror::ObjectArray<mirror::Class>>(args));
+      soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
   if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
     return nullptr;
   }
-  return soa.AddLocalReference<jobject>(result);
+  return soa.AddLocalReference<jobject>(result.Get());
 }
 
 static ALWAYS_INLINE inline bool MethodMatchesConstructor(
@@ -535,18 +545,19 @@
 static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis,
                                                jstring name, jobjectArray args) {
   ScopedFastNativeObjectAccess soa(env);
+  StackHandleScope<1> hs(soa.Self());
   DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
   DCHECK(!Runtime::Current()->IsActiveTransaction());
-  ObjPtr<mirror::Method> result =
+  Handle<mirror::Method> result = hs.NewHandle(
       mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>(
           soa.Self(),
           DecodeClass(soa, javaThis),
           soa.Decode<mirror::String>(name),
-          soa.Decode<mirror::ObjectArray<mirror::Class>>(args));
+          soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
   if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
     return nullptr;
   }
-  return soa.AddLocalReference<jobject>(result);
+  return soa.AddLocalReference<jobject>(result.Get());
 }
 
 static jobjectArray Class_getDeclaredMethodsUnchecked(JNIEnv* env, jobject javaThis,
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index bf36ccf..742e713 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -77,6 +77,7 @@
 jclass WellKnownClasses::java_nio_DirectByteBuffer;
 jclass WellKnownClasses::java_util_ArrayList;
 jclass WellKnownClasses::java_util_Collections;
+jclass WellKnownClasses::java_util_function_Consumer;
 jclass WellKnownClasses::libcore_reflect_AnnotationFactory;
 jclass WellKnownClasses::libcore_reflect_AnnotationMember;
 jclass WellKnownClasses::libcore_util_EmptyArray;
@@ -115,6 +116,7 @@
 jmethodID WellKnownClasses::java_lang_ThreadGroup_add;
 jmethodID WellKnownClasses::java_lang_ThreadGroup_removeThread;
 jmethodID WellKnownClasses::java_nio_DirectByteBuffer_init;
+jmethodID WellKnownClasses::java_util_function_Consumer_accept;
 jmethodID WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation;
 jmethodID WellKnownClasses::libcore_reflect_AnnotationMember_init;
 jmethodID WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
@@ -125,6 +127,7 @@
 jfieldID WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList;
 jfieldID WellKnownClasses::dalvik_system_DexPathList_dexElements;
 jfieldID WellKnownClasses::dalvik_system_DexPathList__Element_dexFile;
+jfieldID WellKnownClasses::dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
 jfieldID WellKnownClasses::java_lang_Thread_daemon;
 jfieldID WellKnownClasses::java_lang_Thread_group;
 jfieldID WellKnownClasses::java_lang_Thread_lock;
@@ -349,6 +352,7 @@
   java_nio_DirectByteBuffer = CacheClass(env, "java/nio/DirectByteBuffer");
   java_util_ArrayList = CacheClass(env, "java/util/ArrayList");
   java_util_Collections = CacheClass(env, "java/util/Collections");
+  java_util_function_Consumer = CacheClass(env, "java/util/function/Consumer");
   libcore_reflect_AnnotationFactory = CacheClass(env, "libcore/reflect/AnnotationFactory");
   libcore_reflect_AnnotationMember = CacheClass(env, "libcore/reflect/AnnotationMember");
   libcore_util_EmptyArray = CacheClass(env, "libcore/util/EmptyArray");
@@ -379,6 +383,7 @@
   java_lang_ThreadGroup_add = CacheMethod(env, java_lang_ThreadGroup, false, "add", "(Ljava/lang/Thread;)V");
   java_lang_ThreadGroup_removeThread = CacheMethod(env, java_lang_ThreadGroup, false, "threadTerminated", "(Ljava/lang/Thread;)V");
   java_nio_DirectByteBuffer_init = CacheMethod(env, java_nio_DirectByteBuffer, false, "<init>", "(JI)V");
+  java_util_function_Consumer_accept = CacheMethod(env, java_util_function_Consumer, false, "accept", "(Ljava/lang/Object;)V");
   libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod(env, libcore_reflect_AnnotationFactory, true, "createAnnotation", "(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;");
   libcore_reflect_AnnotationMember_init = CacheMethod(env, libcore_reflect_AnnotationMember, false, "<init>", "(Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/reflect/Method;)V");
   org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = CacheMethod(env, org_apache_harmony_dalvik_ddmc_DdmServer, true, "broadcast", "(I)V");
@@ -389,6 +394,7 @@
   dalvik_system_DexFile_fileName = CacheField(env, dalvik_system_DexFile, false, "mFileName", "Ljava/lang/String;");
   dalvik_system_DexPathList_dexElements = CacheField(env, dalvik_system_DexPathList, false, "dexElements", "[Ldalvik/system/DexPathList$Element;");
   dalvik_system_DexPathList__Element_dexFile = CacheField(env, dalvik_system_DexPathList__Element, false, "dexFile", "Ldalvik/system/DexFile;");
+  dalvik_system_VMRuntime_nonSdkApiUsageConsumer = CacheField(env, dalvik_system_VMRuntime, true, "nonSdkApiUsageConsumer", "Ljava/util/function/Consumer;");
   java_lang_Thread_daemon = CacheField(env, java_lang_Thread, false, "daemon", "Z");
   java_lang_Thread_group = CacheField(env, java_lang_Thread, false, "group", "Ljava/lang/ThreadGroup;");
   java_lang_Thread_lock = CacheField(env, java_lang_Thread, false, "lock", "Ljava/lang/Object;");
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index d5d7033..7b1a294 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -85,6 +85,7 @@
   static jclass java_lang_Throwable;
   static jclass java_util_ArrayList;
   static jclass java_util_Collections;
+  static jclass java_util_function_Consumer;
   static jclass java_nio_ByteBuffer;
   static jclass java_nio_DirectByteBuffer;
   static jclass libcore_reflect_AnnotationFactory;
@@ -125,6 +126,7 @@
   static jmethodID java_lang_ThreadGroup_add;
   static jmethodID java_lang_ThreadGroup_removeThread;
   static jmethodID java_nio_DirectByteBuffer_init;
+  static jmethodID java_util_function_Consumer_accept;
   static jmethodID libcore_reflect_AnnotationFactory_createAnnotation;
   static jmethodID libcore_reflect_AnnotationMember_init;
   static jmethodID org_apache_harmony_dalvik_ddmc_DdmServer_broadcast;
@@ -135,6 +137,7 @@
   static jfieldID dalvik_system_DexFile_fileName;
   static jfieldID dalvik_system_DexPathList_dexElements;
   static jfieldID dalvik_system_DexPathList__Element_dexFile;
+  static jfieldID dalvik_system_VMRuntime_nonSdkApiUsageConsumer;
   static jfieldID java_lang_reflect_Executable_artMethod;
   static jfieldID java_lang_reflect_Proxy_h;
   static jfieldID java_lang_Thread_daemon;
diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build
index 9012e8f..330a6de 100644
--- a/test/674-hiddenapi/build
+++ b/test/674-hiddenapi/build
@@ -16,15 +16,6 @@
 
 set -e
 
-# Special build logic to handle src-ex .java files which have code that only builds on RI.
-custom_build_logic() {
-  [[ -d ignore.src-ex ]] && mv ignore.src-ex src-ex
-  # src-ex uses code that can only build on RI.
-  ${JAVAC} -source 1.8 -target 1.8 -sourcepath src-ex -sourcepath src -d classes-ex $(find src-ex -name '*.java')
-  # remove src-ex so that default-build doesn't try to build it.
-  [[ -d src-ex ]] && mv src-ex ignore.src-ex
-}
-
 # Build the jars twice. First with applying hiddenapi, creating a boot jar, then
 # a second time without to create a normal jar. We need to do this because we
 # want to load the jar once as an app module and once as a member of the boot
@@ -33,7 +24,6 @@
 # class path dex files, so the boot jar loads fine in the latter case.
 
 export USE_HIDDENAPI=true
-custom_build_logic
 ./default-build "$@"
 
 # Move the jar file into the resource folder to be bundled with the test.
@@ -45,5 +35,4 @@
 rm -rf classes*
 
 export USE_HIDDENAPI=false
-custom_build_logic
 ./default-build "$@"
diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java
index 582e907..822224c 100644
--- a/test/674-hiddenapi/src-ex/ChildClass.java
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import dalvik.system.VMRuntime;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -21,11 +22,7 @@
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.List;
-
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.type.PrimitiveType;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeVisitor;
+import java.util.function.Consumer;
 
 public class ChildClass {
   enum PrimitiveType {
@@ -136,6 +133,47 @@
     }
   }
 
+  static final class RecordingConsumer implements Consumer<String> {
+      public String recordedValue = null;
+
+      @Override
+      public void accept(String value) {
+          recordedValue = value;
+      }
+  }
+
+  private static void checkMemberCallback(Class<?> klass, String name,
+          boolean isPublic, boolean isField) {
+      try {
+          RecordingConsumer consumer = new RecordingConsumer();
+          VMRuntime.setNonSdkApiUsageConsumer(consumer);
+          try {
+              if (isPublic) {
+                  if (isField) {
+                      klass.getField(name);
+                  } else {
+                      klass.getMethod(name);
+                  }
+              } else {
+                  if (isField) {
+                      klass.getDeclaredField(name);
+                  } else {
+                      klass.getDeclaredMethod(name);
+                  }
+              }
+          } catch (NoSuchFieldException|NoSuchMethodException ignored) {
+              // We're not concerned whether an exception is thrown or not - we're
+              // only interested in whether the callback is invoked.
+          }
+
+          if (consumer.recordedValue == null || !consumer.recordedValue.contains(name)) {
+              throw new RuntimeException("No callback for member: " + name);
+          }
+      } finally {
+          VMRuntime.setNonSdkApiUsageConsumer(null);
+      }
+  }
+
   private static void checkField(Class<?> klass, String name, boolean isStatic,
       Visibility visibility, Behaviour behaviour) throws Exception {
 
@@ -174,48 +212,52 @@
 
     // Finish here if we could not discover the field.
 
-    if (!canDiscover) {
-      return;
+    if (canDiscover) {
+      // Test that modifiers are unaffected.
+
+      if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) {
+        throwModifiersException(klass, name, true);
+      }
+
+      // Test getters and setters when meaningful.
+
+      clearWarning();
+      if (!Reflection.canGetField(klass, name)) {
+        throwAccessException(klass, name, true, "Field.getInt()");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "Field.getInt()", setsWarning);
+      }
+
+      clearWarning();
+      if (!Reflection.canSetField(klass, name)) {
+        throwAccessException(klass, name, true, "Field.setInt()");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "Field.setInt()", setsWarning);
+      }
+
+      clearWarning();
+      if (!JNI.canGetField(klass, name, isStatic)) {
+        throwAccessException(klass, name, true, "getIntField");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "getIntField", setsWarning);
+      }
+
+      clearWarning();
+      if (!JNI.canSetField(klass, name, isStatic)) {
+        throwAccessException(klass, name, true, "setIntField");
+      }
+      if (hasPendingWarning() != setsWarning) {
+        throwWarningException(klass, name, true, "setIntField", setsWarning);
+      }
     }
 
-    // Test that modifiers are unaffected.
-
-    if (Reflection.canObserveFieldHiddenAccessFlags(klass, name)) {
-      throwModifiersException(klass, name, true);
-    }
-
-    // Test getters and setters when meaningful.
-
+    // Test that callbacks are invoked correctly.
     clearWarning();
-    if (!Reflection.canGetField(klass, name)) {
-      throwAccessException(klass, name, true, "Field.getInt()");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "Field.getInt()", setsWarning);
-    }
-
-    clearWarning();
-    if (!Reflection.canSetField(klass, name)) {
-      throwAccessException(klass, name, true, "Field.setInt()");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "Field.setInt()", setsWarning);
-    }
-
-    clearWarning();
-    if (!JNI.canGetField(klass, name, isStatic)) {
-      throwAccessException(klass, name, true, "getIntField");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "getIntField", setsWarning);
-    }
-
-    clearWarning();
-    if (!JNI.canSetField(klass, name, isStatic)) {
-      throwAccessException(klass, name, true, "setIntField");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, name, true, "setIntField", setsWarning);
+    if (setsWarning || !canDiscover) {
+      checkMemberCallback(klass, name, isPublic, true /* isField */);
     }
   }
 
@@ -257,42 +299,46 @@
 
     // Finish here if we could not discover the field.
 
-    if (!canDiscover) {
-      return;
+    if (canDiscover) {
+      // Test that modifiers are unaffected.
+
+      if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) {
+        throwModifiersException(klass, name, false);
+      }
+
+      // Test whether we can invoke the method. This skips non-static interface methods.
+
+      if (!klass.isInterface() || isStatic) {
+        clearWarning();
+        if (!Reflection.canInvokeMethod(klass, name)) {
+          throwAccessException(klass, name, false, "invoke()");
+        }
+        if (hasPendingWarning() != setsWarning) {
+          throwWarningException(klass, name, false, "invoke()", setsWarning);
+        }
+
+        clearWarning();
+        if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
+          throwAccessException(klass, name, false, "CallMethodA");
+        }
+        if (hasPendingWarning() != setsWarning) {
+          throwWarningException(klass, name, false, "CallMethodA()", setsWarning);
+        }
+
+        clearWarning();
+        if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
+          throwAccessException(klass, name, false, "CallMethodV");
+        }
+        if (hasPendingWarning() != setsWarning) {
+          throwWarningException(klass, name, false, "CallMethodV()", setsWarning);
+        }
+      }
     }
 
-    // Test that modifiers are unaffected.
-
-    if (Reflection.canObserveMethodHiddenAccessFlags(klass, name)) {
-      throwModifiersException(klass, name, false);
-    }
-
-    // Test whether we can invoke the method. This skips non-static interface methods.
-
-    if (!klass.isInterface() || isStatic) {
-      clearWarning();
-      if (!Reflection.canInvokeMethod(klass, name)) {
-        throwAccessException(klass, name, false, "invoke()");
-      }
-      if (hasPendingWarning() != setsWarning) {
-        throwWarningException(klass, name, false, "invoke()", setsWarning);
-      }
-
-      clearWarning();
-      if (!JNI.canInvokeMethodA(klass, name, isStatic)) {
-        throwAccessException(klass, name, false, "CallMethodA");
-      }
-      if (hasPendingWarning() != setsWarning) {
-        throwWarningException(klass, name, false, "CallMethodA()", setsWarning);
-      }
-
-      clearWarning();
-      if (!JNI.canInvokeMethodV(klass, name, isStatic)) {
-        throwAccessException(klass, name, false, "CallMethodV");
-      }
-      if (hasPendingWarning() != setsWarning) {
-        throwWarningException(klass, name, false, "CallMethodV()", setsWarning);
-      }
+    // Test that callbacks are invoked correctly.
+    clearWarning();
+    if (setsWarning || !canDiscover) {
+        checkMemberCallback(klass, name, isPublic, false /* isField */);
     }
   }