Add compat framework logging to ART

Log first call for every change id in ART to logcat.

Test: manual
Bug: 153061480

Change-Id: I37ff5b88572478ae6c24b0b7dec2020da03b2172
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 12f447b..393c2cb 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -78,6 +78,7 @@
         "class_root.cc",
         "class_table.cc",
         "common_throws.cc",
+        "compat_framework.cc",
         "compiler_filter.cc",
         "debug_print.cc",
         "debugger.cc",
diff --git a/runtime/compat_framework.cc b/runtime/compat_framework.cc
new file mode 100644
index 0000000..40c4540
--- /dev/null
+++ b/runtime/compat_framework.cc
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "compat_framework.h"
+
+#include "android-base/logging.h"
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace art {
+
+// Compat change states as strings.
+static constexpr char kUnknownChangeState[] = "UNKNOWN";
+static constexpr char kEnabledChangeState[] = "ENABLED";
+static constexpr char kDisabledChangeState[] = "DISABLED";
+static constexpr char kLoggedState[] = "LOGGED";
+
+bool CompatFramework::IsChangeEnabled(uint64_t change_id) {
+  const auto enabled = disabled_compat_changes_.count(change_id) == 0;
+  ReportChange(change_id, enabled ? ChangeState::kEnabled : ChangeState::kDisabled);
+  return enabled;
+}
+
+void CompatFramework::LogChange(uint64_t change_id) {
+  ReportChange(change_id, ChangeState::kLogged);
+}
+
+void CompatFramework::ReportChange(uint64_t change_id, ChangeState state) {
+  bool already_reported = reported_compat_changes_.count(change_id) != 0;
+  if (already_reported) {
+    return;
+  }
+  LOG(DEBUG) << "Compat change id reported: " << change_id << "; UID " << getuid()
+            << "; state: " << ChangeStateToString(state);
+  // TODO(145743810): add an up call to java to log to statsd
+  reported_compat_changes_.emplace(change_id);
+}
+
+std::string_view CompatFramework::ChangeStateToString(ChangeState state) {
+  switch (state) {
+    case ChangeState::kUnknown:
+      return kUnknownChangeState;
+    case ChangeState::kEnabled:
+      return kEnabledChangeState;
+    case ChangeState::kDisabled:
+      return kDisabledChangeState;
+    case ChangeState::kLogged:
+      return kLoggedState;
+  }
+}
+
+}  // namespace art
diff --git a/runtime/compat_framework.h b/runtime/compat_framework.h
new file mode 100644
index 0000000..0b2426f
--- /dev/null
+++ b/runtime/compat_framework.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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_RUNTIME_COMPAT_FRAMEWORK_H_
+#define ART_RUNTIME_COMPAT_FRAMEWORK_H_
+
+#include <set>
+
+#include "base/string_view_cpp20.h"
+
+namespace art {
+
+// ART counterpart of the compat framework (go/compat-framework).
+// Created in order to avoid repeated up-calls to Java.
+class CompatFramework {
+ public:
+  // Compat change reported state
+  // This must be kept in sync with AppCompatibilityChangeReported.State in
+  // frameworks/base/cmds/statsd/src/atoms.proto
+  enum class ChangeState {
+    kUnknown,
+    kEnabled,
+    kDisabled,
+    kLogged
+  };
+
+  void SetDisabledCompatChanges(const std::set<uint64_t>& disabled_changes) {
+    disabled_compat_changes_ = disabled_changes;
+  }
+
+  const std::set<uint64_t>& GetDisabledCompatChanges() const {
+    return disabled_compat_changes_;
+  }
+  // Query if a given compatibility change is enabled for the current process.
+  // This also gets logged to logcat, and we add the information we logged in
+  // reported_compat_changes_. This ensures we only log once per change id for the app's lifetime.
+  bool IsChangeEnabled(uint64_t change_id);
+
+  // Logs that the code path for this compatibility change has been reached.
+  // This also gets logged to logcat, and we add the information we logged in
+  // reported_compat_changes_. This ensures we only log once per change id for the app's lifetime.
+  void LogChange(uint64_t change_id);
+
+ private:
+  // Get a string equivalent for a compatibility change state.
+  static std::string_view ChangeStateToString(ChangeState s);
+  // Report the state of a compatibility change to logcat.
+  // TODO(145743810): also report to statsd.
+  void ReportChange(uint64_t change_id, ChangeState state);
+
+  // A set of disabled compat changes for the running app, all other changes are enabled.
+  std::set<uint64_t> disabled_compat_changes_;
+
+  // A set of repoted compat changes for the running app.
+  std::set<uint64_t> reported_compat_changes_;
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_COMPAT_FRAMEWORK_H_
diff --git a/runtime/hidden_api.cc b/runtime/hidden_api.cc
index eb7f7b1..62ff3ff 100644
--- a/runtime/hidden_api.cc
+++ b/runtime/hidden_api.cc
@@ -20,6 +20,7 @@
 
 #include "art_field-inl.h"
 #include "art_method-inl.h"
+#include "compat_framework.h"
 #include "base/dumpable.h"
 #include "base/file_utils.h"
 #include "dex/class_accessor-inl.h"
@@ -476,6 +477,7 @@
 bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {
   DCHECK(member != nullptr);
   Runtime* runtime = Runtime::Current();
+  CompatFramework& compatFramework = runtime->GetCompatFramework();
 
   EnforcementPolicy hiddenApiPolicy = runtime->GetHiddenApiEnforcementPolicy();
   DCHECK(hiddenApiPolicy != EnforcementPolicy::kDisabled)
@@ -501,10 +503,10 @@
     } else {
       switch (api_list.GetMaxAllowedSdkVersion()) {
         case SdkVersion::kP:
-          deny_access = runtime->isChangeEnabled(kHideMaxtargetsdkPHiddenApis);
+          deny_access = compatFramework.IsChangeEnabled(kHideMaxtargetsdkPHiddenApis);
           break;
         case SdkVersion::kQ:
-          deny_access = runtime->isChangeEnabled(kHideMaxtargetsdkQHiddenApis);
+          deny_access = compatFramework.IsChangeEnabled(kHideMaxtargetsdkQHiddenApis);
           break;
         default:
           deny_access = IsSdkVersionSetAndMoreThan(runtime->GetTargetSdkVersion(),
diff --git a/runtime/hidden_api_test.cc b/runtime/hidden_api_test.cc
index 6e573de..6a002e5 100644
--- a/runtime/hidden_api_test.cc
+++ b/runtime/hidden_api_test.cc
@@ -83,13 +83,14 @@
   }
 
   void setChangeIdState(uint64_t change, bool enabled) {
-    std::set<uint64_t> disabled_changes = runtime_->GetDisabledCompatChanges();
+    CompatFramework& compat_framework = runtime_->GetCompatFramework();
+    std::set<uint64_t> disabled_changes = compat_framework.GetDisabledCompatChanges();
     if (enabled) {
       disabled_changes.erase(change);
     } else {
       disabled_changes.insert(change);
     }
-    runtime_->SetDisabledCompatChanges(disabled_changes);
+    compat_framework.SetDisabledCompatChanges(disabled_changes);
   }
 
   bool ShouldDenyAccess(hiddenapi::ApiList list) REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 2d0c60d..2abdd98 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -277,7 +277,7 @@
   for (int i = 0; i < length; i++) {
     disabled_compat_changes_set.insert(static_cast<uint64_t>(elements[i]));
   }
-  Runtime::Current()->SetDisabledCompatChanges(disabled_compat_changes_set);
+  Runtime::Current()->GetCompatFramework().SetDisabledCompatChanges(disabled_compat_changes_set);
 }
 
 static inline size_t clamp_to_size_t(jlong n) {
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 4443045..776b14f 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -24,6 +24,7 @@
 #include "class_linker-inl.h"
 #include "class_root-inl.h"
 #include "common_throws.h"
+#include "compat_framework.h"
 #include "dex/descriptors_names.h"
 #include "dex/dex_file-inl.h"
 #include "dex/dex_file_annotations.h"
@@ -104,8 +105,9 @@
         // and walking over this frame would cause a null pointer dereference
         // (e.g. in 691-hiddenapi-proxy).
         ObjPtr<mirror::Class> proxy_class = GetClassRoot<mirror::Proxy>();
+        CompatFramework& compat_framework = Runtime::Current()->GetCompatFramework();
         if (declaring_class->IsInSamePackage(proxy_class) && declaring_class != proxy_class) {
-          if (Runtime::Current()->isChangeEnabled(kPreventMetaReflectionBlocklistAccess)) {
+          if (compat_framework.IsChangeEnabled(kPreventMetaReflectionBlocklistAccess)) {
             return true;
           }
         }
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index de9cfe4..466a2fb 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -269,6 +269,7 @@
       preinitialization_transactions_(),
       verify_(verifier::VerifyMode::kNone),
       target_sdk_version_(static_cast<uint32_t>(SdkVersion::kUnset)),
+      compat_framework_(),
       implicit_null_checks_(false),
       implicit_so_checks_(false),
       implicit_suspend_checks_(false),
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 0746130..de56de5 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -32,6 +32,7 @@
 #include "base/mem_map.h"
 #include "base/metrics.h"
 #include "base/string_view_cpp20.h"
+#include "compat_framework.h"
 #include "deoptimization_kind.h"
 #include "dex/dex_file_types.h"
 #include "experimental_flags.h"
@@ -672,17 +673,8 @@
     return target_sdk_version_;
   }
 
-  void SetDisabledCompatChanges(const std::set<uint64_t>& disabled_changes) {
-    disabled_compat_changes_ = disabled_changes;
-  }
-
-  std::set<uint64_t> GetDisabledCompatChanges() const {
-    return disabled_compat_changes_;
-  }
-
-  bool isChangeEnabled(uint64_t change_id) const {
-    // TODO(145743810): add an up call to java to log to statsd
-    return disabled_compat_changes_.count(change_id) == 0;
+  CompatFramework& GetCompatFramework() {
+    return compat_framework_;
   }
 
   uint32_t GetZygoteMaxFailedBoots() const {
@@ -1172,8 +1164,8 @@
   // Specifies target SDK version to allow workarounds for certain API levels.
   uint32_t target_sdk_version_;
 
-  // A set of disabled compat changes for the running app, all other changes are enabled.
-  std::set<uint64_t> disabled_compat_changes_;
+  // ART counterpart for the compat framework (go/compat-framework).
+  CompatFramework compat_framework_;
 
   // Implicit checks flags.
   bool implicit_null_checks_;       // NullPointer checks are implicit.
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
index 6113e97..ebe9d10 100644
--- a/test/674-hiddenapi/hiddenapi.cc
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -321,7 +321,8 @@
 
 extern "C" JNIEXPORT void JNICALL Java_Reflection_setHiddenApiCheckHardening(JNIEnv*, jclass,
     jboolean value) {
-  std::set<uint64_t> disabled_changes = Runtime::Current()->GetDisabledCompatChanges();
+  CompatFramework& compat_framework = Runtime::Current()->GetCompatFramework();
+  std::set<uint64_t> disabled_changes = compat_framework.GetDisabledCompatChanges();
   if (value == JNI_TRUE) {
     // If hidden api check hardening is enabled, remove it from the set of disabled changes.
     disabled_changes.erase(kPreventMetaReflectionBlocklistAccess);
@@ -329,7 +330,7 @@
     // If hidden api check hardening is disabled, add it to the set of disabled changes.
     disabled_changes.insert(kPreventMetaReflectionBlocklistAccess);
   }
-  Runtime::Current()->SetDisabledCompatChanges(disabled_changes);
+  compat_framework.SetDisabledCompatChanges(disabled_changes);
 }
 
 }  // namespace Test674HiddenApi