Transcoding: Log transcoder level metrics.

- Add transcoder-level metric logging through west world,
to monitor performance and failures.

- Add new unit tests for TranscodingLogger class.

Bug: 179274112
Test: New and existing unit tests.
Change-Id: Ie8d9bc589c714a7b8be8f4f298c4fa75a81a6e56
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index 534fa91..042850c 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -82,6 +82,7 @@
     srcs: [
         "TranscoderWrapper.cpp",
         "TranscodingClientManager.cpp",
+        "TranscodingLogger.cpp",
         "TranscodingResourcePolicy.cpp",
         "TranscodingSessionController.cpp",
         "TranscodingThermalPolicy.cpp",
@@ -96,6 +97,7 @@
         "libutils",
         "libmediatranscoder",
         "libmediandk",
+        "libstatssocket#30",
     ],
     export_shared_lib_headers: [
         "libmediandk",
@@ -106,6 +108,7 @@
     static_libs: [
         "mediatranscoding_aidl_interface-ndk_platform",
         "resourceobserver_aidl_interface-V1-ndk_platform",
+        "libstatslog_media",
     ],
 
     cflags: [
@@ -126,3 +129,43 @@
         cfi: true,
     },
 }
+
+cc_library_static {
+    name: "libstatslog_media",
+    generated_sources: ["statslog_media.cpp"],
+    generated_headers: ["statslog_media.h"],
+    min_sdk_version: "29",
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    export_generated_headers: ["statslog_media.h"],
+    apex_available: [
+        "com.android.media",
+        "test_com.android.media",
+    ],
+    shared_libs: [
+        "libcutils",
+        "liblog",
+        "libstatssocket#30",
+        "libutils",
+    ],
+}
+
+genrule {
+    name: "statslog_media.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_media.h --module media --namespace android,media,stats",
+    out: [
+        "statslog_media.h",
+    ],
+}
+
+genrule {
+    name: "statslog_media.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_media.cpp --module media --namespace android,media,stats --importHeader statslog_media.h",
+    out: [
+        "statslog_media.cpp",
+    ],
+}
\ No newline at end of file
diff --git a/media/libmediatranscoding/TranscoderWrapper.cpp b/media/libmediatranscoding/TranscoderWrapper.cpp
index 4bd4105..d9c98c6 100644
--- a/media/libmediatranscoding/TranscoderWrapper.cpp
+++ b/media/libmediatranscoding/TranscoderWrapper.cpp
@@ -56,34 +56,34 @@
     }
 }
 
-static AMediaFormat* getVideoFormat(
+static std::shared_ptr<AMediaFormat> getVideoFormat(
         const char* originalMime,
         const std::optional<TranscodingVideoTrackFormat>& requestedFormat) {
     if (requestedFormat == std::nullopt) {
         return nullptr;
     }
 
-    AMediaFormat* format = AMediaFormat_new();
+    std::shared_ptr<AMediaFormat> format =
+            std::shared_ptr<AMediaFormat>(AMediaFormat_new(), &AMediaFormat_delete);
     bool changed = false;
     if (requestedFormat->codecType == TranscodingVideoCodecType::kHevc &&
         strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_HEVC)) {
-        AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
+        AMediaFormat_setString(format.get(), AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_HEVC);
         changed = true;
     } else if (requestedFormat->codecType == TranscodingVideoCodecType::kAvc &&
                strcmp(originalMime, AMEDIA_MIMETYPE_VIDEO_AVC)) {
-        AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
+        AMediaFormat_setString(format.get(), AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
         changed = true;
     }
     if (requestedFormat->bitrateBps > 0) {
-        AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
+        AMediaFormat_setInt32(format.get(), AMEDIAFORMAT_KEY_BIT_RATE, requestedFormat->bitrateBps);
         changed = true;
     }
     // TODO: translate other fields from requestedFormat to the format for MediaTranscoder.
     // Also need to determine more settings to expose in TranscodingVideoTrackFormat.
     if (!changed) {
-        AMediaFormat_delete(format);
         // Use null format for passthru.
-        format = nullptr;
+        format.reset();
     }
     return format;
 }
@@ -180,8 +180,10 @@
 };
 
 TranscoderWrapper::TranscoderWrapper(const std::shared_ptr<TranscoderCallbackInterface>& cb,
+                                     const std::shared_ptr<TranscodingLogger>& logger,
                                      int64_t heartBeatIntervalUs)
       : mCallback(cb),
+        mLogger(logger),
         mHeartBeatIntervalUs(heartBeatIntervalUs),
         mCurrentClientId(0),
         mCurrentSessionId(-1),
@@ -219,10 +221,10 @@
 }
 
 void TranscoderWrapper::start(ClientIdType clientId, SessionIdType sessionId,
-                              const TranscodingRequestParcel& request,
+                              const TranscodingRequestParcel& request, uid_t callingUid,
                               const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     queueEvent(Event::Start, clientId, sessionId, [=, &request] {
-        media_status_t err = handleStart(clientId, sessionId, request, clientCb);
+        media_status_t err = handleStart(clientId, sessionId, request, callingUid, clientCb);
         if (err != AMEDIA_OK) {
             cleanup();
             reportError(clientId, sessionId, err);
@@ -253,10 +255,10 @@
 }
 
 void TranscoderWrapper::resume(ClientIdType clientId, SessionIdType sessionId,
-                               const TranscodingRequestParcel& request,
+                               const TranscodingRequestParcel& request, uid_t callingUid,
                                const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     queueEvent(Event::Resume, clientId, sessionId, [=, &request] {
-        media_status_t err = handleResume(clientId, sessionId, request, clientCb);
+        media_status_t err = handleResume(clientId, sessionId, request, callingUid, clientCb);
         if (err != AMEDIA_OK) {
             cleanup();
             reportError(clientId, sessionId, err);
@@ -280,6 +282,7 @@
             } else {
                 ALOGI("transcoder stopped");
             }
+            logSessionEnded(TranscodingLogger::SessionEndedReason::CANCELLED, err);
             cleanup();
         } else {
             // For sessions that's not currently running, release any pausedState for the session.
@@ -297,6 +300,7 @@
     queueEvent(Event::Finish, clientId, sessionId, [=] {
         if (mTranscoder != nullptr && clientId == mCurrentClientId &&
             sessionId == mCurrentSessionId) {
+            logSessionEnded(TranscodingLogger::SessionEndedReason::FINISHED, AMEDIA_OK);
             cleanup();
         }
 
@@ -314,6 +318,7 @@
             [=] {
                 if (mTranscoder != nullptr && clientId == mCurrentClientId &&
                     sessionId == mCurrentSessionId) {
+                    logSessionEnded(TranscodingLogger::SessionEndedReason::ERROR, error);
                     cleanup();
                 }
                 reportError(clientId, sessionId, error);
@@ -345,7 +350,8 @@
 
 media_status_t TranscoderWrapper::setupTranscoder(
         ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
-        const std::shared_ptr<ITranscodingClientCallback>& clientCb,
+        uid_t callingUid, const std::shared_ptr<ITranscodingClientCallback>& clientCb,
+        TranscodingLogger::SessionEndedReason* failureReason,
         const std::shared_ptr<ndk::ScopedAParcel>& pausedState) {
     if (clientCb == nullptr) {
         ALOGE("client callback is null");
@@ -364,6 +370,7 @@
         status = clientCb->openFileDescriptor(request.sourceFilePath, "r", &srcFd);
         if (!status.isOk() || srcFd.get() < 0) {
             ALOGE("failed to open source");
+            *failureReason = TranscodingLogger::SessionEndedReason::OPEN_SRC_FD_FAILED;
             return AMEDIA_ERROR_IO;
         }
         srcFdInt = srcFd.get();
@@ -377,6 +384,7 @@
         status = clientCb->openFileDescriptor(request.destinationFilePath, "rw", &dstFd);
         if (!status.isOk() || dstFd.get() < 0) {
             ALOGE("failed to open destination");
+            *failureReason = TranscodingLogger::SessionEndedReason::OPEN_DST_FD_FAILED;
             return AMEDIA_ERROR_IO;
         }
         dstFdInt = dstFd.get();
@@ -384,41 +392,46 @@
 
     mCurrentClientId = clientId;
     mCurrentSessionId = sessionId;
+    mCurrentCallingUid = callingUid;
     mTranscoderCb = std::make_shared<CallbackImpl>(shared_from_this(), clientId, sessionId);
     mTranscoder = MediaTranscoder::create(mTranscoderCb, mHeartBeatIntervalUs, request.clientPid,
                                           request.clientUid, pausedState);
     if (mTranscoder == nullptr) {
         ALOGE("failed to create transcoder");
+        *failureReason = TranscodingLogger::SessionEndedReason::CREATE_FAILED;
         return AMEDIA_ERROR_UNKNOWN;
     }
 
     media_status_t err = mTranscoder->configureSource(srcFdInt);
     if (err != AMEDIA_OK) {
         ALOGE("failed to configure source: %d", err);
+        *failureReason = TranscodingLogger::SessionEndedReason::CONFIG_SRC_FAILED;
         return err;
     }
 
     std::vector<std::shared_ptr<AMediaFormat>> trackFormats = mTranscoder->getTrackFormats();
     if (trackFormats.size() == 0) {
         ALOGE("failed to get track formats!");
+        *failureReason = TranscodingLogger::SessionEndedReason::NO_TRACKS;
         return AMEDIA_ERROR_MALFORMED;
     }
 
     for (int i = 0; i < trackFormats.size(); ++i) {
-        AMediaFormat* format = nullptr;
+        std::shared_ptr<AMediaFormat> format;
         const char* mime = nullptr;
         AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
 
         if (!strncmp(mime, "video/", 6)) {
             format = getVideoFormat(mime, request.requestedVideoTrackFormat);
+
+            mSrcFormat = trackFormats[i];
+            mDstFormat = format;
         }
 
-        err = mTranscoder->configureTrackFormat(i, format);
-        if (format != nullptr) {
-            AMediaFormat_delete(format);
-        }
+        err = mTranscoder->configureTrackFormat(i, format.get());
         if (err != AMEDIA_OK) {
             ALOGE("failed to configure track format for track %d: %d", i, err);
+            *failureReason = TranscodingLogger::SessionEndedReason::CONFIG_TRACK_FAILED;
             return err;
         }
     }
@@ -426,6 +439,7 @@
     err = mTranscoder->configureDestination(dstFdInt);
     if (err != AMEDIA_OK) {
         ALOGE("failed to configure dest: %d", err);
+        *failureReason = TranscodingLogger::SessionEndedReason::CONFIG_DST_FAILED;
         return err;
     }
 
@@ -434,17 +448,23 @@
 
 media_status_t TranscoderWrapper::handleStart(
         ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
-        const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+        uid_t callingUid, const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     ALOGI("%s: setting up transcoder for start", __FUNCTION__);
-    media_status_t err = setupTranscoder(clientId, sessionId, request, clientCb);
+    TranscodingLogger::SessionEndedReason reason = TranscodingLogger::SessionEndedReason::UNKNOWN;
+    media_status_t err =
+            setupTranscoder(clientId, sessionId, request, callingUid, clientCb, &reason);
     if (err != AMEDIA_OK) {
         ALOGI("%s: failed to setup transcoder", __FUNCTION__);
+        logSessionEnded(reason, err);
         return err;
     }
 
+    mTranscodeStartTime = std::chrono::steady_clock::now();
+
     err = mTranscoder->start();
     if (err != AMEDIA_OK) {
         ALOGE("%s: failed to start transcoder: %d", __FUNCTION__, err);
+        logSessionEnded(TranscodingLogger::SessionEndedReason::START_FAILED, err);
         return err;
     }
 
@@ -467,6 +487,7 @@
 
     std::shared_ptr<ndk::ScopedAParcel> pauseStates;
     media_status_t err = mTranscoder->pause(&pauseStates);
+    logSessionEnded(TranscodingLogger::SessionEndedReason::PAUSED, err);
     if (err != AMEDIA_OK) {
         ALOGE("%s: failed to pause transcoder: %d", __FUNCTION__, err);
         return err;
@@ -479,7 +500,7 @@
 
 media_status_t TranscoderWrapper::handleResume(
         ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
-        const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
+        uid_t callingUid, const std::shared_ptr<ITranscodingClientCallback>& clientCb) {
     std::shared_ptr<ndk::ScopedAParcel> pausedState;
     auto it = mPausedStateMap.find(SessionKeyType(clientId, sessionId));
     if (it != mPausedStateMap.end()) {
@@ -491,15 +512,23 @@
     }
 
     ALOGI("%s: setting up transcoder for resume", __FUNCTION__);
-    media_status_t err = setupTranscoder(clientId, sessionId, request, clientCb, pausedState);
+    TranscodingLogger::SessionEndedReason reason = TranscodingLogger::SessionEndedReason::UNKNOWN;
+    media_status_t err = setupTranscoder(clientId, sessionId, request, callingUid, clientCb,
+                                         &reason, pausedState);
     if (err != AMEDIA_OK) {
         ALOGE("%s: failed to setup transcoder: %d", __FUNCTION__, err);
+        logSessionEnded(reason, err);
         return err;
     }
 
+    // Note: For now resume() will just restart transcoding from the beginning, so there is no need
+    // to distinguish between resume and start from a performance perspective.
+    mTranscodeStartTime = std::chrono::steady_clock::now();
+
     err = mTranscoder->resume();
     if (err != AMEDIA_OK) {
         ALOGE("%s: failed to resume transcoder: %d", __FUNCTION__, err);
+        logSessionEnded(TranscodingLogger::SessionEndedReason::RESUME_FAILED, err);
         return err;
     }
 
@@ -510,8 +539,23 @@
 void TranscoderWrapper::cleanup() {
     mCurrentClientId = 0;
     mCurrentSessionId = -1;
+    mCurrentCallingUid = -1;
     mTranscoderCb = nullptr;
     mTranscoder = nullptr;
+    mSrcFormat = nullptr;
+    mDstFormat = nullptr;
+}
+
+void TranscoderWrapper::logSessionEnded(const TranscodingLogger::SessionEndedReason& reason,
+                                        int error) {
+    std::chrono::microseconds transcodeDuration(-1);
+    if (reason == TranscodingLogger::SessionEndedReason::FINISHED && error == AMEDIA_OK) {
+        transcodeDuration = std::chrono::duration_cast<std::chrono::microseconds>(
+                std::chrono::steady_clock::now() - mTranscodeStartTime);
+    }
+
+    mLogger->logSessionEnded(reason, mCurrentCallingUid, error, transcodeDuration, mSrcFormat.get(),
+                             mDstFormat.get());
 }
 
 void TranscoderWrapper::queueEvent(Event::Type type, ClientIdType clientId, SessionIdType sessionId,
@@ -555,5 +599,4 @@
         lock.lock();
     }
 }
-
 }  // namespace android
diff --git a/media/libmediatranscoding/TranscodingClientManager.cpp b/media/libmediatranscoding/TranscodingClientManager.cpp
index 76bb33e..06c5421 100644
--- a/media/libmediatranscoding/TranscodingClientManager.cpp
+++ b/media/libmediatranscoding/TranscodingClientManager.cpp
@@ -162,8 +162,8 @@
 
     int32_t sessionId = mNextSessionId.fetch_add(1);
 
-    *_aidl_return = owner->mSessionController->submit(mClientId, sessionId, in_clientUid,
-                                                      in_request, mClientCallback);
+    *_aidl_return = owner->mSessionController->submit(mClientId, sessionId, callingUid,
+                                                      in_clientUid, in_request, mClientCallback);
 
     if (*_aidl_return) {
         out_session->sessionId = sessionId;
diff --git a/media/libmediatranscoding/TranscodingLogger.cpp b/media/libmediatranscoding/TranscodingLogger.cpp
new file mode 100644
index 0000000..29a52b0
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingLogger.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingLogger"
+
+#include <media/NdkCommon.h>
+#include <media/TranscodingLogger.h>
+#include <statslog_media.h>
+#include <utils/Log.h>
+
+#include <cmath>
+#include <string>
+
+namespace android {
+
+static_assert(TranscodingLogger::UNKNOWN ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__UNKNOWN,
+              "Session event mismatch");
+static_assert(TranscodingLogger::FINISHED ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__FINISHED,
+              "Session event mismatch");
+static_assert(TranscodingLogger::ERROR ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__ERROR,
+              "Session event mismatch");
+static_assert(TranscodingLogger::PAUSED ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__PAUSED,
+              "Session event mismatch");
+static_assert(TranscodingLogger::CANCELLED ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CANCELLED,
+              "Session event mismatch");
+static_assert(TranscodingLogger::START_FAILED ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__START_FAILED,
+              "Session event mismatch");
+static_assert(TranscodingLogger::RESUME_FAILED ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__RESUME_FAILED,
+              "Session event mismatch");
+static_assert(TranscodingLogger::CREATE_FAILED ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CREATE_FAILED,
+              "Session event mismatch");
+static_assert(
+        TranscodingLogger::CONFIG_SRC_FAILED ==
+                android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CONFIG_SRC_FAILED,
+        "Session event mismatch");
+static_assert(
+        TranscodingLogger::CONFIG_DST_FAILED ==
+                android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CONFIG_DST_FAILED,
+        "Session event mismatch");
+static_assert(
+        TranscodingLogger::CONFIG_TRACK_FAILED ==
+                android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__CONFIG_TRACK_FAILED,
+        "Session event mismatch");
+static_assert(
+        TranscodingLogger::OPEN_SRC_FD_FAILED ==
+                android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__OPEN_SRC_FD_FAILED,
+        "Session event mismatch");
+static_assert(
+        TranscodingLogger::OPEN_DST_FD_FAILED ==
+                android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__OPEN_DST_FD_FAILED,
+        "Session event mismatch");
+static_assert(TranscodingLogger::NO_TRACKS ==
+                      android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED__REASON__NO_TRACKS,
+              "Session event mismatch");
+
+static inline int32_t getInt32(AMediaFormat* fmt, const char* key, int32_t defaultValue = -1) {
+    int32_t value;
+    if (fmt == nullptr || !AMediaFormat_getInt32(fmt, key, &value)) {
+        ALOGW("Unable to get %s", key);
+        value = defaultValue;
+    }
+    return value;
+}
+
+// Note: returned string is owned by format and only valid until the next getString.
+static inline const char* getString(AMediaFormat* fmt, const char* key,
+                                    const char* defaultValue = "(null)") {
+    const char* value;
+    if (fmt == nullptr || !AMediaFormat_getString(fmt, key, &value)) {
+        ALOGW("Unable to get %s", key);
+        value = defaultValue;
+    }
+    return value;
+}
+
+TranscodingLogger::TranscodingLogger()
+      : mSessionEndedAtomWriter(&android::media::stats::stats_write) {}
+
+void TranscodingLogger::logSessionEnded(enum SessionEndedReason reason, uid_t callingUid,
+                                        int status, std::chrono::microseconds duration,
+                                        AMediaFormat* srcFormat, AMediaFormat* dstFormat) {
+    logSessionEnded(std::chrono::steady_clock::now(), reason, callingUid, status, duration,
+                    srcFormat, dstFormat);
+}
+
+void TranscodingLogger::logSessionEnded(const std::chrono::steady_clock::time_point& now,
+                                        enum SessionEndedReason reason, uid_t callingUid,
+                                        int status, std::chrono::microseconds duration,
+                                        AMediaFormat* srcFormat, AMediaFormat* dstFormat) {
+    if (srcFormat == nullptr) {
+        ALOGE("Source format is null. Dropping event.");
+        return;
+    }
+
+    if (!shouldLogAtom(now, status)) {
+        ALOGD("Maximum logged event count reached. Dropping event.");
+        return;
+    }
+
+    // Extract the pieces of information to log.
+    const int32_t srcWidth = getInt32(srcFormat, AMEDIAFORMAT_KEY_WIDTH);
+    const int32_t srcHeight = getInt32(srcFormat, AMEDIAFORMAT_KEY_HEIGHT);
+    const char* srcMime = getString(srcFormat, AMEDIAFORMAT_KEY_MIME);
+    const int32_t srcProfile = getInt32(srcFormat, AMEDIAFORMAT_KEY_PROFILE);
+    const int32_t srcLevel = getInt32(srcFormat, AMEDIAFORMAT_KEY_LEVEL);
+    const int32_t srcFrameRate = getInt32(srcFormat, AMEDIAFORMAT_KEY_FRAME_RATE);
+    const int32_t srcFrameCount = getInt32(srcFormat, AMEDIAFORMAT_KEY_FRAME_COUNT);
+    const bool srcIsHdr = AMediaFormatUtils::VideoIsHdr(srcFormat);
+
+    int32_t dstWidth = getInt32(dstFormat, AMEDIAFORMAT_KEY_WIDTH, srcWidth);
+    int32_t dstHeight = getInt32(dstFormat, AMEDIAFORMAT_KEY_HEIGHT, srcHeight);
+    const char* dstMime = dstFormat == nullptr
+                                  ? "passthrough"
+                                  : getString(dstFormat, AMEDIAFORMAT_KEY_MIME, srcMime);
+    const bool dstIsHdr = false;  // Transcoder always request SDR output.
+
+    int64_t tmpDurationUs;
+    const int32_t srcDurationMs =
+            AMediaFormat_getInt64(srcFormat, AMEDIAFORMAT_KEY_DURATION, &tmpDurationUs)
+                    ? static_cast<int32_t>(tmpDurationUs / 1000)
+                    : -1;
+
+    int32_t transcodeFrameRate = -1;
+    if (status == 0 && srcFrameCount > 0 && duration.count() > 0) {
+        std::chrono::duration<double> seconds{duration};
+        transcodeFrameRate = static_cast<int32_t>(
+                std::round(static_cast<double>(srcFrameCount) / seconds.count()));
+    }
+
+    // Write the atom.
+    mSessionEndedAtomWriter(android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED,
+                            static_cast<int>(reason), callingUid, status, transcodeFrameRate,
+                            srcWidth, srcHeight, srcMime, srcProfile, srcLevel, srcFrameRate,
+                            srcDurationMs, srcIsHdr, dstWidth, dstHeight, dstMime, dstIsHdr);
+}
+
+bool TranscodingLogger::shouldLogAtom(const std::chrono::steady_clock::time_point& now,
+                                      int status) {
+    std::scoped_lock lock{mLock};
+    static const std::chrono::hours oneDay(24);
+
+    // Remove events older than one day.
+    while (mLastLoggedAtoms.size() > 0 && (now - mLastLoggedAtoms.front().first) >= oneDay) {
+        if (mLastLoggedAtoms.front().second == AMEDIA_OK) {
+            --mSuccessfulCount;
+        }
+        mLastLoggedAtoms.pop();
+    }
+
+    // Don't log if maximum number of events is reached.
+    if (mLastLoggedAtoms.size() >= kMaxAtomsPerDay) {
+        return false;
+    }
+
+    // Don't log if the event is successful and the maximum number of successful events is reached.
+    if (status == AMEDIA_OK && mSuccessfulCount >= kMaxSuccessfulAtomsPerDay) {
+        return false;
+    }
+
+    // Record the event.
+    if (status == AMEDIA_OK) {
+        ++mSuccessfulCount;
+    }
+    mLastLoggedAtoms.emplace(now, status);
+    return true;
+}
+
+void TranscodingLogger::setSessionEndedAtomWriter(const SessionEndedAtomWriter& writer) {
+    mSessionEndedAtomWriter = writer;
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/TranscodingSessionController.cpp b/media/libmediatranscoding/TranscodingSessionController.cpp
index d12af21..54357bd 100644
--- a/media/libmediatranscoding/TranscodingSessionController.cpp
+++ b/media/libmediatranscoding/TranscodingSessionController.cpp
@@ -372,10 +372,12 @@
         if (shouldBeRunning) {
             if (topSession->getState() == Session::NOT_STARTED) {
                 mTranscoder->start(topSession->key.first, topSession->key.second,
-                                   topSession->request, topSession->callback.lock());
+                                   topSession->request, topSession->callingUid,
+                                   topSession->callback.lock());
             } else if (topSession->getState() == Session::PAUSED) {
                 mTranscoder->resume(topSession->key.first, topSession->key.second,
-                                    topSession->request, topSession->callback.lock());
+                                    topSession->request, topSession->callingUid,
+                                    topSession->callback.lock());
             }
             setSessionState_l(topSession, Session::RUNNING);
         }
@@ -393,7 +395,7 @@
     }
 
     // Remove session from uid's queue.
-    const uid_t uid = mSessionMap[sessionKey].uid;
+    const uid_t uid = mSessionMap[sessionKey].clientUid;
     SessionQueueType& sessionQueue = mSessionQueues[uid];
     auto it = std::find(sessionQueue.begin(), sessionQueue.end(), sessionKey);
     if (it == sessionQueue.end()) {
@@ -482,13 +484,13 @@
 }
 
 bool TranscodingSessionController::submit(
-        ClientIdType clientId, SessionIdType sessionId, uid_t uid,
+        ClientIdType clientId, SessionIdType sessionId, uid_t callingUid, uid_t clientUid,
         const TranscodingRequestParcel& request,
         const std::weak_ptr<ITranscodingClientCallback>& callback) {
     SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
 
     ALOGV("%s: session %s, uid %d, prioirty %d", __FUNCTION__, sessionToString(sessionKey).c_str(),
-          uid, (int32_t)request.priority);
+          clientUid, (int32_t)request.priority);
 
     std::scoped_lock lock{mLock};
 
@@ -498,19 +500,20 @@
     }
 
     // Add the uid package name to the store of package names we already know.
-    if (mUidPackageNames.count(uid) == 0) {
-        mUidPackageNames.emplace(uid, request.clientPackageName);
+    if (mUidPackageNames.count(clientUid) == 0) {
+        mUidPackageNames.emplace(clientUid, request.clientPackageName);
     }
 
     // TODO(chz): only support offline vs real-time for now. All kUnspecified sessions
     // go to offline queue.
     if (request.priority == TranscodingSessionPriority::kUnspecified) {
-        uid = OFFLINE_UID;
+        clientUid = OFFLINE_UID;
     }
 
     // Add session to session map.
     mSessionMap[sessionKey].key = sessionKey;
-    mSessionMap[sessionKey].uid = uid;
+    mSessionMap[sessionKey].clientUid = clientUid;
+    mSessionMap[sessionKey].callingUid = callingUid;
     mSessionMap[sessionKey].lastProgress = 0;
     mSessionMap[sessionKey].pauseCount = 0;
     mSessionMap[sessionKey].request = request;
@@ -520,25 +523,25 @@
     // If it's an offline session, the queue was already added in constructor.
     // If it's a real-time sessions, check if a queue is already present for the uid,
     // and add a new queue if needed.
-    if (uid != OFFLINE_UID) {
-        if (mSessionQueues.count(uid) == 0) {
-            mUidPolicy->registerMonitorUid(uid);
-            if (mUidPolicy->isUidOnTop(uid)) {
-                mUidSortedList.push_front(uid);
+    if (clientUid != OFFLINE_UID) {
+        if (mSessionQueues.count(clientUid) == 0) {
+            mUidPolicy->registerMonitorUid(clientUid);
+            if (mUidPolicy->isUidOnTop(clientUid)) {
+                mUidSortedList.push_front(clientUid);
             } else {
                 // Shouldn't be submitting real-time requests from non-top app,
                 // put it in front of the offline queue.
-                mUidSortedList.insert(mOfflineUidIterator, uid);
+                mUidSortedList.insert(mOfflineUidIterator, clientUid);
             }
-        } else if (uid != *mUidSortedList.begin()) {
-            if (mUidPolicy->isUidOnTop(uid)) {
-                mUidSortedList.remove(uid);
-                mUidSortedList.push_front(uid);
+        } else if (clientUid != *mUidSortedList.begin()) {
+            if (mUidPolicy->isUidOnTop(clientUid)) {
+                mUidSortedList.remove(clientUid);
+                mUidSortedList.push_front(clientUid);
             }
         }
     }
     // Append this session to the uid's queue.
-    mSessionQueues[uid].push_back(sessionKey);
+    mSessionQueues[clientUid].push_back(sessionKey);
 
     updateCurrentSession_l();
 
@@ -557,7 +560,7 @@
 
     if (sessionId < 0) {
         for (auto it = mSessionMap.begin(); it != mSessionMap.end(); ++it) {
-            if (it->first.first == clientId && it->second.uid != OFFLINE_UID) {
+            if (it->first.first == clientId && it->second.clientUid != OFFLINE_UID) {
                 sessionsToRemove.push_back(it->first);
             }
         }
diff --git a/media/libmediatranscoding/include/media/ControllerClientInterface.h b/media/libmediatranscoding/include/media/ControllerClientInterface.h
index 3fd4f0c..0d13607 100644
--- a/media/libmediatranscoding/include/media/ControllerClientInterface.h
+++ b/media/libmediatranscoding/include/media/ControllerClientInterface.h
@@ -36,8 +36,8 @@
      * Returns true on success and false on failure. This call will fail is a session identified
      * by <clientId, sessionId> already exists.
      */
-    virtual bool submit(ClientIdType clientId, SessionIdType sessionId, uid_t uid,
-                        const TranscodingRequestParcel& request,
+    virtual bool submit(ClientIdType clientId, SessionIdType sessionId, uid_t callingUid,
+                        uid_t clientUid, const TranscodingRequestParcel& request,
                         const std::weak_ptr<ITranscodingClientCallback>& clientCallback) = 0;
 
     /**
diff --git a/media/libmediatranscoding/include/media/TranscoderInterface.h b/media/libmediatranscoding/include/media/TranscoderInterface.h
index 5f27d82..3b0bd3b 100644
--- a/media/libmediatranscoding/include/media/TranscoderInterface.h
+++ b/media/libmediatranscoding/include/media/TranscoderInterface.h
@@ -33,11 +33,11 @@
 class TranscoderInterface {
 public:
     virtual void start(ClientIdType clientId, SessionIdType sessionId,
-                       const TranscodingRequestParcel& request,
+                       const TranscodingRequestParcel& request, uid_t callingUid,
                        const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
     virtual void pause(ClientIdType clientId, SessionIdType sessionId) = 0;
     virtual void resume(ClientIdType clientId, SessionIdType sessionId,
-                        const TranscodingRequestParcel& request,
+                        const TranscodingRequestParcel& request, uid_t callingUid,
                         const std::shared_ptr<ITranscodingClientCallback>& clientCallback) = 0;
     // Stop the specified session. If abandon is true, the transcoder wrapper will be discarded
     // after the session stops.
diff --git a/media/libmediatranscoding/include/media/TranscoderWrapper.h b/media/libmediatranscoding/include/media/TranscoderWrapper.h
index 7935bbe..d3d4c86 100644
--- a/media/libmediatranscoding/include/media/TranscoderWrapper.h
+++ b/media/libmediatranscoding/include/media/TranscoderWrapper.h
@@ -18,8 +18,11 @@
 #define ANDROID_TRANSCODER_WRAPPER_H
 
 #include <media/NdkMediaError.h>
+#include <media/NdkMediaFormat.h>
 #include <media/TranscoderInterface.h>
+#include <media/TranscodingLogger.h>
 
+#include <chrono>
 #include <list>
 #include <map>
 #include <mutex>
@@ -37,16 +40,17 @@
                           public std::enable_shared_from_this<TranscoderWrapper> {
 public:
     TranscoderWrapper(const std::shared_ptr<TranscoderCallbackInterface>& cb,
+                      const std::shared_ptr<TranscodingLogger>& logger,
                       int64_t heartBeatIntervalUs);
     ~TranscoderWrapper();
 
     // TranscoderInterface
     void start(ClientIdType clientId, SessionIdType sessionId,
-               const TranscodingRequestParcel& request,
+               const TranscodingRequestParcel& request, uid_t callingUid,
                const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
     void pause(ClientIdType clientId, SessionIdType sessionId) override;
     void resume(ClientIdType clientId, SessionIdType sessionId,
-                const TranscodingRequestParcel& request,
+                const TranscodingRequestParcel& request, uid_t callingUid,
                 const std::shared_ptr<ITranscodingClientCallback>& clientCallback) override;
     void stop(ClientIdType clientId, SessionIdType sessionId, bool abandon = false) override;
     // ~TranscoderInterface
@@ -76,6 +80,9 @@
     std::shared_ptr<CallbackImpl> mTranscoderCb;
     std::shared_ptr<MediaTranscoder> mTranscoder;
     std::weak_ptr<TranscoderCallbackInterface> mCallback;
+    std::shared_ptr<TranscodingLogger> mLogger;
+    std::shared_ptr<AMediaFormat> mSrcFormat;
+    std::shared_ptr<AMediaFormat> mDstFormat;
     int64_t mHeartBeatIntervalUs;
     std::mutex mLock;
     std::condition_variable mCondition;
@@ -83,6 +90,9 @@
     std::map<SessionKeyType, std::shared_ptr<ndk::ScopedAParcel>> mPausedStateMap;
     ClientIdType mCurrentClientId;
     SessionIdType mCurrentSessionId;
+    uid_t mCurrentCallingUid;
+    std::chrono::steady_clock::time_point mTranscodeStartTime;
+
     // Whether the looper has been created.
     bool mLooperReady;
 
@@ -93,18 +103,20 @@
     void onHeartBeat(ClientIdType clientId, SessionIdType sessionId);
 
     media_status_t handleStart(ClientIdType clientId, SessionIdType sessionId,
-                               const TranscodingRequestParcel& request,
+                               const TranscodingRequestParcel& request, uid_t callingUid,
                                const std::shared_ptr<ITranscodingClientCallback>& callback);
     media_status_t handlePause(ClientIdType clientId, SessionIdType sessionId);
     media_status_t handleResume(ClientIdType clientId, SessionIdType sessionId,
-                                const TranscodingRequestParcel& request,
+                                const TranscodingRequestParcel& request, uid_t callingUid,
                                 const std::shared_ptr<ITranscodingClientCallback>& callback);
     media_status_t setupTranscoder(
             ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
-            const std::shared_ptr<ITranscodingClientCallback>& callback,
+            uid_t callingUid, const std::shared_ptr<ITranscodingClientCallback>& callback,
+            TranscodingLogger::SessionEndedReason* failureReason /* nonnull */,
             const std::shared_ptr<ndk::ScopedAParcel>& pausedState = nullptr);
 
     void cleanup();
+    void logSessionEnded(const TranscodingLogger::SessionEndedReason& reason, int error);
     void reportError(ClientIdType clientId, SessionIdType sessionId, media_status_t err);
     void queueEvent(Event::Type type, ClientIdType clientId, SessionIdType sessionId,
                     const std::function<void()> runnable, int32_t arg = 0);
diff --git a/media/libmediatranscoding/include/media/TranscodingLogger.h b/media/libmediatranscoding/include/media/TranscodingLogger.h
new file mode 100644
index 0000000..dc24551
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingLogger.h
@@ -0,0 +1,97 @@
+/*
+ * 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 ANDROID_MEDIA_TRANSCODING_LOGGER_H
+#define ANDROID_MEDIA_TRANSCODING_LOGGER_H
+
+#include <media/NdkMediaFormat.h>
+#include <utils/Condition.h>
+
+#include <chrono>
+#include <memory>
+#include <mutex>
+#include <queue>
+
+namespace android {
+
+/** Class for logging transcoding events. */
+class TranscodingLogger {
+public:
+    /** The maximum number of atoms pushed to statsd per day. */
+    static constexpr int kMaxAtomsPerDay = 50;
+
+    /** The maximum number of successful transcoding atoms pushed to statsd per day. */
+    static constexpr int kMaxSuccessfulAtomsPerDay = 35;
+
+    /** Reason transcoding session ended. Maps to MediaTranscodingSessionEnded atom's Reason. */
+    enum SessionEndedReason {
+        UNKNOWN = 0,
+        FINISHED,
+        ERROR,
+        PAUSED,
+        CANCELLED,
+        START_FAILED,
+        RESUME_FAILED,
+        CREATE_FAILED,
+        CONFIG_SRC_FAILED,
+        CONFIG_DST_FAILED,
+        CONFIG_TRACK_FAILED,
+        OPEN_SRC_FD_FAILED,
+        OPEN_DST_FD_FAILED,
+        NO_TRACKS,
+    };
+
+    TranscodingLogger();
+    ~TranscodingLogger() = default;
+
+    /**
+     * Logs a transcoding session ended event (MediaTranscodingSessionEnded atom).
+     * @param reason Reason for the transcoding session to end.
+     * @param callingUid UID of the caller connecting to the transcoding service.
+     * @param status Status (error code) of the transcoding session.
+     * @param duration Duration of the transcoding session.
+     * @param srcFormat The source video track format.
+     * @param dstFormat The destination video track format.
+     */
+    void logSessionEnded(enum SessionEndedReason reason, uid_t callingUid, int status,
+                         std::chrono::microseconds duration, AMediaFormat* srcFormat,
+                         AMediaFormat* dstFormat);
+
+private:
+    friend class TranscodingLoggerTest;
+
+    // Function prototype for writing out the session ended atom.
+    using SessionEndedAtomWriter = std::function<int(
+            int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, char const*, int32_t,
+            int32_t, int32_t, int32_t, bool arg12, int32_t, int32_t, char const*, bool)>;
+
+    std::mutex mLock;
+    std::queue<std::pair<std::chrono::steady_clock::time_point, int>> mLastLoggedAtoms
+            GUARDED_BY(mLock);
+    uint32_t mSuccessfulCount = 0;
+    SessionEndedAtomWriter mSessionEndedAtomWriter;
+
+    void logSessionEnded(const std::chrono::steady_clock::time_point& now,
+                         enum SessionEndedReason reason, uid_t callingUid, int status,
+                         std::chrono::microseconds duration, AMediaFormat* srcFormat,
+                         AMediaFormat* dstFormat);
+    bool shouldLogAtom(const std::chrono::steady_clock::time_point& now, int status);
+    // Used for testing to validate what gets sent to statsd.
+    void setSessionEndedAtomWriter(const SessionEndedAtomWriter& writer);
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODING_LOGGER_H
diff --git a/media/libmediatranscoding/include/media/TranscodingSessionController.h b/media/libmediatranscoding/include/media/TranscodingSessionController.h
index 34e9506..cfa2f13 100644
--- a/media/libmediatranscoding/include/media/TranscodingSessionController.h
+++ b/media/libmediatranscoding/include/media/TranscodingSessionController.h
@@ -48,7 +48,7 @@
     virtual ~TranscodingSessionController();
 
     // ControllerClientInterface
-    bool submit(ClientIdType clientId, SessionIdType sessionId, uid_t uid,
+    bool submit(ClientIdType clientId, SessionIdType sessionId, uid_t callingUid, uid_t clientUid,
                 const TranscodingRequestParcel& request,
                 const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override;
     bool cancel(ClientIdType clientId, SessionIdType sessionId) override;
@@ -108,7 +108,8 @@
             ERROR,
         };
         SessionKeyType key;
-        uid_t uid;
+        uid_t clientUid;
+        uid_t callingUid;
         int32_t lastProgress;
         int32_t pauseCount;
         std::chrono::time_point<std::chrono::system_clock> stateEnterTime;
diff --git a/media/libmediatranscoding/tests/Android.bp b/media/libmediatranscoding/tests/Android.bp
index 06b9b17..603611a 100644
--- a/media/libmediatranscoding/tests/Android.bp
+++ b/media/libmediatranscoding/tests/Android.bp
@@ -72,3 +72,15 @@
 
     srcs: ["AdjustableMaxPriorityQueue_tests.cpp"],
 }
+
+//
+// TranscodingLogger unit test
+//
+cc_test {
+    name: "TranscodingLogger_tests",
+    defaults: ["libmediatranscoding_test_defaults"],
+    shared_libs: ["libmediandk", "libstatssocket#30"],
+    static_libs: ["libmediatranscoder", "libstatslog_media"],
+
+    srcs: ["TranscodingLogger_tests.cpp"],
+}
diff --git a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
index 1a50923..57a2e27 100644
--- a/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingClientManager_tests.cpp
@@ -135,7 +135,7 @@
 
     virtual ~TestController() { ALOGI("TestController Destroyed"); }
 
-    bool submit(ClientIdType clientId, SessionIdType sessionId, uid_t /*uid*/,
+    bool submit(ClientIdType clientId, SessionIdType sessionId, uid_t /*callingUid*/, uid_t /*uid*/,
                 const TranscodingRequestParcel& request,
                 const std::weak_ptr<ITranscodingClientCallback>& clientCallback) override {
         SessionKeyType sessionKey = std::make_pair(clientId, sessionId);
diff --git a/media/libmediatranscoding/tests/TranscodingLogger_tests.cpp b/media/libmediatranscoding/tests/TranscodingLogger_tests.cpp
new file mode 100644
index 0000000..39e5cd4
--- /dev/null
+++ b/media/libmediatranscoding/tests/TranscodingLogger_tests.cpp
@@ -0,0 +1,286 @@
+/*
+ * 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.
+ */
+
+// Unit Test for TranscodingLogger
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingLoggerTest"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <media/NdkCommon.h>
+#include <media/TranscodingLogger.h>
+#include <statslog_media.h>
+#include <utils/Log.h>
+
+#include <chrono>
+
+namespace android {
+
+using Reason = TranscodingLogger::SessionEndedReason;
+
+// Data structure corresponding to MediaTranscodingEnded atom.
+struct SessionEndedAtom {
+    SessionEndedAtom(int32_t atomCode, int32_t reason, int32_t callingUid, int32_t status,
+                     int32_t transcoderFps, int32_t srcWidth, int32_t srcHeight,
+                     char const* srcMime, int32_t srcProfile, int32_t srcLevel, int32_t srcFps,
+                     int32_t srcDurationMs, bool srcIsHdr, int32_t dstWidth, int32_t dstHeight,
+                     char const* dstMime, bool dstIsHdr)
+          : atomCode(atomCode),
+            reason(reason),
+            callingUid(callingUid),
+            status(status),
+            transcoderFps(transcoderFps),
+            srcWidth(srcWidth),
+            srcHeight(srcHeight),
+            srcMime(srcMime),
+            srcProfile(srcProfile),
+            srcLevel(srcLevel),
+            srcFps(srcFps),
+            srcDurationMs(srcDurationMs),
+            srcIsHdr(srcIsHdr),
+            dstWidth(dstWidth),
+            dstHeight(dstHeight),
+            dstMime(dstMime),
+            dstIsHdr(dstIsHdr) {}
+
+    int32_t atomCode;
+    int32_t reason;
+    int32_t callingUid;
+    int32_t status;
+    int32_t transcoderFps;
+    int32_t srcWidth;
+    int32_t srcHeight;
+    std::string srcMime;
+    int32_t srcProfile;
+    int32_t srcLevel;
+    int32_t srcFps;
+    int32_t srcDurationMs;
+    bool srcIsHdr;
+    int32_t dstWidth;
+    int32_t dstHeight;
+    std::string dstMime;
+    bool dstIsHdr;
+};
+
+// Default configuration values.
+static constexpr int32_t kDefaultCallingUid = 1;
+static constexpr std::chrono::microseconds kDefaultTranscodeDuration = std::chrono::seconds{2};
+
+static constexpr int32_t kDefaultSrcWidth = 1920;
+static constexpr int32_t kDefaultSrcHeight = 1080;
+static const std::string kDefaultSrcMime{AMEDIA_MIMETYPE_VIDEO_HEVC};
+static constexpr int32_t kDefaultSrcProfile = 1;    // HEVC Main
+static constexpr int32_t kDefaultSrcLevel = 65536;  // HEVCMainTierLevel51
+static constexpr int32_t kDefaultSrcFps = 30;
+static constexpr int32_t kDefaultSrcFrameCount = 120;
+static constexpr int64_t kDefaultSrcDurationUs = 1000000 * kDefaultSrcFrameCount / kDefaultSrcFps;
+
+static constexpr int32_t kDefaultDstWidth = 1280;
+static constexpr int32_t kDefaultDstHeight = 720;
+static const std::string kDefaultDstMime{AMEDIA_MIMETYPE_VIDEO_AVC};
+
+// Util for creating a default source video format.
+static AMediaFormat* CreateSrcFormat() {
+    AMediaFormat* fmt = AMediaFormat_new();
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, kDefaultSrcWidth);
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, kDefaultSrcHeight);
+    AMediaFormat_setString(fmt, AMEDIAFORMAT_KEY_MIME, kDefaultSrcMime.c_str());
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_PROFILE, kDefaultSrcProfile);
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_LEVEL, kDefaultSrcLevel);
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_FRAME_RATE, kDefaultSrcFps);
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_FRAME_COUNT, kDefaultSrcFrameCount);
+    AMediaFormat_setInt64(fmt, AMEDIAFORMAT_KEY_DURATION, kDefaultSrcDurationUs);
+    return fmt;
+}
+
+// Util for creating a default destination video format.
+static AMediaFormat* CreateDstFormat() {
+    AMediaFormat* fmt = AMediaFormat_new();
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, kDefaultDstWidth);
+    AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, kDefaultDstHeight);
+    AMediaFormat_setString(fmt, AMEDIAFORMAT_KEY_MIME, kDefaultDstMime.c_str());
+    return fmt;
+}
+
+class TranscodingLoggerTest : public ::testing::Test {
+public:
+    TranscodingLoggerTest() { ALOGI("TranscodingLoggerTest created"); }
+
+    void SetUp() override {
+        ALOGI("TranscodingLoggerTest set up");
+        mLogger.reset(new TranscodingLogger());
+        mLoggedAtoms.clear();
+        mSrcFormat.reset();
+        mDstFormat.reset();
+
+        // Set a custom atom writer that saves all data, so the test can validate it afterwards.
+        mLogger->setSessionEndedAtomWriter(
+                [=](int32_t atomCode, int32_t reason, int32_t callingUid, int32_t status,
+                    int32_t transcoderFps, int32_t srcWidth, int32_t srcHeight, char const* srcMime,
+                    int32_t srcProfile, int32_t srcLevel, int32_t srcFps, int32_t srcDurationMs,
+                    bool srcIsHdr, int32_t dstWidth, int32_t dstHeight, char const* dstMime,
+                    bool dstIsHdr) -> int {
+                    mLoggedAtoms.emplace_back(atomCode, reason, callingUid, status, transcoderFps,
+                                              srcWidth, srcHeight, srcMime, srcProfile, srcLevel,
+                                              srcFps, srcDurationMs, srcIsHdr, dstWidth, dstHeight,
+                                              dstMime, dstIsHdr);
+                    return 0;
+                });
+    }
+
+    void logSession(const std::chrono::steady_clock::time_point& time, Reason reason, int status,
+                    AMediaFormat* srcFormat, AMediaFormat* dstFormat) {
+        mLogger->logSessionEnded(time, reason, kDefaultCallingUid, status,
+                                 kDefaultTranscodeDuration, srcFormat, dstFormat);
+    }
+
+    void logSession(const std::chrono::steady_clock::time_point& time, Reason reason, int status) {
+        if (!mSrcFormat) {
+            mSrcFormat = std::shared_ptr<AMediaFormat>(CreateSrcFormat(), &AMediaFormat_delete);
+        }
+        if (!mDstFormat) {
+            mDstFormat = std::shared_ptr<AMediaFormat>(CreateDstFormat(), &AMediaFormat_delete);
+        }
+        logSession(time, reason, status, mSrcFormat.get(), mDstFormat.get());
+    }
+
+    void logSessionFinished(const std::chrono::steady_clock::time_point& time) {
+        logSession(time, Reason::FINISHED, 0);
+    }
+
+    void logSessionFailed(const std::chrono::steady_clock::time_point& time) {
+        logSession(time, Reason::ERROR, AMEDIA_ERROR_UNKNOWN);
+    }
+
+    int logCount() const { return mLoggedAtoms.size(); }
+
+    void validateLatestAtom(Reason reason, int status, bool passthrough = false) {
+        const SessionEndedAtom& atom = mLoggedAtoms.back();
+
+        EXPECT_EQ(atom.atomCode, android::media::stats::MEDIA_TRANSCODING_SESSION_ENDED);
+        EXPECT_EQ(atom.reason, static_cast<int>(reason));
+        EXPECT_EQ(atom.callingUid, kDefaultCallingUid);
+        EXPECT_EQ(atom.status, status);
+        EXPECT_EQ(atom.srcWidth, kDefaultSrcWidth);
+        EXPECT_EQ(atom.srcHeight, kDefaultSrcHeight);
+        EXPECT_EQ(atom.srcMime, kDefaultSrcMime);
+        EXPECT_EQ(atom.srcProfile, kDefaultSrcProfile);
+        EXPECT_EQ(atom.srcLevel, kDefaultSrcLevel);
+        EXPECT_EQ(atom.srcFps, kDefaultSrcFps);
+        EXPECT_EQ(atom.srcDurationMs, kDefaultSrcDurationUs / 1000);
+        EXPECT_FALSE(atom.srcIsHdr);
+        EXPECT_EQ(atom.dstWidth, passthrough ? kDefaultSrcWidth : kDefaultDstWidth);
+        EXPECT_EQ(atom.dstHeight, passthrough ? kDefaultSrcHeight : kDefaultDstHeight);
+        EXPECT_EQ(atom.dstMime, passthrough ? "passthrough" : kDefaultDstMime);
+        EXPECT_FALSE(atom.dstIsHdr);
+
+        // Transcoder frame rate is only present on successful sessions.
+        if (status == AMEDIA_OK) {
+            std::chrono::duration<double> seconds{kDefaultTranscodeDuration};
+            const int32_t transcoderFps =
+                    static_cast<int32_t>(kDefaultSrcFrameCount / seconds.count());
+            EXPECT_EQ(atom.transcoderFps, transcoderFps);
+        } else {
+            EXPECT_EQ(atom.transcoderFps, -1);
+        }
+    }
+
+    void TearDown() override { ALOGI("TranscodingLoggerTest tear down"); }
+    ~TranscodingLoggerTest() { ALOGD("TranscodingLoggerTest destroyed"); }
+
+    std::shared_ptr<TranscodingLogger> mLogger;
+    std::vector<SessionEndedAtom> mLoggedAtoms;
+
+    std::shared_ptr<AMediaFormat> mSrcFormat;
+    std::shared_ptr<AMediaFormat> mDstFormat;
+};
+
+TEST_F(TranscodingLoggerTest, TestDailyLogQuota) {
+    ALOGD("TestDailyLogQuota");
+    auto start = std::chrono::steady_clock::now();
+
+    EXPECT_LT(TranscodingLogger::kMaxSuccessfulAtomsPerDay, TranscodingLogger::kMaxAtomsPerDay);
+
+    // 1. Check that the first kMaxSuccessfulAtomsPerDay successful atoms are logged.
+    for (int i = 0; i < TranscodingLogger::kMaxSuccessfulAtomsPerDay; ++i) {
+        logSessionFinished(start + std::chrono::seconds{i});
+        EXPECT_EQ(logCount(), i + 1);
+    }
+
+    // 2. Check that subsequent successful atoms within the same 24h interval are not logged.
+    for (int i = 1; i < 24; ++i) {
+        logSessionFinished(start + std::chrono::hours{i});
+        EXPECT_EQ(logCount(), TranscodingLogger::kMaxSuccessfulAtomsPerDay);
+    }
+
+    // 3. Check that failed atoms are logged up to kMaxAtomsPerDay.
+    for (int i = TranscodingLogger::kMaxSuccessfulAtomsPerDay;
+         i < TranscodingLogger::kMaxAtomsPerDay; ++i) {
+        logSessionFailed(start + std::chrono::seconds{i});
+        EXPECT_EQ(logCount(), i + 1);
+    }
+
+    // 4. Check that subsequent failed atoms within the same 24h interval are not logged.
+    for (int i = 1; i < 24; ++i) {
+        logSessionFailed(start + std::chrono::hours{i});
+        EXPECT_EQ(logCount(), TranscodingLogger::kMaxAtomsPerDay);
+    }
+
+    // 5. Check that failed and successful atoms are logged again after 24h.
+    logSessionFinished(start + std::chrono::hours{24});
+    EXPECT_EQ(logCount(), TranscodingLogger::kMaxAtomsPerDay + 1);
+
+    logSessionFailed(start + std::chrono::hours{24} + std::chrono::seconds{1});
+    EXPECT_EQ(logCount(), TranscodingLogger::kMaxAtomsPerDay + 2);
+}
+
+TEST_F(TranscodingLoggerTest, TestNullFormats) {
+    ALOGD("TestNullFormats");
+    auto srcFormat = std::shared_ptr<AMediaFormat>(CreateSrcFormat(), &AMediaFormat_delete);
+    auto dstFormat = std::shared_ptr<AMediaFormat>(CreateDstFormat(), &AMediaFormat_delete);
+    auto now = std::chrono::steady_clock::now();
+
+    // Source format null, should not log.
+    logSession(now, Reason::FINISHED, AMEDIA_OK, nullptr /*srcFormat*/, dstFormat.get());
+    EXPECT_EQ(logCount(), 0);
+
+    // Both formats null, should not log.
+    logSession(now, Reason::FINISHED, AMEDIA_OK, nullptr /*srcFormat*/, nullptr /*dstFormat*/);
+    EXPECT_EQ(logCount(), 0);
+
+    // Destination format null (passthrough mode), should log.
+    logSession(now, Reason::FINISHED, AMEDIA_OK, srcFormat.get(), nullptr /*dstFormat*/);
+    EXPECT_EQ(logCount(), 1);
+    validateLatestAtom(Reason::FINISHED, AMEDIA_OK, true /*passthrough*/);
+}
+
+TEST_F(TranscodingLoggerTest, TestAtomContentCorrectness) {
+    ALOGD("TestAtomContentCorrectness");
+    auto now = std::chrono::steady_clock::now();
+
+    // Log and validate a failure.
+    logSession(now, Reason::ERROR, AMEDIA_ERROR_MALFORMED);
+    EXPECT_EQ(logCount(), 1);
+    validateLatestAtom(Reason::ERROR, AMEDIA_ERROR_MALFORMED);
+
+    // Log and validate a success.
+    logSession(now, Reason::FINISHED, AMEDIA_OK);
+    EXPECT_EQ(logCount(), 2);
+    validateLatestAtom(Reason::FINISHED, AMEDIA_OK);
+}
+
+}  // namespace android
diff --git a/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp b/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp
index 2e9daee..3cd9112 100644
--- a/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp
@@ -123,7 +123,7 @@
 
     // TranscoderInterface
     void start(ClientIdType clientId, SessionIdType sessionId,
-               const TranscodingRequestParcel& /*request*/,
+               const TranscodingRequestParcel& /*request*/, uid_t /*callingUid*/,
                const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) override {
         append(Start(clientId, sessionId));
     }
@@ -131,7 +131,7 @@
         append(Pause(clientId, sessionId));
     }
     void resume(ClientIdType clientId, SessionIdType sessionId,
-                const TranscodingRequestParcel& /*request*/,
+                const TranscodingRequestParcel& /*request*/, uid_t /*callingUid*/,
                 const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) override {
         append(Resume(clientId, sessionId));
     }
@@ -349,32 +349,32 @@
 
     // Submit offline session to CLIENT(0) in UID(0).
     // Should start immediately (because this is the only session).
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mOfflineRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mOfflineRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), 0));
 
     // Submit real-time session to CLIENT(0).
     // Should pause offline session and start new session,  even if UID(0) is not on top.
-    mController->submit(CLIENT(0), SESSION(1), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(1), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(1)));
 
     // Submit real-time session to CLIENT(0), should be queued after the previous session.
-    mController->submit(CLIENT(0), SESSION(2), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(2), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit real-time session to CLIENT(1) in same uid, should be queued after the previous
     // session.
-    mController->submit(CLIENT(1), SESSION(0), UID(0), mRealtimeRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(0), mRealtimeRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit real-time session to CLIENT(2) in UID(1).
     // Should pause previous session and start new session, because UID(1) is (has been) top.
-    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    mController->submit(CLIENT(2), SESSION(0), UID(2), UID(1), mRealtimeRequest, mClientCallback2);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(1)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
 
     // Submit offline session, shouldn't generate any event.
-    mController->submit(CLIENT(2), SESSION(1), UID(1), mOfflineRequest, mClientCallback2);
+    mController->submit(CLIENT(2), SESSION(1), UID(2), UID(1), mOfflineRequest, mClientCallback2);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Bring UID(0) to top.
@@ -388,15 +388,15 @@
     ALOGD("TestCancelSession");
 
     // Submit real-time session SESSION(0), should start immediately.
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit real-time session SESSION(1), should not start.
-    mController->submit(CLIENT(0), SESSION(1), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(1), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit offline session SESSION(2), should not start.
-    mController->submit(CLIENT(0), SESSION(2), UID(0), mOfflineRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(2), UID(0), UID(0), mOfflineRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Cancel queued real-time session.
@@ -408,7 +408,7 @@
     EXPECT_TRUE(mController->cancel(CLIENT(0), SESSION(2)));
 
     // Submit offline session SESSION(3), shouldn't cause any event.
-    mController->submit(CLIENT(0), SESSION(3), UID(0), mOfflineRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(3), UID(0), UID(0), mOfflineRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Cancel running real-time session SESSION(0).
@@ -420,7 +420,7 @@
 
     // Submit real-time session SESSION(4), offline SESSION(3) should pause and SESSION(4)
     // should start.
-    mController->submit(CLIENT(0), SESSION(4), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(4), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(3)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(4)));
 
@@ -438,16 +438,16 @@
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit offline session SESSION(0), should start immediately.
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mOfflineRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mOfflineRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit real-time session SESSION(1), should pause offline session and start immediately.
-    mController->submit(CLIENT(0), SESSION(1), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(1), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(1)));
 
     // Submit real-time session SESSION(2), should not start.
-    mController->submit(CLIENT(0), SESSION(2), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(2), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Finish when the session never started, should be ignored.
@@ -458,7 +458,7 @@
     mUidPolicy->setTop(UID(1));
     // Submit real-time session to CLIENT(1) in UID(1), should pause previous session and start
     // new session.
-    mController->submit(CLIENT(1), SESSION(0), UID(1), mRealtimeRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(1), mRealtimeRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(1)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), SESSION(0)));
 
@@ -495,16 +495,16 @@
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Submit offline session SESSION(0), should start immediately.
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mOfflineRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mOfflineRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit real-time session SESSION(1), should pause offline session and start immediately.
-    mController->submit(CLIENT(0), SESSION(1), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(1), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(1)));
 
     // Submit real-time session SESSION(2), should not start.
-    mController->submit(CLIENT(0), SESSION(2), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(2), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Fail when the session never started, should be ignored.
@@ -515,7 +515,7 @@
     mUidPolicy->setTop(UID(1));
     // Submit real-time session to CLIENT(1) in UID(1), should pause previous session and start
     // new session.
-    mController->submit(CLIENT(1), SESSION(0), UID(1), mRealtimeRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(1), mRealtimeRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(1)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), SESSION(0)));
 
@@ -549,11 +549,11 @@
 
     // Start with unspecified top UID.
     // Submit real-time session to CLIENT(0), session should start immediately.
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit offline session to CLIENT(0), should not start.
-    mController->submit(CLIENT(1), SESSION(0), UID(0), mOfflineRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(0), mOfflineRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Move UID(1) to top.
@@ -562,7 +562,7 @@
 
     // Submit real-time session to CLIENT(2) in different uid UID(1).
     // Should pause previous session and start new session.
-    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    mController->submit(CLIENT(2), SESSION(0), UID(2), UID(1), mRealtimeRequest, mClientCallback2);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
 
@@ -591,11 +591,11 @@
 
     // Start with unspecified top UID.
     // Submit real-time session to CLIENT(0), session should start immediately.
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit offline session to CLIENT(0), should not start.
-    mController->submit(CLIENT(1), SESSION(0), UID(0), mOfflineRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(0), mOfflineRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Set UID(0), UID(1) to top set.
@@ -605,7 +605,7 @@
 
     // Submit real-time session to CLIENT(2) in different uid UID(1).
     // UID(0) should pause and UID(1) should start.
-    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    mController->submit(CLIENT(2), SESSION(0), UID(2), UID(1), mRealtimeRequest, mClientCallback2);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
 
@@ -647,12 +647,12 @@
     // Start with unspecified top UID.
     // Submit real-time session to CLIENT(0), session should start immediately.
     mRealtimeRequest.clientPid = PID(0);
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit offline session to CLIENT(0), should not start.
     mOfflineRequest.clientPid = PID(0);
-    mController->submit(CLIENT(1), SESSION(0), UID(0), mOfflineRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(0), mOfflineRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Move UID(1) to top.
@@ -662,7 +662,7 @@
     // Submit real-time session to CLIENT(2) in different uid UID(1).
     // Should pause previous session and start new session.
     mRealtimeRequest.clientPid = PID(1);
-    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    mController->submit(CLIENT(2), SESSION(0), UID(2), UID(1), mRealtimeRequest, mClientCallback2);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
 
@@ -719,7 +719,7 @@
 
     // Submit real-time session to CLIENT(3) in UID(2), session shouldn't start due to no resource.
     mRealtimeRequest.clientPid = PID(2);
-    mController->submit(CLIENT(3), SESSION(0), UID(2), mRealtimeRequest, mClientCallback3);
+    mController->submit(CLIENT(3), SESSION(0), UID(3), UID(2), mRealtimeRequest, mClientCallback3);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Signal resource available, CLIENT(3)'s session should start.
@@ -734,12 +734,12 @@
     // Start with unspecified top UID.
     // Submit real-time session to CLIENT(0), session should start immediately.
     mRealtimeRequest.clientPid = PID(0);
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit offline session to CLIENT(0), should not start.
     mOfflineRequest.clientPid = PID(0);
-    mController->submit(CLIENT(1), SESSION(0), UID(0), mOfflineRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(0), mOfflineRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Move UID(1) to top.
@@ -749,7 +749,7 @@
     // Submit real-time session to CLIENT(2) in different uid UID(1).
     // Should pause previous session and start new session.
     mRealtimeRequest.clientPid = PID(1);
-    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    mController->submit(CLIENT(2), SESSION(0), UID(2), UID(1), mRealtimeRequest, mClientCallback2);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
 
@@ -788,7 +788,7 @@
     mUidPolicy->setTop(UID(2));
     // Submit real-time session to CLIENT(3) in UID(2), session shouldn't start during throttling.
     mRealtimeRequest.clientPid = PID(2);
-    mController->submit(CLIENT(3), SESSION(0), UID(2), mRealtimeRequest, mClientCallback3);
+    mController->submit(CLIENT(3), SESSION(0), UID(3), UID(2), mRealtimeRequest, mClientCallback3);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
     // Throttling stops, CLIENT(3)'s session should start.
     mController->onThrottlingStopped();
@@ -802,12 +802,12 @@
     // Start with unspecified top UID.
     // Submit real-time session to CLIENT(0), session should start immediately.
     mRealtimeRequest.clientPid = PID(0);
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     // Submit offline session to CLIENT(0), should not start.
     mOfflineRequest.clientPid = PID(0);
-    mController->submit(CLIENT(1), SESSION(0), UID(0), mOfflineRequest, mClientCallback1);
+    mController->submit(CLIENT(1), SESSION(0), UID(1), UID(0), mOfflineRequest, mClientCallback1);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Move UID(1) to top.
@@ -817,7 +817,7 @@
     // Submit real-time session to CLIENT(2) in different uid UID(1).
     // Should pause previous session and start new session.
     mRealtimeRequest.clientPid = PID(1);
-    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    mController->submit(CLIENT(2), SESSION(0), UID(2), UID(1), mRealtimeRequest, mClientCallback2);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
 
@@ -859,7 +859,7 @@
 
     // Submit session to CLIENT(0) in UID(0).
     // Should start immediately (because this is the only session).
-    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
 
     int32_t expectedGen = 2;
@@ -867,7 +867,7 @@
     expectTimeout(CLIENT(0), SESSION(0), expectedGen++);
 
     // Test 2: No timeout as long as keep-alive coming; timeout after keep-alive stops.
-    mController->submit(CLIENT(0), SESSION(1), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(1), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(1)));
     for (int i = 0; i < 5; i++) {
         EXPECT_EQ(mTranscoder->popEvent(1000000), TestTranscoder::NoEvent);
@@ -876,7 +876,7 @@
     expectTimeout(CLIENT(0), SESSION(1), expectedGen++);
 
     // Test 3a: No timeout for paused session even if no keep-alive is sent.
-    mController->submit(CLIENT(0), SESSION(2), UID(0), mOfflineRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(2), UID(0), UID(0), mOfflineRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(2)));
     // Trigger a pause by sending a resource lost.
     mController->onResourceLost(CLIENT(0), SESSION(2));
@@ -886,12 +886,12 @@
     expectTimeout(CLIENT(0), SESSION(2), expectedGen++);
 
     // Test 3b: No timeout for paused session even if no keep-alive is sent.
-    mController->submit(CLIENT(0), SESSION(3), UID(0), mOfflineRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(3), UID(0), UID(0), mOfflineRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(3)));
     // Let the session run almost to timeout, to test timeout reset after pause.
     EXPECT_EQ(mTranscoder->popEvent(2900000), TestTranscoder::NoEvent);
     // Trigger a pause by submitting a higher-priority request.
-    mController->submit(CLIENT(0), SESSION(4), UID(0), mRealtimeRequest, mClientCallback0);
+    mController->submit(CLIENT(0), SESSION(4), UID(0), UID(0), mRealtimeRequest, mClientCallback0);
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(3)));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(4)));
     // Finish the higher-priority session, lower-priority session should resume,
diff --git a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
index 5db9258..3cbf1dd 100644
--- a/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
+++ b/media/libmediatranscoding/tests/build_and_run_all_unit_tests.sh
@@ -32,3 +32,7 @@
 echo "testing TranscodingSessionController"
 #adb shell /data/nativetest64/TranscodingSessionController_tests/TranscodingSessionController_tests
 adb shell /data/nativetest/TranscodingSessionController_tests/TranscodingSessionController_tests
+
+echo "testing TranscodingLogger"
+#adb shell /data/nativetest64/TranscodingLogger_tests/TranscodingLogger_tests
+adb shell /data/nativetest/TranscodingLogger_tests/TranscodingLogger_tests
diff --git a/media/libmediatranscoding/transcoder/NdkCommon.cpp b/media/libmediatranscoding/transcoder/NdkCommon.cpp
index f5c9594..fb909b2 100644
--- a/media/libmediatranscoding/transcoder/NdkCommon.cpp
+++ b/media/libmediatranscoding/transcoder/NdkCommon.cpp
@@ -90,4 +90,29 @@
 DEFINE_SET_DEFAULT_FORMAT_VALUE_FUNC(float, Float);
 DEFINE_SET_DEFAULT_FORMAT_VALUE_FUNC(int32_t, Int32);
 
-}  // namespace AMediaFormatUtils
\ No newline at end of file
+// Determines whether a track format describes HDR video content or not. The
+// logic is based on isHdr() in libstagefright/Utils.cpp.
+bool VideoIsHdr(AMediaFormat* format) {
+    // If VUI signals HDR content, this internal flag is set by the extractor.
+    int32_t isHdr;
+    if (AMediaFormat_getInt32(format, "android._is-hdr", &isHdr)) {
+        return isHdr;
+    }
+
+    // If container supplied HDR static info without transfer set, assume HDR.
+    const char* hdrInfo;
+    int32_t transfer;
+    if ((AMediaFormat_getString(format, AMEDIAFORMAT_KEY_HDR_STATIC_INFO, &hdrInfo) ||
+         AMediaFormat_getString(format, "hdr10-plus-info", &hdrInfo)) &&
+        !AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_TRANSFER, &transfer)) {
+        return true;
+    }
+
+    // Otherwise, check if an HDR transfer function is set.
+    if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_TRANSFER, &transfer)) {
+        return transfer == COLOR_TRANSFER_ST2084 || transfer == COLOR_TRANSFER_HLG;
+    }
+
+    return false;
+}
+}  // namespace AMediaFormatUtils
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index 04a513e..acf5f6c 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -51,32 +51,6 @@
 // Default frame rate.
 static constexpr int32_t kDefaultFrameRate = 30;
 
-// Determines whether a track format describes HDR video content or not. The
-// logic is based on isHdr() in libstagefright/Utils.cpp.
-static bool isHdr(AMediaFormat* format) {
-    // If VUI signals HDR content, this internal flag is set by the extractor.
-    int32_t isHdr;
-    if (AMediaFormat_getInt32(format, "android._is-hdr", &isHdr)) {
-        return isHdr;
-    }
-
-    // If container supplied HDR static info without transfer set, assume HDR.
-    const char* hdrInfo;
-    int32_t transfer;
-    if ((AMediaFormat_getString(format, AMEDIAFORMAT_KEY_HDR_STATIC_INFO, &hdrInfo) ||
-         AMediaFormat_getString(format, "hdr10-plus-info", &hdrInfo)) &&
-        !AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_TRANSFER, &transfer)) {
-        return true;
-    }
-
-    // Otherwise, check if an HDR transfer function is set.
-    if (AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_TRANSFER, &transfer)) {
-        return transfer == COLOR_TRANSFER_ST2084 || transfer == COLOR_TRANSFER_HLG;
-    }
-
-    return false;
-}
-
 template <typename T>
 void VideoTrackTranscoder::BlockingQueue<T>::push(T const& value, bool front) {
     {
@@ -347,7 +321,7 @@
     }
 
     // Request decoder to convert HDR content to SDR.
-    const bool sourceIsHdr = isHdr(mSourceFormat.get());
+    const bool sourceIsHdr = VideoIsHdr(mSourceFormat.get());
     if (sourceIsHdr) {
         AMediaFormat_setInt32(decoderFormat.get(),
                               TBD_AMEDIACODEC_PARAMETER_KEY_COLOR_TRANSFER_REQUEST,
diff --git a/media/libmediatranscoding/transcoder/include/media/NdkCommon.h b/media/libmediatranscoding/transcoder/include/media/NdkCommon.h
index cc3399a..a7ed6a7 100644
--- a/media/libmediatranscoding/transcoder/include/media/NdkCommon.h
+++ b/media/libmediatranscoding/transcoder/include/media/NdkCommon.h
@@ -88,5 +88,7 @@
 bool SetDefaultFormatValueFloat(const char* key, AMediaFormat* format, float value);
 bool SetDefaultFormatValueInt32(const char* key, AMediaFormat* format, int32_t value);
 
+bool VideoIsHdr(AMediaFormat* format);
+
 }  // namespace AMediaFormatUtils
 #endif  // ANDROID_MEDIA_TRANSCODING_NDK_COMMON_H