Add logging funtionatily to the example app.

Logs can now be sent over from CPP to Kotlin and displayed in the
LogOutput Card.

Bug: 296272152
Test: Built and run on device.
Change-Id: I79641dd9fbd6d04db0615579591a2bef4b24c914
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
index d6305f8..bb573c5 100644
--- a/libs/bufferstreams/examples/app/Android.bp
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -23,6 +23,9 @@
     kotlincflags: [
         "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
     ],
+    optimize: {
+        proguard_flags_files: ["proguard-rules.pro"],
+    },
 
     resource_dirs: ["res"],
 
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
index a2db934..ede7793 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
@@ -10,18 +10,18 @@
      * A native method that is implemented by the 'bufferstreamsdemoapp' native library, which is
      * packaged with this application.
      */
-    external fun stringFromJNI()
-    external fun testBufferQueueCreation()
+    external fun stringFromJNI(): String;
+    external fun testBufferQueueCreation();
 
     companion object {
-        fun companion_stringFromJNI() {
+        fun companion_stringFromJNI(): String {
             val instance = BufferStreamJNI()
-            instance.stringFromJNI()
+            return instance.stringFromJNI()
         }
 
         fun companion_testBufferQueueCreation() {
             val instance = BufferStreamJNI()
-            instance.testBufferQueueCreation()
+            return instance.testBufferQueueCreation()
         }
     }
 }
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
index 46ce028..95e415e 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
@@ -4,10 +4,8 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.Button
-import androidx.compose.material3.Card
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -17,17 +15,21 @@
 @Composable
 fun DemoScreen1(modifier: Modifier = Modifier) {
     Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
-        Card(modifier = Modifier.fillMaxWidth().weight(1f, false).padding(16.dp).height(400.dp)) {
-            Text("Log output", modifier = Modifier.padding(16.dp))
-        }
+        LogOutput.getInstance().LogOutputComposable()
         Row(modifier = Modifier.weight(1f, false).padding(16.dp)) {
             Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
                 Button(
-                        modifier = Modifier.fillMaxWidth(),
-                        onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }
-                ) { Text("Run") }
-                OutlinedButton(modifier = Modifier.fillMaxWidth(), onClick = {}) { Text("Clear") }
+                    modifier = Modifier.fillMaxWidth(),
+                    onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }) {
+                        Text("Run")
+                    }
+
+                OutlinedButton(
+                    modifier = Modifier.fillMaxWidth(),
+                    onClick = { LogOutput.getInstance().clearText() }) {
+                        Text("Clear")
+                    }
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
new file mode 100644
index 0000000..3f0926f
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
@@ -0,0 +1,65 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import java.util.Collections
+
+/*
+LogOutput centralizes logging: storing, displaying, adding, and clearing log messages with
+thread safety. It is a singleton that's also accessed from C++. The private constructor will
+not allow this class to be initialized, limiting it to getInstance().
+ */
+class LogOutput private constructor() {
+    val logs = Collections.synchronizedList(mutableStateListOf<String>())
+
+    @Composable
+    fun LogOutputComposable() {
+        val rlogs = remember { logs }
+
+        Card(modifier = Modifier.fillMaxWidth().padding(16.dp).height(400.dp)) {
+            Column(
+                modifier =
+                    Modifier.padding(10.dp).size(380.dp).verticalScroll(rememberScrollState())) {
+                    for (log in rlogs) {
+                        Text(log, modifier = Modifier.padding(0.dp))
+                    }
+                }
+        }
+    }
+
+    fun clearText() {
+        logs.clear()
+    }
+
+    fun addLog(log: String) {
+        logs.add(log)
+    }
+
+    companion object {
+        @Volatile private var instance: LogOutput? = null
+
+        @JvmStatic
+        fun getInstance(): LogOutput {
+            if (instance == null) {
+                synchronized(this) {
+                    if (instance == null) {
+                        instance = LogOutput()
+                    }
+                }
+            }
+            return instance!!
+        }
+    }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
index f3f4404..2ccd8d7 100644
--- a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
@@ -31,17 +31,19 @@
 import androidx.navigation.compose.currentBackStackEntryAsState
 import androidx.navigation.compose.rememberNavController
 import com.android.graphics.bufferstreamsdemoapp.ui.theme.JetpackTheme
+import java.util.*
 
 class MainActivity : ComponentActivity() {
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         setContent {
             JetpackTheme {
                 Surface(
-                        modifier = Modifier.fillMaxSize(),
-                        color = MaterialTheme.colorScheme.background
-                ) { BufferDemosApp() }
+                    modifier = Modifier.fillMaxSize(),
+                    color = MaterialTheme.colorScheme.background) {
+                        BufferDemosApp()
+                    }
             }
         }
     }
@@ -67,38 +69,32 @@
     val backStackEntry by navController.currentBackStackEntryAsState()
     // Get the name of the current screen
     val currentScreen =
-            BufferDemoScreen.findByRoute(
-                    backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route
-            )
+        BufferDemoScreen.findByRoute(
+            backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route)
 
     Scaffold(
-            topBar = {
-                BufferDemosAppBar(
-                        currentScreen = currentScreen,
-                        canNavigateBack = navController.previousBackStackEntry != null,
-                        navigateUp = { navController.navigateUp() }
-                )
-            }
-    ) {
-        NavHost(
+        topBar = {
+            BufferDemosAppBar(
+                currentScreen = currentScreen,
+                canNavigateBack = navController.previousBackStackEntry != null,
+                navigateUp = { navController.navigateUp() })
+        }) {
+            NavHost(
                 navController = navController,
                 startDestination = BufferDemoScreen.Start.route,
-                modifier = Modifier.padding(10.dp)
-        ) {
-            composable(route = BufferDemoScreen.Start.route) {
-                DemoList(
-                        onButtonClicked = {
-                            navController.navigate(it)
-                        },
-                )
-            }
-            composable(route = BufferDemoScreen.Demo1.route) {
-                DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
-            }
-            composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
-            composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+                modifier = Modifier.padding(10.dp)) {
+                    composable(route = BufferDemoScreen.Start.route) {
+                        DemoList(
+                            onButtonClicked = { navController.navigate(it) },
+                        )
+                    }
+                    composable(route = BufferDemoScreen.Demo1.route) {
+                        DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
+                    }
+                    composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
+                    composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+                }
         }
-    }
 }
 
 @Composable
@@ -107,25 +103,25 @@
 
     Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
         Column(
-                modifier = Modifier.fillMaxWidth(),
-                horizontalAlignment = Alignment.CenterHorizontally,
-                verticalArrangement = Arrangement.spacedBy(8.dp)
-        ) {
-            Spacer(modifier = Modifier.height(100.dp))
-            Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
-            Spacer(modifier = Modifier.height(8.dp))
-        }
+            modifier = Modifier.fillMaxWidth(),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(8.dp)) {
+                Spacer(modifier = Modifier.height(100.dp))
+                Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
+                Spacer(modifier = Modifier.height(8.dp))
+            }
         Row(modifier = Modifier.weight(2f, false)) {
             Column(
-                    modifier = Modifier.fillMaxWidth(),
-                    horizontalAlignment = Alignment.CenterHorizontally,
-                    verticalArrangement = Arrangement.spacedBy(16.dp)
-            ) {
-                for (item in BufferDemoScreen.values()) {
-                    if (item.route != BufferDemoScreen.Start.route)
-                        SelectDemoButton(name = stringResource(item.title), onClick = { onButtonClicked(item.route) })
+                modifier = Modifier.fillMaxWidth(),
+                horizontalAlignment = Alignment.CenterHorizontally,
+                verticalArrangement = Arrangement.spacedBy(16.dp)) {
+                    for (item in BufferDemoScreen.values()) {
+                        if (item.route != BufferDemoScreen.Start.route)
+                            SelectDemoButton(
+                                name = stringResource(item.title),
+                                onClick = { onButtonClicked(item.route) })
+                    }
                 }
-            }
         }
     }
 }
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
index 3d3fee4..550ad22 100644
--- a/libs/bufferstreams/examples/app/jni/main.cpp
+++ b/libs/bufferstreams/examples/app/jni/main.cpp
@@ -13,25 +13,41 @@
 // limitations under the License.
 
 #include <jni.h>
+#include <string>
 
 #include <gui/BufferQueue.h>
 
-extern "C"
-{
-    JNIEXPORT jstring JNICALL
-    Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(
-            JNIEnv* env,
-            jobject /* this */) {
-        const char* hello = "Hello from C++";
-        return env->NewStringUTF(hello);
-    }
+void log(JNIEnv* env, std::string l) {
+    jclass clazz = env->FindClass("com/android/graphics/bufferstreamsdemoapp/LogOutput");
+    jmethodID getInstance = env->GetStaticMethodID(clazz, "getInstance",
+        "()Lcom/android/graphics/bufferstreamsdemoapp/LogOutput;");
+    jmethodID addLog = env->GetMethodID(clazz, "addLog", "(Ljava/lang/String;)V");
+    jobject dmg = env->CallStaticObjectMethod(clazz, getInstance);
 
-    JNIEXPORT void JNICALL
-    Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
-            JNIEnv* /* env */,
-            jobject /* this */) {
-        android::sp<android::IGraphicBufferProducer> producer;
-        android::sp<android::IGraphicBufferConsumer> consumer;
-        android::BufferQueue::createBufferQueue(&producer, &consumer);
-    }
+    jstring jlog = env->NewStringUTF(l.c_str());
+    env->CallVoidMethod(dmg, addLog, jlog);
+}
+
+extern "C" {
+
+JNIEXPORT jstring JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(JNIEnv* env,
+                                                                             jobject /* this */) {
+    const char* hello = "Hello from C++";
+    return env->NewStringUTF(hello);
+}
+
+JNIEXPORT void JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
+        JNIEnv* env, jobject /* thiz */) {
+
+    log(env, "Calling testBufferQueueCreation.");
+    android::sp<android::IGraphicBufferProducer> producer;
+    log(env, "Created producer.");
+    android::sp<android::IGraphicBufferConsumer> consumer;
+    log(env, "Created consumer.");
+    android::BufferQueue::createBufferQueue(&producer, &consumer);
+    log(env, "Created BufferQueue successfully.");
+    log(env, "Done!");
+}
 }
\ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/proguard-rules.pro b/libs/bufferstreams/examples/app/proguard-rules.pro
new file mode 100644
index 0000000..7a987fc
--- /dev/null
+++ b/libs/bufferstreams/examples/app/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-keep,allowoptimization,allowobfuscation class com.android.graphics.bufferstreamsdemoapp.** { *; }