hiddenapi: Print warnings for @CorePlatformApi violations

When accessing a method/field at runtime, determine the context of both
the caller and the callee, and add new logic for the case
"platform -> core-platform" which used to be always allowed.

If the callee is marked with kAccCorePlatformApi, access is allowed.
If not, a warning is printed into logcat.

Bug: 119068555
Test: 674-hiddenapi
Change-Id: I64839596bf6eb06d7a169fd59b18fd82c140ce6e
diff --git a/libartbase/base/hiddenapi_domain.h b/libartbase/base/hiddenapi_domain.h
new file mode 100644
index 0000000..4cbc22d
--- /dev/null
+++ b/libartbase/base/hiddenapi_domain.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_LIBARTBASE_BASE_HIDDENAPI_DOMAIN_H_
+#define ART_LIBARTBASE_BASE_HIDDENAPI_DOMAIN_H_
+
+namespace art {
+namespace hiddenapi {
+
+// List of domains supported by the hidden API access checks. Domain with a lower
+// ordinal is considered more "trusted", i.e. always allowed to access members of
+// domains with a greater ordinal. Access checks are performed when code tries to
+// access a method/field from a more trusted domain than itself.
+enum class Domain {
+  kCorePlatform = 0,
+  kPlatform,
+  kApplication,
+};
+
+inline bool IsDomainMoreTrustedThan(Domain domainA, Domain domainB) {
+  return static_cast<uint32_t>(domainA) <= static_cast<uint32_t>(domainB);
+}
+
+}  // namespace hiddenapi
+}  // namespace art
+
+
+#endif  // ART_LIBARTBASE_BASE_HIDDENAPI_DOMAIN_H_
diff --git a/libdexfile/dex/art_dex_file_loader.cc b/libdexfile/dex/art_dex_file_loader.cc
index 57e838f..a814b66 100644
--- a/libdexfile/dex/art_dex_file_loader.cc
+++ b/libdexfile/dex/art_dex_file_loader.cc
@@ -547,7 +547,7 @@
   // that this will call `realpath`.
   std::string path = DexFileLoader::GetDexCanonicalLocation(location.c_str());
   if (dex_file != nullptr && LocationIsOnSystemFramework(path.c_str())) {
-    dex_file->SetIsPlatformDexFile();
+    dex_file->SetHiddenapiDomain(hiddenapi::Domain::kPlatform);
   }
 
   return dex_file;
diff --git a/libdexfile/dex/art_dex_file_loader_test.cc b/libdexfile/dex/art_dex_file_loader_test.cc
index f9516db..8c9258b 100644
--- a/libdexfile/dex/art_dex_file_loader_test.cc
+++ b/libdexfile/dex/art_dex_file_loader_test.cc
@@ -340,7 +340,7 @@
 
   ASSERT_GE(dex_files.size(), 1u);
   for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    ASSERT_FALSE(dex_file->IsPlatformDexFile());
+    ASSERT_NE(dex_file->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
   }
 
   dex_files.clear();
@@ -368,7 +368,7 @@
 
   ASSERT_GE(dex_files.size(), 1u);
   for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    ASSERT_FALSE(dex_file->IsPlatformDexFile());
+    ASSERT_NE(dex_file->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
   }
 
   dex_files.clear();
@@ -396,7 +396,7 @@
 
   ASSERT_GE(dex_files.size(), 1u);
   for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    ASSERT_TRUE(dex_file->IsPlatformDexFile());
+    ASSERT_EQ(dex_file->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
   }
 
   dex_files.clear();
@@ -424,7 +424,7 @@
 
   ASSERT_GT(dex_files.size(), 1u);
   for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    ASSERT_FALSE(dex_file->IsPlatformDexFile());
+    ASSERT_NE(dex_file->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
   }
 
   dex_files.clear();
@@ -453,7 +453,7 @@
 
   ASSERT_GT(dex_files.size(), 1u);
   for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    ASSERT_FALSE(dex_file->IsPlatformDexFile());
+    ASSERT_NE(dex_file->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
   }
 
   dex_files.clear();
@@ -482,7 +482,7 @@
 
   ASSERT_GT(dex_files.size(), 1u);
   for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    ASSERT_TRUE(dex_file->IsPlatformDexFile());
+    ASSERT_EQ(dex_file->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
   }
 
   dex_files.clear();
diff --git a/libdexfile/dex/dex_file.cc b/libdexfile/dex/dex_file.cc
index d3cdf13..39377a3 100644
--- a/libdexfile/dex/dex_file.cc
+++ b/libdexfile/dex/dex_file.cc
@@ -123,7 +123,7 @@
       oat_dex_file_(oat_dex_file),
       container_(std::move(container)),
       is_compact_dex_(is_compact_dex),
-      is_platform_dex_(false) {
+      hiddenapi_domain_(hiddenapi::Domain::kApplication) {
   CHECK(begin_ != nullptr) << GetLocation();
   CHECK_GT(size_, 0U) << GetLocation();
   // Check base (=header) alignment.
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index a940a66..c7fbe78 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -24,6 +24,7 @@
 #include <android-base/logging.h>
 
 #include "base/globals.h"
+#include "base/hiddenapi_domain.h"
 #include "base/macros.h"
 #include "base/value_object.h"
 #include "class_iterator.h"
@@ -754,13 +755,8 @@
   ALWAYS_INLINE const StandardDexFile* AsStandardDexFile() const;
   ALWAYS_INLINE const CompactDexFile* AsCompactDexFile() const;
 
-  ALWAYS_INLINE bool IsPlatformDexFile() const {
-    return is_platform_dex_;
-  }
-
-  ALWAYS_INLINE void SetIsPlatformDexFile() {
-    is_platform_dex_ = true;
-  }
+  hiddenapi::Domain GetHiddenapiDomain() const { return hiddenapi_domain_; }
+  void SetHiddenapiDomain(hiddenapi::Domain value) { hiddenapi_domain_ = value; }
 
   bool IsInMainSection(const void* addr) const {
     return Begin() <= addr && addr < Begin() + Size();
@@ -874,8 +870,7 @@
   // If the dex file is a compact dex file. If false then the dex file is a standard dex file.
   const bool is_compact_dex_;
 
-  // If the dex file is located in /system/framework/.
-  bool is_platform_dex_;
+  hiddenapi::Domain hiddenapi_domain_;
 
   friend class DexFileLoader;
   friend class DexFileVerifierTest;
diff --git a/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc
index 41e4291..a3e06e6 100644
--- a/openjdkjvmti/fixed_up_dex_file.cc
+++ b/openjdkjvmti/fixed_up_dex_file.cc
@@ -141,14 +141,12 @@
       /*verify_checksum=*/false,
       &error);
 
-  if (new_dex_file  == nullptr) {
+  if (new_dex_file == nullptr) {
     LOG(ERROR) << "Unable to open dex file from memory for unquickening! error: " << error;
     return nullptr;
   }
 
-  if (original.IsPlatformDexFile()) {
-    const_cast<art::DexFile*>(new_dex_file.get())->SetIsPlatformDexFile();
-  }
+  const_cast<art::DexFile*>(new_dex_file.get())->SetHiddenapiDomain(original.GetHiddenapiDomain());
 
   DoDexUnquicken(*new_dex_file, original);
 
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index adcf6b3..af5e67a 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -67,6 +67,19 @@
   return os;
 }
 
+static inline std::ostream& operator<<(std::ostream& os, const AccessContext& value)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (!value.GetClass().IsNull()) {
+    std::string tmp;
+    os << value.GetClass()->GetDescriptor(&tmp);
+  } else if (value.GetDexFile() != nullptr) {
+    os << value.GetDexFile()->GetLocation();
+  } else {
+    os << "<unknown_caller>";
+  }
+  return os;
+}
+
 namespace detail {
 
 // Do not change the values of items in this enum, as they are written to the
@@ -349,9 +362,18 @@
 }
 
 template<typename T>
-bool ShouldDenyAccessToMemberImpl(T* member,
-                                  hiddenapi::ApiList api_list,
-                                  AccessMethod access_method) {
+void MaybeReportCorePlatformApiViolation(T* member,
+                                         const AccessContext& caller_context,
+                                         AccessMethod access_method) {
+  if (access_method != AccessMethod::kNone) {
+    MemberSignature sig(member);
+    LOG(ERROR) << "CorePlatformApi violation: " << Dumpable<MemberSignature>(sig)
+               << " from " << caller_context << " using " << access_method;
+  }
+}
+
+template<typename T>
+bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {
   DCHECK(member != nullptr);
   Runtime* runtime = Runtime::Current();
 
@@ -406,14 +428,20 @@
   return deny_access;
 }
 
-// Need to instantiate this.
+// Need to instantiate these.
 template uint32_t GetDexFlags<ArtField>(ArtField* member);
 template uint32_t GetDexFlags<ArtMethod>(ArtMethod* member);
+template void MaybeReportCorePlatformApiViolation(ArtField* member,
+                                                  const AccessContext& caller_context,
+                                                  AccessMethod access_method);
+template void MaybeReportCorePlatformApiViolation(ArtMethod* member,
+                                                  const AccessContext& caller_context,
+                                                  AccessMethod access_method);
 template bool ShouldDenyAccessToMemberImpl<ArtField>(ArtField* member,
-                                                     hiddenapi::ApiList api_list,
+                                                     ApiList api_list,
                                                      AccessMethod access_method);
 template bool ShouldDenyAccessToMemberImpl<ArtMethod>(ArtMethod* member,
-                                                      hiddenapi::ApiList api_list,
+                                                      ApiList api_list,
                                                       AccessMethod access_method);
 }  // namespace detail
 
diff --git a/runtime/hidden_api.h b/runtime/hidden_api.h
index f085033..c73a710 100644
--- a/runtime/hidden_api.h
+++ b/runtime/hidden_api.h
@@ -52,50 +52,91 @@
   kLinking,
 };
 
-struct AccessContext {
+// Represents the API domain of a caller/callee.
+class AccessContext {
  public:
-  explicit AccessContext(bool is_trusted) : is_trusted_(is_trusted) {}
+  // Initialize to either the fully-trusted or fully-untrusted domain.
+  explicit AccessContext(bool is_trusted)
+      : klass_(nullptr),
+        dex_file_(nullptr),
+        domain_(ComputeDomain(is_trusted)) {}
 
-  explicit AccessContext(ObjPtr<mirror::Class> klass) : is_trusted_(GetIsTrusted(klass)) {}
-
+  // Initialize from class loader and dex file (via dex cache).
   AccessContext(ObjPtr<mirror::ClassLoader> class_loader, ObjPtr<mirror::DexCache> dex_cache)
-      : is_trusted_(GetIsTrusted(class_loader, dex_cache)) {}
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      : klass_(nullptr),
+        dex_file_(GetDexFileFromDexCache(dex_cache)),
+        domain_(ComputeDomain(class_loader, dex_file_)) {}
 
-  bool IsTrusted() const { return is_trusted_; }
+  // Initialize from Class.
+  explicit AccessContext(ObjPtr<mirror::Class> klass)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      : klass_(klass),
+        dex_file_(GetDexFileFromDexCache(klass->GetDexCache())),
+        domain_(ComputeDomain(klass, dex_file_)) {}
+
+  ObjPtr<mirror::Class> GetClass() const { return klass_; }
+  const DexFile* GetDexFile() const { return dex_file_; }
+  Domain GetDomain() const { return domain_; }
+
+  bool IsUntrustedDomain() const { return domain_ == Domain::kApplication; }
+
+  // Returns true if this domain is always allowed to access the domain of `callee`.
+  bool CanAlwaysAccess(const AccessContext& callee) const {
+    return IsDomainMoreTrustedThan(domain_, callee.domain_);
+  }
 
  private:
-  static bool GetIsTrusted(ObjPtr<mirror::ClassLoader> class_loader,
-                           ObjPtr<mirror::DexCache> dex_cache)
+  static const DexFile* GetDexFileFromDexCache(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;
+    return dex_cache.IsNull() ? nullptr : dex_cache->GetDexFile();
   }
 
-  static bool GetIsTrusted(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK(!klass.IsNull());
+  static Domain ComputeDomain(bool is_trusted) {
+    return is_trusted ? Domain::kCorePlatform : Domain::kApplication;
+  }
 
-    if (klass->ShouldSkipHiddenApiChecks() && Runtime::Current()->IsJavaDebuggable()) {
-      // Class is known, it is marked trusted and we are in debuggable mode.
-      return true;
+  static Domain ComputeDomain(ObjPtr<mirror::ClassLoader> class_loader, const DexFile* dex_file)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (dex_file == nullptr) {
+      return ComputeDomain(/* is_trusted= */ class_loader.IsNull());
     }
 
+    Domain dex_domain = dex_file->GetHiddenapiDomain();
+    if (class_loader.IsNull() && dex_domain == Domain::kApplication) {
+      LOG(WARNING) << "DexFile " << dex_file->GetLocation() << " is in boot classpath "
+                   << "but is assigned untrusted domain";
+      dex_domain = Domain::kPlatform;
+    }
+    return dex_domain;
+  }
+
+  static Domain ComputeDomain(ObjPtr<mirror::Class> klass, const DexFile* dex_file)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
     // Check other aspects of the context.
-    return GetIsTrusted(klass->GetClassLoader(), klass->GetDexCache());
+    Domain domain = ComputeDomain(klass->GetClassLoader(), dex_file);
+
+    if (domain == Domain::kApplication &&
+        klass->ShouldSkipHiddenApiChecks() &&
+        Runtime::Current()->IsJavaDebuggable()) {
+      // Class is known, it is marked trusted and we are in debuggable mode.
+      domain = ComputeDomain(/* is_trusted= */ true);
+    }
+
+    return domain;
   }
 
-  bool is_trusted_;
+  // Pointer to declaring class of the caller/callee (null if not provided).
+  // This is not safe across GC but we're only using this class for passing
+  // information about the caller to the access check logic and never retain
+  // the AccessContext instance beyond that.
+  const ObjPtr<mirror::Class> klass_;
+
+  // DexFile of the caller/callee (null if not provided).
+  const DexFile* const dex_file_;
+
+  // Computed domain of the caller/callee.
+  const Domain domain_;
 };
 
 class ScopedHiddenApiEnforcementPolicySetting {
@@ -169,6 +210,12 @@
 uint32_t GetDexFlags(T* member) REQUIRES_SHARED(Locks::mutator_lock_);
 
 template<typename T>
+void MaybeReportCorePlatformApiViolation(T* member,
+                                         const AccessContext& caller_context,
+                                         AccessMethod access_method)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+template<typename T>
 bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -309,45 +356,86 @@
                                      AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(member != nullptr);
+  const uint32_t runtime_flags = GetRuntimeFlags(member);
 
   // Exit early if member is public API. This flag is also set for non-boot class
   // path fields/methods.
-  if ((GetRuntimeFlags(member) & kAccPublicApi) != 0) {
+  if ((runtime_flags & kAccPublicApi) != 0) {
     return false;
   }
 
-  // Exit early if access checks are completely disabled.
-  if (Runtime::Current()->GetHiddenApiEnforcementPolicy() == EnforcementPolicy::kDisabled) {
+  // Determine which domain the caller and callee belong to.
+  // This can be *very* expensive. This is why ShouldDenyAccessToMember
+  // should not be called on every individual access.
+  const AccessContext caller_context = fn_get_access_context();
+  const AccessContext callee_context(member->GetDeclaringClass());
+
+  // Non-boot classpath callers should have exited early.
+  DCHECK(!callee_context.IsUntrustedDomain());
+
+  // Check if the caller is always allowed to access members in the callee context.
+  if (caller_context.CanAlwaysAccess(callee_context)) {
     return false;
   }
 
-  // Check if caller is exempted from access checks.
-  // This can be *very* expensive. Save it for last.
-  if (fn_get_access_context().IsTrusted()) {
-    return false;
+  // Check if this is platform accessing core platform. We may warn if `member` is
+  // not part of core platform API.
+  switch (caller_context.GetDomain()) {
+    case Domain::kApplication: {
+      DCHECK(!callee_context.IsUntrustedDomain());
+
+      // Exit early if access checks are completely disabled.
+      EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
+      if (policy == EnforcementPolicy::kDisabled) {
+        return false;
+      }
+
+      // Decode hidden API access flags from the dex file.
+      // This is an O(N) operation scaling with the number of fields/methods
+      // in the class. Only do this on slow path and only do it once.
+      ApiList api_list(detail::GetDexFlags(member));
+      DCHECK(api_list.IsValid());
+
+      // Member is hidden and caller is not exempted. Enter slow path.
+      return detail::ShouldDenyAccessToMemberImpl(member, api_list, access_method);
+    }
+
+    case Domain::kPlatform: {
+      DCHECK(callee_context.GetDomain() == Domain::kCorePlatform);
+
+      // Member is part of core platform API. Accessing it is allowed.
+      if ((runtime_flags & kAccCorePlatformApi) != 0) {
+        return false;
+      }
+
+      // Allow access if access checks are disabled.
+      EnforcementPolicy policy = Runtime::Current()->GetCorePlatformApiEnforcementPolicy();
+      if (policy == EnforcementPolicy::kDisabled) {
+        return false;
+      }
+
+      // Access checks are not disabled, report the violation.
+      detail::MaybeReportCorePlatformApiViolation(member, caller_context, access_method);
+
+      // Deny access if the policy is enabled.
+      return policy == EnforcementPolicy::kEnabled;
+    }
+
+    case Domain::kCorePlatform: {
+      LOG(FATAL) << "CorePlatform domain should be allowed to access all domains";
+      UNREACHABLE();
+    }
   }
-
-  // Decode hidden API access flags from the dex file.
-  // This is an O(N) operation scaling with the number of fields/methods
-  // in the class. Only do this on slow path and only do it once.
-  ApiList api_list(detail::GetDexFlags(member));
-  DCHECK(api_list.IsValid());
-
-  // Member is hidden and caller is not exempted. Enter slow path.
-  return detail::ShouldDenyAccessToMemberImpl(member, api_list, access_method);
 }
 
 // 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 bool ShouldDenyAccessToMember(T* member,
-                                     AccessContext access_context,
+                                     const AccessContext& access_context,
                                      AccessMethod access_method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  return ShouldDenyAccessToMember(
-      member,
-      [&] () REQUIRES_SHARED(Locks::mutator_lock_) { return access_context; },
-      access_method);
+  return ShouldDenyAccessToMember(member, [&]() { return access_context; }, access_method);
 }
 
 }  // namespace hiddenapi
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 4fa7271..72a8727 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -185,13 +185,13 @@
 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::AccessMethod::kReflection;
+  constexpr hiddenapi::AccessMethod kAccessMethod = hiddenapi::AccessMethod::kReflection;
   return hiddenapi::ShouldDenyAccessToMember(
       member,
       [&]() REQUIRES_SHARED(Locks::mutator_lock_) {
         return hiddenapi::AccessContext(frame->GetMethod()->GetDeclaringClass());
       },
-      access_method);
+      kAccessMethod);
 }
 
 void UnstartedRuntime::UnstartedClassForNameCommon(Thread* self,
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 52482b7..daf02fe 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -836,8 +836,9 @@
     return;
   }
 
+  // Assign core platform domain as the dex files are allowed to access all the other domains.
   for (const DexFile* dex_file : dex_files) {
-    const_cast<DexFile*>(dex_file)->SetIsPlatformDexFile();
+    const_cast<DexFile*>(dex_file)->SetHiddenapiDomain(hiddenapi::Domain::kCorePlatform);
   }
 }
 
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index d022c3b..4115791 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -130,7 +130,7 @@
 // callers (hiddenapi_context).
 template<typename T>
 ALWAYS_INLINE static bool IsDiscoverable(bool public_only,
-                                         hiddenapi::AccessContext access_context,
+                                         const hiddenapi::AccessContext& access_context,
                                          T* member)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (public_only && ((member->GetAccessFlags() & kAccPublic) == 0)) {
@@ -508,8 +508,9 @@
 }
 
 static ALWAYS_INLINE inline bool MethodMatchesConstructor(
-    ArtMethod* m, bool public_only, hiddenapi::AccessContext hiddenapi_context)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
+    ArtMethod* m,
+    bool public_only,
+    const hiddenapi::AccessContext& hiddenapi_context) REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK(m != nullptr);
   return m->IsConstructor() &&
          !m->IsStatic() &&
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index d537de5..4853187 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -273,6 +273,7 @@
       is_low_memory_mode_(false),
       safe_mode_(false),
       hidden_api_policy_(hiddenapi::EnforcementPolicy::kDisabled),
+      core_platform_api_policy_(hiddenapi::EnforcementPolicy::kJustWarn),
       dedupe_hidden_api_warnings_(true),
       hidden_api_access_event_log_rate_(0),
       dump_native_stack_on_sig_quit_(true),
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 1d1d0d3..ee2c514 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -539,6 +539,14 @@
     return hidden_api_policy_;
   }
 
+  void SetCorePlatformApiEnforcementPolicy(hiddenapi::EnforcementPolicy policy) {
+    core_platform_api_policy_ = policy;
+  }
+
+  hiddenapi::EnforcementPolicy GetCorePlatformApiEnforcementPolicy() const {
+    return core_platform_api_policy_;
+  }
+
   void SetHiddenApiExemptions(const std::vector<std::string>& exemptions) {
     hidden_api_exemptions_ = exemptions;
   }
@@ -1075,23 +1083,17 @@
   // Whether access checks on hidden API should be performed.
   hiddenapi::EnforcementPolicy hidden_api_policy_;
 
+  // Whether access checks on core platform API should be performed.
+  hiddenapi::EnforcementPolicy core_platform_api_policy_;
+
   // List of signature prefixes of methods that have been removed from the blacklist, and treated
   // as if whitelisted.
   std::vector<std::string> hidden_api_exemptions_;
 
-  // Whether the application has used an API which is not restricted but we
-  // should issue a warning about it.
-  bool pending_hidden_api_warning_;
-
   // Do not warn about the same hidden API access violation twice.
   // This is only used for testing.
   bool dedupe_hidden_api_warnings_;
 
-  // Hidden API can print warnings into the log and/or set a flag read by the
-  // framework to show a UI warning. If this flag is set, always set the flag
-  // when there is a warning. This is only used for testing.
-  bool always_set_hidden_api_warning_flag_;
-
   // How often to log hidden API access to the event log. An integer between 0
   // (never) and 0x10000 (always).
   uint32_t hidden_api_access_event_log_rate_;
diff --git a/test/674-hiddenapi/hiddenapi-flags.csv b/test/674-hiddenapi/hiddenapi-flags.csv
index d875bdf..42626f7 100644
--- a/test/674-hiddenapi/hiddenapi-flags.csv
+++ b/test/674-hiddenapi/hiddenapi-flags.csv
@@ -1,30 +1,41 @@
+LNullaryConstructorBlacklistAndCorePlatformApi;-><init>()V,blacklist,core-platform-api
 LNullaryConstructorBlacklist;-><init>()V,blacklist
 LNullaryConstructorDarkGreylist;-><init>()V,greylist-max-o
 LNullaryConstructorLightGreylist;-><init>()V,greylist
+LParentClass;->fieldPackageBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldPackageBlacklist:I,blacklist
 LParentClass;->fieldPackageDarkGreylist:I,greylist-max-o
 LParentClass;->fieldPackageLightGreylist:I,greylist
+LParentClass;->fieldPackageStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldPackageStaticBlacklist:I,blacklist
 LParentClass;->fieldPackageStaticDarkGreylist:I,greylist-max-o
 LParentClass;->fieldPackageStaticLightGreylist:I,greylist
+LParentClass;->fieldPrivateBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldPrivateBlacklist:I,blacklist
 LParentClass;->fieldPrivateDarkGreylist:I,greylist-max-o
 LParentClass;->fieldPrivateLightGreylist:I,greylist
+LParentClass;->fieldPrivateStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldPrivateStaticBlacklist:I,blacklist
 LParentClass;->fieldPrivateStaticDarkGreylist:I,greylist-max-o
 LParentClass;->fieldPrivateStaticLightGreylist:I,greylist
+LParentClass;->fieldProtectedBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldProtectedBlacklist:I,blacklist
 LParentClass;->fieldProtectedDarkGreylist:I,greylist-max-o
 LParentClass;->fieldProtectedLightGreylist:I,greylist
+LParentClass;->fieldProtectedStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldProtectedStaticBlacklist:I,blacklist
 LParentClass;->fieldProtectedStaticDarkGreylist:I,greylist-max-o
 LParentClass;->fieldProtectedStaticLightGreylist:I,greylist
+LParentClass;->fieldPublicBlacklistAndCorePlatformApiB:I,blacklist,core-platform-api
+LParentClass;->fieldPublicBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldPublicBlacklistB:I,blacklist
 LParentClass;->fieldPublicBlacklist:I,blacklist
 LParentClass;->fieldPublicDarkGreylistB:I,greylist-max-o
 LParentClass;->fieldPublicDarkGreylist:I,greylist-max-o
 LParentClass;->fieldPublicLightGreylistB:I,greylist
 LParentClass;->fieldPublicLightGreylist:I,greylist
+LParentClass;->fieldPublicStaticBlacklistAndCorePlatformApiB:I,blacklist,core-platform-api
+LParentClass;->fieldPublicStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentClass;->fieldPublicStaticBlacklistB:I,blacklist
 LParentClass;->fieldPublicStaticBlacklist:I,blacklist
 LParentClass;->fieldPublicStaticDarkGreylistB:I,greylist-max-o
@@ -33,49 +44,65 @@
 LParentClass;->fieldPublicStaticLightGreylist:I,greylist
 LParentClass;-><init>(DB)V,greylist-max-o
 LParentClass;-><init>(DC)V,blacklist
+LParentClass;-><init>(DI)V,blacklist,core-platform-api
 LParentClass;-><init>(DZ)V,greylist
 LParentClass;-><init>(FB)V,greylist-max-o
 LParentClass;-><init>(FC)V,blacklist
+LParentClass;-><init>(FI)V,blacklist,core-platform-api
 LParentClass;-><init>(FZ)V,greylist
 LParentClass;-><init>(IB)V,greylist-max-o
 LParentClass;-><init>(IC)V,blacklist
+LParentClass;-><init>(II)V,blacklist,core-platform-api
 LParentClass;-><init>(IZ)V,greylist
 LParentClass;-><init>(JB)V,greylist-max-o
 LParentClass;-><init>(JC)V,blacklist
+LParentClass;-><init>(JI)V,blacklist,core-platform-api
 LParentClass;-><init>(JZ)V,greylist
+LParentClass;->methodPackageBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodPackageBlacklist()I,blacklist
 LParentClass;->methodPackageDarkGreylist()I,greylist-max-o
 LParentClass;->methodPackageLightGreylist()I,greylist
+LParentClass;->methodPackageStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodPackageStaticBlacklist()I,blacklist
 LParentClass;->methodPackageStaticDarkGreylist()I,greylist-max-o
 LParentClass;->methodPackageStaticLightGreylist()I,greylist
+LParentClass;->methodPrivateBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodPrivateBlacklist()I,blacklist
 LParentClass;->methodPrivateDarkGreylist()I,greylist-max-o
 LParentClass;->methodPrivateLightGreylist()I,greylist
+LParentClass;->methodPrivateStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodPrivateStaticBlacklist()I,blacklist
 LParentClass;->methodPrivateStaticDarkGreylist()I,greylist-max-o
 LParentClass;->methodPrivateStaticLightGreylist()I,greylist
+LParentClass;->methodProtectedBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodProtectedBlacklist()I,blacklist
 LParentClass;->methodProtectedDarkGreylist()I,greylist-max-o
 LParentClass;->methodProtectedLightGreylist()I,greylist
+LParentClass;->methodProtectedStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodProtectedStaticBlacklist()I,blacklist
 LParentClass;->methodProtectedStaticDarkGreylist()I,greylist-max-o
 LParentClass;->methodProtectedStaticLightGreylist()I,greylist
+LParentClass;->methodPublicBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodPublicBlacklist()I,blacklist
 LParentClass;->methodPublicDarkGreylist()I,greylist-max-o
 LParentClass;->methodPublicLightGreylist()I,greylist
+LParentClass;->methodPublicStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentClass;->methodPublicStaticBlacklist()I,blacklist
 LParentClass;->methodPublicStaticDarkGreylist()I,greylist-max-o
 LParentClass;->methodPublicStaticLightGreylist()I,greylist
+LParentInterface;->fieldPublicStaticBlacklistAndCorePlatformApi:I,blacklist,core-platform-api
 LParentInterface;->fieldPublicStaticBlacklist:I,blacklist
 LParentInterface;->fieldPublicStaticDarkGreylist:I,greylist-max-o
 LParentInterface;->fieldPublicStaticLightGreylist:I,greylist
+LParentInterface;->methodPublicBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentInterface;->methodPublicBlacklist()I,blacklist
 LParentInterface;->methodPublicDarkGreylist()I,greylist-max-o
+LParentInterface;->methodPublicDefaultBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentInterface;->methodPublicDefaultBlacklist()I,blacklist
 LParentInterface;->methodPublicDefaultDarkGreylist()I,greylist-max-o
 LParentInterface;->methodPublicDefaultLightGreylist()I,greylist
 LParentInterface;->methodPublicLightGreylist()I,greylist
+LParentInterface;->methodPublicStaticBlacklistAndCorePlatformApi()I,blacklist,core-platform-api
 LParentInterface;->methodPublicStaticBlacklist()I,blacklist
 LParentInterface;->methodPublicStaticDarkGreylist()I,greylist-max-o
 LParentInterface;->methodPublicStaticLightGreylist()I,greylist
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
index 2464263..8dfb402 100644
--- a/test/674-hiddenapi/hiddenapi.cc
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -27,40 +27,59 @@
 namespace art {
 namespace Test674HiddenApi {
 
+std::vector<std::vector<std::unique_ptr<const DexFile>>> opened_dex_files;
+
 extern "C" JNIEXPORT void JNICALL Java_Main_init(JNIEnv*, jclass) {
   Runtime* runtime = Runtime::Current();
   runtime->SetHiddenApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled);
+  runtime->SetCorePlatformApiEnforcementPolicy(hiddenapi::EnforcementPolicy::kEnabled);
   runtime->SetTargetSdkVersion(
       static_cast<uint32_t>(hiddenapi::ApiList::GreylistMaxO().GetMaxAllowedSdkVersion()));
   runtime->SetDedupeHiddenApiWarnings(false);
 }
 
-extern "C" JNIEXPORT void JNICALL Java_Main_appendToBootClassLoader(
-    JNIEnv* env, jclass, jstring jpath) {
+extern "C" JNIEXPORT void JNICALL Java_Main_setDexDomain(
+    JNIEnv*, jclass, jint int_index, jboolean is_core_platform) {
+  size_t index = static_cast<size_t>(int_index);
+  CHECK_LT(index, opened_dex_files.size());
+  for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files[index]) {
+    const_cast<DexFile*>(dex_file.get())->SetHiddenapiDomain(
+        (is_core_platform == JNI_FALSE) ? hiddenapi::Domain::kPlatform
+                                        : hiddenapi::Domain::kCorePlatform);
+  }
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_appendToBootClassLoader(
+    JNIEnv* env, jclass klass, jstring jpath, jboolean is_core_platform) {
   ScopedUtfChars utf(env, jpath);
   const char* path = utf.c_str();
-  if (path == nullptr) {
-    return;
-  }
+  CHECK(path != nullptr);
+
+  const size_t index = opened_dex_files.size();
+  const jint int_index = static_cast<jint>(index);
+  opened_dex_files.push_back(std::vector<std::unique_ptr<const DexFile>>());
 
   ArtDexFileLoader dex_loader;
   std::string error_msg;
-  std::vector<std::unique_ptr<const DexFile>> dex_files;
+
   if (!dex_loader.Open(path,
                        path,
                        /* verify */ false,
                        /* verify_checksum */ true,
                        &error_msg,
-                       &dex_files)) {
+                       &opened_dex_files[index])) {
     LOG(FATAL) << "Could not open " << path << " for boot classpath extension: " << error_msg;
     UNREACHABLE();
   }
 
+  Java_Main_setDexDomain(env, klass, int_index, is_core_platform);
+
   ScopedObjectAccess soa(Thread::Current());
-  for (std::unique_ptr<const DexFile>& dex_file : dex_files) {
-    Runtime::Current()->GetClassLinker()->AppendToBootClassPath(
-        Thread::Current(), *dex_file.release());
+  for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files[index]) {
+    Runtime::Current()->GetClassLinker()->AppendToBootClassPath(Thread::Current(), *dex_file.get());
   }
+
+  return int_index;
 }
 
 static jobject NewInstance(JNIEnv* env, jclass klass) {
diff --git a/test/674-hiddenapi/run b/test/674-hiddenapi/run
new file mode 100755
index 0000000..2babeef
--- /dev/null
+++ b/test/674-hiddenapi/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Make verification soft fail so that we can re-verify boot classpath
+# methods at runtime.
+exec ${RUN} $@ --verify-soft-fail
\ No newline at end of file
diff --git a/test/674-hiddenapi/src-art/Main.java b/test/674-hiddenapi/src-art/Main.java
index 782748c..190f4ac 100644
--- a/test/674-hiddenapi/src-art/Main.java
+++ b/test/674-hiddenapi/src-art/Main.java
@@ -28,6 +28,13 @@
 import java.util.zip.ZipFile;
 
 public class Main {
+  // This needs to be kept in sync with DexDomain in ChildClass.
+  enum DexDomain {
+    CorePlatform,
+    Platform,
+    Application
+  }
+
   public static void main(String[] args) throws Exception {
     System.loadLibrary(args[0]);
     prepareNativeLibFileName(args[0]);
@@ -40,70 +47,88 @@
     // or test the wrong thing. We rely on not deduping hidden API warnings
     // here for the same reasons), meaning the code under test and production
     // code are running in different configurations. Each test should be run in
-    // a fresh process to ensure that they are working correcting and not
-    // accidentally interfering with eachother.
+    // a fresh process to ensure that they are working correctly and not
+    // accidentally interfering with each other.
+    // As a side effect, we also cannot test Platform->Platform and later
+    // Platform->CorePlatform as the former succeeds in verifying linkage usages
+    // that should fail in the latter.
 
     // Run test with both parent and child dex files loaded with class loaders.
     // The expectation is that hidden members in parent should be visible to
     // the child.
-    doTest(false, false, false);
+    doTest(DexDomain.Application, DexDomain.Application, false);
     doUnloading();
 
     // Now append parent dex file to boot class path and run again. This time
     // the child dex file should not be able to access private APIs of the
     // parent.
-    appendToBootClassLoader(DEX_PARENT_BOOT);
-    doTest(true, false, false);
+    int parentIdx = appendToBootClassLoader(DEX_PARENT_BOOT, /* isCorePlatform */ false);
+    doTest(DexDomain.Platform, DexDomain.Application, false);
     doUnloading();
 
     // Now run the same test again, but with the blacklist exmemptions list set
     // to "L" which matches everything.
-    doTest(true, false, true);
+    doTest(DexDomain.Platform, DexDomain.Application, true);
     doUnloading();
 
-    // And finally append to child to boot class path as well. With both in the
-    // boot class path, access should be granted.
-    appendToBootClassLoader(DEX_CHILD);
-    doTest(true, true, false);
+    // Repeat the two tests above, only with parent being a core-platform dex file.
+    setDexDomain(parentIdx, /* isCorePlatform */ true);
+    doTest(DexDomain.CorePlatform, DexDomain.Application, false);
+    doUnloading();
+    doTest(DexDomain.CorePlatform, DexDomain.Application, true);
+    doUnloading();
+
+    // Append child to boot class path, first as a platform dex file.
+    // It should not be allowed to access non-public, non-core platform API members.
+    int childIdx = appendToBootClassLoader(DEX_CHILD, /* isCorePlatform */ false);
+    doTest(DexDomain.CorePlatform, DexDomain.Platform, false);
+    doUnloading();
+
+    // And finally change child to core-platform dex. With both in the boot classpath
+    // and both core-platform, access should be granted.
+    setDexDomain(childIdx, /* isCorePlatform */ true);
+    doTest(DexDomain.CorePlatform, DexDomain.CorePlatform, false);
     doUnloading();
   }
 
-  private static void doTest(boolean parentInBoot, boolean childInBoot, boolean whitelistAllApis)
-      throws Exception {
+  private static void doTest(DexDomain parentDomain, DexDomain childDomain,
+      boolean whitelistAllApis) throws Exception {
     // Load parent dex if it is not in boot class path.
     ClassLoader parentLoader = null;
-    if (parentInBoot) {
-      parentLoader = BOOT_CLASS_LOADER;
-    } else {
+    if (parentDomain == DexDomain.Application) {
       parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader());
+    } else {
+      parentLoader = BOOT_CLASS_LOADER;
     }
 
     // Load child dex if it is not in boot class path.
     ClassLoader childLoader = null;
-    if (childInBoot) {
+    if (childDomain == DexDomain.Application) {
+      childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader);
+    } else {
       if (parentLoader != BOOT_CLASS_LOADER) {
         throw new IllegalStateException(
             "DeclaringClass must be in parent class loader of CallingClass");
       }
       childLoader = BOOT_CLASS_LOADER;
-    } else {
-      childLoader = new InMemoryDexClassLoader(readDexFile(DEX_CHILD), parentLoader);
     }
 
     // Create a unique copy of the native library. Each shared library can only
     // be loaded once, but for some reason even classes from a class loader
     // cannot register their native methods against symbols in a shared library
     // loaded by their parent class loader.
-    String nativeLibCopy = createNativeLibCopy(parentInBoot, childInBoot, whitelistAllApis);
+    String nativeLibCopy = createNativeLibCopy(parentDomain, childDomain, whitelistAllApis);
 
     if (whitelistAllApis) {
       VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"});
     }
 
     // Invoke ChildClass.runTest
-    Class.forName("ChildClass", true, childLoader)
-        .getDeclaredMethod("runTest", String.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE)
-            .invoke(null, nativeLibCopy, parentInBoot, childInBoot, whitelistAllApis);
+    Class<?> childClass = Class.forName("ChildClass", true, childLoader);
+    Method runTestMethod = childClass.getDeclaredMethod(
+        "runTest", String.class, Integer.TYPE, Integer.TYPE, Boolean.TYPE);
+    runTestMethod.invoke(null, nativeLibCopy, parentDomain.ordinal(), childDomain.ordinal(),
+        whitelistAllApis);
 
     VMRuntime.getRuntime().setHiddenApiExemptions(new String[0]);
   }
@@ -146,11 +171,11 @@
 
   // Copy native library to a new file with a unique name so it does not
   // conflict with other loaded instance of the same binary file.
-  private static String createNativeLibCopy(
-      boolean parentInBoot, boolean childInBoot, boolean whitelistAllApis) throws Exception {
+  private static String createNativeLibCopy(DexDomain parentDomain, DexDomain childDomain,
+      boolean whitelistAllApis) throws Exception {
     String tempFileName = System.mapLibraryName(
-        "hiddenapitest_" + (parentInBoot ? "1" : "0") + (childInBoot ? "1" : "0") +
-         (whitelistAllApis ? "1" : "0"));
+        "hiddenapitest_" + (parentDomain.ordinal()) + (childDomain.ordinal()) +
+        (whitelistAllApis ? "1" : "0"));
     File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName);
     Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath());
     return tempFile.getAbsolutePath();
@@ -175,6 +200,7 @@
 
   private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
 
-  private static native void appendToBootClassLoader(String dexPath);
+  private static native int appendToBootClassLoader(String dexPath, boolean isCorePlatform);
+  private static native void setDexDomain(int index, boolean isCorePlatform);
   private static native void init();
 }
diff --git a/test/674-hiddenapi/src-ex/ChildClass.java b/test/674-hiddenapi/src-ex/ChildClass.java
index 3427b8e..f120bda 100644
--- a/test/674-hiddenapi/src-ex/ChildClass.java
+++ b/test/674-hiddenapi/src-ex/ChildClass.java
@@ -45,7 +45,8 @@
     Whitelist(PrimitiveType.TShort),
     LightGreylist(PrimitiveType.TBoolean),
     DarkGreylist(PrimitiveType.TByte),
-    Blacklist(PrimitiveType.TCharacter);
+    Blacklist(PrimitiveType.TCharacter),
+    BlacklistAndCorePlatformApi(PrimitiveType.TInteger);
 
     Hiddenness(PrimitiveType type) { mAssociatedType = type; }
     public PrimitiveType mAssociatedType;
@@ -67,19 +68,34 @@
     Denied,
   }
 
+  // This needs to be kept in sync with DexDomain in Main.
+  enum DexDomain {
+    CorePlatform,
+    Platform,
+    Application
+  }
+
   private static final boolean booleanValues[] = new boolean[] { false, true };
 
-  public static void runTest(String libFileName, boolean expectedParentInBoot,
-      boolean expectedChildInBoot, boolean everythingWhitelisted) throws Exception {
+  public static void runTest(String libFileName, int parentDomainOrdinal,
+      int childDomainOrdinal, boolean everythingWhitelisted) throws Exception {
     System.load(libFileName);
 
+    parentDomain = DexDomain.values()[parentDomainOrdinal];
+    childDomain = DexDomain.values()[childDomainOrdinal];
+
+    configMessage = "parentDomain=" + parentDomain.name() + ", childDomain=" + childDomain.name()
+        + ", everythingWhitelisted=" + everythingWhitelisted;
+
     // Check expectations about loading into boot class path.
-    isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null);
+    boolean isParentInBoot = (ParentClass.class.getClassLoader().getParent() == null);
+    boolean expectedParentInBoot = (parentDomain != DexDomain.Application);
     if (isParentInBoot != expectedParentInBoot) {
       throw new RuntimeException("Expected ParentClass " +
                                  (expectedParentInBoot ? "" : "not ") + "in boot class path");
     }
-    isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null);
+    boolean isChildInBoot = (ChildClass.class.getClassLoader().getParent() == null);
+    boolean expectedChildInBoot = (childDomain != DexDomain.Application);
     if (isChildInBoot != expectedChildInBoot) {
       throw new RuntimeException("Expected ChildClass " + (expectedChildInBoot ? "" : "not ") +
                                  "in boot class path");
@@ -92,14 +108,26 @@
     // Run meaningful combinations of access flags.
     for (Hiddenness hiddenness : Hiddenness.values()) {
       final Behaviour expected;
+      final boolean invokesMemberCallback;
       // Warnings are now disabled whenever access is granted, even for
       // greylisted APIs. This is the behaviour for release builds.
-      if (isSameBoot || everythingWhitelisted || hiddenness == Hiddenness.Whitelist) {
+      if (everythingWhitelisted || hiddenness == Hiddenness.Whitelist) {
         expected = Behaviour.Granted;
-      } else if (hiddenness == Hiddenness.Blacklist) {
+        invokesMemberCallback = false;
+      } else if (parentDomain == DexDomain.CorePlatform && childDomain == DexDomain.Platform) {
+        expected = (hiddenness == Hiddenness.BlacklistAndCorePlatformApi)
+            ? Behaviour.Granted : Behaviour.Denied;
+        invokesMemberCallback = false;
+      } else if (isSameBoot) {
+        expected = Behaviour.Granted;
+        invokesMemberCallback = false;
+      } else if (hiddenness == Hiddenness.Blacklist ||
+                 hiddenness == Hiddenness.BlacklistAndCorePlatformApi) {
         expected = Behaviour.Denied;
+        invokesMemberCallback = true;
       } else {
         expected = Behaviour.Warning;
+        invokesMemberCallback = true;
       }
 
       for (boolean isStatic : booleanValues) {
@@ -109,8 +137,10 @@
           // Test reflection and JNI on methods and fields
           for (Class klass : new Class<?>[] { ParentClass.class, ParentInterface.class }) {
             String baseName = visibility.name() + suffix;
-            checkField(klass, "field" + baseName, isStatic, visibility, expected);
-            checkMethod(klass, "method" + baseName, isStatic, visibility, expected);
+            checkField(klass, "field" + baseName, isStatic, visibility, expected,
+                invokesMemberCallback);
+            checkMethod(klass, "method" + baseName, isStatic, visibility, expected,
+                invokesMemberCallback);
           }
 
           // Check whether one can use a class constructor.
@@ -118,7 +148,8 @@
 
           // Check whether one can use an interface default method.
           String name = "method" + visibility.name() + "Default" + hiddenness.name();
-          checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected);
+          checkMethod(ParentInterface.class, name, /*isStatic*/ false, visibility, expected,
+              invokesMemberCallback);
         }
 
         // Test whether static linking succeeds.
@@ -181,11 +212,10 @@
   }
 
   private static void checkField(Class<?> klass, String name, boolean isStatic,
-      Visibility visibility, Behaviour behaviour) throws Exception {
+      Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback) throws Exception {
 
     boolean isPublic = (visibility == Visibility.Public);
     boolean canDiscover = (behaviour != Behaviour.Denied);
-    boolean invokesMemberCallback = (behaviour != Behaviour.Granted);
 
     if (klass.isInterface() && (!isStatic || !isPublic)) {
       // Interfaces only have public static fields.
@@ -275,7 +305,7 @@
   }
 
   private static void checkMethod(Class<?> klass, String name, boolean isStatic,
-      Visibility visibility, Behaviour behaviour) throws Exception {
+      Visibility visibility, Behaviour behaviour, boolean invokesMemberCallback) throws Exception {
 
     boolean isPublic = (visibility == Visibility.Public);
     if (klass.isInterface() && !isPublic) {
@@ -284,7 +314,6 @@
     }
 
     boolean canDiscover = (behaviour != Behaviour.Denied);
-    boolean invokesMemberCallback = (behaviour != Behaviour.Granted);
 
     // Test discovery with reflection.
 
@@ -428,8 +457,7 @@
 
     if (Reflection.canUseNewInstance(klass) != canAccess) {
       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
-          "be able to construct " + klass.getName() + ". " +
-          "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
+          "be able to construct " + klass.getName() + ". " + configMessage);
     }
   }
 
@@ -439,8 +467,7 @@
 
     if (Linking.canAccess(className, takesParameter) != canAccess) {
       throw new RuntimeException("Expected to " + (canAccess ? "" : "not ") +
-          "be able to verify " + className + "." +
-          "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot);
+          "be able to verify " + className + "." + configMessage);
     }
   }
 
@@ -448,16 +475,13 @@
       String fn, boolean canAccess) {
     throw new RuntimeException("Expected " + (isField ? "field " : "method ") + klass.getName() +
         "." + name + " to " + (canAccess ? "" : "not ") + "be discoverable with " + fn + ". " +
-        "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot + ", " +
-        "everythingWhitelisted = " + everythingWhitelisted);
+        configMessage);
   }
 
   private static void throwAccessException(Class<?> klass, String name, boolean isField,
       String fn) {
     throw new RuntimeException("Expected to be able to access " + (isField ? "field " : "method ") +
-        klass.getName() + "." + name + " using " + fn + ". " +
-        "isParentInBoot = " + isParentInBoot + ", " + "isChildInBoot = " + isChildInBoot + ", " +
-        "everythingWhitelisted = " + everythingWhitelisted);
+        klass.getName() + "." + name + " using " + fn + ". " + configMessage);
   }
 
   private static void throwModifiersException(Class<?> klass, String name, boolean isField) {
@@ -465,7 +489,9 @@
         "." + name + " to not expose hidden modifiers");
   }
 
-  private static boolean isParentInBoot;
-  private static boolean isChildInBoot;
+  private static DexDomain parentDomain;
+  private static DexDomain childDomain;
   private static boolean everythingWhitelisted;
+
+  private static String configMessage;
 }
diff --git a/test/674-hiddenapi/src-ex/Linking.java b/test/674-hiddenapi/src-ex/Linking.java
index 0fa0b19..5aa3663 100644
--- a/test/674-hiddenapi/src-ex/Linking.java
+++ b/test/674-hiddenapi/src-ex/Linking.java
@@ -62,6 +62,12 @@
   }
 }
 
+class LinkFieldGetBlacklistAndCorePlatformApi {
+  public static int access() {
+    return new ParentClass().fieldPublicBlacklistAndCorePlatformApi;
+  }
+}
+
 // INSTANCE FIELD SET
 
 class LinkFieldSetWhitelist {
@@ -92,6 +98,13 @@
   }
 }
 
+class LinkFieldSetBlacklistAndCorePlatformApi {
+  public static void access(int x) {
+    // Need to use a different field from the getter to bypass DexCache.
+    new ParentClass().fieldPublicBlacklistAndCorePlatformApiB = x;
+  }
+}
+
 // STATIC FIELD GET
 
 class LinkFieldGetStaticWhitelist {
@@ -118,6 +131,12 @@
   }
 }
 
+class LinkFieldGetStaticBlacklistAndCorePlatformApi {
+  public static int access() {
+    return ParentClass.fieldPublicStaticBlacklistAndCorePlatformApi;
+  }
+}
+
 // STATIC FIELD SET
 
 class LinkFieldSetStaticWhitelist {
@@ -148,6 +167,13 @@
   }
 }
 
+class LinkFieldSetStaticBlacklistAndCorePlatformApi {
+  public static void access(int x) {
+    // Need to use a different field from the getter to bypass DexCache.
+    ParentClass.fieldPublicStaticBlacklistAndCorePlatformApiB = x;
+  }
+}
+
 // INVOKE INSTANCE METHOD
 
 class LinkMethodWhitelist {
@@ -174,6 +200,12 @@
   }
 }
 
+class LinkMethodBlacklistAndCorePlatformApi {
+  public static int access() {
+    return new ParentClass().methodPublicBlacklistAndCorePlatformApi();
+  }
+}
+
 // INVOKE INSTANCE INTERFACE METHOD
 
 class LinkMethodInterfaceWhitelist {
@@ -200,6 +232,12 @@
   }
 }
 
+class LinkMethodInterfaceBlacklistAndCorePlatformApi {
+  public static int access() {
+    return DummyClass.getInterfaceInstance().methodPublicBlacklistAndCorePlatformApi();
+  }
+}
+
 // INVOKE STATIC METHOD
 
 class LinkMethodStaticWhitelist {
@@ -226,6 +264,12 @@
   }
 }
 
+class LinkMethodStaticBlacklistAndCorePlatformApi {
+  public static int access() {
+    return ParentClass.methodPublicStaticBlacklistAndCorePlatformApi();
+  }
+}
+
 // INVOKE INTERFACE STATIC METHOD
 
 class LinkMethodInterfaceStaticWhitelist {
@@ -251,3 +295,9 @@
     return ParentInterface.methodPublicStaticBlacklist();
   }
 }
+
+class LinkMethodInterfaceStaticBlacklistAndCorePlatformApi {
+  public static int access() {
+    return ParentInterface.methodPublicStaticBlacklistAndCorePlatformApi();
+  }
+}
diff --git a/test/674-hiddenapi/src/DummyClass.java b/test/674-hiddenapi/src/DummyClass.java
index 51281a2..afba747 100644
--- a/test/674-hiddenapi/src/DummyClass.java
+++ b/test/674-hiddenapi/src/DummyClass.java
@@ -19,6 +19,7 @@
   public int methodPublicLightGreylist() { return 2; }
   public int methodPublicDarkGreylist() { return 3; }
   public int methodPublicBlacklist() { return 4; }
+  public int methodPublicBlacklistAndCorePlatformApi() { return 5; }
 
   public static ParentInterface getInterfaceInstance() {
     return new DummyClass();
diff --git a/test/674-hiddenapi/src/NullaryConstructorBlacklistAndCorePlatformApi.java b/test/674-hiddenapi/src/NullaryConstructorBlacklistAndCorePlatformApi.java
new file mode 100644
index 0000000..86af29e
--- /dev/null
+++ b/test/674-hiddenapi/src/NullaryConstructorBlacklistAndCorePlatformApi.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class NullaryConstructorBlacklistAndCorePlatformApi {
+  public NullaryConstructorBlacklistAndCorePlatformApi() { x = 22; }
+  public NullaryConstructorBlacklistAndCorePlatformApi(int y) { x = y; }
+  protected int x;
+}
diff --git a/test/674-hiddenapi/src/ParentClass.java b/test/674-hiddenapi/src/ParentClass.java
index 07e84cc..1442392 100644
--- a/test/674-hiddenapi/src/ParentClass.java
+++ b/test/674-hiddenapi/src/ParentClass.java
@@ -43,6 +43,12 @@
   private int fieldPrivateBlacklist = 244;
   public int fieldPublicBlacklistB = 245;
 
+  public int fieldPublicBlacklistAndCorePlatformApi = 251;
+  int fieldPackageBlacklistAndCorePlatformApi = 252;
+  protected int fieldProtectedBlacklistAndCorePlatformApi = 253;
+  private int fieldPrivateBlacklistAndCorePlatformApi = 254;
+  public int fieldPublicBlacklistAndCorePlatformApiB = 255;
+
   // STATIC FIELD
 
   public static int fieldPublicStaticWhitelist = 111;
@@ -69,6 +75,12 @@
   private static int fieldPrivateStaticBlacklist = 144;
   public static int fieldPublicStaticBlacklistB = 145;
 
+  public static int fieldPublicStaticBlacklistAndCorePlatformApi = 151;
+  static int fieldPackageStaticBlacklistAndCorePlatformApi = 152;
+  protected static int fieldProtectedStaticBlacklistAndCorePlatformApi = 153;
+  private static int fieldPrivateStaticBlacklistAndCorePlatformApi = 154;
+  public static int fieldPublicStaticBlacklistAndCorePlatformApiB = 155;
+
   // INSTANCE METHOD
 
   public int methodPublicWhitelist() { return 411; }
@@ -91,6 +103,11 @@
   protected int methodProtectedBlacklist() { return 443; }
   private int methodPrivateBlacklist() { return 444; }
 
+  public int methodPublicBlacklistAndCorePlatformApi() { return 451; }
+  int methodPackageBlacklistAndCorePlatformApi() { return 452; }
+  protected int methodProtectedBlacklistAndCorePlatformApi() { return 453; }
+  private int methodPrivateBlacklistAndCorePlatformApi() { return 454; }
+
   // STATIC METHOD
 
   public static int methodPublicStaticWhitelist() { return 311; }
@@ -113,6 +130,11 @@
   protected static int methodProtectedStaticBlacklist() { return 343; }
   private static int methodPrivateStaticBlacklist() { return 344; }
 
+  public static int methodPublicStaticBlacklistAndCorePlatformApi() { return 351; }
+  static int methodPackageStaticBlacklistAndCorePlatformApi() { return 352; }
+  protected static int methodProtectedStaticBlacklistAndCorePlatformApi() { return 353; }
+  private static int methodPrivateStaticBlacklistAndCorePlatformApi() { return 354; }
+
   // CONSTRUCTOR
 
   // Whitelist
@@ -139,6 +161,12 @@
   protected ParentClass(long x, char y) {}
   private ParentClass(double x, char y) {}
 
+  // Blacklist and CorePlatformApi
+  public ParentClass(int x, int y) {}
+  ParentClass(float x, int y) {}
+  protected ParentClass(long x, int y) {}
+  private ParentClass(double x, int y) {}
+
   // HELPERS
 
   public int callMethodPublicWhitelist() { return methodPublicWhitelist(); }
@@ -157,4 +185,15 @@
   public int callMethodPackageBlacklist() { return methodPackageBlacklist(); }
   public int callMethodProtectedBlacklist() { return methodProtectedBlacklist(); }
 
+  public int callMethodPublicBlacklistAndCorePlatformApi() {
+    return methodPublicBlacklistAndCorePlatformApi();
+  }
+
+  public int callMethodPackageBlacklistAndCorePlatformApi() {
+    return methodPackageBlacklistAndCorePlatformApi();
+  }
+
+  public int callMethodProtectedBlacklistAndCorePlatformApi() {
+    return methodProtectedBlacklistAndCorePlatformApi();
+  }
 }
diff --git a/test/674-hiddenapi/src/ParentInterface.java b/test/674-hiddenapi/src/ParentInterface.java
index f79ac9d..1c5b58f 100644
--- a/test/674-hiddenapi/src/ParentInterface.java
+++ b/test/674-hiddenapi/src/ParentInterface.java
@@ -20,22 +20,26 @@
   static int fieldPublicStaticLightGreylist = 12;
   static int fieldPublicStaticDarkGreylist = 13;
   static int fieldPublicStaticBlacklist = 14;
+  static int fieldPublicStaticBlacklistAndCorePlatformApi = 15;
 
   // INSTANCE METHOD
   int methodPublicWhitelist();
   int methodPublicLightGreylist();
   int methodPublicDarkGreylist();
   int methodPublicBlacklist();
+  int methodPublicBlacklistAndCorePlatformApi();
 
   // STATIC METHOD
   static int methodPublicStaticWhitelist() { return 21; }
   static int methodPublicStaticLightGreylist() { return 22; }
   static int methodPublicStaticDarkGreylist() { return 23; }
   static int methodPublicStaticBlacklist() { return 24; }
+  static int methodPublicStaticBlacklistAndCorePlatformApi() { return 25; }
 
   // DEFAULT METHOD
   default int methodPublicDefaultWhitelist() { return 31; }
   default int methodPublicDefaultLightGreylist() { return 32; }
   default int methodPublicDefaultDarkGreylist() { return 33; }
   default int methodPublicDefaultBlacklist() { return 34; }
+  default int methodPublicDefaultBlacklistAndCorePlatformApi() { return 35; }
 }
diff --git a/test/999-redefine-hiddenapi/src/Main.java b/test/999-redefine-hiddenapi/src/Main.java
index 3d9bbda..014ea16 100644
--- a/test/999-redefine-hiddenapi/src/Main.java
+++ b/test/999-redefine-hiddenapi/src/Main.java
@@ -27,7 +27,7 @@
     init();
 
     // Load the '-ex' APK and attach it to the boot class path.
-    appendToBootClassLoader(DEX_EXTRA);
+    appendToBootClassLoader(DEX_EXTRA, /* isCorePlatform */ false);
 
     // Find the test class in boot class loader and verify that its members are hidden.
     Class<?> klass = Class.forName("art.Test999", true, BOOT_CLASS_LOADER);
@@ -67,7 +67,7 @@
   private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
 
   // Native functions. Note that these are implemented in 674-hiddenapi/hiddenapi.cc.
-  private static native void appendToBootClassLoader(String dexPath);
+  private static native void appendToBootClassLoader(String dexPath, boolean isCorePlatform);
   private static native void init();
 
   /**