MediaDrm: API to retrieve LogMessages

Bug: 162255728
Test: cts/gts MediaDrmTest
Change-Id: Icba4945a75eb00b737d65572b40cf2035504ca08
diff --git a/core/api/current.txt b/core/api/current.txt
index e264f1d..09be18b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -21591,6 +21591,7 @@
     method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel();
     method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String);
     method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException;
+    method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages();
     method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel();
     method public static int getMaxSecurityLevel();
     method public int getMaxSessionCount();
@@ -21699,6 +21700,12 @@
     field public static final int STATUS_USABLE_IN_FUTURE = 5; // 0x5
   }
 
+  public static class MediaDrm.LogMessage {
+    field @NonNull public final String message;
+    field public final int priority;
+    field public final long timestampMillis;
+  }
+
   public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
     method @NonNull public String getDiagnosticInfo();
   }
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 49f9d66..adb8a54c 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -2493,4 +2494,80 @@
             return mPlaybackId;
         }
     }
+
+    /**
+     * Returns recent {@link LogMessage LogMessages} associated with this {@link MediaDrm}
+     * instance.
+     */
+    @NonNull
+    public native List<LogMessage> getLogMessages();
+
+    /**
+     * A {@link LogMessage} records an event in the {@link MediaDrm} framework
+     * or vendor plugin.
+     */
+    public static class LogMessage {
+
+        /**
+         * Timing of the recorded event measured in milliseconds since the Epoch,
+         * 1970-01-01 00:00:00 +0000 (UTC).
+         */
+        public final long timestampMillis;
+
+        /**
+         * Priority of the recorded event.
+         * <p>
+         * Possible priority constants are defined in {@link Log}, e.g.:
+         * <ul>
+         *     <li>{@link Log#ASSERT}</li>
+         *     <li>{@link Log#ERROR}</li>
+         *     <li>{@link Log#WARN}</li>
+         *     <li>{@link Log#INFO}</li>
+         *     <li>{@link Log#DEBUG}</li>
+         *     <li>{@link Log#VERBOSE}</li>
+         * </ul>
+         */
+        @Log.Level
+        public final int priority;
+
+        /**
+         * Description of the recorded event.
+         */
+        @NonNull
+        public final String message;
+
+        private LogMessage(long timestampMillis, int priority, String message) {
+            this.timestampMillis = timestampMillis;
+            if (priority < Log.VERBOSE || priority > Log.ASSERT) {
+                throw new IllegalArgumentException("invalid log priority " + priority);
+            }
+            this.priority = priority;
+            this.message = message;
+        }
+
+        private char logPriorityChar() {
+            switch (priority) {
+                case Log.VERBOSE:
+                    return 'V';
+                case Log.DEBUG:
+                    return 'D';
+                case Log.INFO:
+                    return 'I';
+                case Log.WARN:
+                    return 'W';
+                case Log.ERROR:
+                    return 'E';
+                case Log.ASSERT:
+                    return 'F';
+                default:
+            }
+            return 'U';
+        }
+
+        @Override
+        public String toString() {
+            return String.format("LogMessage{%s %c %s}",
+                    Instant.ofEpochMilli(timestampMillis), logPriorityChar(), message);
+        }
+    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 4972529..6160b81 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -64,6 +64,7 @@
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hardware.drm@1.3",
+        "android.hardware.drm@1.4",
         "android.hidl.memory@1.0",
         "android.hidl.token@1.0-utils",
     ],
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 0e8719e..22c3572 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -37,6 +37,7 @@
 #include <mediadrm/DrmUtils.h>
 #include <mediadrm/IDrmMetricsConsumer.h>
 #include <mediadrm/IDrm.h>
+#include <utils/Vector.h>
 
 using ::android::os::PersistableBundle;
 namespace drm = ::android::hardware::drm;
@@ -187,6 +188,11 @@
     jclass classId;
 };
 
+struct LogMessageFields {
+    jmethodID init;
+    jclass classId;
+};
+
 struct fields_t {
     jfieldID context;
     jmethodID post_event;
@@ -208,6 +214,7 @@
     jmethodID createFromParcelId;
     jclass parcelCreatorClassId;
     KeyStatusFields keyStatus;
+    LogMessageFields logMessage;
 };
 
 static fields_t gFields;
@@ -224,6 +231,19 @@
     return result;
 }
 
+jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessage> &logs) {
+    jclass clazz = gFields.arraylistClassId;
+    jobject arrayList = env->NewObject(clazz, gFields.arraylist.init);
+    clazz = gFields.logMessage.classId;
+    for (auto log: logs) {
+        jobject jLog = env->NewObject(clazz, gFields.logMessage.init,
+                static_cast<jlong>(log.timeMs),
+                static_cast<jint>(log.priority),
+                env->NewStringUTF(log.message.c_str()));
+        env->CallBooleanMethod(arrayList, gFields.arraylist.add, jLog);
+    }
+    return arrayList;
+}
 }  // namespace anonymous
 
 // ----------------------------------------------------------------------------
@@ -907,6 +927,10 @@
     FIND_CLASS(clazz, "android/media/MediaDrm$KeyStatus");
     gFields.keyStatus.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
     GET_METHOD_ID(gFields.keyStatus.init, clazz, "<init>", "([BI)V");
+
+    FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage");
+    gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+    GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V");
 }
 
 static void android_media_MediaDrm_native_setup(
@@ -1996,6 +2020,22 @@
     throwExceptionAsNecessary(env, err, "Failed to set playbackId");
 }
 
+static jobject android_media_MediaDrm_getLogMessages(
+        JNIEnv *env, jobject thiz) {
+    sp<IDrm> drm = GetDrm(env, thiz);
+    if (!CheckDrm(env, drm)) {
+        return NULL;
+    }
+
+    Vector<drm::V1_4::LogMessage> logs;
+    status_t err = drm->getLogMessages(logs);
+    ALOGI("drm->getLogMessages %zu logs", logs.size());
+    if (throwExceptionAsNecessary(env, err, "Failed to get log messages")) {
+        return NULL;
+    }
+    return hidlLogMessagesToJavaList(env, logs);
+}
+
 static const JNINativeMethod gMethods[] = {
     { "native_release", "()V", (void *)android_media_MediaDrm_native_release },
 
@@ -2123,6 +2163,9 @@
 
     { "setPlaybackId", "([BLjava/lang/String;)V",
       (void *)android_media_MediaDrm_setPlaybackId },
+
+    { "getLogMessages", "()Ljava/util/List;",
+      (void *)android_media_MediaDrm_getLogMessages },
 };
 
 int register_android_media_Drm(JNIEnv *env) {