aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-12-02 00:27:50 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-12-02 00:27:50 +0000
commit5c74f573ba7440b95b762608ced4a4db36134411 (patch)
treea23151903ff517dd777b28486fff1546ff6efad9
parent74155c9cc3035d0cee2a79b464a9fca3d9047f78 (diff)
parent1eee21ee5c1c3dafbadc0879565d26e4ea2f14bd (diff)
downloadCar-android13-d4-release.tar.gz
Change-Id: Id31cafee7e919fd778727275d76368bd2f438c30
-rw-r--r--car-evs-helper-lib/Android.bp37
-rw-r--r--car-evs-helper-lib/AndroidManifest.xml19
-rw-r--r--car-evs-helper-lib/README.md82
-rw-r--r--car-evs-helper-lib/jni/Android.bp (renamed from tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp)6
-rw-r--r--car-evs-helper-lib/jni/CarEvsBufferRenderer.cpp (renamed from tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/CarEvsCameraPreviewRenderer.cpp)20
-rw-r--r--car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java101
-rw-r--r--car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java (renamed from tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer.java)174
-rw-r--r--car-lib/src/android/car/telemetry/telemetry.proto5
-rw-r--r--car-maps-placeholder/res/values-ms/strings.xml2
-rw-r--r--cpp/evs/manager/aidl/include/VirtualCamera.h10
-rw-r--r--cpp/evs/manager/aidl/src/VirtualCamera.cpp125
-rw-r--r--packages/ScriptExecutor/src/BundleWrapper.cpp31
-rw-r--r--packages/ScriptExecutor/src/BundleWrapper.h3
-rw-r--r--packages/ScriptExecutor/src/JniUtils.cpp43
-rw-r--r--packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java57
-rw-r--r--packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java8
-rw-r--r--packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp48
-rw-r--r--service/proto/android/car/telemetry/atoms.proto14
-rw-r--r--service/src/com/android/car/telemetry/publisher/StatsPublisher.java78
-rw-r--r--service/src/com/android/car/telemetry/publisher/statsconverters/AtomListConverter.java3
-rw-r--r--service/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverter.java109
-rw-r--r--tests/CarEvsCameraPreviewApp/Android.bp7
-rw-r--r--tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraGLSurfaceView.java55
-rw-r--r--tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java26
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml25
-rw-r--r--tests/EmbeddedKitchenSinkApp/res/values/strings.xml3
-rw-r--r--tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java52
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java3
-rw-r--r--tests/carservice_unit_test/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverterTest.java192
29 files changed, 1034 insertions, 304 deletions
diff --git a/car-evs-helper-lib/Android.bp b/car-evs-helper-lib/Android.bp
new file mode 100644
index 0000000000..f383376a71
--- /dev/null
+++ b/car-evs-helper-lib/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 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.
+
+// This project contains libraries that are used internally, mostly by
+// CarService and CarServiceHelperService.
+//
+// They're not meant to be used by other system apps and hence are not
+// supported.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "car-evs-helper-lib",
+ srcs: [
+ "src/**/*.java",
+ ],
+ libs: [
+ "android.car",
+ ],
+ platform_apis: true,
+ required : [
+ "libcarevsglrenderer_jni",
+ ],
+}
diff --git a/car-evs-helper-lib/AndroidManifest.xml b/car-evs-helper-lib/AndroidManifest.xml
new file mode 100644
index 0000000000..ba31871a3c
--- /dev/null
+++ b/car-evs-helper-lib/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.car.internal.evs" >
+</manifest>
diff --git a/car-evs-helper-lib/README.md b/car-evs-helper-lib/README.md
new file mode 100644
index 0000000000..416a8a8cdd
--- /dev/null
+++ b/car-evs-helper-lib/README.md
@@ -0,0 +1,82 @@
+<!--
+ Copyright (C) 2022 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
+ -->
+# car-evs-helper-lib
+This directory contains two modules that are used by other apps to process
+CarEvsBufferDescriptor and render its contents to the display with EGL.
+* `car-evs-helper-lib:` This library contains `CarEvsGLSurfaceView` and
+ `CarEvsBufferRenderer` classes.
+* `libcarevsglrenderer_jni`: This is a JNI library `CarEvsBufferRenderer` uses
+ to render the contents of `CarEvsBufferDescriptor` with EGL.
+## How to use
+Please follow below instructions to delegate a `CarEvsBufferDescriptor` rendering
+to this library. A reference implementation is also available at
+`packages/services/Car/tests/CarEvsCameraPreviewApp`.
+1. Make the application refer to `car-evs-helper-lib` and
+ `libcarevsglrenderer_jni` libraries by adding below lines to `Android.bp`.
+```
+static_libs: ["car-evs-helper-lib"],
+jni_libs: ["libcarevsglrenderer_jni"],
+```
+2. Implement `CarEvsGLSurfaceView.Callback` interface. For example,
+```
+/**
+ * This method is called by the renderer to fetch a new frame to draw.
+ */
+@Override
+public CarEvsBufferDescriptor getNewFrame() {
+ synchronized(mLock) {
+ // Return a buffer to render.
+ return mBufferToRender;
+ }
+}
+/**
+ * This method is called by the renderer when it is done with a passed
+ * CarEvsBufferDescriptor object.
+ */
+@Override
+public void returnBuffer(CarEvsBufferDescriptor buffer) {
+ // Return a buffer to CarEvsService.
+ try {
+ mEvsManager.returnFrameBuffer(buffer);
+ } catch (Exception e) {
+ ...
+ }
+ ...
+}
+```
+3. Create `CarEvsGLSurfaceView` with the application context,
+ `CarEvsGLSurfaceView.Callback` object, and, optionally, a desired in-plane
+ rotation angle.
+```
+private CarEvsGLSurfaceView mView;
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+ ...
+ mView = CarEvsGLSurfaceView(getAppliation(), this, /* angleInDegree= */ 0);
+ ...
+}
+```
+4. Start a video stream and update wheneven new frame buffer arrives. For
+ example,
+```
+private final CarEvsManager.CarEvsStreamCallback mStreamHandler =
+ new CarEvsManager.CarEvsStreamCallback() {
+ ...
+ @Override
+ public void onNewFrame(CarEvsBufferDescriptor buffer) {
+ synchronized(mLock) {
+ mBufferToRender = buffer;
+ }
+ }
+}
+```
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp b/car-evs-helper-lib/jni/Android.bp
index d06620ac5b..702a23ee14 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp
+++ b/car-evs-helper-lib/jni/Android.bp
@@ -23,9 +23,7 @@ cc_library_shared {
sdk_version: "current",
- srcs: [
- "CarEvsCameraPreviewRenderer.cpp",
- ],
+ srcs: ["./**/*.cpp"],
header_libs: ["jni_headers"],
@@ -43,7 +41,7 @@ cc_library_shared {
},
cflags: [
- "-DLOG_TAG=\"CarEvsCameraRendererJNI\"",
+ "-DLOG_TAG=\"CarEvsBufferRendererJNI\"",
"-DGL_GLEXT_PROTOTYPES",
"-DEGL_EGLEXT_PROTOTYPES",
"-Wall",
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/CarEvsCameraPreviewRenderer.cpp b/car-evs-helper-lib/jni/CarEvsBufferRenderer.cpp
index 91ca40bd8e..654703ff13 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/CarEvsCameraPreviewRenderer.cpp
+++ b/car-evs-helper-lib/jni/CarEvsBufferRenderer.cpp
@@ -20,19 +20,18 @@
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
-
#include <android/hardware_buffer_jni.h>
+
#include <jni.h>
namespace {
-const char kClassName[] = "com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer";
+const char kClassName[] = "com/android/car/internal/evs/GLES20CarEvsBufferRenderer";
EGLImageKHR gKHRImage = EGL_NO_IMAGE_KHR;
-jboolean nativeUpdateTexture(
- JNIEnv* env, jobject /*thiz*/, jobject hardwareBufferObj, jint textureId) {
-
+jboolean nativeUpdateTexture(JNIEnv* env, jobject /*thiz*/, jobject hardwareBufferObj,
+ jint textureId) {
EGLDisplay eglCurrentDisplay = eglGetCurrentDisplay();
if (gKHRImage != EGL_NO_IMAGE_KHR) {
// Release a previous EGL image
@@ -49,11 +48,8 @@ jboolean nativeUpdateTexture(
// Create EGL image from a native hardware buffer
EGLClientBuffer eglBuffer = eglGetNativeClientBufferANDROID(nativeBuffer);
EGLint eglImageAttributes[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};
- gKHRImage = eglCreateImageKHR(eglCurrentDisplay,
- EGL_NO_CONTEXT,
- EGL_NATIVE_BUFFER_ANDROID,
- eglBuffer,
- eglImageAttributes);
+ gKHRImage = eglCreateImageKHR(eglCurrentDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
+ eglBuffer, eglImageAttributes);
if (gKHRImage == EGL_NO_IMAGE_KHR) {
return JNI_FALSE;
}
@@ -74,7 +70,7 @@ jboolean nativeUpdateTexture(
return JNI_TRUE;
}
-} // namespace unnamed
+} // namespace
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
JNIEnv* env;
@@ -85,7 +81,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
// Registers native methods
static const JNINativeMethod methods[] = {
{"nUpdateTexture", "(Landroid/hardware/HardwareBuffer;I)Z",
- reinterpret_cast<void*>(nativeUpdateTexture)},
+ reinterpret_cast<void*>(nativeUpdateTexture)},
};
jclass clazz = env->FindClass(kClassName);
diff --git a/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java b/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java
new file mode 100644
index 0000000000..c798ee39a9
--- /dev/null
+++ b/car-evs-helper-lib/src/com/android/car/internal/evs/CarEvsGLSurfaceView.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 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.car.internal.evs;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.car.evs.CarEvsBufferDescriptor;
+import android.content.Context;
+import android.opengl.GLSurfaceView;
+import android.view.MotionEvent;
+
+import com.android.internal.util.Preconditions;
+import com.android.car.internal.evs.GLES20CarEvsBufferRenderer;
+
+/**
+ * GPU-backed SurfaceView to render a hardware buffer described by {@link CarEvsBufferDescriptor}.
+ */
+public final class CarEvsGLSurfaceView extends GLSurfaceView {
+ private static final String TAG = CarEvsGLSurfaceView.class.getSimpleName();
+ private static final int DEFAULT_IN_PLANE_ROTATION_ANGLE = 0;
+
+ private final GLES20CarEvsBufferRenderer mRenderer;
+
+ /** An interface to pull and return {@code CarEvsBufferDescriptor} object to render. */
+ public interface BufferCallback {
+ /**
+ * Requests a new {@link CarEvsBufferDescriptor} to draw.
+ *
+ * This method may return a {@code null} if no new frame has been prepared since the last
+ * frame was drawn.
+ *
+ * @return {@link CarEvsBufferDescriptor} object to process.
+ */
+ @Nullable CarEvsBufferDescriptor onBufferRequested();
+
+ /**
+ * Notifies that the buffer is processed.
+ *
+ * @param buffer {@link CarEvsBufferDescriptor} object we are done with.
+ */
+ void onBufferProcessed(@NonNull CarEvsBufferDescriptor buffer);
+ }
+
+ private CarEvsGLSurfaceView(Context context, BufferCallback callback, int angleInDegree) {
+ super(context);
+ setEGLContextClientVersion(2);
+
+ mRenderer = new GLES20CarEvsBufferRenderer(context, callback, angleInDegree);
+ setRenderer(mRenderer);
+
+ setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+ }
+
+ /**
+ * Returns all buffers held by the renderer.
+ */
+ public void reset() {
+ mRenderer.clearBuffer();
+ }
+
+ /**
+ * Creates a {@link CarEvsGLSurfaceView} object with the default in-plane rotation angle.
+ *
+ * @param context Current appliation context.
+ * @param callback {@link CarEvsGLSurfaceView.BufferCallback} object.
+ *
+ */
+ public static CarEvsGLSurfaceView create(Context context, BufferCallback callback) {
+ return create(context, callback, DEFAULT_IN_PLANE_ROTATION_ANGLE);
+ }
+
+ /**
+ * Creates a {@link CarEvsGLSurfaceView} object with a given in-plane rotation angle in degree.
+ *
+ * @param context Current appliation context.
+ * @param callback {@link CarEvsGLSurfaceView.BufferCallback} object.
+ * @param angleInDegree In-plane counter-clockwise rotation angle in degree.
+ */
+ public static CarEvsGLSurfaceView create(Context context, BufferCallback callback,
+ int angleInDegree) {
+
+ Preconditions.checkArgument(context != null, "Context cannot be null.");
+ Preconditions.checkArgument(callback != null, "BufferCallback cannot be null.");
+
+ return new CarEvsGLSurfaceView(context, callback, angleInDegree);
+ }
+}
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer.java b/car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java
index f04d15ba84..8de69f1e6e 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/GLES20CarEvsCameraPreviewRenderer.java
+++ b/car-evs-helper-lib/src/com/android/car/internal/evs/GLES20CarEvsBufferRenderer.java
@@ -14,37 +14,36 @@
* limitations under the License.
*/
-package com.google.android.car.evs;
+package com.android.car.internal.evs;
import static android.opengl.GLU.gluErrorString;
+import android.annotation.NonNull;
import android.car.evs.CarEvsBufferDescriptor;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
import android.hardware.HardwareBuffer;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
-import android.opengl.GLUtils;
import android.util.Log;
-import androidx.annotation.GuardedBy;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
-import java.util.Random;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
- * GLES20 SurfaceView Renderer
+ * GLES20 SurfaceView Renderer for CarEvsBufferDescriptor.
*/
-public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Renderer {
- private static final String TAG = GLES20CarEvsCameraPreviewRenderer.class.getSimpleName();
+public final class GLES20CarEvsBufferRenderer implements GLSurfaceView.Renderer {
+
+ private static final String TAG = GLES20CarEvsBufferRenderer.class.getSimpleName()
+ .replace("GLES20", "");
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final int FLOAT_SIZE_BYTES = 4;
private static final float[] sVertCarPosData = {
@@ -66,49 +65,58 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
0.0f, 0.0f, 0.0f, 1.0f };
private final String mVertexShader =
- "#version 300 es \n" +
- "layout(location = 0) in vec4 pos; \n" +
- "layout(location = 1) in vec2 tex; \n" +
- "uniform mat4 cameraMat; \n" +
- "out vec2 uv; \n" +
- "void main() \n" +
- "{ \n" +
- " gl_Position = cameraMat * pos; \n" +
- " uv = tex; \n" +
- "} \n";
+ "attribute vec4 pos; \n" +
+ "attribute vec2 tex; \n" +
+ "uniform mat4 cameraMat; \n" +
+ "varying vec2 uv; \n" +
+ "void main() \n" +
+ "{ \n" +
+ " gl_Position = cameraMat * pos; \n" +
+ " uv = tex; \n" +
+ "} \n";
private final String mFragmentShader =
- "#version 300 es \n" +
- "precision mediump float; \n" +
- "uniform sampler2D tex; \n" +
- "in vec2 uv; \n" +
- "out vec4 color; \n" +
- "void main() \n" +
- "{ \n" +
- " vec4 texel = texture(tex, uv); \n" +
- " color = texel; \n" +
- "} \n";
+ "precision mediump float; \n" +
+ "uniform sampler2D tex; \n" +
+ "varying vec2 uv; \n" +
+ "void main() \n" +
+ "{ \n" +
+ " gl_FragColor = texture2D(tex, uv); \n" +
+ "} \n";
private final Object mLock = new Object();
+ private final CarEvsGLSurfaceView.BufferCallback mCallback;
+ private final Context mContext;
+ private final FloatBuffer mVertCarPos;
+ private final FloatBuffer mVertCarTex;
- private CarEvsCameraPreviewActivity mActivity;
+ private int mProgram;
+ private int mTextureId;
+ private int mWidth;
+ private int mHeight;
+ // Native method to update the texture with a received frame buffer
@GuardedBy("mLock")
- private CarEvsBufferDescriptor mBufferInUse = null;
+ private CarEvsBufferDescriptor mBufferInUse;
+
+ /** Load jni on initialization. */
+ static {
+ System.loadLibrary("carevsglrenderer_jni");
+ }
+
+ public GLES20CarEvsBufferRenderer(@NonNull Context context,
+ @NonNull CarEvsGLSurfaceView.BufferCallback callback, int angleInDegree) {
- public GLES20CarEvsCameraPreviewRenderer(Context context,
- CarEvsCameraPreviewActivity activity) {
+ Preconditions.checkArgument(context != null, "Context cannot be null.");
+ Preconditions.checkArgument(callback != null, "Callback cannot be null.");
mContext = context;
- mActivity = activity;
+ mCallback = callback;
mVertCarPos = ByteBuffer.allocateDirect(sVertCarPosData.length * FLOAT_SIZE_BYTES)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVertCarPos.put(sVertCarPosData).position(0);
- // Rotates the matrix in counter-clockwise
- int angleInDegree = mContext.getResources().getInteger(
- R.integer.config_evsRearviewCameraInPlaneRotationAngle);
double angleInRadian = Math.toRadians(angleInDegree);
float[] rotated = {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f};
float sin = (float)Math.sin(angleInRadian);
@@ -140,7 +148,7 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
}
// bufferToReturn is not null here.
- mActivity.returnBuffer(bufferToReturn);
+ mCallback.onBufferProcessed(bufferToReturn);
}
@Override
@@ -149,7 +157,8 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
CarEvsBufferDescriptor bufferToRender = null;
CarEvsBufferDescriptor bufferToReturn = null;
- CarEvsBufferDescriptor newFrame = mActivity.getNewFrame();
+ CarEvsBufferDescriptor newFrame = mCallback.onBufferRequested();
+
synchronized (mLock) {
if (newFrame != null) {
// If a new frame has not been delivered yet, we're using a previous frame.
@@ -161,8 +170,15 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
bufferToRender = mBufferInUse;
}
+ if (bufferToRender == null) {
+ if (DBG) {
+ Log.d(TAG, "No buffer to draw.");
+ }
+ return;
+ }
+
if (bufferToReturn != null) {
- mActivity.returnBuffer(bufferToReturn);
+ mCallback.onBufferProcessed(bufferToReturn);
}
// Specify a shader program to use
@@ -175,22 +191,13 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
}
GLES20.glUniformMatrix4fv(matrix, 1, false, sIdentityMatrix, 0);
- if (bufferToRender == null) {
- // Show the default screen
- drawDefaultScreen();
- } else {
- // Retrieve a hardware buffer from a descriptor and update the texture
- HardwareBuffer buffer = bufferToRender.getHardwareBuffer();
- if (buffer == null) {
- Log.e(TAG, "HardwareBuffer is invalid.");
- drawDefaultScreen();
- } else {
- // Update the texture with a given hardware buffer
- if (!nUpdateTexture(buffer, mTextureId)) {
- throw new RuntimeException(
- "Failed to update the texture with the preview frame");
- }
- }
+ // Retrieve a hardware buffer from a descriptor and update the texture
+ HardwareBuffer buffer = bufferToRender.getHardwareBuffer();
+
+ // Update the texture with a given hardware buffer
+ if (!nUpdateTexture(buffer, mTextureId)) {
+ throw new RuntimeException(
+ "Failed to update the texture with the preview frame");
}
GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
@@ -239,7 +246,8 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
mHeight = height;
}
- @Override public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+ @Override
+ public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
// Use the GLES20 class's static methods instead of a passed GL10 interface.
mProgram = buildShaderProgram(mVertexShader, mFragmentShader);
if (mProgram == 0) {
@@ -270,34 +278,6 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
GLES20.GL_CLAMP_TO_EDGE);
}
- public void setTextLocation(float x, float y) {
- mTextX = x;
- mTextY = y;
- }
-
- private void drawDefaultScreen() {
- Drawable drawable = mContext.getResources().getDrawable(R.drawable.rearview);
- Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_4444);
- Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, mWidth, mHeight);
- drawable.draw(canvas);
-
- Paint fontColor = new Paint();
- fontColor.setTextSize(Math.min(mWidth, mHeight) * 0.10f);
- fontColor.setAntiAlias(true);
-
- // Pick a font color randomly
- fontColor.setColor((0xFF << 24) + (mRandom.nextInt(0xFF) << 16)
- + (mRandom.nextInt(0xFF) << 8) + mRandom.nextInt(0xFF));
-
- // Set a location of the text relative to the surface size
- canvas.drawText("The rearview is not available.", mTextX, mTextY, fontColor);
-
- // Draw
- GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level = */ 0, bitmap, /* border = */ 0);
- bitmap.recycle();
- }
-
private int loadShader(int shaderType, String source) {
int shader = GLES20.glCreateShader(shaderType);
if (shader == 0) {
@@ -342,6 +322,10 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
checkGlError("glAttachShader");
GLES20.glAttachShader(program, fragmentShader);
checkGlError("glAttachShader");
+
+ GLES20.glBindAttribLocation(program, 0, "pos");
+ GLES20.glBindAttribLocation(program, 1, "tex");
+
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
@@ -363,21 +347,5 @@ public final class GLES20CarEvsCameraPreviewRenderer implements GLSurfaceView.Re
}
}
- private Context mContext;
- private int mProgram;
- private int mTextureId;
- private FloatBuffer mVertCarPos;
- private FloatBuffer mVertCarTex;
- private int mWidth;
- private int mHeight;
- private float mTextX;
- private float mTextY;
- private Random mRandom = new Random();
-
- static {
- System.loadLibrary("carevsglrenderer_jni");
- }
-
- // Native method to update the texture with a received frame buffer
private native boolean nUpdateTexture(HardwareBuffer buffer, int textureId);
}
diff --git a/car-lib/src/android/car/telemetry/telemetry.proto b/car-lib/src/android/car/telemetry/telemetry.proto
index de1dac1b5b..e384e83960 100644
--- a/car-lib/src/android/car/telemetry/telemetry.proto
+++ b/car-lib/src/android/car/telemetry/telemetry.proto
@@ -84,6 +84,7 @@ message StatsPublisher {
// Collects all the app start events with the initial used RSS/CACHE/SWAP memory.
APP_START_MEMORY_STATE_CAPTURED = 1;
// Collects memory state of processes in 5-minute buckets (1 memory measurement per bucket).
+ // Consider using PROCESS_MEMORY_SNAPSHOT instead for smaller data size.
PROCESS_MEMORY_STATE = 2;
// Collects activity foreground/background transition events.
ACTIVITY_FOREGROUND_STATE_CHANGED = 3;
@@ -95,6 +96,10 @@ message StatsPublisher {
ANR_OCCURRED = 6;
// Collects "wtf"-level log events.
WTF_OCCURRED = 7;
+ // Collects memory snapshot of processes in 5-minute buckets (1 memory measurement per bucket).
+ // It differs from PROCESS_MEMORY_STATE in that the snapshot can be used for leaked memory
+ // detection by tracking anon RSS + swap usage.
+ PROCESS_MEMORY_SNAPSHOT = 8;
}
// Required.
diff --git a/car-maps-placeholder/res/values-ms/strings.xml b/car-maps-placeholder/res/values-ms/strings.xml
index 6b68645abc..09a2c32753 100644
--- a/car-maps-placeholder/res/values-ms/strings.xml
+++ b/car-maps-placeholder/res/values-ms/strings.xml
@@ -16,6 +16,6 @@ limitations under the License.
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_name" msgid="6575346965016311017">"Peta"</string>
+ <string name="app_name" msgid="6575346965016311017">"Maps"</string>
<string name="error_text" msgid="5575174711944349180">"Tiada aplikasi peta dipasang. Sila hubungi pengeluar kereta anda."</string>
</resources>
diff --git a/cpp/evs/manager/aidl/include/VirtualCamera.h b/cpp/evs/manager/aidl/include/VirtualCamera.h
index 04e64aa5e3..20072158ff 100644
--- a/cpp/evs/manager/aidl/include/VirtualCamera.h
+++ b/cpp/evs/manager/aidl/include/VirtualCamera.h
@@ -27,6 +27,7 @@
#include <aidl/android/hardware/automotive/evs/IEvsDisplay.h>
#include <aidl/android/hardware/automotive/evs/ParameterRange.h>
#include <aidl/android/hardware/automotive/evs/Stream.h>
+#include <android-base/thread_annotations.h>
#include <condition_variable>
#include <deque>
@@ -78,7 +79,10 @@ public:
virtual ~VirtualCamera();
unsigned getAllowedBuffers() { return mFramesAllowed; };
- bool isStreaming() { return mStreamState == RUNNING; }
+ bool isStreaming() {
+ std::lock_guard lock(mMutex);
+ return mStreamState == RUNNING;
+ }
std::vector<std::shared_ptr<HalCamera>> getHalCameras();
void setDescriptor(aidlevs::CameraDesc* desc) { mDesc = desc; }
@@ -87,7 +91,7 @@ public:
bool deliverFrame(const aidlevs::BufferDesc& bufDesc);
// Dump current status to a given file descriptor
- std::string toString(const char* indent = "") const;
+ std::string toString(const char* indent = "") const NO_THREAD_SAFETY_ANALYSIS;
private:
void shutdown();
@@ -104,7 +108,7 @@ private:
STOPPED,
RUNNING,
STOPPING,
- } mStreamState = STOPPED;
+ } mStreamState GUARDED_BY(mMutex) = STOPPED;
std::unordered_map<std::string, std::deque<aidlevs::BufferDesc>> mFramesHeld;
std::thread mCaptureThread;
diff --git a/cpp/evs/manager/aidl/src/VirtualCamera.cpp b/cpp/evs/manager/aidl/src/VirtualCamera.cpp
index e1b2968a17..7aa6dd1816 100644
--- a/cpp/evs/manager/aidl/src/VirtualCamera.cpp
+++ b/cpp/evs/manager/aidl/src/VirtualCamera.cpp
@@ -463,8 +463,16 @@ ScopedAStatus VirtualCamera::startVideoStream(const std::shared_ptr<IEvsCameraSt
constexpr auto kFrameTimeout = 5s; // timeout in seconds.
int64_t lastFrameTimestamp = -1;
EvsResult status = EvsResult::OK;
- while (mStreamState == RUNNING) {
+ while (true) {
std::unique_lock lock(mMutex);
+ ::android::base::ScopedLockAssertion assume_lock(mMutex);
+
+ if (mStreamState != RUNNING) {
+ // A video stream is stopped while a capture thread is acquiring
+ // a lock.
+ LOG(DEBUG) << "Requested to stop capturing frames";
+ break;
+ }
unsigned count = 0;
for (auto&& [key, hwCamera] : mHalCamera) {
@@ -485,7 +493,7 @@ ScopedAStatus VirtualCamera::startVideoStream(const std::shared_ptr<IEvsCameraSt
break;
}
- if (!mFramesReadySignal.wait_for(lock, kFrameTimeout, [this]() {
+ if (!mFramesReadySignal.wait_for(lock, kFrameTimeout, [this]() REQUIRES(mMutex) {
// Stops waiting if
// 1) we've requested to stop capturing
// new frames
@@ -498,35 +506,43 @@ ScopedAStatus VirtualCamera::startVideoStream(const std::shared_ptr<IEvsCameraSt
LOG(DEBUG) << "Timer for a new frame expires";
status = EvsResult::UNDERLYING_SERVICE_ERROR;
break;
- } else if (mStreamState == RUNNING) {
- // Fetch frames and forward to the client
- if (!mFramesHeld.empty() && mStream) {
- // Pass this buffer through to our client
- std::vector<BufferDesc> frames;
- frames.resize(count);
- unsigned i = 0;
- for (auto&& [key, hwCamera] : mHalCamera) {
- std::shared_ptr<HalCamera> pHwCamera = hwCamera.lock();
- if (!pHwCamera || mFramesHeld[key].empty()) {
- continue;
- }
-
- // Duplicate the latest buffer and forward it to the
- // active clients
- auto frame = Utils::dupBufferDesc(mFramesHeld[key].back(),
- /* doDup= */ true);
- if (frame.timestamp > lastFrameTimestamp) {
- lastFrameTimestamp = frame.timestamp;
- }
- frames[i++] = std::move(frame);
- }
-
- if (!mStream->deliverFrame(frames).isOk()) {
- LOG(WARNING) << "Failed to forward frames";
- }
+ }
+
+ if (mStreamState != RUNNING || !mStream) {
+ // A video stream is stopped while a capture thread is waiting
+ // for a new frame or we have lost a client.
+ LOG(DEBUG) << "Requested to stop capturing frames or lost a client";
+ break;
+ }
+
+ // Fetch frames and forward to the client
+ if (mFramesHeld.empty()) {
+ // We do not have any frame to forward.
+ continue;
+ }
+
+ // Pass this buffer through to our client
+ std::vector<BufferDesc> frames;
+ frames.resize(count);
+ unsigned i = 0;
+ for (auto&& [key, hwCamera] : mHalCamera) {
+ std::shared_ptr<HalCamera> pHwCamera = hwCamera.lock();
+ if (!pHwCamera || mFramesHeld[key].empty()) {
+ continue;
}
- } else if (mStreamState != RUNNING) {
- LOG(DEBUG) << "Requested to stop capturing frames";
+
+ // Duplicate the latest buffer and forward it to the
+ // active clients
+ auto frame = Utils::dupBufferDesc(mFramesHeld[key].back(),
+ /* doDup= */ true);
+ if (frame.timestamp > lastFrameTimestamp) {
+ lastFrameTimestamp = frame.timestamp;
+ }
+ frames[i++] = std::move(frame);
+ }
+
+ if (!mStream->deliverFrame(frames).isOk()) {
+ LOG(WARNING) << "Failed to forward frames";
}
}
@@ -554,6 +570,10 @@ ScopedAStatus VirtualCamera::startVideoStream(const std::shared_ptr<IEvsCameraSt
ScopedAStatus VirtualCamera::stopVideoStream() {
{
std::lock_guard lock(mMutex);
+ if (mStreamState != RUNNING) {
+ // No action is required.
+ return ScopedAStatus::ok();
+ }
if (!mStream || mStreamState != RUNNING) {
// Safely ignore a request to stop video stream
@@ -570,7 +590,7 @@ ScopedAStatus VirtualCamera::stopVideoStream() {
EvsEventDesc event{
.aType = EvsEventType::STREAM_STOPPED,
};
- if (!mStream->notify(std::move(event)).isOk()) {
+ if (mStream && !mStream->notify(std::move(event)).isOk()) {
LOG(WARNING) << "Error delivering end of stream event";
}
@@ -579,20 +599,20 @@ ScopedAStatus VirtualCamera::stopVideoStream() {
// Note, however, that there still might be frames already queued that client will see
// after returning from the client side of this call.
mStreamState = STOPPED;
+ }
- // Give the underlying hardware camera the heads up that it might be time to stop
- for (auto&& [_, hwCamera] : mHalCamera) {
- auto pHwCamera = hwCamera.lock();
- if (pHwCamera) {
- pHwCamera->clientStreamEnding(this);
- }
+ // Give the underlying hardware camera the heads up that it might be time to stop
+ for (auto&& [_, hwCamera] : mHalCamera) {
+ auto pHwCamera = hwCamera.lock();
+ if (pHwCamera) {
+ pHwCamera->clientStreamEnding(this);
}
-
- // Signal a condition to unblock a capture thread and then join
- mSourceCameras.clear();
- mFramesReadySignal.notify_all();
}
+ // Signal a condition to unblock a capture thread and then join
+ mSourceCameras.clear();
+ mFramesReadySignal.notify_all();
+
if (mCaptureThread.joinable()) {
mCaptureThread.join();
}
@@ -740,18 +760,23 @@ bool VirtualCamera::deliverFrame(const BufferDesc& bufDesc) {
bool VirtualCamera::notify(const EvsEventDesc& event) {
switch (event.aType) {
- case EvsEventType::STREAM_STOPPED:
- if (mStreamState != STOPPING) {
+ case EvsEventType::STREAM_STOPPED: {
+ {
+ std::lock_guard lock(mMutex);
+ if (mStreamState != RUNNING) {
+ // We're not actively consuming a video stream or already in
+ // a process to stop a video stream.
+ return true;
+ }
+
// Warn if we got an unexpected stream termination
LOG(WARNING) << "Stream unexpectedly stopped, current status " << mStreamState;
-
- // Clean up the resource and forward an event to the client
- stopVideoStream();
-
- // This event is handled properly.
- return true;
}
- break;
+
+ // Clean up the resource and forward an event to the client
+ stopVideoStream();
+ return true;
+ }
// v1.0 client will ignore all other events.
case EvsEventType::PARAMETER_CHANGED:
diff --git a/packages/ScriptExecutor/src/BundleWrapper.cpp b/packages/ScriptExecutor/src/BundleWrapper.cpp
index 01c4d24c78..799f37ca1f 100644
--- a/packages/ScriptExecutor/src/BundleWrapper.cpp
+++ b/packages/ScriptExecutor/src/BundleWrapper.cpp
@@ -107,6 +107,37 @@ Result<void> BundleWrapper::putString(const char* key, const char* value) {
return {}; // ok result
}
+Result<void> BundleWrapper::putBooleanArray(const char* key,
+ const std::vector<unsigned char>& value) {
+ ScopedLocalRef<jstring> keyStringRef(mJNIEnv, mJNIEnv->NewStringUTF(key));
+ if (keyStringRef == nullptr) {
+ return Error() << "Failed to create a string for a key=" << key << " due to OOM error";
+ }
+
+ jmethodID putBooleanArrayMethod =
+ mJNIEnv->GetMethodID(mBundleClass, "putBooleanArray", "(Ljava/lang/String;[Z)V");
+
+ ScopedLocalRef<jbooleanArray> arrayRef(mJNIEnv, mJNIEnv->NewBooleanArray(value.size()));
+ mJNIEnv->SetBooleanArrayRegion(arrayRef.get(), 0, value.size(), &value.at(0));
+ mJNIEnv->CallVoidMethod(mBundle, putBooleanArrayMethod, keyStringRef.get(), arrayRef.get());
+ return {}; // ok result
+}
+
+Result<void> BundleWrapper::putDoubleArray(const char* key, const std::vector<double>& value) {
+ ScopedLocalRef<jstring> keyStringRef(mJNIEnv, mJNIEnv->NewStringUTF(key));
+ if (keyStringRef == nullptr) {
+ return Error() << "Failed to create a string for a key=" << key << " due to OOM error";
+ }
+
+ jmethodID putDoubleArrayMethod =
+ mJNIEnv->GetMethodID(mBundleClass, "putDoubleArray", "(Ljava/lang/String;[D)V");
+
+ ScopedLocalRef<jdoubleArray> arrayRef(mJNIEnv, mJNIEnv->NewDoubleArray(value.size()));
+ mJNIEnv->SetDoubleArrayRegion(arrayRef.get(), 0, value.size(), &value[0]);
+ mJNIEnv->CallVoidMethod(mBundle, putDoubleArrayMethod, keyStringRef.get(), arrayRef.get());
+ return {}; // ok result
+}
+
Result<void> BundleWrapper::putLongArray(const char* key, const std::vector<int64_t>& value) {
ScopedLocalRef<jstring> keyStringRef(mJNIEnv, mJNIEnv->NewStringUTF(key));
if (keyStringRef == nullptr) {
diff --git a/packages/ScriptExecutor/src/BundleWrapper.h b/packages/ScriptExecutor/src/BundleWrapper.h
index 094f1dd33c..e7473d8e80 100644
--- a/packages/ScriptExecutor/src/BundleWrapper.h
+++ b/packages/ScriptExecutor/src/BundleWrapper.h
@@ -45,6 +45,9 @@ public:
::android::base::Result<void> putLong(const char* key, int64_t value);
::android::base::Result<void> putDouble(const char* key, double value);
::android::base::Result<void> putString(const char* key, const char* value);
+ ::android::base::Result<void> putBooleanArray(const char* key,
+ const std::vector<unsigned char>& value);
+ ::android::base::Result<void> putDoubleArray(const char* key, const std::vector<double>& value);
::android::base::Result<void> putLongArray(const char* key, const std::vector<int64_t>& value);
::android::base::Result<void> putStringArray(const char* key,
const std::vector<std::string>& value);
diff --git a/packages/ScriptExecutor/src/JniUtils.cpp b/packages/ScriptExecutor/src/JniUtils.cpp
index b33bfc24a0..119debd6ec 100644
--- a/packages/ScriptExecutor/src/JniUtils.cpp
+++ b/packages/ScriptExecutor/src/JniUtils.cpp
@@ -55,12 +55,11 @@ void pushBundleToLuaTable(JNIEnv* env, lua_State* lua, jobject bundle) {
ScopedLocalRef<jclass> longClass(env, env->FindClass("java/lang/Long"));
ScopedLocalRef<jclass> numberClass(env, env->FindClass("java/lang/Number"));
ScopedLocalRef<jclass> stringClass(env, env->FindClass("java/lang/String"));
+ ScopedLocalRef<jclass> booleanArrayClass(env, env->FindClass("[Z"));
ScopedLocalRef<jclass> intArrayClass(env, env->FindClass("[I"));
ScopedLocalRef<jclass> longArrayClass(env, env->FindClass("[J"));
ScopedLocalRef<jclass> doubleArrayClass(env, env->FindClass("[D"));
ScopedLocalRef<jclass> stringArrayClass(env, env->FindClass("[Ljava/lang/String;"));
- // TODO(b/188816922): Handle more types such as float and integer arrays,
- // and perhaps nested Bundles.
jmethodID getMethod = env->GetMethodID(persistableBundleClass.get(), "get",
"(Ljava/lang/String;)Ljava/lang/Object;");
@@ -100,6 +99,23 @@ void pushBundleToLuaTable(JNIEnv* env, lua_State* lua, jobject bundle) {
env->GetStringUTFChars(static_cast<jstring>(value.get()), nullptr);
lua_pushstring(lua, rawStringValue);
env->ReleaseStringUTFChars(static_cast<jstring>(value.get()), rawStringValue);
+ } else if (env->IsInstanceOf(value.get(), booleanArrayClass.get())) {
+ jbooleanArray booleanArray = static_cast<jbooleanArray>(value.get());
+ const auto kLength = env->GetArrayLength(booleanArray);
+ // Arrays are represented as a table of sequential elements in Lua.
+ // We are creating a nested table to represent this array. We specify number of elements
+ // in the Java array to preallocate memory accordingly.
+ lua_createtable(lua, kLength, 0);
+ jboolean* rawBooleanArray = env->GetBooleanArrayElements(booleanArray, nullptr);
+ // Fills in the table at stack idx -2 with key value pairs, where key is a
+ // Lua index and value is an integer from the long array at that index
+ for (int i = 0; i < kLength; i++) {
+ lua_pushboolean(lua, rawBooleanArray[i]);
+ lua_rawseti(lua, /* idx= */ -2,
+ i + 1); // lua index starts from 1
+ }
+ // JNI_ABORT is used because we do not need to copy back elements.
+ env->ReleaseBooleanArrayElements(booleanArray, rawBooleanArray, JNI_ABORT);
} else if (env->IsInstanceOf(value.get(), intArrayClass.get())) {
jintArray intArray = static_cast<jintArray>(value.get());
const auto kLength = env->GetArrayLength(intArray);
@@ -264,6 +280,8 @@ Result<void> convertLuaTableToBundle(JNIEnv* env, lua_State* lua, BundleWrapper*
"is unrecoverable.";
}
+ std::vector<unsigned char> boolArray;
+ std::vector<double> doubleArray;
std::vector<int64_t> longArray;
std::vector<std::string> stringArray;
int originalLuaType = LUA_TNIL;
@@ -289,13 +307,15 @@ Result<void> convertLuaTableToBundle(JNIEnv* env, lua_State* lua, BundleWrapper*
"unrecoverable.";
}
switch (currentType) {
+ case LUA_TBOOLEAN:
+ boolArray.push_back(
+ static_cast<unsigned char>(lua_toboolean(lua, /* index = */ -1)));
+ break;
case LUA_TNUMBER:
- if (!lua_isinteger(lua, /* index = */ -1)) {
- return Error() << "Returned value for key=" << key
- << " contains a floating number array, which is not "
- "supported yet.";
- } else {
+ if (lua_isinteger(lua, /* index = */ -1)) {
longArray.push_back(lua_tointeger(lua, /* index = */ -1));
+ } else {
+ doubleArray.push_back(lua_tonumber(lua, /* index = */ -1));
}
break;
case LUA_TSTRING:
@@ -313,8 +333,15 @@ Result<void> convertLuaTableToBundle(JNIEnv* env, lua_State* lua, BundleWrapper*
lua_pop(lua, 1);
}
switch (originalLuaType) {
+ case LUA_TBOOLEAN:
+ bundleInsertionResult = bundleWrapper->putBooleanArray(key, boolArray);
+ break;
case LUA_TNUMBER:
- bundleInsertionResult = bundleWrapper->putLongArray(key, longArray);
+ if (longArray.size() > 0) {
+ bundleInsertionResult = bundleWrapper->putLongArray(key, longArray);
+ } else {
+ bundleInsertionResult = bundleWrapper->putDoubleArray(key, doubleArray);
+ }
break;
case LUA_TSTRING:
bundleInsertionResult = bundleWrapper->putStringArray(key, stringArray);
diff --git a/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
index e0760ef9b3..8ae1f27a8f 100644
--- a/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
+++ b/packages/ScriptExecutor/tests/functional/src/com/android/car/scriptexecutortest/functional/ScriptExecutorFunctionalTest.java
@@ -284,16 +284,22 @@ public final class ScriptExecutorFunctionalTest {
String script =
"function arrays(data, state)\n"
+ " result = {}\n"
+ + " result.boolean_array = state.boolean_array\n"
+ + " result.double_array = state.double_array\n"
+ " result.int_array = state.int_array\n"
+ " result.long_array = state.long_array\n"
+ " result.string_array = state.string_array\n"
+ " on_success(result)\n"
+ "end\n";
PersistableBundle previousState = new PersistableBundle();
+ boolean[] boolean_array = new boolean[] {true, true, false};
+ double[] double_array = new double[] {1.5, 2.222};
int[] int_array = new int[] {1, 2};
long[] int_array_in_long = new long[] {1, 2};
long[] long_array = new long[] {1, 2, 3};
String[] string_array = new String[] {"one", "two", "three"};
+ previousState.putBooleanArray("boolean_array", boolean_array);
+ previousState.putDoubleArray("double_array", double_array);
previousState.putIntArray("int_array", int_array);
previousState.putLongArray("long_array", long_array);
previousState.putStringArray("string_array", string_array);
@@ -301,9 +307,12 @@ public final class ScriptExecutorFunctionalTest {
runScriptAndWaitForResponse(script, "arrays", mEmptyPublishedData, previousState);
// Verify that keys are preserved but the values are modified as expected.
- assertThat(mListener.mInterimResult.size()).isEqualTo(3);
+ assertThat(mListener.mInterimResult.size()).isEqualTo(5);
// Lua has only one lua_Integer. Here Java long is used to represent it when data is
// transferred from Lua to CarTelemetryService.
+ assertThat(mListener.mInterimResult.getBooleanArray("boolean_array"))
+ .isEqualTo(boolean_array);
+ assertThat(mListener.mInterimResult.getDoubleArray("double_array")).isEqualTo(double_array);
assertThat(mListener.mInterimResult.getLongArray("int_array")).isEqualTo(int_array_in_long);
assertThat(mListener.mInterimResult.getLongArray("long_array")).isEqualTo(long_array);
assertThat(mListener.mInterimResult.getStringArray("string_array")).isEqualTo(string_array);
@@ -886,52 +895,6 @@ public final class ScriptExecutorFunctionalTest {
}
@Test
- public void invokeScript_returnedFloatingArraysNotSupported()
- throws RemoteException, InterruptedException {
- // Verifies that we do not support return values that contain floating number arrays.
- String script =
- "function floating_point_arrays(data, state)\n"
- + " array = {}\n"
- + " array[0] = 1.1\n"
- + " array[1] = 1.2\n"
- + " result = {data = array}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(
- script, "floating_point_arrays", mEmptyPublishedData, mEmptyIterimResult);
-
- // Verify that the expected error is received.
- assertThat(mListener.mErrorType)
- .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mListener.mMessage)
- .contains("a floating number array, which is not supported yet");
- }
-
- @Test
- public void invokeScript_returnedBooleanArraysNotSupported()
- throws RemoteException, InterruptedException {
- // Verifies that we do not yet support return values that contain boolean arrays.
- String script =
- "function array_of_booleans(data, state)\n"
- + " array = {}\n"
- + " array[0] = false\n"
- + " array[1] = true\n"
- + " result = {data = array}\n"
- + " on_success(result)\n"
- + "end\n";
-
- runScriptAndWaitForResponse(
- script, "array_of_booleans", mEmptyPublishedData, mEmptyIterimResult);
-
- // Verify that the expected error is received.
- assertThat(mListener.mErrorType)
- .isEqualTo(IScriptExecutorListener.ERROR_TYPE_LUA_SCRIPT_ERROR);
- assertThat(mListener.mMessage)
- .contains("is an array with values of type=boolean, which is not supported yet");
- }
-
- @Test
public void invokeScript_onMetricsReport_returnsReport() throws Exception {
String returnFinalResultScript =
"function script_completes(data, state)\n"
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
index be10d69719..3e4f33eb1d 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTest.java
@@ -38,6 +38,7 @@ public final class JniUtilsTest {
private static final String INT_KEY = "int_key";
private static final String STRING_KEY = "string_key";
private static final String NUMBER_KEY = "number_key";
+ private static final String BOOLEAN_ARRAY_KEY = "boolean_array_key";
private static final String INT_ARRAY_KEY = "int_array_key";
private static final String LONG_ARRAY_KEY = "long_array_key";
private static final String DOUBLE_ARRAY_KEY = "double_array_key";
@@ -48,6 +49,7 @@ public final class JniUtilsTest {
private static final int INT_VALUE = 10;
private static final int INT_VALUE_2 = 20;
private static final String STRING_VALUE = "test";
+ private static final boolean[] BOOLEAN_ARRAY_VALUE = new boolean[]{true, false, true};
private static final int[] INT_ARRAY_VALUE = new int[]{1, 2, 3};
private static final long[] LONG_ARRAY_VALUE = new long[]{1, 2, 3, 4};
private static final double[] DOUBLE_ARRAY_VALUE = new double[]{1.1d, 2.2d, 3.3d, 4.4d};
@@ -97,6 +99,9 @@ public final class JniUtilsTest {
private native boolean nativeHasIntValue(long luaEnginePtr, String key, int value);
+ private native boolean nativeHasBooleanArrayValue(
+ long luaEnginePtr, String key, boolean[] value);
+
private native boolean nativeHasDoubleValue(long luaEnginePtr, String key, double value);
private native boolean nativeHasIntArrayValue(long luaEnginePtr, String key, int[] value);
@@ -163,6 +168,7 @@ public final class JniUtilsTest {
@Test
public void pushBundleToLuaTable_arrays() {
PersistableBundle bundle = new PersistableBundle();
+ bundle.putBooleanArray(BOOLEAN_ARRAY_KEY, BOOLEAN_ARRAY_VALUE);
bundle.putIntArray(INT_ARRAY_KEY, INT_ARRAY_VALUE);
bundle.putLongArray(LONG_ARRAY_KEY, LONG_ARRAY_VALUE);
bundle.putDoubleArray(DOUBLE_ARRAY_KEY, DOUBLE_ARRAY_VALUE);
@@ -174,6 +180,8 @@ public final class JniUtilsTest {
// Check contents of Lua table.
// Java int and long arrays both end up being arrays of Lua's Integer type,
// which is interpreted as a 8-byte int type.
+ assertThat(nativeHasBooleanArrayValue(
+ mLuaEnginePtr, BOOLEAN_ARRAY_KEY, BOOLEAN_ARRAY_VALUE)).isTrue();
assertThat(nativeHasIntArrayValue(mLuaEnginePtr, INT_ARRAY_KEY, INT_ARRAY_VALUE)).isTrue();
assertThat(nativeHasLongArrayValue(mLuaEnginePtr, LONG_ARRAY_KEY, LONG_ARRAY_VALUE))
.isTrue();
diff --git a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
index ce3be6660a..5597e50d1a 100644
--- a/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
+++ b/packages/ScriptExecutor/tests/unit/src/com/android/car/scriptexecutortest/unit/JniUtilsTestHelper.cpp
@@ -72,6 +72,44 @@ bool hasValidNumberArray(JNIEnv* env, jobject object, jlong luaEnginePtr, jstrin
return result;
}
+template <typename T>
+bool hasValidBooleanArray(JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key,
+ T rawInputArray, const int arrayLength) {
+ const char* rawKey = env->GetStringUTFChars(key, nullptr);
+ scriptexecutor::LuaEngine* engine =
+ reinterpret_cast<scriptexecutor::LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ // Assumes the table is on top of the stack.
+ auto* luaState = engine->getLuaState();
+ lua_pushstring(luaState, rawKey);
+ env->ReleaseStringUTFChars(key, rawKey);
+ lua_gettable(luaState, -2);
+ bool result = false;
+ if (!lua_istable(luaState, -1)) {
+ result = false;
+ } else {
+ // First, compare the input and Lua array sizes.
+ const auto kActualLength = lua_rawlen(luaState, -1);
+ if (arrayLength != kActualLength) {
+ // No need to compare further if number of elements in the two arrays are not equal.
+ result = false;
+ } else {
+ // Do element by element comparison.
+ bool is_equal = true;
+ for (int i = 0; i < arrayLength; ++i) {
+ lua_rawgeti(luaState, -1, i + 1);
+ is_equal = lua_isboolean(luaState, /* idx = */ -1) &&
+ lua_toboolean(luaState, /* idx = */ -1) ==
+ static_cast<bool>(rawInputArray[i]);
+ lua_pop(luaState, 1);
+ if (!is_equal) break;
+ }
+ result = is_equal;
+ }
+ }
+ lua_pop(luaState, 1);
+ return result;
+}
+
extern "C" {
#include "lua.h"
@@ -198,6 +236,16 @@ Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasStringValue(
}
JNIEXPORT bool JNICALL
+Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasBooleanArrayValue(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jbooleanArray value) {
+ jboolean* rawInputArray = env->GetBooleanArrayElements(value, nullptr);
+ const auto kInputLength = env->GetArrayLength(value);
+ bool result = hasValidBooleanArray(env, object, luaEnginePtr, key, rawInputArray, kInputLength);
+ env->ReleaseBooleanArrayElements(value, rawInputArray, JNI_ABORT);
+ return result;
+}
+
+JNIEXPORT bool JNICALL
Java_com_android_car_scriptexecutortest_unit_JniUtilsTest_nativeHasIntArrayValue(
JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jintArray value) {
jint* rawInputArray = env->GetIntArrayElements(value, nullptr);
diff --git a/service/proto/android/car/telemetry/atoms.proto b/service/proto/android/car/telemetry/atoms.proto
index 4aad7166f9..cd504f1c1d 100644
--- a/service/proto/android/car/telemetry/atoms.proto
+++ b/service/proto/android/car/telemetry/atoms.proto
@@ -37,6 +37,7 @@ message Atom {
oneof pulled {
ProcessMemoryState process_memory_state = 10013;
ProcessCpuTime process_cpu_time = 10035;
+ ProcessMemorySnapshot process_memory_snapshot = 10064;
}
}
@@ -167,6 +168,19 @@ message WTFOccurred {
optional ErrorSource error_source = 5;
}
+message ProcessMemorySnapshot {
+ optional int32 uid = 1;
+ optional string process_name = 2;
+ optional int32 pid = 3;
+ optional int32 oom_score_adj = 4;
+ optional int32 rss_in_kilobytes = 5;
+ optional int32 anon_rss_in_kilobytes = 6;
+ optional int32 swap_in_kilobytes = 7;
+ optional int32 anon_rss_and_swap_in_kilobytes = 8;
+ optional int32 gpu_memory_kb = 9;
+ optional bool has_foreground_services = 10;
+}
+
message CarPowerStateChanged {
enum State {
WAIT_FOR_VHAL = 0;
diff --git a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
index 328ef2c0f2..2897f2c2e8 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -21,6 +21,7 @@ import static com.android.car.telemetry.AtomsProto.Atom.ANR_OCCURRED_FIELD_NUMBE
import static com.android.car.telemetry.AtomsProto.Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
import static com.android.car.telemetry.AtomsProto.Atom.APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER;
import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_CPU_TIME_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER;
import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER;
import static com.android.car.telemetry.AtomsProto.Atom.WTF_OCCURRED_FIELD_NUMBER;
import static com.android.car.telemetry.CarTelemetryService.DEBUG;
@@ -41,6 +42,7 @@ import android.util.LongSparseArray;
import com.android.car.CarLog;
import com.android.car.telemetry.AtomsProto.ProcessCpuTime;
+import com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot;
import com.android.car.telemetry.AtomsProto.ProcessMemoryState;
import com.android.car.telemetry.ResultStore;
import com.android.car.telemetry.StatsLogProto;
@@ -98,6 +100,10 @@ public class StatsPublisher extends AbstractPublisher {
static final long WTF_OCCURRED_ATOM_MATCHER_ID = 13;
@VisibleForTesting
static final long WTF_OCCURRED_EVENT_METRIC_ID = 14;
+ @VisibleForTesting
+ static final long PROCESS_MEMORY_SNAPSHOT_ATOM_MATCHER_ID = 15;
+ @VisibleForTesting
+ static final long PROCESS_MEMORY_SNAPSHOT_GAUGE_METRIC_ID = 16;
// TODO(b/202115033): Flatten the load spike by pulling reports for each MetricsConfigs
// using separate periodical timers.
@@ -135,6 +141,30 @@ public class StatsPublisher extends AbstractPublisher {
.setField(ProcessCpuTime.SYSTEM_TIME_MILLIS_FIELD_NUMBER))
.build();
+ @VisibleForTesting
+ static final StatsdConfigProto.FieldMatcher PROCESS_MEMORY_SNAPSHOT_FIELDS_MATCHER =
+ StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(
+ PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER)
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.PID_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.OOM_SCORE_ADJ_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.RSS_IN_KILOBYTES_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.ANON_RSS_IN_KILOBYTES_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.SWAP_IN_KILOBYTES_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot
+ .ANON_RSS_AND_SWAP_IN_KILOBYTES_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.GPU_MEMORY_KB_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.HAS_FOREGROUND_SERVICES_FIELD_NUMBER))
+ .build();
+
private final StatsManagerProxy mStatsManager;
private final ResultStore mResultStore;
private final Handler mTelemetryHandler;
@@ -192,12 +222,10 @@ public class StatsPublisher extends AbstractPublisher {
if (!mIsPullingReports) {
if (DEBUG) {
- Slogf.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in "
- + PULL_REPORTS_PERIOD.toMinutes() + " minutes.");
+ Slogf.d(CarLog.TAG_TELEMETRY, "Triggering pull stats reports");
}
mIsPullingReports = true;
- mTelemetryHandler.postDelayed(
- mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
+ mTelemetryHandler.post(mPullReportsPeriodically);
}
}
@@ -247,12 +275,17 @@ public class StatsPublisher extends AbstractPublisher {
case WTF_OCCURRED:
metricId = WTF_OCCURRED_EVENT_METRIC_ID;
break;
+ case PROCESS_MEMORY_SNAPSHOT:
+ metricId = PROCESS_MEMORY_SNAPSHOT_GAUGE_METRIC_ID;
+ break;
default:
return;
}
if (!metricBundles.containsKey(metricId)) {
Slogf.w(CarLog.TAG_TELEMETRY,
- "No reports for metric id " + metricId + " for config " + configKey);
+ "No reports for metric id " + metricId + " ("
+ + subscriber.getPublisherParam().getStats().getSystemMetric()
+ + ") for config " + configKey);
return;
}
PersistableBundle bundle = metricBundles.get(metricId);
@@ -551,6 +584,8 @@ public class StatsPublisher extends AbstractPublisher {
return buildAnrOccurredStatsdConfig(builder);
case WTF_OCCURRED:
return buildWtfOccurredStatsdConfig(builder);
+ case PROCESS_MEMORY_SNAPSHOT:
+ return buildProcessMemorySnapshotStatsdConfig(builder);
default:
throw new IllegalArgumentException("Unsupported metric " + metric.name());
}
@@ -702,6 +737,39 @@ public class StatsPublisher extends AbstractPublisher {
.build();
}
+ @NonNull
+ private static StatsdConfig buildProcessMemorySnapshotStatsdConfig(
+ @NonNull StatsdConfig.Builder builder) {
+ return builder
+ .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
+ // The id must be unique within StatsdConfig/matchers
+ .setId(PROCESS_MEMORY_SNAPSHOT_ATOM_MATCHER_ID)
+ .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+ .setAtomId(PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER)))
+ .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
+ // The id must be unique within StatsdConfig/metrics
+ .setId(PROCESS_MEMORY_SNAPSHOT_GAUGE_METRIC_ID)
+ .setWhat(PROCESS_MEMORY_SNAPSHOT_ATOM_MATCHER_ID)
+ .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER)
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.UID_FIELD_NUMBER))
+ .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
+ .setField(ProcessMemorySnapshot.PROCESS_NAME_FIELD_NUMBER))
+ )
+ .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder()
+ .setFields(PROCESS_MEMORY_SNAPSHOT_FIELDS_MATCHER)
+ ) // setGaugeFieldsFilter
+ .setSamplingType(
+ StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
+ .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES)
+ )
+ .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder()
+ .setAtomId(PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER)
+ .addPackages("AID_SYSTEM"))
+ .build();
+ }
+
@Override
protected void handleSessionStateChange(SessionAnnotation annotation) {}
}
diff --git a/service/src/com/android/car/telemetry/publisher/statsconverters/AtomListConverter.java b/service/src/com/android/car/telemetry/publisher/statsconverters/AtomListConverter.java
index c9227f5006..85e0eaecdc 100644
--- a/service/src/com/android/car/telemetry/publisher/statsconverters/AtomListConverter.java
+++ b/service/src/com/android/car/telemetry/publisher/statsconverters/AtomListConverter.java
@@ -44,7 +44,8 @@ public class AtomListConverter {
// Map of pulled atom cases to corresponding atom converter.
private static Map<Atom.PulledCase, AbstractAtomConverter<?>> sPulledCaseConverters = Map.of(
Atom.PulledCase.PROCESS_MEMORY_STATE, new ProcessMemoryStateConverter(),
- Atom.PulledCase.PROCESS_CPU_TIME, new ProcessCpuTimeConverter());
+ Atom.PulledCase.PROCESS_CPU_TIME, new ProcessCpuTimeConverter(),
+ Atom.PulledCase.PROCESS_MEMORY_SNAPSHOT, new ProcessMemorySnapshotConverter());
/**
* Converts a list of atoms to separate the atoms fields values into arrays to be put into the
diff --git a/service/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverter.java b/service/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverter.java
new file mode 100644
index 0000000000..42845e87e1
--- /dev/null
+++ b/service/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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.car.telemetry.publisher.statsconverters;
+
+import android.annotation.NonNull;
+import android.util.SparseArray;
+
+import com.android.car.telemetry.AtomsProto.Atom;
+import com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Atom data converter for atoms of type {@link ProcessMemorySnapshot}.
+ */
+public class ProcessMemorySnapshotConverter extends AbstractAtomConverter<ProcessMemorySnapshot> {
+
+ private static final SparseArray<AtomFieldAccessor<ProcessMemorySnapshot, ?>>
+ sAtomFieldAccessorMap = new SparseArray<>();
+ static {
+ sAtomFieldAccessorMap.append(1, new AtomFieldAccessor<>(
+ "uid",
+ a -> a.hasUid(),
+ a -> a.getUid()
+ ));
+ sAtomFieldAccessorMap.append(2, new AtomFieldAccessor<>(
+ "process_name",
+ a -> a.hasProcessName(),
+ a -> a.getProcessName()
+ ));
+ sAtomFieldAccessorMap.append(3, new AtomFieldAccessor<>(
+ "pid",
+ a -> a.hasPid(),
+ a -> a.getPid()
+ ));
+ sAtomFieldAccessorMap.append(4, new AtomFieldAccessor<>(
+ "oom_score_adj",
+ a -> a.hasOomScoreAdj(),
+ a -> a.getOomScoreAdj()
+ ));
+ sAtomFieldAccessorMap.append(5, new AtomFieldAccessor<>(
+ "rss_in_kilobytes",
+ a -> a.hasRssInKilobytes(),
+ a -> a.getRssInKilobytes()
+ ));
+ sAtomFieldAccessorMap.append(6, new AtomFieldAccessor<>(
+ "anon_rss_in_kilobytes",
+ a -> a.hasAnonRssInKilobytes(),
+ a -> a.getAnonRssInKilobytes()
+ ));
+ sAtomFieldAccessorMap.append(7, new AtomFieldAccessor<>(
+ "swap_in_kilobytes",
+ a -> a.hasSwapInKilobytes(),
+ a -> a.getSwapInKilobytes()
+ ));
+ sAtomFieldAccessorMap.append(8, new AtomFieldAccessor<>(
+ "anon_rss_and_swap_in_kilobytes",
+ a -> a.hasAnonRssAndSwapInKilobytes(),
+ a -> a.getAnonRssAndSwapInKilobytes()
+ ));
+ sAtomFieldAccessorMap.append(9, new AtomFieldAccessor<>(
+ "gpu_memory_kb",
+ a -> a.hasGpuMemoryKb(),
+ a -> a.getGpuMemoryKb()
+ ));
+ sAtomFieldAccessorMap.append(10, new AtomFieldAccessor<>(
+ "has_foreground_services",
+ a -> a.hasHasForegroundServices(),
+ a -> a.getHasForegroundServices()
+ ));
+ }
+
+ ProcessMemorySnapshotConverter() {
+ super();
+ }
+
+ @NonNull
+ @Override
+ SparseArray<AtomFieldAccessor<ProcessMemorySnapshot, ?>> getAtomFieldAccessorMap() {
+ return sAtomFieldAccessorMap;
+ }
+
+ @NonNull
+ @Override
+ ProcessMemorySnapshot getAtomData(@NonNull Atom atom) {
+ Preconditions.checkArgument(
+ atom.hasProcessMemorySnapshot(), "Atom doesn't contain ProcessMemorySnapshot");
+ return atom.getProcessMemorySnapshot();
+ }
+
+ @NonNull
+ @Override
+ String getAtomDataClassName() {
+ return ProcessMemorySnapshot.class.getSimpleName();
+ }
+}
diff --git a/tests/CarEvsCameraPreviewApp/Android.bp b/tests/CarEvsCameraPreviewApp/Android.bp
index fea6aafeac..a570123ae8 100644
--- a/tests/CarEvsCameraPreviewApp/Android.bp
+++ b/tests/CarEvsCameraPreviewApp/Android.bp
@@ -43,11 +43,14 @@ android_app {
},
libs: [
+ "android.car",
"android.car-system-stubs",
- "androidx.annotation_annotation",
],
- static_libs: ["androidx.annotation_annotation"],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "car-evs-helper-lib",
+ ],
// To make this app be able to re-installed
use_embedded_native_libs: true,
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraGLSurfaceView.java b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraGLSurfaceView.java
deleted file mode 100644
index 0383b74996..0000000000
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraGLSurfaceView.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2021 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.google.android.car.evs;
-
-import android.content.Context;
-import android.opengl.GLSurfaceView;
-import android.view.MotionEvent;
-
-/**
- * GPU-backed SurfaceView to render a hardware buffer described by CarEvsBufferDescriptor.
- */
-public final class CarEvsCameraGLSurfaceView extends GLSurfaceView {
- private static final String TAG = CarEvsCameraGLSurfaceView.class.getSimpleName();
-
- private final GLES20CarEvsCameraPreviewRenderer mRenderer;
-
- public CarEvsCameraGLSurfaceView(Context context, CarEvsCameraPreviewActivity activity) {
- super(context);
- setEGLContextClientVersion(2);
-
- mRenderer = new GLES20CarEvsCameraPreviewRenderer(context, activity);
- setRenderer(mRenderer);
-
- setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent e) {
- float x = e.getX();
- float y = e.getY();
-
- // Update a location of a text to tell the rearview is not available.
- mRenderer.setTextLocation(x, y);
-
- return true;
- }
-
- public void clearBuffer() {
- mRenderer.clearBuffer();
- }
-}
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
index 01c0667213..4bdcf03b36 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/CarEvsCameraPreviewActivity.java
@@ -44,11 +44,16 @@ import android.widget.LinearLayout;
import androidx.annotation.GuardedBy;
+import com.android.car.internal.evs.CarEvsGLSurfaceView;
+import com.android.car.internal.evs.GLES20CarEvsBufferRenderer;
+
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-public class CarEvsCameraPreviewActivity extends Activity {
+public class CarEvsCameraPreviewActivity extends Activity
+ implements CarEvsGLSurfaceView.BufferCallback {
+
private static final String TAG = CarEvsCameraPreviewActivity.class.getSimpleName();
/**
* ActivityManagerService encodes the reason for a request to close system dialogs with this
@@ -97,7 +102,7 @@ public class CarEvsCameraPreviewActivity extends Activity {
private final ExecutorService mCallbackExecutor = Executors.newFixedThreadPool(1);
/** GL backed surface view to render the camera preview */
- private CarEvsCameraGLSurfaceView mEvsView;
+ private CarEvsGLSurfaceView mEvsView;
private ViewGroup mRootView;
private LinearLayout mPreviewContainer;
@@ -142,7 +147,7 @@ public class CarEvsCameraPreviewActivity extends Activity {
if (mStreamState == STREAM_STATE_INVISIBLE) {
// When the activity becomes invisible (e.g. goes background), we immediately
// returns received frame buffers instead of stopping a video stream.
- returnBufferLocked(buffer);
+ doneWithBufferLocked(buffer);
} else {
// Enqueues a new frame and posts a rendering job
mBufferQueue.add(buffer);
@@ -255,7 +260,8 @@ public class CarEvsCameraPreviewActivity extends Activity {
Car.createCar(getApplicationContext(), /* handler = */ null,
Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener);
- mEvsView = new CarEvsCameraGLSurfaceView(getApplication(), this);
+ mEvsView = CarEvsGLSurfaceView.create(getApplication(), this, getApplicationContext()
+ .getResources().getInteger(R.integer.config_evsRearviewCameraInPlaneRotationAngle));
mRootView = (ViewGroup) LayoutInflater.from(this).inflate(
R.layout.evs_preview_activity, /* root= */ null);
mPreviewContainer = mRootView.findViewById(R.id.evs_preview_container);
@@ -439,8 +445,8 @@ public class CarEvsCameraPreviewActivity extends Activity {
return state;
}
- /** Get a new frame */
- CarEvsBufferDescriptor getNewFrame() {
+ @Override
+ public CarEvsBufferDescriptor onBufferRequested() {
synchronized (mLock) {
if (mBufferQueue.isEmpty()) {
return null;
@@ -455,15 +461,15 @@ public class CarEvsCameraPreviewActivity extends Activity {
}
}
- /** Request to return a buffer we're done with */
- void returnBuffer(CarEvsBufferDescriptor buffer) {
+ @Override
+ public void onBufferProcessed(CarEvsBufferDescriptor buffer) {
synchronized (mLock) {
- returnBufferLocked(buffer);
+ doneWithBufferLocked(buffer);
}
}
@GuardedBy("mLock")
- private void returnBufferLocked(CarEvsBufferDescriptor buffer) {
+ private void doneWithBufferLocked(CarEvsBufferDescriptor buffer) {
try {
mEvsManager.returnFrameBuffer(buffer);
} catch (Exception e) {
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
index 2a5d05ffeb..8340a55b6f 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
@@ -364,6 +364,31 @@
android:layout_height="wrap_content"
android:text="@string/get_report"/>
</LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/process_memory_snapshot_config"/>
+ <Button
+ android:id="@+id/send_on_process_memory_snapshot_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/add_metrics_config"/>
+ <Button
+ android:id="@+id/remove_on_process_memory_snapshot_config"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/remove_metrics_config"/>
+ <Button
+ android:id="@+id/get_on_process_memory_snapshot_report"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/get_report"/>
+ </LinearLayout>
</LinearLayout> <!-- @+id/metrics_config_buttons -->
<TextView
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 841e2ff534..054a784164 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -387,7 +387,7 @@
<!-- CarTelemetryService Test -->
<string name="gear_change_config" translatable="false">on_gear_change:</string>
- <string name="process_memory_config" translatable="false">process_memory:</string>
+ <string name="process_memory_config" translatable="false">process_memory_state:</string>
<string name="app_start_memory_state_captured_config" translatable="false">app_start_memory_state_captured:</string>
<string name="activity_foreground_state_changed_config" translatable="false">activity_foreground_state_changed:</string>
<string name="process_cpu_time_config" translatable="false">process_cpu_time:</string>
@@ -397,6 +397,7 @@
<string name="wifi_netstats_config" translatable="false">wifi_netstats_top_consumers:</string>
<string name="stats_and_connectivity_config" translatable="false">stats and connectivity:</string>
<string name="memory_config" translatable="false">memory:</string>
+ <string name="process_memory_snapshot_config" translatable="false">process_memory_snapshot:</string>
<string name="add_metrics_config" translatable="false">Add MetricsConfig</string>
<string name="remove_metrics_config" translatable="false">Remove MetricsConfig</string>
<string name="download_data" translatable="false">Download Data</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
index 812e5bdc83..40a965f5e7 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
@@ -21,6 +21,7 @@ import static android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.A
import static android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.APP_CRASH_OCCURRED;
import static android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.APP_START_MEMORY_STATE_CAPTURED;
import static android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.PROCESS_CPU_TIME;
+import static android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.PROCESS_MEMORY_SNAPSHOT;
import static android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.PROCESS_MEMORY_STATE;
import static android.car.telemetry.TelemetryProto.StatsPublisher.SystemMetric.WTF_OCCURRED;
@@ -465,6 +466,32 @@ public class CarTelemetryTestFragment extends Fragment {
private static final String MEMORY_CONFIG_NAME =
METRICS_CONFIG_MEMORY_V1.getName();
+ /** ProcessMemorySnapshot section. */
+ private static final String LUA_SCRIPT_ON_PROCESS_MEMORY_SNAPSHOT = new StringBuilder()
+ .append("function onProcessMemorySnapshot(published_data, state)\n")
+ .append(" on_script_finished(published_data)\n")
+ .append("end\n")
+ .toString();
+ private static final TelemetryProto.Publisher PROCESS_MEMORY_SNAPSHOT_PUBLISHER =
+ TelemetryProto.Publisher.newBuilder()
+ .setStats(
+ TelemetryProto.StatsPublisher.newBuilder()
+ .setSystemMetric(PROCESS_MEMORY_SNAPSHOT))
+ .build();
+ private static final TelemetryProto.MetricsConfig METRICS_CONFIG_PROCESS_MEMORY_SNAPSHOT_V1 =
+ TelemetryProto.MetricsConfig.newBuilder()
+ .setName("process_memory_snapshot_metrics_config")
+ .setVersion(1)
+ .setScript(LUA_SCRIPT_ON_PROCESS_MEMORY_SNAPSHOT)
+ .addSubscribers(
+ TelemetryProto.Subscriber.newBuilder()
+ .setHandler("onProcessMemorySnapshot")
+ .setPublisher(PROCESS_MEMORY_SNAPSHOT_PUBLISHER)
+ .setPriority(SCRIPT_EXECUTION_PRIORITY_HIGH))
+ .build();
+ private static final String PROCESS_MEMORY_SNAPSHOT_CONFIG_NAME =
+ METRICS_CONFIG_PROCESS_MEMORY_SNAPSHOT_V1.getName();
+
private final Executor mExecutor = Executors.newSingleThreadExecutor();
private boolean mReceiveReportNotification = false;
@@ -592,6 +619,13 @@ public class CarTelemetryTestFragment extends Fragment {
.setOnClickListener(this::onRemoveMemoryConfigBtnClick);
view.findViewById(R.id.get_memory_report)
.setOnClickListener(this::onGetMemoryReportBtnClick);
+ /** StatsPublisher process_memory_snapshot */
+ view.findViewById(R.id.send_on_process_memory_snapshot_config)
+ .setOnClickListener(this::onSendProcessMemorySnapshotConfigBtnClick);
+ view.findViewById(R.id.remove_on_process_memory_snapshot_config)
+ .setOnClickListener(this::onRemoveProcessMemorySnapshotConfigBtnClick);
+ view.findViewById(R.id.get_on_process_memory_snapshot_report)
+ .setOnClickListener(this::onGetProcessMemorySnapshotReportBtnClick);
/** Print mem info button */
view.findViewById(R.id.print_mem_info_btn).setOnClickListener(this::onPrintMemInfoBtnClick);
return view;
@@ -982,6 +1016,24 @@ public class CarTelemetryTestFragment extends Fragment {
mCarTelemetryManager.getFinishedReport(MEMORY_CONFIG_NAME, mExecutor, mListener);
}
+ private void onSendProcessMemorySnapshotConfigBtnClick(View view) {
+ mCarTelemetryManager.addMetricsConfig(
+ PROCESS_MEMORY_SNAPSHOT_CONFIG_NAME,
+ METRICS_CONFIG_PROCESS_MEMORY_SNAPSHOT_V1.toByteArray(),
+ mExecutor,
+ mAddMetricsConfigCallback);
+ }
+
+ private void onRemoveProcessMemorySnapshotConfigBtnClick(View view) {
+ showOutput("Removing MetricsConfig that listens for PROCESS_MEMORY_SNAPSHOT...");
+ mCarTelemetryManager.removeMetricsConfig(PROCESS_MEMORY_SNAPSHOT_CONFIG_NAME);
+ }
+
+ private void onGetProcessMemorySnapshotReportBtnClick(View view) {
+ mCarTelemetryManager.getFinishedReport(
+ PROCESS_MEMORY_SNAPSHOT_CONFIG_NAME, mExecutor, mListener);
+ }
+
/** Gets a MemoryInfo object for the device's current memory status. */
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = getActivity().getSystemService(ActivityManager.class);
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
index 59e27e043c..d3858e4a54 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
@@ -387,8 +387,7 @@ public class StatsPublisherTest {
assertThat(mFakeHandlerWrapper.getQueuedMessages()).hasSize(1);
Message msg = mFakeHandlerWrapper.getQueuedMessages().get(0);
- long expectedPullPeriodMillis = 10 * 60 * 1000; // 10 minutes
- assertThatMessageIsScheduledWithGivenDelay(msg, expectedPullPeriodMillis);
+ assertThatMessageIsScheduledWithGivenDelay(msg, 0);
}
@Test
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverterTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverterTest.java
new file mode 100644
index 0000000000..715830ef35
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/statsconverters/ProcessMemorySnapshotConverterTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 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.car.telemetry.publisher.statsconverters;
+
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.ANON_RSS_AND_SWAP_IN_KILOBYTES_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.ANON_RSS_IN_KILOBYTES_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.GPU_MEMORY_KB_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.HAS_FOREGROUND_SERVICES_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.OOM_SCORE_ADJ_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.PID_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.PROCESS_NAME_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.RSS_IN_KILOBYTES_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.SWAP_IN_KILOBYTES_FIELD_NUMBER;
+import static com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot.UID_FIELD_NUMBER;
+import static com.android.car.telemetry.publisher.Constants.STATS_BUNDLE_KEY_PREFIX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.PersistableBundle;
+import android.util.SparseArray;
+
+import com.android.car.telemetry.AtomsProto.Atom;
+import com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot;
+import com.android.car.telemetry.StatsLogProto.DimensionsValue;
+import com.android.car.telemetry.publisher.HashUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class ProcessMemorySnapshotConverterTest {
+ private static final Atom ATOM_A =
+ Atom.newBuilder()
+ .setProcessMemorySnapshot(ProcessMemorySnapshot.newBuilder()
+ .setPid(88)
+ .setOomScoreAdj(100)
+ .setRssInKilobytes(1234)
+ .setAnonRssInKilobytes(99)
+ .setSwapInKilobytes(101)
+ .setAnonRssAndSwapInKilobytes(200)
+ .setGpuMemoryKb(0)
+ .setHasForegroundServices(true))
+ .build();
+
+ private static final Atom ATOM_B =
+ Atom.newBuilder()
+ .setProcessMemorySnapshot(ProcessMemorySnapshot.newBuilder()
+ .setPid(99)
+ .setOomScoreAdj(200)
+ .setRssInKilobytes(6666)
+ .setAnonRssInKilobytes(100)
+ .setSwapInKilobytes(100)
+ .setAnonRssAndSwapInKilobytes(200)
+ .setGpuMemoryKb(1)
+ .setHasForegroundServices(false))
+ .build();
+
+ private static final Atom ATOM_MISMATCH =
+ Atom.newBuilder()
+ .setProcessMemorySnapshot(ProcessMemorySnapshot.newBuilder()
+ // Some fields are not set, creating mismatch with above atoms
+ .setSwapInKilobytes(333))
+ .build();
+
+ private static final List<Integer> DIM_FIELDS_IDS = Arrays.asList(1, 2);
+ private static final Long HASH_1 = HashUtils.murmur2Hash64("process.name.1");
+ private static final Long HASH_2 = HashUtils.murmur2Hash64("process.name.2");
+ private static final Map<Long, String> HASH_STR_MAP = Map.of(
+ HASH_1, "process.name.1",
+ HASH_2, "process.name.2");
+
+ private static final List<DimensionsValue> DV_PAIR_A =
+ Arrays.asList(
+ DimensionsValue.newBuilder().setValueInt(1000).build(),
+ DimensionsValue.newBuilder().setValueStrHash(HASH_1).build());
+
+ private static final List<DimensionsValue> DV_PAIR_B =
+ Arrays.asList(
+ DimensionsValue.newBuilder().setValueInt(2000).build(),
+ DimensionsValue.newBuilder().setValueStrHash(HASH_2).build());
+
+ private static final List<DimensionsValue> DV_PAIR_MALFORMED =
+ Arrays.asList(
+ DimensionsValue.newBuilder().setValueInt(3000).build(),
+ // Wrong format since leaf level dimension value should set value, not field
+ DimensionsValue.newBuilder().setField(3).build());
+
+ // Subject of the test.
+ private ProcessMemorySnapshotConverter mConverter = new ProcessMemorySnapshotConverter();
+
+ @Test
+ public void testConvertAtomsListWithDimensionValues_putsCorrectDataToPersistableBundle()
+ throws StatsConversionException {
+ List<Atom> atomsList = Arrays.asList(ATOM_A, ATOM_B);
+ List<List<DimensionsValue>> dimensionsValuesList = Arrays.asList(DV_PAIR_A, DV_PAIR_B);
+ SparseArray<AtomFieldAccessor<ProcessMemorySnapshot, ?>> accessorMap =
+ mConverter.getAtomFieldAccessorMap();
+
+ PersistableBundle bundle = mConverter.convert(atomsList, DIM_FIELDS_IDS,
+ dimensionsValuesList, HASH_STR_MAP);
+
+ assertThat(bundle.size()).isEqualTo(10);
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(UID_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(1000, 2000).inOrder();
+ assertThat(Arrays.asList(bundle.getStringArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ PROCESS_NAME_FIELD_NUMBER).getFieldName())))
+ .containsExactly("process.name.1", "process.name.2").inOrder();
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(PID_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(88, 99);
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ OOM_SCORE_ADJ_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(100, 200).inOrder();
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ RSS_IN_KILOBYTES_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(1234, 6666).inOrder();
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ ANON_RSS_IN_KILOBYTES_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(99, 100).inOrder();
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ SWAP_IN_KILOBYTES_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(101, 100).inOrder();
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ ANON_RSS_AND_SWAP_IN_KILOBYTES_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(200, 200).inOrder();
+ assertThat(bundle.getIntArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ GPU_MEMORY_KB_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(0, 1).inOrder();
+ assertThat(bundle.getBooleanArray(
+ STATS_BUNDLE_KEY_PREFIX + accessorMap.get(
+ HAS_FOREGROUND_SERVICES_FIELD_NUMBER).getFieldName()))
+ .asList().containsExactly(true, false).inOrder();
+ }
+
+ @Test
+ public void testAtomSetFieldInconsistency_throwsException() {
+ List<Atom> atomsList = Arrays.asList(ATOM_A, ATOM_MISMATCH);
+ List<List<DimensionsValue>> dimensionsValuesList = Arrays.asList(DV_PAIR_A, DV_PAIR_B);
+
+ assertThrows(
+ StatsConversionException.class,
+ () -> mConverter.convert(
+ atomsList,
+ DIM_FIELDS_IDS,
+ dimensionsValuesList,
+ HASH_STR_MAP));
+ }
+
+ @Test
+ public void testMalformedDimensionValue_throwsException() {
+ List<Atom> atomsList = Arrays.asList(ATOM_A, ATOM_B);
+ List<List<DimensionsValue>> dimensionsValuesList =
+ Arrays.asList(DV_PAIR_A, DV_PAIR_MALFORMED);
+
+ assertThrows(
+ StatsConversionException.class,
+ () -> mConverter.convert(
+ atomsList,
+ DIM_FIELDS_IDS,
+ dimensionsValuesList,
+ HASH_STR_MAP));
+ }
+}