Merge "Implement stats logging for Compatibility API."
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 52b7d4b..bf71998 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -319,6 +319,9 @@
             217 [(log_from_module) = "permissioncontroller"];
         PermissionAppsFragmentViewed permission_apps_fragment_viewed =
             218  [(log_from_module) = "permissioncontroller"];
+
+        AppCompatibilityChangeReported app_compatibility_change_reported =
+            228 [(allow_from_any_uid) = true];
     }
 
     // Pulled events will start at field 10000.
@@ -6779,3 +6782,39 @@
     }
     optional Category category = 6;
 }
+
+/**
+ * Logs when a compatibility change is affecting an app.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/android/app/AppCompatCallbacks.java and
+ *   frameworks/base/services/core/java/com/android/server/compat/PlatformCompat.java
+ */
+message AppCompatibilityChangeReported {
+    // The UID of the app being affected by the compatibilty change.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The ID of the change affecting the app.
+    optional int64 change_id = 2;
+
+    enum State {
+        UNKNOWN_STATE = 0;
+        ENABLED = 1;
+        DISABLED = 2;
+        LOGGED = 3;
+    }
+
+    // The state of the change - if logged from gating whether it was enabled or disabled, or just
+    // logged otherwise.
+    optional State state = 3;
+
+    enum Source {
+        UNKNOWN_SOURCE = 0;
+        APP_PROCESS = 1;
+        SYSTEM_SERVER = 2;
+    }
+
+    // Where it was logged from.
+    optional Source source = 4;
+
+}
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 17697db..08c97eb 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -19,6 +19,9 @@
 import android.compat.Compatibility;
 import android.os.Process;
 import android.util.Log;
+import android.util.StatsLog;
+
+import com.android.internal.compat.ChangeReporter;
 
 import java.util.Arrays;
 
@@ -28,10 +31,10 @@
  * @hide
  */
 public final class AppCompatCallbacks extends Compatibility.Callbacks {
-
     private static final String TAG = "Compatibility";
 
     private final long[] mDisabledChanges;
+    private final ChangeReporter mChangeReporter;
 
     /**
      * Install this class into the current process.
@@ -45,20 +48,29 @@
     private AppCompatCallbacks(long[] disabledChanges) {
         mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
         Arrays.sort(mDisabledChanges);
+        mChangeReporter = new ChangeReporter();
     }
 
     protected void reportChange(long changeId) {
-        Log.d(TAG, "Compat change reported: " + changeId + "; UID " + Process.myUid());
-        // TODO log via StatsLog
+        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
     }
 
     protected boolean isChangeEnabled(long changeId) {
         if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
             // Not present in the disabled array
-            reportChange(changeId);
+            reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
         }
+        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
         return false;
     }
 
+    private void reportChange(long changeId, int state) {
+        int uid = Process.myUid();
+        //TODO(b/138374585): Implement rate limiting for the logs.
+        Log.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+        mChangeReporter.reportChange(uid, changeId,
+                state, /* source */StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
+    }
+
 }
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
new file mode 100644
index 0000000..1ce071b
--- /dev/null
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package com.android.internal.compat;
+
+import android.util.StatsLog;
+
+/**
+ * A helper class to report changes to stats log.
+ *
+ * @hide
+ */
+public final class ChangeReporter {
+
+    /**
+     * Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string.
+     *
+     * @param state to transform
+     * @return a string representing the state
+     */
+    private static String stateToString(int state) {
+        switch (state) {
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED:
+                return "LOGGED";
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED:
+                return "ENABLED";
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED:
+                return "DISABLED";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Constructs and returns a string to be logged to logcat when a change is reported.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @return string to log
+     */
+    public static String createLogString(int uid, long changeId, int state) {
+        return String.format("Compat change id reported: %d; UID %d; state: %s", changeId, uid,
+                stateToString(state));
+    }
+
+    /**
+     * Report the change to stats log.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @param source   of the logging - app process or system server
+     */
+    public void reportChange(int uid, long changeId, int state, int source) {
+        //TODO(b/138374585): Implement rate limiting for stats log.
+        StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
+                state, source);
+    }
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index fc38735..81e507c 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.util.Slog;
+import android.util.StatsLog;
 
+import com.android.internal.compat.ChangeReporter;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
 
@@ -34,23 +36,27 @@
     private static final String TAG = "Compatibility";
 
     private final Context mContext;
+    private final ChangeReporter mChangeReporter;
 
     public PlatformCompat(Context context) {
         mContext = context;
+        mChangeReporter = new ChangeReporter();
     }
 
     @Override
     public void reportChange(long changeId, ApplicationInfo appInfo) {
-        Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid);
-        // TODO log via StatsLog
+        reportChange(changeId, appInfo, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
     }
 
     @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
         if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
-            reportChange(changeId, appInfo);
+            reportChange(changeId, appInfo,
+                    StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
         }
+        reportChange(changeId, appInfo,
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
         return false;
     }
 
@@ -59,4 +65,13 @@
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
         CompatConfig.get().dumpConfig(pw);
     }
+
+    private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
+        int uid = appInfo.uid;
+        //TODO(b/138374585): Implement rate limiting for the logs.
+        Slog.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+        mChangeReporter.reportChange(uid, changeId,
+                state, /* source */
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+    }
 }