Add out-of-memory (OOM) kills to ApplicationExitInfo

Adding the capability to track processes that have been affected by the
OOM killer.

Test: Trigger OOM kill and verify `noteAppKill()` is called with the matching OOM victim data.
Bug: 244232958
Bug: 234500267
Change-Id: I7e948a7826751a5a6f64f9ac9614864b3632fb4e
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 7b3d017..d15c79f 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -468,6 +468,15 @@
      */
     public static final int SUBREASON_EXCESSIVE_BINDER_OBJECTS = 29;
 
+    /**
+     * The process was killed by the [kernel] Out-of-memory (OOM) killer; this
+     * would be set only when the reason is {@link #REASON_LOW_MEMORY}.
+     *
+     * For internal use only.
+     * @hide
+     */
+    public static final int SUBREASON_OOM_KILL = 30;
+
     // If there is any OEM code which involves additional app kill reasons, it should
     // be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
 
@@ -644,6 +653,7 @@
         SUBREASON_PACKAGE_UPDATE,
         SUBREASON_UNDELIVERED_BROADCAST,
         SUBREASON_EXCESSIVE_BINDER_OBJECTS,
+        SUBREASON_OOM_KILL,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SubReason {}
@@ -1371,6 +1381,8 @@
                 return "UNDELIVERED BROADCAST";
             case SUBREASON_EXCESSIVE_BINDER_OBJECTS:
                 return "EXCESSIVE BINDER OBJECTS";
+            case SUBREASON_OOM_KILL:
+                return "OOM KILL";
             default:
                 return "UNKNOWN";
         }
diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
new file mode 100644
index 0000000..151a65f
--- /dev/null
+++ b/core/java/android/os/OomKillRecord.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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 android.os;
+
+
+/**
+ * Expected data to get back from the OOM event's file.
+ * Note that this should be equivalent to the struct <b>OomKill</b> inside
+ * <pre>
+ * system/memory/libmeminfo/libmemevents/include/memevents.h
+ * </pre>
+ *
+ * @hide
+ */
+public final class OomKillRecord {
+    private long mTimeStampInMillis;
+    private int mPid;
+    private int mUid;
+    private String mProcessName;
+    private short mOomScoreAdj;
+
+    public OomKillRecord(long timeStampInMillis, int pid, int uid,
+                            String processName, short oomScoreAdj) {
+        this.mTimeStampInMillis = timeStampInMillis;
+        this.mPid = pid;
+        this.mUid = uid;
+        this.mProcessName = processName;
+        this.mOomScoreAdj = oomScoreAdj;
+    }
+
+    public long getTimestampMilli() {
+        return mTimeStampInMillis;
+    }
+
+    public int getPid() {
+        return mPid;
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public String getProcessName() {
+        return mProcessName;
+    }
+
+    public short getOomScoreAdj() {
+        return mOomScoreAdj;
+    }
+}
diff --git a/services/core/java/com/android/server/am/OomConnection.java b/services/core/java/com/android/server/am/OomConnection.java
new file mode 100644
index 0000000..17a4ce5
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomConnection.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 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.server.am;
+
+import android.os.OomKillRecord;
+import android.util.Slog;
+
+/** Connection to the out-of-memory (OOM) events' file */
+public final class OomConnection {
+    private static final String TAG = "OomConnection";
+
+    /** Connection listener interface */
+    public interface OomConnectionListener {
+
+        /**
+         * Callback function to handle the newest OOM kills.
+         *
+         * @param oomKills List of oom kills received from `waitOom()`
+         */
+        void handleOomEvent(OomKillRecord[] oomKills);
+    }
+
+    private final OomConnectionListener mOomListener;
+
+    private final OomConnectionThread mOomConnectionThread;
+
+    private static native OomKillRecord[] waitOom();
+
+    public OomConnection(OomConnectionListener listener) {
+        mOomListener = listener;
+        mOomConnectionThread = new OomConnectionThread();
+        mOomConnectionThread.start();
+    }
+
+    private final class OomConnectionThread extends Thread {
+        public void run() {
+            while (true) {
+                OomKillRecord[] oom_kills = null;
+                try {
+                    oom_kills = waitOom();
+                    mOomListener.handleOomEvent(oom_kills);
+                } catch (RuntimeException e) {
+                    Slog.e(TAG, "failed waiting for OOM events: " + e);
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0615ecf..74d84e4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -97,6 +97,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.OomKillRecord;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -412,6 +413,8 @@
 
     private static LmkdConnection sLmkdConnection = null;
 
+    private static OomConnection sOomConnection = null;
+
     private boolean mOomLevelsSet = false;
 
     private boolean mAppDataIsolationEnabled = false;
@@ -855,6 +858,21 @@
                     THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
             sKillThread.start();
             sKillHandler = new KillHandler(sKillThread.getLooper());
+            sOomConnection = new OomConnection(new OomConnection.OomConnectionListener() {
+                @Override
+                public void handleOomEvent(OomKillRecord[] oomKills) {
+                    for (OomKillRecord oomKill: oomKills) {
+                        synchronized (mProcLock) {
+                            noteAppKill(
+                                oomKill.getPid(),
+                                oomKill.getUid(),
+                                ApplicationExitInfo.REASON_LOW_MEMORY,
+                                ApplicationExitInfo.SUBREASON_OOM_KILL,
+                                "oom");
+                        }
+                    }
+                }
+            });
             sLmkdConnection = new LmkdConnection(sKillThread.getLooper().getQueue(),
                     new LmkdConnection.LmkdConnectionListener() {
                         @Override
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ec5378f..bc70658 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -78,6 +78,7 @@
         "onload.cpp",
         ":lib_cachedAppOptimizer_native",
         ":lib_gameManagerService_native",
+        ":lib_oomConnection_native",
     ],
 
     include_dirs: [
@@ -122,6 +123,7 @@
         "libhardware_legacy",
         "libhidlbase",
         "libmeminfo",
+        "libmemevents",
         "libmemtrackproxy",
         "libmtp",
         "libnativehelper",
@@ -239,3 +241,8 @@
         "com_android_server_app_GameManagerService.cpp",
     ],
 }
+
+filegroup {
+    name: "lib_oomConnection_native",
+    srcs: ["com_android_server_am_OomConnection.cpp",],
+}
diff --git a/services/core/jni/com_android_server_am_OomConnection.cpp b/services/core/jni/com_android_server_am_OomConnection.cpp
new file mode 100644
index 0000000..e892d23
--- /dev/null
+++ b/services/core/jni/com_android_server_am_OomConnection.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define LOG_TAG "OomConnection"
+
+#include <core_jni_helpers.h>
+#include <jni.h>
+#include <memevents/memevents.h>
+
+namespace android {
+
+// Used to cache the results of the JNI name lookup
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} sOomKillRecordInfo;
+
+static memevents::MemEventListener memevent_listener;
+
+/**
+ * Initialize listening and waiting for new out-of-memory (OOM) events to occur.
+ * Once a OOM event is detected, we then fetch the list of OOM kills, and return
+ * a corresponding java array with the information gathered.
+ *
+ * In the case that we encounter an error, we make sure to close the epfd, and
+ * the OOM file descriptor, by calling `deregisterAllEvents()`.
+ *
+ * @return list of `android.os.OomKillRecord`
+ * @throws java.lang.RuntimeException
+ */
+static jobjectArray android_server_am_OomConnection_waitOom(JNIEnv* env, jobject) {
+    const memevents::MemEvent oom_event = memevents::MemEvent::OOM_KILL;
+    if (!memevent_listener.registerEvent(oom_event)) {
+        memevent_listener.deregisterAllEvents();
+        jniThrowRuntimeException(env, "listener failed to register to OOM events");
+        return nullptr;
+    }
+
+    memevents::MemEvent event_received;
+    do {
+        event_received = memevent_listener.listen();
+        if (event_received == memevents::MemEvent::ERROR) {
+            memevent_listener.deregisterAllEvents();
+            jniThrowRuntimeException(env, "listener received error event");
+            return nullptr;
+        }
+    } while (event_received != oom_event);
+
+    std::vector<memevents::OomKill> oom_events;
+    if (!memevent_listener.getOomEvents(oom_events)) {
+        memevent_listener.deregisterAllEvents();
+        jniThrowRuntimeException(env, "Failed to get OOM events");
+        return nullptr;
+    }
+
+    jobjectArray java_oom_array =
+            env->NewObjectArray(oom_events.size(), sOomKillRecordInfo.clazz, nullptr);
+    if (java_oom_array == NULL) {
+        memevent_listener.deregisterAllEvents();
+        jniThrowRuntimeException(env, "Failed to create OomKillRecord array");
+        return nullptr;
+    }
+
+    for (int i = 0; i < oom_events.size(); i++) {
+        const memevents::OomKill oom_event = oom_events[i];
+        jstring process_name = env->NewStringUTF(oom_event.process_name);
+        if (process_name == NULL) {
+            memevent_listener.deregisterAllEvents();
+            jniThrowRuntimeException(env, "Failed creating java string for process name");
+        }
+        jobject java_oom_kill = env->NewObject(sOomKillRecordInfo.clazz, sOomKillRecordInfo.ctor,
+                                               oom_event.timestamp_ms, oom_event.pid, oom_event.uid,
+                                               process_name, oom_event.oom_score_adj);
+        if (java_oom_kill == NULL) {
+            memevent_listener.deregisterAllEvents();
+            jniThrowRuntimeException(env, "Failed to create OomKillRecord object");
+            return java_oom_array;
+        }
+        env->SetObjectArrayElement(java_oom_array, i, java_oom_kill);
+    }
+    return java_oom_array;
+}
+
+static const JNINativeMethod sOomConnectionMethods[] = {
+        /* name, signature, funcPtr */
+        {"waitOom", "()[Landroid/os/OomKillRecord;",
+         (void*)android_server_am_OomConnection_waitOom},
+};
+
+int register_android_server_am_OomConnection(JNIEnv* env) {
+    sOomKillRecordInfo.clazz = FindClassOrDie(env, "android/os/OomKillRecord");
+    sOomKillRecordInfo.clazz = MakeGlobalRefOrDie(env, sOomKillRecordInfo.clazz);
+
+    sOomKillRecordInfo.ctor =
+            GetMethodIDOrDie(env, sOomKillRecordInfo.clazz, "<init>", "(JIILjava/lang/String;S)V");
+
+    return RegisterMethodsOrDie(env, "com/android/server/am/OomConnection", sOomConnectionMethods,
+                                NELEM(sOomConnectionMethods));
+}
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index f6f6737..df44895 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -52,6 +52,7 @@
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
 int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_server_am_OomConnection(JNIEnv* env);
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
@@ -112,6 +113,7 @@
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
     register_android_hardware_display_DisplayViewport(env);
+    register_android_server_am_OomConnection(env);
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_LowMemDetector(env);
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);