Add EvsStats to evs_app to compute camera latency

EvsStats computes e2e camera->display latency of the
first frame.

Test: mmm -j packages/services/Car/cpp/evs/apps/default
Test: Tested on seahawk by installing evs_app and enabling EVS
      and running the app. Verified that ICarTelemetry receives
      the data.
Bug: 173158650
Change-Id: Iec4eee04193a379e77deee7167de73d71b0c3d85
diff --git a/cpp/evs/apps/default/Android.bp b/cpp/evs/apps/default/Android.bp
index 3b393e1..4b1fadf 100644
--- a/cpp/evs/apps/default/Android.bp
+++ b/cpp/evs/apps/default/Android.bp
@@ -25,6 +25,7 @@
     srcs: [
         "evs_app.cpp",
         "EvsStateControl.cpp",
+        "EvsStats.cpp",
         "RenderBase.cpp",
         "RenderDirectView.cpp",
         "RenderTopView.cpp",
@@ -50,10 +51,13 @@
         "libhardware",
         "libpng",
         "libcamera_metadata",
+        "android.frameworks.automotive.telemetry-V1-ndk_platform",
         "android.hardware.camera.device@3.2",
         "android.hardware.automotive.evs@1.0",
         "android.hardware.automotive.evs@1.1",
         "android.hardware.automotive.vehicle@2.0",
+        "libcartelemetry-evs-proto",
+        "libprotobuf-cpp-lite",
     ],
 
     static_libs: [
@@ -81,6 +85,20 @@
 
 }
 
+cc_library {
+    name: "libcartelemetry-evs-proto",
+    srcs: [
+        ":cartelemetry-evs-proto-srcs",
+    ],
+    proto: {
+        export_proto_headers: true,
+        type: "lite",
+    },
+    shared_libs: [
+        "libprotobuf-cpp-lite",
+    ],
+}
+
 prebuilt_etc {
     name: "config.json",
 
diff --git a/cpp/evs/apps/default/EvsStateControl.cpp b/cpp/evs/apps/default/EvsStateControl.cpp
index e3d9757..cb5faf1 100644
--- a/cpp/evs/apps/default/EvsStateControl.cpp
+++ b/cpp/evs/apps/default/EvsStateControl.cpp
@@ -44,17 +44,14 @@
             & static_cast<int32_t>(VehiclePropertyType::MASK));
 }
 
-
-EvsStateControl::EvsStateControl(android::sp <IVehicle>       pVnet,
-                                 android::sp <IEvsEnumerator> pEvs,
-                                 android::sp <IEvsDisplay>    pDisplay,
-                                 const ConfigManager&         config) :
-    mVehicle(pVnet),
-    mEvs(pEvs),
-    mDisplay(pDisplay),
-    mConfig(config),
-    mCurrentState(OFF) {
-
+EvsStateControl::EvsStateControl(android::sp<IVehicle> pVnet, android::sp<IEvsEnumerator> pEvs,
+                                 android::sp<IEvsDisplay> pDisplay, const ConfigManager& config) :
+      mVehicle(pVnet),
+      mEvs(pEvs),
+      mDisplay(pDisplay),
+      mConfig(config),
+      mCurrentState(OFF),
+      mEvsStats(EvsStats::build()) {
     // Initialize the property value containers we'll be updating (they'll be zeroed by default)
     static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32,
                   "Unexpected type for GEAR_SELECTION property");
@@ -114,7 +111,6 @@
     LOG(DEBUG) << "State controller ready";
 }
 
-
 bool EvsStateControl::startUpdateLoop() {
     // Create the thread and report success if it gets started
     mRenderThread = std::thread([this](){ updateLoop(); });
@@ -198,6 +194,12 @@
 
                 // Send the finished image back for display
                 mDisplay->returnTargetBufferForDisplay(tgtBuffer);
+
+                if (!mFirstFrameIsDisplayed) {
+                    mFirstFrameIsDisplayed = true;
+                    // returnTargetBufferForDisplay() is finished, the frame should be displayed
+                    mEvsStats.finishComputingFirstFrameLatency(android::uptimeMillis());
+                }
             }
         } else if (run) {
             // No active renderer, so sleep until somebody wakes us with another command
@@ -214,6 +216,9 @@
         mCurrentRenderer->deactivate();
     }
 
+    // If `ICarTelemetry` service was not ready before, we need to try sending data again.
+    mEvsStats.sendCollectedDataBlocking();
+
     printf("Shutting down app due to state control loop ending\n");
     LOG(ERROR) << "Shutting down app due to state control loop ending";
 }
@@ -295,6 +300,9 @@
         return true;
     }
 
+    // Used by CarStats to accurately compute stats, it needs to be close to the beginning.
+    auto desiredStateTimeMillis = android::uptimeMillis();
+
     LOG(DEBUG) << "Switching to state " << desiredState;
     LOG(DEBUG) << "  Current state " << mCurrentState
                << " has " << mCameraList[mCurrentState].size() << " cameras";
@@ -382,5 +390,12 @@
     LOG(INFO) << "Activated state " << desiredState;
     mCurrentState = desiredState;
 
+    mFirstFrameIsDisplayed = false;  // Got a new renderer, mark first frame is not displayed.
+
+    if (mCurrentRenderer != nullptr && desiredState == State::REVERSE) {
+        // Start computing the latency when the evs state changes.
+        mEvsStats.startComputingFirstFrameLatency(desiredStateTimeMillis);
+    }
+
     return true;
 }
diff --git a/cpp/evs/apps/default/EvsStateControl.h b/cpp/evs/apps/default/EvsStateControl.h
index 1953906..d847195 100644
--- a/cpp/evs/apps/default/EvsStateControl.h
+++ b/cpp/evs/apps/default/EvsStateControl.h
@@ -17,18 +17,18 @@
 #ifndef CAR_EVS_APP_EVSSTATECONTROL_H
 #define CAR_EVS_APP_EVSSTATECONTROL_H
 
-#include "StreamHandler.h"
 #include "ConfigManager.h"
+#include "EvsStats.h"
 #include "RenderBase.h"
+#include "StreamHandler.h"
 
-#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
-#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
-#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
 #include <android/hardware/automotive/evs/1.1/IEvsCamera.h>
+#include <android/hardware/automotive/evs/1.1/IEvsDisplay.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/vehicle/2.0/IVehicle.h>
 
 #include <thread>
 
-
 using namespace ::android::hardware::automotive::evs::V1_1;
 using namespace ::android::hardware::automotive::vehicle::V2_0;
 using ::android::hardware::Return;
@@ -112,6 +112,12 @@
     std::mutex                  mLock;
     std::condition_variable     mWakeSignal;
     std::queue<Command>         mCommandQueue;
+
+    EvsStats                    mEvsStats;  // Not thread-safe
+
+    // True if the first frame displayed on the mCurrentRenderer. Resets to false when
+    // mCurrentRenderer changes.
+    bool                        mFirstFrameIsDisplayed;
 };
 
 
diff --git a/cpp/evs/apps/default/EvsStats.cpp b/cpp/evs/apps/default/EvsStats.cpp
new file mode 100644
index 0000000..f43ae75
--- /dev/null
+++ b/cpp/evs/apps/default/EvsStats.cpp
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+#include "EvsStats.h"
+
+#include "packages/services/Car/cpp/telemetry/proto/evs.pb.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <utils/SystemClock.h>
+
+#include <vector>
+
+namespace {
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::android::automotive::telemetry::EvsFirstFrameLatency;
+
+// Name of ICarTelemetry service that consumes RVC latency metrics.
+constexpr const char kCarTelemetryServiceName[] =
+        "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+
+const int kCollectedDataSizeLimit = 200;  // arbitrary chosen
+// 10kb limit is imposed by ICarTelemetry, the limit if only for
+// CarData.content vector.
+const int kCarTelemetryMaxDataSizePerWrite = 10 * 1024;
+
+// Defined in packages/services/Car/cpp/telemetry/proto/CarData.proto
+const int kEvsFirstFrameLatencyId = 1;
+}  // namespace
+
+// static
+EvsStats EvsStats::build() {
+    // No need to enable stats if ICarTelemetry is not available.
+    bool enabled = ::AServiceManager_isDeclared(kCarTelemetryServiceName);
+    return EvsStats(enabled);
+}
+
+void EvsStats::startComputingFirstFrameLatency(int64_t startTimeMillis) {
+    mFirstFrameLatencyStartTimeMillis = startTimeMillis;
+}
+
+void EvsStats::finishComputingFirstFrameLatency(int64_t finishTimeMillis) {
+    if (!mEnabled) {
+        return;
+    }
+    if (mFirstFrameLatencyStartTimeMillis == EvsStatsState::NOT_STARTED) {
+        LOG(WARNING) << __func__ << "EvsStats received finishComputingFirstFrameLatency, but "
+                     << "startComputingFirstFrameLatency was not called before.";
+        return;
+    }
+    auto firstFrameLatencyMillis = finishTimeMillis - mFirstFrameLatencyStartTimeMillis;
+    mFirstFrameLatencyStartTimeMillis = EvsStatsState::NOT_STARTED;
+
+    LOG(DEBUG) << __func__ << ": firstFrameLatencyMillis = " << firstFrameLatencyMillis;
+
+    EvsFirstFrameLatency latency;
+    latency.set_start_timestamp_millis(mFirstFrameLatencyStartTimeMillis);
+    latency.set_latency_millis(firstFrameLatencyMillis);
+    std::vector<uint8_t> bytes(latency.ByteSize());
+    latency.SerializeToArray(&bytes[0], latency.ByteSize());
+    CarData msg;
+    msg.id = kEvsFirstFrameLatencyId;
+    msg.content = std::move(bytes);
+
+    if (msg.content.size() > kCarTelemetryMaxDataSizePerWrite) {
+        LOG(WARNING) << __func__ << "finishComputingFirstFrameLatency is trying to store data "
+                     << "with size " << msg.content.size() << ", which is larger than allowed "
+                     << kCarTelemetryMaxDataSizePerWrite;
+        return;
+    }
+
+    mCollectedData.push_back(msg);
+
+    while (mCollectedData.size() > kCollectedDataSizeLimit) {
+        mCollectedData.pop_front();
+    }
+
+    sendCollectedDataUnsafe(/* waitIfNotReady= */ false);
+}
+
+void EvsStats::sendCollectedDataBlocking() {
+    if (!mEnabled || mCollectedData.empty()) {
+        return;
+    }
+    sendCollectedDataUnsafe(/* waitIfNotReady= */ true);
+}
+
+std::shared_ptr<ICarTelemetry> EvsStats::getCarTelemetry(bool waitIfNotReady) {
+    {
+        const std::scoped_lock<std::mutex> lock(mMutex);
+        if (mCarTelemetry != nullptr) {
+            return mCarTelemetry;
+        }
+    }
+
+    AIBinder* binder;
+    if (waitIfNotReady) {
+        binder = ::AServiceManager_getService(kCarTelemetryServiceName);
+    } else {
+        binder = ::AServiceManager_checkService(kCarTelemetryServiceName);
+    }
+
+    if (binder == nullptr) {
+        LOG(WARNING) << __func__ << ": ICarTelemetry is not ready";
+        return nullptr;
+    }
+
+    const std::scoped_lock<std::mutex> lock(mMutex);  // locks until the end of the method
+    mCarTelemetry = ICarTelemetry::fromBinder(ndk::SpAIBinder(binder));
+    auto status = ndk::ScopedAStatus::fromStatus(
+            ::AIBinder_linkToDeath(mCarTelemetry->asBinder().get(), mBinderDeathRecipient.get(),
+                                   this));
+    if (!status.isOk()) {
+        LOG(WARNING) << __func__
+                     << ": Failed to linkToDeath, continuing anyway: " << status.getMessage();
+    }
+    return mCarTelemetry;
+}
+
+void EvsStats::sendCollectedDataUnsafe(bool waitIfNotReady) {
+    std::shared_ptr<ICarTelemetry> telemetry = getCarTelemetry(waitIfNotReady);
+    if (telemetry == nullptr) {
+        LOG(INFO) << __func__ << ": CarTelemetry is not ready, ignoring";
+        return;
+    }
+
+    // Send data chunk by chnk, because Binder has transfer data size limit.
+    // Adds the oldest elements to `sendingData` and tries to push ICarTelemetryService.
+    // If successful, erases then from `mCollectedData`, otherwise leaves them there to try again
+    // later.
+    while (!mCollectedData.empty()) {
+        int sendingDataSizeBytes = 0;
+        std::vector<CarData> sendingData;
+        auto it = mCollectedData.begin();
+        while (it != mCollectedData.end()) {
+            sendingDataSizeBytes += it->content.size();
+            if (sendingDataSizeBytes > kCarTelemetryMaxDataSizePerWrite) {
+                break;
+            }
+            sendingData.push_back(*it);
+            it++;
+        }
+        ndk::ScopedAStatus status = telemetry->write(sendingData);
+        if (!status.isOk()) {
+            LOG(WARNING) << __func__
+                         << "Failed to write data to ICarTelemetry: " << status.getMessage();
+            return;
+        }
+        mCollectedData.erase(mCollectedData.begin(), it);
+    }
+}
+
+// Removes the listener if its binder dies.
+void EvsStats::telemetryBinderDiedImpl() {
+    LOG(WARNING) << __func__ << "ICarTelemetry service died, resetting the state";
+    const std::scoped_lock<std::mutex> lock(mMutex);
+    mCarTelemetry = nullptr;
+}
+
+// static
+void EvsStats::telemetryBinderDied(void* cookie) {
+    // We expect the pointer to be alive as there is only a single instance of
+    // EvsStats and if EvsStats is destructed, then the whole evs_app should be dead too.
+    auto thiz = static_cast<EvsStats*>(cookie);
+    thiz->telemetryBinderDiedImpl();
+}
diff --git a/cpp/evs/apps/default/EvsStats.h b/cpp/evs/apps/default/EvsStats.h
new file mode 100644
index 0000000..0d9d47f
--- /dev/null
+++ b/cpp/evs/apps/default/EvsStats.h
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#ifndef CAR_EVS_APP_EVSSTATS_H
+#define CAR_EVS_APP_EVSSTATS_H
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android/binder_status.h>
+#include <utils/Mutex.h>
+
+#include <deque>
+#include <memory>
+
+// Performs metric computations, sends to `ICarTelemetry`.
+//
+// Not thread-safe. Methods `startComputingFirstFrameLatency`, `finishComputingFirstFrameLatency`
+// and `sendCollectedDataBlocking` must be called from the same thread.
+class EvsStats {
+public:
+    // Instantiates EvsStats.
+    static EvsStats build();
+
+    // Starts computing end-2-end first frame latency: from the time of the event that starts the
+    // camera to the time of the display of the very first frame.
+    // Call this method when an event that enables the camera occurred, e.g. gear shift to REAR.
+    // Param `startTimeMillis` should be `android::uptimeMillis()`.
+    void startComputingFirstFrameLatency(int64_t startTimeMillis);
+
+    // Computes the latency and sends the data to `ICarTelemetry` if the receiving service is up.
+    // Call this method when the first camera frame is displayed on the screen and don't call
+    // after that unless a new computation is started.
+    // Param `finishTimeMillis` should be `android::uptimeMillis()`.
+    void finishComputingFirstFrameLatency(int64_t finishTimeMillis);
+
+    // Sends the collected data to `ICarTelemetry`. Blocks for short amount of time if the service
+    // is unavailable.
+    void sendCollectedDataBlocking();
+
+private:
+    enum EvsStatsState {
+        NOT_STARTED = -1,
+    };
+
+private:
+    EvsStats(bool enabled) :
+          mEnabled(enabled),
+          mBinderDeathRecipient(::AIBinder_DeathRecipient_new(EvsStats::telemetryBinderDied)) {}
+
+    // Death recipient callback that is called when ICarTelemetry dies.
+    // The cookie is a pointer to a EvsStats object.
+    static void telemetryBinderDied(void* cookie);
+
+    void telemetryBinderDiedImpl();
+
+    // Tries sending data if the receiving service is up.
+    // Must be called when both `mEnabled` is true and `mCollectedData` is not empty.
+    //
+    // \param waitIfNotReady - if true, it can block briefly until ICarTelemetry is ready.
+    void sendCollectedDataUnsafe(bool waitIfNotReady);
+
+    // Returns the instance of ICarTelemetry.
+    //
+    // \param waitIfNotReady - if true, it can block briefly until ICarTelemetry is ready.
+    std::shared_ptr<aidl::android::frameworks::automotive::telemetry::ICarTelemetry>
+    getCarTelemetry(bool waitIfNotReady);
+
+    std::mutex mMutex;
+    bool mEnabled;
+    int64_t mFirstFrameLatencyStartTimeMillis = EvsStatsState::NOT_STARTED;
+    // This is a ring buffer
+    std::deque<aidl::android::frameworks::automotive::telemetry::CarData> mCollectedData;
+    std::shared_ptr<aidl::android::frameworks::automotive::telemetry::ICarTelemetry> mCarTelemetry;
+    ndk::ScopedAIBinder_DeathRecipient mBinderDeathRecipient;
+};
+
+#endif  // CAR_EVS_APP_EVSSTATS_H
diff --git a/cpp/telemetry/proto/Android.bp b/cpp/telemetry/proto/Android.bp
new file mode 100644
index 0000000..862a537
--- /dev/null
+++ b/cpp/telemetry/proto/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "cartelemetry-evs-proto-srcs",
+    srcs: [
+        "evs.proto",
+    ],
+}
diff --git a/cpp/telemetry/proto/CarData.proto b/cpp/telemetry/proto/CarData.proto
new file mode 100644
index 0000000..96e8c2e
--- /dev/null
+++ b/cpp/telemetry/proto/CarData.proto
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+/**
+ * This file contains all the known CarData messages used in AAOS. Some messages
+ * might be defined in OEM's private repo, the owners of those messages must
+ * make sure the CarData IDs are not colliding.
+ *
+ * All the messages must be defined in the CarData message and given a unique
+ * ID.
+ */
+
+syntax = "proto2";
+
+package android.automotive.telemetry;
+option java_package = "com.android.automotive.telemetry";
+option java_outer_classname = "CarDataProto";
+
+import "packages/services/Car/cpp/telemetry/proto/evs.proto";
+
+message CarData {
+  oneof pushed {
+    EvsFirstFrameLatency evs_first_frame_latency = 1;
+  }
+
+  // DO NOT USE field numbers above 100,000 in AOSP.
+  // Field numbers 100,000 - 199,999 are reserved for non-AOSP (e.g. OEMs) to
+  // use. Field numbers 200,000 and above are reserved for future use; do not
+  // use them at all.
+}
diff --git a/cpp/telemetry/proto/evs.proto b/cpp/telemetry/proto/evs.proto
new file mode 100644
index 0000000..c39434f
--- /dev/null
+++ b/cpp/telemetry/proto/evs.proto
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/**
+ * This file contains all the CarData messages used in EVS related processes.
+ * It's preferred to group CarData messages used in a single process or service
+ * to improve performance and memory usage.
+ *
+ * All the messages must be defined in the CarData message and given a unique
+ * ID.
+ */
+
+syntax = "proto2";
+
+package android.automotive.telemetry;
+option java_package = "com.android.automotive.telemetry";
+option java_outer_classname = "EvsProto";
+
+message EvsFirstFrameLatency {
+  // Latency measurement start time since boot.
+  optional int64 start_timestamp_millis = 1;
+
+  // end-2-end latency since gear shifted to reverse and the
+  // first frame from camera is displayed on the screen.
+  optional int64 latency_millis = 2;
+}