Simplify hidden_api.h logic

Refactor GetMemberAction to return a boolean whether access to a class
member should be denied. This also moves StrictMode consumer
notification into hidden_api.cc and removes notifications for toasts.
Tests are changed accordingly.

Test: phone boots
Test: m test-art
Merged-In: I02902143de0ff91d402ba79c83f28226b1822a6f
Change-Id: I02902143de0ff91d402ba79c83f28226b1822a6f
(cherry picked from commit 51995f90adaa0e5047dee56d22f15e4225e70517)
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index cc4f56c..5d40266 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -5070,8 +5070,10 @@
       ArtField* resolved_field = dex_cache->GetResolvedField(field_idx, image_pointer_size_);
       if (resolved_field == nullptr) {
         // Populating cache of a dex file which defines `klass` should always be allowed.
-        DCHECK_EQ(hiddenapi::GetMemberAction(
-            field, class_loader.Get(), dex_cache.Get(), hiddenapi::kNone), hiddenapi::kAllow);
+        DCHECK(!hiddenapi::ShouldDenyAccessToMember(
+            field,
+            hiddenapi::AccessContext(class_loader.Get(), dex_cache.Get()),
+            hiddenapi::AccessMethod::kNone));
         dex_cache->SetResolvedField(field_idx, field, image_pointer_size_);
       } else {
         DCHECK_EQ(field, resolved_field);
@@ -8102,8 +8104,9 @@
   }
   DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr);
   if (resolved != nullptr &&
-      hiddenapi::GetMemberAction(
-          resolved, class_loader, dex_cache, hiddenapi::kLinking) == hiddenapi::kDeny) {
+      hiddenapi::ShouldDenyAccessToMember(resolved,
+                                          hiddenapi::AccessContext(class_loader, dex_cache),
+                                          hiddenapi::AccessMethod::kLinking)) {
     resolved = nullptr;
   }
   if (resolved != nullptr) {
@@ -8133,11 +8136,9 @@
                               ObjPtr<mirror::ClassLoader> class_loader)
       REQUIRES_SHARED(Locks::mutator_lock_) {
   return method == nullptr ||
-         hiddenapi::GetMemberAction(method,
-                                    class_loader,
-                                    dex_cache,
-                                    hiddenapi::kNone)  // do not print warnings
-             == hiddenapi::kDeny;
+         hiddenapi::ShouldDenyAccessToMember(method,
+                                             hiddenapi::AccessContext(class_loader, dex_cache),
+                                             hiddenapi::AccessMethod::kNone);  // no warnings
 }
 
 ArtMethod* ClassLinker::FindIncompatibleMethod(ObjPtr<mirror::Class> klass,
@@ -8273,8 +8274,10 @@
     resolved = klass->FindClassMethod(dex_cache.Get(), method_idx, image_pointer_size_);
   }
   if (resolved != nullptr &&
-      hiddenapi::GetMemberAction(
-          resolved, class_loader.Get(), dex_cache.Get(), hiddenapi::kLinking) == hiddenapi::kDeny) {
+      hiddenapi::ShouldDenyAccessToMember(
+          resolved,
+          hiddenapi::AccessContext(class_loader.Get(), dex_cache.Get()),
+          hiddenapi::AccessMethod::kLinking)) {
     resolved = nullptr;
   }
   return resolved;
@@ -8373,8 +8376,9 @@
   }
 
   if (resolved != nullptr &&
-      hiddenapi::GetMemberAction(
-          resolved, class_loader, dex_cache, hiddenapi::kLinking) == hiddenapi::kDeny) {
+      hiddenapi::ShouldDenyAccessToMember(resolved,
+                                          hiddenapi::AccessContext(class_loader, dex_cache),
+                                          hiddenapi::AccessMethod::kLinking)) {
     resolved = nullptr;
   }
 
@@ -8399,8 +8403,9 @@
   resolved = mirror::Class::FindField(self, klass, name, type);
 
   if (resolved != nullptr &&
-      hiddenapi::GetMemberAction(
-          resolved, class_loader, dex_cache, hiddenapi::kLinking) == hiddenapi::kDeny) {
+      hiddenapi::ShouldDenyAccessToMember(resolved,
+                                          hiddenapi::AccessContext(class_loader, dex_cache),
+                                          hiddenapi::AccessMethod::kLinking)) {
     resolved = nullptr;
   }
 
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index ed3a18d..b11e368 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -66,7 +66,7 @@
   }
   runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv);
 
-  if (runtime->GetHiddenApiEnforcementPolicy() != hiddenapi::EnforcementPolicy::kNoChecks) {
+  if (runtime->GetHiddenApiEnforcementPolicy() != hiddenapi::EnforcementPolicy::kDisabled) {
     argv.push_back("--runtime-arg");
     argv.push_back("-Xhidden-api-checks");
   }
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index f355276..3b7b938 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -44,38 +44,53 @@
 
 static inline std::ostream& operator<<(std::ostream& os, AccessMethod value) {
   switch (value) {
-    case kNone:
+    case AccessMethod::kNone:
       LOG(FATAL) << "Internal access to hidden API should not be logged";
       UNREACHABLE();
-    case kReflection:
+    case AccessMethod::kReflection:
       os << "reflection";
       break;
-    case kJNI:
+    case AccessMethod::kJNI:
       os << "JNI";
       break;
-    case kLinking:
+    case AccessMethod::kLinking:
       os << "linking";
       break;
   }
   return os;
 }
 
-static constexpr bool EnumsEqual(EnforcementPolicy policy, hiddenapi::ApiList apiList) {
-  return static_cast<int>(policy) == static_cast<int>(apiList);
-}
-
-// GetMemberAction-related static_asserts.
-static_assert(
-    EnumsEqual(EnforcementPolicy::kDarkGreyAndBlackList, hiddenapi::ApiList::kDarkGreylist) &&
-    EnumsEqual(EnforcementPolicy::kBlacklistOnly, hiddenapi::ApiList::kBlacklist),
-    "Mismatch between EnforcementPolicy and ApiList enums");
-static_assert(
-    EnforcementPolicy::kJustWarn < EnforcementPolicy::kDarkGreyAndBlackList &&
-    EnforcementPolicy::kDarkGreyAndBlackList < EnforcementPolicy::kBlacklistOnly,
-    "EnforcementPolicy values ordering not correct");
-
 namespace detail {
 
+// Do not change the values of items in this enum, as they are written to the
+// event log for offline analysis. Any changes will interfere with that analysis.
+enum AccessContextFlags {
+  // Accessed member is a field if this bit is set, else a method
+  kMemberIsField = 1 << 0,
+  // Indicates if access was denied to the member, instead of just printing a warning.
+  kAccessDenied  = 1 << 1,
+};
+
+static int32_t GetMaxAllowedSdkVersionForApiList(ApiList api_list) {
+  SdkCodes sdk = SdkCodes::kVersionNone;
+  switch (api_list) {
+    case ApiList::kWhitelist:
+    case ApiList::kLightGreylist:
+      sdk = SdkCodes::kVersionUnlimited;
+      break;
+    case ApiList::kDarkGreylist:
+      sdk = SdkCodes::kVersionO_MR1;
+      break;
+    case ApiList::kBlacklist:
+      sdk = SdkCodes::kVersionNone;
+      break;
+    case ApiList::kNoList:
+      LOG(FATAL) << "Unexpected value";
+      UNREACHABLE();
+  }
+  return static_cast<int32_t>(sdk);
+}
+
 MemberSignature::MemberSignature(ArtField* field) {
   class_name_ = field->GetDeclaringClass()->GetDescriptor(&tmp_);
   member_name_ = field->GetName();
@@ -137,6 +152,7 @@
   LOG(WARNING) << "Accessing hidden " << (type_ == kField ? "field " : "method ")
                << Dumpable<MemberSignature>(*this) << " (" << list << ", " << access_method << ")";
 }
+
 #ifdef ART_TARGET_ANDROID
 // Convert an AccessMethod enum to a value for logging from the proto enum.
 // This method may look odd (the enum values are current the same), but it
@@ -145,13 +161,13 @@
 // future.
 inline static int32_t GetEnumValueForLog(AccessMethod access_method) {
   switch (access_method) {
-    case kNone:
+    case AccessMethod::kNone:
       return android::metricslogger::ACCESS_METHOD_NONE;
-    case kReflection:
+    case AccessMethod::kReflection:
       return android::metricslogger::ACCESS_METHOD_REFLECTION;
-    case kJNI:
+    case AccessMethod::kJNI:
       return android::metricslogger::ACCESS_METHOD_JNI;
-    case kLinking:
+    case AccessMethod::kLinking:
       return android::metricslogger::ACCESS_METHOD_LINKING;
     default:
       DCHECK(false);
@@ -159,9 +175,9 @@
 }
 #endif
 
-void MemberSignature::LogAccessToEventLog(AccessMethod access_method, Action action_taken) {
+void MemberSignature::LogAccessToEventLog(AccessMethod access_method, bool access_denied) {
 #ifdef ART_TARGET_ANDROID
-  if (access_method == kLinking || access_method == kNone) {
+  if (access_method == AccessMethod::kLinking || access_method == AccessMethod::kNone) {
     // Linking warnings come from static analysis/compilation of the bytecode
     // and can contain false positives (i.e. code that is never run). We choose
     // not to log these in the event log.
@@ -170,7 +186,7 @@
   }
   ComplexEventLogger log_maker(ACTION_HIDDEN_API_ACCESSED);
   log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_METHOD, GetEnumValueForLog(access_method));
-  if (action_taken == kDeny) {
+  if (access_denied) {
     log_maker.AddTaggedData(FIELD_HIDDEN_API_ACCESS_DENIED, 1);
   }
   const std::string& package_name = Runtime::Current()->GetProcessPackageName();
@@ -183,10 +199,42 @@
   log_maker.Record();
 #else
   UNUSED(access_method);
-  UNUSED(action_taken);
+  UNUSED(access_denied);
 #endif
 }
 
+void MemberSignature::NotifyHiddenApiListener(AccessMethod access_method) {
+  if (access_method != AccessMethod::kReflection && access_method != AccessMethod::kJNI) {
+    // We can only up-call into Java during reflection and JNI down-calls.
+    return;
+  }
+
+  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) {
+      std::ostringstream member_signature_str;
+      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());
+    }
+  }
+}
+
 static ALWAYS_INLINE bool CanUpdateMemberAccessFlags(ArtField*) {
   return true;
 }
@@ -205,116 +253,68 @@
 }
 
 template<typename T>
-Action GetMemberActionImpl(T* member,
-                           hiddenapi::ApiList api_list,
-                           Action action,
-                           AccessMethod access_method) {
-  DCHECK_NE(action, kAllow);
-
-  // Get the signature, we need it later.
-  MemberSignature member_signature(member);
+bool ShouldDenyAccessToMemberImpl(T* member,
+                                  hiddenapi::ApiList api_list,
+                                  AccessMethod access_method) {
+  DCHECK(member != nullptr);
 
   Runtime* runtime = Runtime::Current();
+  EnforcementPolicy policy = runtime->GetHiddenApiEnforcementPolicy();
+
+  const bool deny_access =
+      (policy == EnforcementPolicy::kEnabled) &&
+      (runtime->GetTargetSdkVersion() > GetMaxAllowedSdkVersionForApiList(api_list));
+
+  MemberSignature member_signature(member);
 
   // Check for an exemption first. Exempted APIs are treated as white list.
-  // We only do this if we're about to deny, or if the app is debuggable. This is because:
-  // - we only print a warning for light greylist violations for debuggable apps
-  // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs.
-  // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever
-  //   possible.
-  const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
-  if (shouldWarn || action == kDeny) {
-    if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
-      action = kAllow;
-      // Avoid re-examining the exemption list next time.
-      // Note this results in no warning for the member, which seems like what one would expect.
-      // Exemptions effectively adds new members to the whitelist.
-      MaybeWhitelistMember(runtime, member);
-      return kAllow;
-    }
+  if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
+    // Avoid re-examining the exemption list next time.
+    // Note this results in no warning for the member, which seems like what one would expect.
+    // Exemptions effectively adds new members to the whitelist.
+    MaybeWhitelistMember(runtime, member);
+    return false;
+  }
 
-    if (access_method != kNone) {
-      // Print a log message with information about this class member access.
-      // We do this if we're about to block access, or the app is debuggable.
+  if (access_method != AccessMethod::kNone) {
+    // Print a log message with information about this class member access.
+    // We do this if we're about to deny access, or the app is debuggable.
+    if (kLogAllAccesses || deny_access || runtime->IsJavaDebuggable()) {
       member_signature.WarnAboutAccess(access_method, api_list);
     }
-  }
 
-  if (kIsTargetBuild && !kIsTargetLinux) {
-    uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
-    // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
-    static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
-    if (eventLogSampleRate != 0 &&
-        (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) {
-      member_signature.LogAccessToEventLog(access_method, action);
+    // If there is a StrictMode listener, notify it about this violation.
+    member_signature.NotifyHiddenApiListener(access_method);
+
+    // If event log sampling is enabled, report this violation.
+    if (kIsTargetBuild && !kIsTargetLinux) {
+      uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
+      // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
+      static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
+      if (eventLogSampleRate != 0 &&
+          (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) {
+        member_signature.LogAccessToEventLog(access_method, deny_access);
+      }
+    }
+
+    // If this access was not denied, move the member into whitelist and skip
+    // the warning the next time the member is accessed.
+    if (!deny_access) {
+      MaybeWhitelistMember(runtime, member);
     }
   }
 
-  if (action == kDeny) {
-    // Block access
-    return action;
-  }
-
-  // Allow access to this member but print a warning.
-  DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast);
-
-  if (access_method != kNone) {
-    // Depending on a runtime flag, we might move the member into whitelist and
-    // skip the warning the next time the member is accessed.
-    MaybeWhitelistMember(runtime, member);
-
-    // If this action requires a UI warning, set the appropriate flag.
-    if (shouldWarn &&
-        (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) {
-      runtime->SetPendingHiddenApiWarning(true);
-    }
-  }
-
-  return action;
+  return deny_access;
 }
 
 // Need to instantiate this.
-template Action GetMemberActionImpl<ArtField>(ArtField* member,
-                                              hiddenapi::ApiList api_list,
-                                              Action action,
-                                              AccessMethod access_method);
-template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member,
-                                               hiddenapi::ApiList api_list,
-                                               Action action,
-                                               AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtField>(ArtField* member,
+                                                     hiddenapi::ApiList api_list,
+                                                     AccessMethod access_method);
+template bool ShouldDenyAccessToMemberImpl<ArtMethod>(ArtMethod* member,
+                                                      hiddenapi::ApiList api_list,
+                                                      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 57f1a59..ed00e2a 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -32,11 +32,10 @@
 // This must be kept in sync with ApplicationInfo.ApiEnforcementPolicy in
 // frameworks/base/core/java/android/content/pm/ApplicationInfo.java
 enum class EnforcementPolicy {
-  kNoChecks             = 0,
+  kDisabled             = 0,
   kJustWarn             = 1,  // keep checks enabled, but allow everything (enables logging)
-  kDarkGreyAndBlackList = 2,  // ban dark grey & blacklist
-  kBlacklistOnly        = 3,  // ban blacklist violations only
-  kMax = kBlacklistOnly,
+  kEnabled              = 2,  // ban dark grey & blacklist
+  kMax = kEnabled,
 };
 
 inline EnforcementPolicy EnforcementPolicyFromInt(int api_policy_int) {
@@ -45,56 +44,59 @@
   return static_cast<EnforcementPolicy>(api_policy_int);
 }
 
-enum Action {
-  kAllow,
-  kAllowButWarn,
-  kAllowButWarnAndToast,
-  kDeny
-};
-
-enum AccessMethod {
+enum class AccessMethod {
   kNone,  // internal test that does not correspond to an actual access by app
   kReflection,
   kJNI,
   kLinking,
 };
 
-// Do not change the values of items in this enum, as they are written to the
-// event log for offline analysis. Any changes will interfere with that analysis.
-enum AccessContextFlags {
-  // Accessed member is a field if this bit is set, else a method
-  kMemberIsField = 1 << 0,
-  // Indicates if access was denied to the member, instead of just printing a warning.
-  kAccessDenied  = 1 << 1,
+struct AccessContext {
+ public:
+  explicit AccessContext(bool is_trusted) : is_trusted_(is_trusted) {}
+
+  explicit AccessContext(ObjPtr<mirror::Class> klass) : is_trusted_(GetIsTrusted(klass)) {}
+
+  AccessContext(ObjPtr<mirror::ClassLoader> class_loader, ObjPtr<mirror::DexCache> dex_cache)
+      : is_trusted_(GetIsTrusted(class_loader, dex_cache)) {}
+
+  bool IsTrusted() const { return is_trusted_; }
+
+ private:
+  static bool GetIsTrusted(ObjPtr<mirror::ClassLoader> class_loader,
+                           ObjPtr<mirror::DexCache> dex_cache)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    // Trust if the caller is in is boot class loader.
+    if (class_loader.IsNull()) {
+      return true;
+    }
+
+    // Trust if caller is in a platform dex file.
+    if (!dex_cache.IsNull()) {
+      const DexFile* dex_file = dex_cache->GetDexFile();
+      if (dex_file != nullptr && dex_file->IsPlatformDexFile()) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  static bool GetIsTrusted(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(!klass.IsNull());
+
+    if (klass->ShouldSkipHiddenApiChecks() && Runtime::Current()->IsJavaDebuggable()) {
+      // Class is known, it is marked trusted and we are in debuggable mode.
+      return true;
+    }
+
+    // Check other aspects of the context.
+    return GetIsTrusted(klass->GetClassLoader(), klass->GetDexCache());
+  }
+
+  bool is_trusted_;
 };
 
-inline Action GetActionFromAccessFlags(ApiList api_list) {
-  if (api_list == ApiList::kWhitelist) {
-    return kAllow;
-  }
-
-  EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
-  if (policy == EnforcementPolicy::kNoChecks) {
-    // Exit early. Nothing to enforce.
-    return kAllow;
-  }
-
-  // if policy is "just warn", always warn. We returned above for whitelist APIs.
-  if (policy == EnforcementPolicy::kJustWarn) {
-    return kAllowButWarn;
-  }
-  DCHECK(policy >= EnforcementPolicy::kDarkGreyAndBlackList);
-  // The logic below relies on equality of values in the enums EnforcementPolicy and
-  // ApiList, and their ordering. Assertions are in hidden_api.cc.
-  if (static_cast<int>(policy) > static_cast<int>(api_list)) {
-    return api_list == ApiList::kDarkGreylist
-        ? kAllowButWarnAndToast
-        : kAllowButWarn;
-  } else {
-    return kDeny;
-  }
-}
-
 class ScopedHiddenApiEnforcementPolicySetting {
  public:
   explicit ScopedHiddenApiEnforcementPolicySetting(EnforcementPolicy new_policy)
@@ -114,6 +116,13 @@
 // Implementation details. DO NOT ACCESS DIRECTLY.
 namespace detail {
 
+enum class SdkCodes {
+  kVersionNone      = std::numeric_limits<int32_t>::min(),
+  kVersionUnlimited = std::numeric_limits<int32_t>::max(),
+  kVersionO_MR1     = 27,
+  kVersionP         = 28,
+};
+
 // Class to encapsulate the signature of a member (ArtField or ArtMethod). This
 // is used as a helper when matching prefixes, and when logging the signature.
 class MemberSignature {
@@ -146,59 +155,31 @@
 
   void WarnAboutAccess(AccessMethod access_method, ApiList list);
 
-  void LogAccessToEventLog(AccessMethod access_method, Action action_taken);
+  void LogAccessToEventLog(AccessMethod access_method, bool access_denied);
+
+  // 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.
+  void NotifyHiddenApiListener(AccessMethod access_method);
 };
 
 template<typename T>
-Action GetMemberActionImpl(T* member,
-                           ApiList api_list,
-                           Action action,
-                           AccessMethod access_method)
+bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, 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
-// a dex file located in ${ANDROID_ROOT}/framework/.
-ALWAYS_INLINE
-inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller,
-                            ObjPtr<mirror::ClassLoader> caller_class_loader,
-                            ObjPtr<mirror::DexCache> caller_dex_cache)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  if (caller_class_loader.IsNull()) {
-    // Boot class loader.
-    return true;
-  }
-
-  if (!caller_dex_cache.IsNull()) {
-    const DexFile* caller_dex_file = caller_dex_cache->GetDexFile();
-    if (caller_dex_file != nullptr && caller_dex_file->IsPlatformDexFile()) {
-      // Caller is in a platform dex file.
-      return true;
-    }
-  }
-
-  if (!caller.IsNull() &&
-      caller->ShouldSkipHiddenApiChecks() &&
-      Runtime::Current()->IsJavaDebuggable()) {
-    // We are in debuggable mode and this caller has been marked trusted.
-    return true;
-  }
-
-  return false;
-}
-
 }  // namespace detail
 
-// Returns true if access to `member` should be denied to the caller of the
-// reflective query. The decision is based on whether the caller is trusted or
-// not. Because different users of this function determine this in a different
-// way, `fn_caller_is_trusted(self)` is called and should return true if the
-// caller is allowed to access the platform.
+// Returns true if access to `member` should be denied in the given context.
+// The decision is based on whether the caller is in a trusted context or not.
+// Because determining the access context can be expensive, a lambda function
+// "fn_get_access_context" is lazily invoked after other criteria have been
+// considered.
 // This function might print warnings into the log if the member is hidden.
 template<typename T>
-inline Action GetMemberAction(T* member,
-                              Thread* self,
-                              std::function<bool(Thread*)> fn_caller_is_trusted,
-                              AccessMethod access_method)
+inline bool ShouldDenyAccessToMember(T* member,
+                                     std::function<AccessContext()> fn_get_access_context,
+                                     AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(member != nullptr);
 
@@ -210,53 +191,34 @@
   // results, e.g. print whitelist warnings (b/78327881).
   ApiList api_list = member->GetHiddenApiAccessFlags();
 
-  Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());
-  if (action == kAllow) {
-    // Nothing to do.
-    return action;
+  // Exit early if member is on the whitelist.
+  if (api_list == ApiList::kWhitelist) {
+    return false;
   }
 
-  // Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access.
+  // Check if caller is exempted from access checks.
   // This can be *very* expensive. Save it for last.
-  if (fn_caller_is_trusted(self)) {
-    // Caller is trusted. Exit.
-    return kAllow;
+  if (fn_get_access_context().IsTrusted()) {
+    return false;
   }
 
-  // Member is hidden and caller is not in the platform.
-  return detail::GetMemberActionImpl(member, api_list, action, access_method);
+  // Member is hidden and caller is not exempted. Enter slow path.
+  return detail::ShouldDenyAccessToMemberImpl(member, api_list, access_method);
 }
 
-inline bool IsCallerTrusted(ObjPtr<mirror::Class> caller) REQUIRES_SHARED(Locks::mutator_lock_) {
-  return !caller.IsNull() &&
-      detail::IsCallerTrusted(caller, caller->GetClassLoader(), caller->GetDexCache());
-}
-
-// Returns true if access to `member` should be denied to a caller loaded with
-// `caller_class_loader`.
-// This function might print warnings into the log if the member is hidden.
+// Helper method for callers where access context can be determined beforehand.
+// Wraps AccessContext in a lambda and passes it to the real ShouldDenyAccessToMember.
 template<typename T>
-inline Action GetMemberAction(T* member,
-                              ObjPtr<mirror::ClassLoader> caller_class_loader,
-                              ObjPtr<mirror::DexCache> caller_dex_cache,
-                              AccessMethod access_method)
+inline bool ShouldDenyAccessToMember(T* member,
+                                     AccessContext access_context,
+                                     AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  bool is_caller_trusted =
-      detail::IsCallerTrusted(/* caller= */ nullptr, caller_class_loader, caller_dex_cache);
-  return GetMemberAction(member,
-                         /* thread= */ nullptr,
-                         [is_caller_trusted] (Thread*) { return is_caller_trusted; },
-                         access_method);
+  return ShouldDenyAccessToMember(
+      member,
+      [&] () REQUIRES_SHARED(Locks::mutator_lock_) { return access_context; },
+      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/hidden_api_test.cc b/runtime/hidden_api_test.cc
index 1727af0..627d9a7 100644
--- a/runtime/hidden_api_test.cc
+++ b/runtime/hidden_api_test.cc
@@ -23,7 +23,7 @@
 namespace art {
 
 using hiddenapi::detail::MemberSignature;
-using hiddenapi::GetActionFromAccessFlags;
+using hiddenapi::detail::ShouldDenyAccessToMemberImpl;
 
 class HiddenApiTest : public CommonRuntimeTest {
  protected:
@@ -68,6 +68,15 @@
     return art_field;
   }
 
+  bool ShouldDenyAccess(hiddenapi::ApiList list) REQUIRES_SHARED(Locks::mutator_lock_) {
+    // Choose parameters such that there are no side effects (AccessMethod::kNone)
+    // and that the member is not on the exemptions list (here we choose one which
+    // is not even in boot class path).
+    return ShouldDenyAccessToMemberImpl(/* member= */ class1_field1_,
+                                        list,
+                                        /* access_method= */ hiddenapi::AccessMethod::kNone);
+  }
+
  protected:
   Thread* self_;
   jobject jclass_loader_;
@@ -88,41 +97,33 @@
 };
 
 TEST_F(HiddenApiTest, CheckGetActionFromRuntimeFlags) {
-  runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kNoChecks);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kWhitelist), hiddenapi::kAllow);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kLightGreylist), hiddenapi::kAllow);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kDarkGreylist), hiddenapi::kAllow);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kBlacklist), hiddenapi::kAllow);
+  ScopedObjectAccess soa(self_);
+
+  runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kDisabled);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kWhitelist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kLightGreylist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kDarkGreylist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kBlacklist), false);
 
   runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kJustWarn);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kWhitelist),
-            hiddenapi::kAllow);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kLightGreylist),
-            hiddenapi::kAllowButWarn);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kDarkGreylist),
-            hiddenapi::kAllowButWarn);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kBlacklist),
-            hiddenapi::kAllowButWarn);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kWhitelist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kLightGreylist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kDarkGreylist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kBlacklist), false);
 
-  runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kDarkGreyAndBlackList);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kWhitelist),
-            hiddenapi::kAllow);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kLightGreylist),
-            hiddenapi::kAllowButWarn);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kDarkGreylist),
-            hiddenapi::kDeny);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kBlacklist),
-            hiddenapi::kDeny);
+  runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled);
+  runtime_->SetTargetSdkVersion(static_cast<int32_t>(hiddenapi::detail::SdkCodes::kVersionO_MR1));
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kWhitelist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kLightGreylist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kDarkGreylist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kBlacklist), true);
 
-  runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kBlacklistOnly);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kWhitelist),
-            hiddenapi::kAllow);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kLightGreylist),
-            hiddenapi::kAllowButWarn);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kDarkGreylist),
-            hiddenapi::kAllowButWarnAndToast);
-  ASSERT_EQ(GetActionFromAccessFlags(hiddenapi::ApiList::kBlacklist),
-            hiddenapi::kDeny);
+  runtime_->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled);
+  runtime_->SetTargetSdkVersion(static_cast<int32_t>(hiddenapi::detail::SdkCodes::kVersionP));
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kWhitelist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kLightGreylist), false);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kDarkGreylist), true);
+  ASSERT_EQ(ShouldDenyAccess(hiddenapi::ApiList::kBlacklist), true);
 }
 
 TEST_F(HiddenApiTest, CheckMembersRead) {
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 9bc2179..e292a76 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -182,15 +182,16 @@
 }
 
 template<typename T>
-static ALWAYS_INLINE bool ShouldBlockAccessToMember(T* member, ShadowFrame* frame)
+static ALWAYS_INLINE bool ShouldDenyAccessToMember(T* member, ShadowFrame* frame)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   // All uses in this file are from reflection
-  constexpr hiddenapi::AccessMethod access_method = hiddenapi::kReflection;
-  return hiddenapi::GetMemberAction(
+  constexpr hiddenapi::AccessMethod access_method = hiddenapi::AccessMethod::kReflection;
+  return hiddenapi::ShouldDenyAccessToMember(
       member,
-      frame->GetMethod()->GetDeclaringClass()->GetClassLoader(),
-      frame->GetMethod()->GetDeclaringClass()->GetDexCache(),
-      access_method) == hiddenapi::kDeny;
+      [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
+        return hiddenapi::AccessContext(frame->GetMethod()->GetDeclaringClass());
+      },
+      access_method);
 }
 
 void UnstartedRuntime::UnstartedClassForNameCommon(Thread* self,
@@ -297,7 +298,7 @@
   auto* cl = Runtime::Current()->GetClassLinker();
   if (cl->EnsureInitialized(self, h_klass, true, true)) {
     ArtMethod* cons = h_klass->FindConstructor("()V", cl->GetImagePointerSize());
-    if (cons != nullptr && ShouldBlockAccessToMember(cons, shadow_frame)) {
+    if (cons != nullptr && ShouldDenyAccessToMember(cons, shadow_frame)) {
       cons = nullptr;
     }
     if (cons != nullptr) {
@@ -342,7 +343,7 @@
       }
     }
   }
-  if (found != nullptr && ShouldBlockAccessToMember(found, shadow_frame)) {
+  if (found != nullptr && ShouldDenyAccessToMember(found, shadow_frame)) {
     found = nullptr;
   }
   if (found == nullptr) {
@@ -407,7 +408,7 @@
           self, klass, name, args);
     }
   }
-  if (method != nullptr && ShouldBlockAccessToMember(method->GetArtMethod(), shadow_frame)) {
+  if (method != nullptr && ShouldDenyAccessToMember(method->GetArtMethod(), shadow_frame)) {
     method = nullptr;
   }
   result->SetL(method);
@@ -445,7 +446,7 @@
     }
   }
   if (constructor != nullptr &&
-      ShouldBlockAccessToMember(constructor->GetArtMethod(), shadow_frame)) {
+      ShouldDenyAccessToMember(constructor->GetArtMethod(), shadow_frame)) {
     constructor = nullptr;
   }
   result->SetL(constructor);
diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc
index 5e01b79..af86cc0 100644
--- a/runtime/jni/jni_internal.cc
+++ b/runtime/jni/jni_internal.cc
@@ -84,20 +84,20 @@
 // things not rendering correctly. E.g. b/16858794
 static constexpr bool kWarnJniAbort = false;
 
-static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
-  return hiddenapi::IsCallerTrusted(GetCallingClass(self, /* num_frames= */ 1));
-}
-
 template<typename T>
-ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
+ALWAYS_INLINE static bool ShouldDenyAccessToMember(T* member, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  hiddenapi::Action action = hiddenapi::GetMemberAction(
-      member, self, IsCallerTrusted, hiddenapi::kJNI);
-  if (action != hiddenapi::kAllow) {
-    hiddenapi::NotifyHiddenApiListener(member);
-  }
-
-  return action == hiddenapi::kDeny;
+  return hiddenapi::ShouldDenyAccessToMember(
+      member,
+      [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
+        // Construct AccessContext from the first calling class on stack.
+        // If the calling class cannot be determined, e.g. unattached threads,
+        // we conservatively assume the caller is trusted.
+        ObjPtr<mirror::Class> caller = GetCallingClass(self, /* num_frames */ 1);
+        return caller.IsNull() ? hiddenapi::AccessContext(/* is_trusted= */ true)
+                               : hiddenapi::AccessContext(caller);
+      },
+      hiddenapi::AccessMethod::kJNI);
 }
 
 // Helpers to call instrumentation functions for fields. These take jobjects so we don't need to set
@@ -259,7 +259,7 @@
   } else {
     method = c->FindClassMethod(name, sig, pointer_size);
   }
-  if (method != nullptr && ShouldBlockAccessToMember(method, soa.Self())) {
+  if (method != nullptr && ShouldDenyAccessToMember(method, soa.Self())) {
     method = nullptr;
   }
   if (method == nullptr || method->IsStatic() != is_static) {
@@ -338,7 +338,7 @@
   } else {
     field = c->FindInstanceField(name, field_type->GetDescriptor(&temp));
   }
-  if (field != nullptr && ShouldBlockAccessToMember(field, soa.Self())) {
+  if (field != nullptr && ShouldDenyAccessToMember(field, soa.Self())) {
     field = nullptr;
   }
   if (field == nullptr) {
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index 4d3ad62..e4bc1b7 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -304,8 +304,7 @@
   // Our system thread ID, etc, has changed so reset Thread state.
   thread->InitAfterFork();
   runtime_flags = EnableDebugFeatures(runtime_flags);
-  hiddenapi::EnforcementPolicy api_enforcement_policy = hiddenapi::EnforcementPolicy::kNoChecks;
-  bool dedupe_hidden_api_warnings = true;
+  hiddenapi::EnforcementPolicy api_enforcement_policy = hiddenapi::EnforcementPolicy::kDisabled;
 
   if ((runtime_flags & DISABLE_VERIFIER) != 0) {
     Runtime::Current()->DisableVerifier();
@@ -372,14 +371,14 @@
     }
   }
 
-  bool do_hidden_api_checks = api_enforcement_policy != hiddenapi::EnforcementPolicy::kNoChecks;
+  bool do_hidden_api_checks = api_enforcement_policy != hiddenapi::EnforcementPolicy::kDisabled;
   DCHECK(!(is_system_server && do_hidden_api_checks))
       << "SystemServer should be forked with EnforcementPolicy::kDisable";
   DCHECK(!(is_zygote && do_hidden_api_checks))
       << "Child zygote processes should be forked with EnforcementPolicy::kDisable";
   Runtime::Current()->SetHiddenApiEnforcementPolicy(api_enforcement_policy);
-  Runtime::Current()->SetDedupeHiddenApiWarnings(dedupe_hidden_api_warnings);
-  if (api_enforcement_policy != hiddenapi::EnforcementPolicy::kNoChecks &&
+  Runtime::Current()->SetDedupeHiddenApiWarnings(true);
+  if (api_enforcement_policy != hiddenapi::EnforcementPolicy::kDisabled &&
       Runtime::Current()->GetHiddenApiEventLogSampleRate() != 0) {
     // Hidden API checks are enabled, and we are sampling access for the event log. Initialize the
     // random seed, to ensure the sampling is actually random. We do this post-fork, as doing it
@@ -387,9 +386,6 @@
     std::srand(static_cast<uint32_t>(NanoTime()));
   }
 
-  // Clear the hidden API warning flag, in case it was set.
-  Runtime::Current()->SetPendingHiddenApiWarning(false);
-
   if (is_zygote) {
     // If creating a child-zygote, do not call into the runtime's post-fork logic.
     // Doing so would spin up threads for Binder and JDWP. Instead, the Java side
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index c7b8ad4..612a4b3 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -54,11 +54,12 @@
 
 namespace art {
 
-// Returns true if the first caller outside of the Class class or java.lang.invoke package
-// is in a platform DEX file.
-static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Walk the stack and find the first frame not from java.lang.Class and not from java.lang.invoke.
-  // This is very expensive. Save this till the last.
+// Walks the stack, finds the caller of this reflective call and returns
+// a hiddenapi AccessContext formed from its declaring class.
+static hiddenapi::AccessContext GetReflectionCaller(Thread* self)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // Walk the stack and find the first frame not from java.lang.Class and not
+  // from java.lang.invoke. This is very expensive. Save this till the last.
   struct FirstExternalCallerVisitor : public StackVisitor {
     explicit FirstExternalCallerVisitor(Thread* thread)
         : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
@@ -102,50 +103,42 @@
 
   FirstExternalCallerVisitor visitor(self);
   visitor.WalkStack();
-  return visitor.caller != nullptr &&
-         hiddenapi::IsCallerTrusted(visitor.caller->GetDeclaringClass());
-}
 
-// Returns true if the first non-ClassClass caller up the stack is not allowed to
-// access hidden APIs. This can be *very* expensive. Never call this in a loop.
-ALWAYS_INLINE static bool ShouldEnforceHiddenApi(Thread* self)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  hiddenapi::EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
-  return policy != hiddenapi::EnforcementPolicy::kNoChecks && !IsCallerTrusted(self);
+  // Construct AccessContext from the calling class found on the stack.
+  // If the calling class cannot be determined, e.g. unattached threads,
+  // we conservatively assume the caller is trusted.
+  ObjPtr<mirror::Class> caller = (visitor.caller == nullptr)
+      ? nullptr : visitor.caller->GetDeclaringClass();
+  return caller.IsNull() ? hiddenapi::AccessContext(/* is_trusted= */ true)
+                         : hiddenapi::AccessContext(caller);
 }
 
 // Returns true if the first non-ClassClass caller up the stack should not be
 // allowed access to `member`.
 template<typename T>
-ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self)
+ALWAYS_INLINE static bool ShouldDenyAccessToMember(T* member, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  hiddenapi::Action action = hiddenapi::GetMemberAction(
-      member, self, IsCallerTrusted, hiddenapi::kReflection);
-  if (action != hiddenapi::kAllow) {
-    hiddenapi::NotifyHiddenApiListener(member);
-  }
-
-  return action == hiddenapi::kDeny;
+  return hiddenapi::ShouldDenyAccessToMember(
+      member,
+      [&]() REQUIRES_SHARED(Locks::mutator_lock_) { return GetReflectionCaller(self); },
+      hiddenapi::AccessMethod::kReflection);
 }
 
 // Returns true if a class member should be discoverable with reflection given
 // the criteria. Some reflection calls only return public members
 // (public_only == true), some members should be hidden from non-boot class path
-// callers (enforce_hidden_api == true).
+// callers (hiddenapi_context).
 template<typename T>
 ALWAYS_INLINE static bool IsDiscoverable(bool public_only,
-                                         bool enforce_hidden_api,
+                                         hiddenapi::AccessContext access_context,
                                          T* member)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (public_only && ((member->GetAccessFlags() & kAccPublic) == 0)) {
     return false;
   }
 
-  return hiddenapi::GetMemberAction(member,
-                                    nullptr,
-                                    [enforce_hidden_api] (Thread*) { return !enforce_hidden_api; },
-                                    hiddenapi::kNone)
-      != hiddenapi::kDeny;
+  return !hiddenapi::ShouldDenyAccessToMember(
+      member, access_context, hiddenapi::AccessMethod::kNone);
 }
 
 ALWAYS_INLINE static inline ObjPtr<mirror::Class> DecodeClass(
@@ -266,15 +259,15 @@
   IterationRange<StrideIterator<ArtField>> ifields = klass->GetIFields();
   IterationRange<StrideIterator<ArtField>> sfields = klass->GetSFields();
   size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields();
-  bool enforce_hidden_api = ShouldEnforceHiddenApi(self);
+  hiddenapi::AccessContext hiddenapi_context = GetReflectionCaller(self);
   // Lets go subtract all the non discoverable fields.
   for (ArtField& field : ifields) {
-    if (!IsDiscoverable(public_only, enforce_hidden_api, &field)) {
+    if (!IsDiscoverable(public_only, hiddenapi_context, &field)) {
       --array_size;
     }
   }
   for (ArtField& field : sfields) {
-    if (!IsDiscoverable(public_only, enforce_hidden_api, &field)) {
+    if (!IsDiscoverable(public_only, hiddenapi_context, &field)) {
       --array_size;
     }
   }
@@ -285,7 +278,7 @@
     return nullptr;
   }
   for (ArtField& field : ifields) {
-    if (IsDiscoverable(public_only, enforce_hidden_api, &field)) {
+    if (IsDiscoverable(public_only, hiddenapi_context, &field)) {
       auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self,
                                                                                    &field,
                                                                                    force_resolve);
@@ -300,7 +293,7 @@
     }
   }
   for (ArtField& field : sfields) {
-    if (IsDiscoverable(public_only, enforce_hidden_api, &field)) {
+    if (IsDiscoverable(public_only, hiddenapi_context, &field)) {
       auto* reflect_field = mirror::Field::CreateFromArtField<kRuntimePointerSize>(self,
                                                                                    &field,
                                                                                    force_resolve);
@@ -459,8 +452,7 @@
   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())) {
+  if (field.Get() == nullptr || ShouldDenyAccessToMember(field->GetArtField(), soa.Self())) {
     return nullptr;
   }
   return soa.AddLocalReference<jobject>(field.Get());
@@ -477,7 +469,7 @@
   Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis));
   Handle<mirror::Field> result =
       hs.NewHandle(GetDeclaredField(soa.Self(), h_klass.Get(), h_string.Get()));
-  if (result == nullptr || ShouldBlockAccessToMember(result->GetArtField(), soa.Self())) {
+  if (result == nullptr || ShouldDenyAccessToMember(result->GetArtField(), soa.Self())) {
     std::string name_str = h_string->ToModifiedUtf8();
     if (name_str == "value" && h_klass->IsStringClass()) {
       // We log the error for this specific case, as the user might just swallow the exception.
@@ -509,19 +501,19 @@
       soa.Self(),
       DecodeClass(soa, javaThis),
       soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
-  if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
+  if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
     return nullptr;
   }
   return soa.AddLocalReference<jobject>(result.Get());
 }
 
 static ALWAYS_INLINE inline bool MethodMatchesConstructor(
-    ArtMethod* m, bool public_only, bool enforce_hidden_api)
+    ArtMethod* m, bool public_only, hiddenapi::AccessContext hiddenapi_context)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(m != nullptr);
   return m->IsConstructor() &&
          !m->IsStatic() &&
-         IsDiscoverable(public_only, enforce_hidden_api, m);
+         IsDiscoverable(public_only, hiddenapi_context, m);
 }
 
 static jobjectArray Class_getDeclaredConstructorsInternal(
@@ -529,12 +521,12 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<2> hs(soa.Self());
   bool public_only = (publicOnly != JNI_FALSE);
-  bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self());
+  hiddenapi::AccessContext hiddenapi_context = GetReflectionCaller(soa.Self());
   Handle<mirror::Class> h_klass = hs.NewHandle(DecodeClass(soa, javaThis));
   size_t constructor_count = 0;
   // Two pass approach for speed.
   for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) {
-    constructor_count += MethodMatchesConstructor(&m, public_only, enforce_hidden_api) ? 1u : 0u;
+    constructor_count += MethodMatchesConstructor(&m, public_only, hiddenapi_context) ? 1u : 0u;
   }
   auto h_constructors = hs.NewHandle(mirror::ObjectArray<mirror::Constructor>::Alloc(
       soa.Self(), GetClassRoot<mirror::ObjectArray<mirror::Constructor>>(), constructor_count));
@@ -544,7 +536,7 @@
   }
   constructor_count = 0;
   for (auto& m : h_klass->GetDirectMethods(kRuntimePointerSize)) {
-    if (MethodMatchesConstructor(&m, public_only, enforce_hidden_api)) {
+    if (MethodMatchesConstructor(&m, public_only, hiddenapi_context)) {
       DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
       DCHECK(!Runtime::Current()->IsActiveTransaction());
       ObjPtr<mirror::Constructor> constructor =
@@ -571,7 +563,7 @@
           DecodeClass(soa, javaThis),
           soa.Decode<mirror::String>(name),
           soa.Decode<mirror::ObjectArray<mirror::Class>>(args)));
-  if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) {
+  if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) {
     return nullptr;
   }
   return soa.AddLocalReference<jobject>(result.Get());
@@ -582,7 +574,7 @@
   ScopedFastNativeObjectAccess soa(env);
   StackHandleScope<2> hs(soa.Self());
 
-  bool enforce_hidden_api = ShouldEnforceHiddenApi(soa.Self());
+  hiddenapi::AccessContext hiddenapi_context = GetReflectionCaller(soa.Self());
   bool public_only = (publicOnly != JNI_FALSE);
 
   Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
@@ -591,7 +583,7 @@
     uint32_t modifiers = m.GetAccessFlags();
     // Add non-constructor declared methods.
     if ((modifiers & kAccConstructor) == 0 &&
-        IsDiscoverable(public_only, enforce_hidden_api, &m)) {
+        IsDiscoverable(public_only, hiddenapi_context, &m)) {
       ++num_methods;
     }
   }
@@ -605,7 +597,7 @@
   for (ArtMethod& m : klass->GetDeclaredMethods(kRuntimePointerSize)) {
     uint32_t modifiers = m.GetAccessFlags();
     if ((modifiers & kAccConstructor) == 0 &&
-        IsDiscoverable(public_only, enforce_hidden_api, &m)) {
+        IsDiscoverable(public_only, hiddenapi_context, &m)) {
       DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize);
       DCHECK(!Runtime::Current()->IsActiveTransaction());
       ObjPtr<mirror::Method> method =
@@ -819,7 +811,7 @@
       soa.Self(),
       ScopedNullHandle<mirror::ObjectArray<mirror::Class>>(),
       kRuntimePointerSize);
-  if (UNLIKELY(constructor == nullptr) || ShouldBlockAccessToMember(constructor, soa.Self())) {
+  if (UNLIKELY(constructor == nullptr) || ShouldDenyAccessToMember(constructor, soa.Self())) {
     soa.Self()->ThrowNewExceptionF("Ljava/lang/InstantiationException;",
                                    "%s has no zero argument constructor",
                                    klass->PrettyClass().c_str());
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 3dfa0c4..ccbc2d9 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -269,10 +269,8 @@
       oat_file_manager_(nullptr),
       is_low_memory_mode_(false),
       safe_mode_(false),
-      hidden_api_policy_(hiddenapi::EnforcementPolicy::kNoChecks),
-      pending_hidden_api_warning_(false),
+      hidden_api_policy_(hiddenapi::EnforcementPolicy::kDisabled),
       dedupe_hidden_api_warnings_(true),
-      always_set_hidden_api_warning_flag_(false),
       hidden_api_access_event_log_rate_(0),
       dump_native_stack_on_sig_quit_(true),
       pruned_dalvik_cache_(false),
@@ -1235,8 +1233,8 @@
   // As is, we're encoding some logic here about which specific policy to use, which would be better
   // controlled by the framework.
   hidden_api_policy_ = do_hidden_api_checks
-      ? hiddenapi::EnforcementPolicy::kDarkGreyAndBlackList
-      : hiddenapi::EnforcementPolicy::kNoChecks;
+      ? hiddenapi::EnforcementPolicy::kEnabled
+      : hiddenapi::EnforcementPolicy::kDisabled;
 
   no_sig_chain_ = runtime_options.Exists(Opt::NoSigChain);
   force_native_bridge_ = runtime_options.Exists(Opt::ForceNativeBridge);
diff --git a/runtime/runtime.h b/runtime/runtime.h
index ad4d3bb..f6a5634 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -535,10 +535,6 @@
     return hidden_api_policy_;
   }
 
-  void SetPendingHiddenApiWarning(bool value) {
-    pending_hidden_api_warning_ = value;
-  }
-
   void SetHiddenApiExemptions(const std::vector<std::string>& exemptions) {
     hidden_api_exemptions_ = exemptions;
   }
@@ -547,10 +543,6 @@
     return hidden_api_exemptions_;
   }
 
-  bool HasPendingHiddenApiWarning() const {
-    return pending_hidden_api_warning_;
-  }
-
   void SetDedupeHiddenApiWarnings(bool value) {
     dedupe_hidden_api_warnings_ = value;
   }
@@ -559,14 +551,6 @@
     return dedupe_hidden_api_warnings_;
   }
 
-  void AlwaysSetHiddenApiWarningFlag() {
-    always_set_hidden_api_warning_flag_ = true;
-  }
-
-  bool ShouldAlwaysSetHiddenApiWarningFlag() const {
-    return always_set_hidden_api_warning_flag_;
-  }
-
   void SetHiddenApiEventLogSampleRate(uint32_t rate) {
     hidden_api_access_event_log_rate_ = rate;
   }
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 65039bc..d390611 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -293,7 +293,7 @@
 
 void WellKnownClasses::Init(JNIEnv* env) {
   hiddenapi::ScopedHiddenApiEnforcementPolicySetting hiddenapi_exemption(
-      hiddenapi::EnforcementPolicy::kNoChecks);
+      hiddenapi::EnforcementPolicy::kDisabled);
 
   dalvik_annotation_optimization_CriticalNative =
       CacheClass(env, "dalvik/annotation/optimization/CriticalNative");
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
index 96754c3..8e3e4eb 100644
--- a/test/674-hiddenapi/hiddenapi.cc
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -28,9 +28,9 @@
 
 extern "C" JNIEXPORT void JNICALL Java_Main_init(JNIEnv*, jclass) {
   Runtime* runtime = Runtime::Current();
-  runtime->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kBlacklistOnly);
+  runtime->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled);
+  runtime->SetTargetSdkVersion(static_cast<int32_t>(hiddenapi::detail::SdkCodes::kVersionO_MR1));
   runtime->SetDedupeHiddenApiWarnings(false);
-  runtime->AlwaysSetHiddenApiWarningFlag();
 }
 
 extern "C" JNIEXPORT void JNICALL Java_Main_appendToBootClassLoader(
@@ -287,13 +287,5 @@
   return static_cast<jint>(kAccHiddenApiBits);
 }
 
-extern "C" JNIEXPORT jboolean JNICALL Java_ChildClass_hasPendingWarning(JNIEnv*, jclass) {
-  return Runtime::Current()->HasPendingHiddenApiWarning();
-}
-
-extern "C" JNIEXPORT void JNICALL Java_ChildClass_clearWarning(JNIEnv*, jclass) {
-  Runtime::Current()->SetPendingHiddenApiWarning(false);
-}
-
 }  // namespace Test674HiddenApi
 }  // namespace art
diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java
index db3ba6d..3427b8e 100644
--- a/test/674-hiddenapi/src-ex/ChildClass.java
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -98,10 +98,8 @@
         expected = Behaviour.Granted;
       } else if (hiddenness == Hiddenness.Blacklist) {
         expected = Behaviour.Denied;
-      } else if (isDebuggable) {
-        expected = Behaviour.Warning;
       } else {
-        expected = Behaviour.Granted;
+        expected = Behaviour.Warning;
       }
 
       for (boolean isStatic : booleanValues) {
@@ -145,7 +143,7 @@
   }
 
   private static void checkMemberCallback(Class<?> klass, String name,
-          boolean isPublic, boolean isField) {
+          boolean isPublic, boolean isField, boolean expectedCallback) {
       try {
           RecordingConsumer consumer = new RecordingConsumer();
           VMRuntime.setNonSdkApiUsageConsumer(consumer);
@@ -168,8 +166,14 @@
               // only interested in whether the callback is invoked.
           }
 
-          if (consumer.recordedValue == null || !consumer.recordedValue.contains(name)) {
-              throw new RuntimeException("No callback for member: " + name);
+          boolean actualCallback = consumer.recordedValue != null &&
+                          consumer.recordedValue.contains(name);
+          if (expectedCallback != actualCallback) {
+              if (expectedCallback) {
+                throw new RuntimeException("Expected callback for member: " + name);
+              } else {
+                throw new RuntimeException("Did not expect callback for member: " + name);
+              }
           }
       } finally {
           VMRuntime.setNonSdkApiUsageConsumer(null);
@@ -181,7 +185,7 @@
 
     boolean isPublic = (visibility == Visibility.Public);
     boolean canDiscover = (behaviour != Behaviour.Denied);
-    boolean setsWarning = (behaviour == Behaviour.Warning);
+    boolean invokesMemberCallback = (behaviour != Behaviour.Granted);
 
     if (klass.isInterface() && (!isStatic || !isPublic)) {
       // Interfaces only have public static fields.
@@ -243,8 +247,6 @@
                               canDiscover);
     }
 
-    // Finish here if we could not discover the field.
-
     if (canDiscover) {
       // Test that modifiers are unaffected.
 
@@ -254,44 +256,22 @@
 
       // 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 callbacks are invoked correctly.
-    clearWarning();
-    if (setsWarning || !canDiscover) {
-      checkMemberCallback(klass, name, isPublic, true /* isField */);
-    }
+    checkMemberCallback(klass, name, isPublic, true /* isField */, invokesMemberCallback);
   }
 
   private static void checkMethod(Class<?> klass, String name, boolean isStatic,
@@ -304,7 +284,7 @@
     }
 
     boolean canDiscover = (behaviour != Behaviour.Denied);
-    boolean setsWarning = (behaviour == Behaviour.Warning);
+    boolean invokesMemberCallback = (behaviour != Behaviour.Granted);
 
     // Test discovery with reflection.
 
@@ -354,39 +334,21 @@
       }
 
       // 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 */);
-    }
+    checkMemberCallback(klass, name, isPublic, false /* isField */, invokesMemberCallback);
   }
 
   private static void checkConstructor(Class<?> klass, Visibility visibility, Hiddenness hiddenness,
@@ -403,7 +365,6 @@
     MethodType methodType = MethodType.methodType(void.class, args);
 
     boolean canDiscover = (behaviour != Behaviour.Denied);
-    boolean setsWarning = (behaviour == Behaviour.Warning);
 
     // Test discovery with reflection.
 
@@ -446,70 +407,41 @@
                               canDiscover);
     }
 
-    // Finish here if we could not discover the constructor.
+    if (canDiscover) {
+      // Test whether we can invoke the constructor.
 
-    if (!canDiscover) {
-      return;
-    }
-
-    // Test whether we can invoke the constructor.
-
-    clearWarning();
-    if (!Reflection.canInvokeConstructor(klass, args, initargs)) {
-      throwAccessException(klass, fullName, false, "invoke()");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, fullName, false, "invoke()", setsWarning);
-    }
-
-    clearWarning();
-    if (!JNI.canInvokeConstructorA(klass, signature)) {
-      throwAccessException(klass, fullName, false, "NewObjectA");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, fullName, false, "NewObjectA", setsWarning);
-    }
-
-    clearWarning();
-    if (!JNI.canInvokeConstructorV(klass, signature)) {
-      throwAccessException(klass, fullName, false, "NewObjectV");
-    }
-    if (hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, fullName, false, "NewObjectV", setsWarning);
+      if (!Reflection.canInvokeConstructor(klass, args, initargs)) {
+        throwAccessException(klass, fullName, false, "invoke()");
+      }
+      if (!JNI.canInvokeConstructorA(klass, signature)) {
+        throwAccessException(klass, fullName, false, "NewObjectA");
+      }
+      if (!JNI.canInvokeConstructorV(klass, signature)) {
+        throwAccessException(klass, fullName, false, "NewObjectV");
+      }
     }
   }
 
   private static void checkNullaryConstructor(Class<?> klass, Behaviour behaviour)
       throws Exception {
     boolean canAccess = (behaviour != Behaviour.Denied);
-    boolean setsWarning = (behaviour == Behaviour.Warning);
 
-    clearWarning();
     if (Reflection.canUseNewInstance(klass) != canAccess) {
       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
           "be able to construct " + klass.getName() + ". " +
           "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
     }
-    if (canAccess && hasPendingWarning() != setsWarning) {
-      throwWarningException(klass, "nullary constructor", false, "newInstance", setsWarning);
-    }
   }
 
   private static void checkLinking(String className, boolean takesParameter, Behaviour behaviour)
       throws Exception {
     boolean canAccess = (behaviour != Behaviour.Denied);
-    boolean setsWarning = (behaviour == Behaviour.Warning);
 
-    clearWarning();
     if (Linking.canAccess(className, takesParameter) != canAccess) {
       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
           "be able to verify " + className + "." +
           "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
     }
-    if (canAccess && hasPendingWarning() != setsWarning) {
-      throwWarningException(
-          Class.forName(className), "access", false, "static linking", setsWarning);
-    }
   }
 
   private static void throwDiscoveryException(Class<?> klass, String name, boolean isField,
@@ -528,15 +460,6 @@
         "everythingWhitelisted = " + everythingWhitelisted);
   }
 
-  private static void throwWarningException(Class<?> klass, String name, boolean isField,
-      String fn, boolean setsWarning) {
-    throw new RuntimeException("Expected access to " + (isField ? "field " : "method ") +
-        klass.getName() + "." + name + " using " + fn + " to " + (setsWarning ? "" : "not ") +
-        "set the warning flag. " +
-        "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot + ", " +
-        "everythingWhitelisted = " + everythingWhitelisted);
-  }
-
   private static void throwModifiersException(Class<?> klass, String name, boolean isField) {
     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
         "." + name + " to not expose hidden modifiers");
@@ -545,7 +468,4 @@
   private static boolean isParentInBoot;
   private static boolean isChildInBoot;
   private static boolean everythingWhitelisted;
-
-  private static native boolean hasPendingWarning();
-  private static native void clearWarning();
 }