Merge "cameraserver: Fix DYNAMIC_DEPTH / HEIC keys selected for UHR stream configurations." into udc-dev
diff --git a/camera/Android.bp b/camera/Android.bp
index f27eb31..b3f70f4 100644
--- a/camera/Android.bp
+++ b/camera/Android.bp
@@ -142,14 +142,15 @@
 filegroup {
     name: "libcamera_client_aidl",
     srcs: [
+        "aidl/android/hardware/CameraExtensionSessionStats.aidl",
         "aidl/android/hardware/ICameraService.aidl",
         "aidl/android/hardware/ICameraServiceListener.aidl",
         "aidl/android/hardware/ICameraServiceProxy.aidl",
         "aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl",
         "aidl/android/hardware/camera2/ICameraDeviceUser.aidl",
-        "aidl/android/hardware/camera2/ICameraOfflineSession.aidl",
         "aidl/android/hardware/camera2/ICameraInjectionCallback.aidl",
         "aidl/android/hardware/camera2/ICameraInjectionSession.aidl",
+        "aidl/android/hardware/camera2/ICameraOfflineSession.aidl",
     ],
     path: "aidl",
 }
diff --git a/camera/CameraSessionStats.cpp b/camera/CameraSessionStats.cpp
index 26c612a..9e9793d 100644
--- a/camera/CameraSessionStats.cpp
+++ b/camera/CameraSessionStats.cpp
@@ -278,7 +278,9 @@
         mRequestCount(0),
         mResultErrorCount(0),
         mDeviceError(false),
-        mVideoStabilizationMode(-1) {}
+        mVideoStabilizationMode(-1),
+        mSessionIndex(0),
+        mCameraExtensionSessionStats() {}
 
 CameraSessionStats::CameraSessionStats(const String16& cameraId,
         int facing, int newCameraState, const String16& clientName,
@@ -297,7 +299,9 @@
                 mRequestCount(0),
                 mResultErrorCount(0),
                 mDeviceError(0),
-                mVideoStabilizationMode(-1) {}
+                mVideoStabilizationMode(-1),
+                mSessionIndex(0),
+                mCameraExtensionSessionStats() {}
 
 status_t CameraSessionStats::readFromParcel(const android::Parcel* parcel) {
     if (parcel == NULL) {
@@ -409,6 +413,18 @@
         return err;
     }
 
+    int32_t sessionIdx;
+    if ((err = parcel->readInt32(&sessionIdx)) != OK) {
+        ALOGE("%s: Failed to read session index from parcel", __FUNCTION__);
+        return err;
+    }
+
+    CameraExtensionSessionStats extStats{};
+    if ((err = extStats.readFromParcel(parcel)) != OK) {
+        ALOGE("%s: Failed to read extension session stats from parcel", __FUNCTION__);
+        return err;
+    }
+
     mCameraId = id;
     mFacing = facing;
     mNewCameraState = newCameraState;
@@ -426,6 +442,8 @@
     mStreamStats = std::move(streamStats);
     mUserTag = userTag;
     mVideoStabilizationMode = videoStabilizationMode;
+    mSessionIndex = sessionIdx;
+    mCameraExtensionSessionStats = extStats;
 
     return OK;
 }
@@ -523,6 +541,16 @@
         return err;
     }
 
+    if ((err = parcel->writeInt32(mSessionIndex)) != OK) {
+        ALOGE("%s: Failed to write session index!", __FUNCTION__);
+        return err;
+    }
+
+    if ((err = mCameraExtensionSessionStats.writeToParcel(parcel)) != OK) {
+        ALOGE("%s: Failed to write extension sessions stats!", __FUNCTION__);
+        return err;
+    }
+
     return OK;
 }
 
diff --git a/camera/aidl/android/hardware/CameraExtensionSessionStats.aidl b/camera/aidl/android/hardware/CameraExtensionSessionStats.aidl
new file mode 100644
index 0000000..1c81831
--- /dev/null
+++ b/camera/aidl/android/hardware/CameraExtensionSessionStats.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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 android.hardware;
+
+/**
+ * Metrics specific to Extension Sessions (see CameraExtensionSession) for logging.
+ *
+ * Each Extension Session is mapped to one camera session internally, and will be sent to
+ * CameraServiceProxy with IDLE/CLOSE calls.
+ * @hide
+ */
+parcelable CameraExtensionSessionStats {
+    /**
+     * Value should match {@code CameraExtensionCharacteristics#EXTENSION_*}
+     */
+    @Backing(type="int")
+    enum Type {
+        EXTENSION_NONE = -1,
+        EXTENSION_AUTOMATIC = 0,
+        EXTENSION_FACE_RETOUCH = 1,
+        EXTENSION_BOKEH = 2,
+        EXTENSION_HDR = 3,
+        EXTENSION_NIGHT = 4
+    }
+
+    /**
+     * Key to uniquely identify the session this stat is associated with. The first call to
+     * 'ICameraService.reportExtensionSessionStats' should set this to an empty string.
+     * 'ICameraService.reportExtensionSessionStats' will return the key which should be used with
+     * the next calls.
+     */
+    String key;
+
+    /**
+     * Camera ID for which the stats is being reported.
+     */
+    String cameraId;
+
+    /**
+     * Package name of the client using the camera
+     */
+    String clientName;
+
+
+    /**
+     * Type of extension session requested by the app. Note that EXTENSION_AUTOMATIC is reported
+     * as such.
+     */
+    Type type = Type.EXTENSION_NONE;
+
+    /**
+     * true if advanced extensions are being used, false otherwise
+     */
+    boolean isAdvanced = false;
+}
\ No newline at end of file
diff --git a/camera/aidl/android/hardware/ICameraService.aidl b/camera/aidl/android/hardware/ICameraService.aidl
index 9f32595..f8e1631 100644
--- a/camera/aidl/android/hardware/ICameraService.aidl
+++ b/camera/aidl/android/hardware/ICameraService.aidl
@@ -30,6 +30,7 @@
 import android.hardware.ICameraServiceListener;
 import android.hardware.CameraInfo;
 import android.hardware.CameraStatus;
+import android.hardware.CameraExtensionSessionStats;
 
 /**
  * Binder interface for the native camera service running in mediaserver.
@@ -214,6 +215,26 @@
      */
     oneway void notifyDeviceStateChange(long newState);
 
+    /**
+     * Report Extension specific metrics to camera service for logging. This should only be called
+     * by CameraExtensionSession to log extension metrics. All calls after the first must set
+     * CameraExtensionSessionStats.key to the value returned by this function.
+     *
+     * Each subsequent call fully overwrites the existing CameraExtensionSessionStats for the
+     * current session, so the caller is responsible for keeping the stats complete.
+     *
+     * Due to cameraservice and cameraservice_proxy architecture, there is no guarantee that
+     * {@code stats} will be logged immediately (or at all). CameraService will log whatever
+     * extension stats it has at the time of camera session closing which may be before the app
+     * process receives a session/device closed callback; so CameraExtensionSession
+     * should send metrics to the cameraservice preriodically, and cameraservice must handle calls
+     * to this function from sessions that have not been logged yet and from sessions that have
+     * already been closed.
+     *
+     * @return the key that must be used to report updates to previously reported stats.
+     */
+    String reportExtensionSessionStats(in CameraExtensionSessionStats stats);
+
     // Bitfield constants for notifyDeviceStateChange
     // All bits >= 32 are for custom vendor states
     // Written as ints since AIDL does not support long constants.
diff --git a/camera/aidl/android/hardware/ICameraServiceProxy.aidl b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
index be8a00f..4faa6b4 100644
--- a/camera/aidl/android/hardware/ICameraServiceProxy.aidl
+++ b/camera/aidl/android/hardware/ICameraServiceProxy.aidl
@@ -17,6 +17,7 @@
 package android.hardware;
 
 import android.hardware.CameraSessionStats;
+import android.hardware.CameraExtensionSessionStats;
 
 /**
  * Binder interface for the camera service proxy running in system_server.
diff --git a/camera/include/camera/CameraSessionStats.h b/camera/include/camera/CameraSessionStats.h
index 091a7ff..071bc73 100644
--- a/camera/include/camera/CameraSessionStats.h
+++ b/camera/include/camera/CameraSessionStats.h
@@ -20,6 +20,7 @@
 #include <binder/Parcelable.h>
 
 #include <camera/CameraMetadata.h>
+#include <android/hardware/CameraExtensionSessionStats.h>
 
 namespace android {
 namespace hardware {
@@ -158,6 +159,9 @@
     std::vector<CameraStreamStats> mStreamStats;
     String16 mUserTag;
     int mVideoStabilizationMode;
+    int mSessionIndex;
+
+    CameraExtensionSessionStats mCameraExtensionSessionStats;
 
     // Constructors
     CameraSessionStats();
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 1bd3603..bd679e5 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -4487,8 +4487,8 @@
      * <p>The guaranteed stream combinations related to stream use case for a camera device with
      * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE">CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE</a>
      * capability is documented in the camera device
-     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#createCaptureSession">guideline</a>. The
-     * application is strongly recommended to use one of the guaranteed stream combinations.
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>. The application is strongly recommended to use one of the guaranteed stream
+     * combinations.
      * If the application creates a session with a stream combination not in the guaranteed
      * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session,
      * the camera device may ignore some stream use cases due to hardware constraints
@@ -9866,9 +9866,10 @@
      * </ul>
      * <p><a href="https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#SCALER_AVAILABLE_STREAM_USE_CASES">CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES</a>
      * lists all of the supported stream use cases.</p>
-     * <p>Refer to <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#createCaptureSession">CameraDevice#createCaptureSession</a> for the
-     * mandatory stream combinations involving stream use cases, which can also be queried
-     * via <a href="https://developer.android.com/reference/android/hardware/camera2/params/MandatoryStreamCombination.html">MandatoryStreamCombination</a>.</p>
+     * <p>Refer to
+     * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice.html#stream-use-case-capability-additional-guaranteed-configurations">CameraDevice#stream-use-case-capability-additional-guaranteed-configurations</a>
+     * for the mandatory stream combinations involving stream use cases, which can also be
+     * queried via <a href="https://developer.android.com/reference/android/hardware/camera2/params/MandatoryStreamCombination.html">MandatoryStreamCombination</a>.</p>
      */
     ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE           = 19,
 
@@ -10728,7 +10729,8 @@
      * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or
      * better.</p>
      * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p>
+     * {@link ACameraDevice_createCaptureSession }
+     * documentation are guaranteed to be supported.</p>
      * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic
      * support for color image capture. The only exception is that the device may
      * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth
@@ -10754,7 +10756,8 @@
     /**
      * <p>This camera device is capable of supporting advanced imaging applications.</p>
      * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the
-     * {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p>
+     * {@link ACameraDevice_createCaptureSession }
+     * documentation are guaranteed to be supported.</p>
      * <p>A <code>FULL</code> device will support below capabilities:</p>
      * <ul>
      * <li><code>BURST_CAPTURE</code> capability (ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains
@@ -10781,7 +10784,9 @@
 
     /**
      * <p>This camera device is running in backward compatibility mode.</p>
-     * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are supported.</p>
+     * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the
+     * {@link ACameraDevice_createCaptureSession }
+     * documentation are supported.</p>
      * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual
      * post-processing, arbitrary cropping regions, and has relaxed performance constraints.
      * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a
@@ -10803,7 +10808,9 @@
      * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to
      * FULL-level capabilities.</p>
      * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and
-     * <code>LIMITED</code> tables in the {@link ACameraDevice_createCaptureSession createCaptureSession} documentation are guaranteed to be supported.</p>
+     * <code>LIMITED</code> tables in the
+     * {@link ACameraDevice_createCaptureSession }
+     * documentation are guaranteed to be supported.</p>
      * <p>The following additional capabilities are guaranteed to be supported:</p>
      * <ul>
      * <li><code>YUV_REPROCESSING</code> capability (ACAMERA_REQUEST_AVAILABLE_CAPABILITIES contains
diff --git a/cmds/screenrecord/screenrecord.cpp b/cmds/screenrecord/screenrecord.cpp
index cd4932d..f53fc0a 100644
--- a/cmds/screenrecord/screenrecord.cpp
+++ b/cmds/screenrecord/screenrecord.cpp
@@ -122,7 +122,7 @@
 static uint32_t gBitRate = 20000000;     // 20Mbps
 static uint32_t gTimeLimitSec = kMaxTimeLimitSec;
 static uint32_t gBframes = 0;
-static PhysicalDisplayId gPhysicalDisplayId;
+static std::optional<PhysicalDisplayId> gPhysicalDisplayId;
 // Set by signal handler to stop recording.
 static volatile bool gStopRequested = false;
 
@@ -336,6 +336,24 @@
 }
 
 /*
+ * Gets the physical id of the display to record. If the user specified a physical
+ * display id, then that id will be set. Otherwise, the default display will be set.
+ */
+static status_t getPhysicalDisplayId(PhysicalDisplayId& outDisplayId) {
+    if (gPhysicalDisplayId) {
+        outDisplayId = *gPhysicalDisplayId;
+        return NO_ERROR;
+    }
+
+    const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+    if (ids.empty()) {
+        return INVALID_OPERATION;
+    }
+    outDisplayId = ids.front();
+    return NO_ERROR;
+}
+
+/*
  * Configures the virtual display.  When this completes, virtual display
  * frames will start arriving from the buffer producer.
  */
@@ -350,7 +368,12 @@
     setDisplayProjection(t, dpy, displayState);
     ui::LayerStack layerStack = ui::LayerStack::fromValue(std::rand());
     t.setDisplayLayerStack(dpy, layerStack);
-    *mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(gPhysicalDisplayId);
+    PhysicalDisplayId displayId;
+    status_t err = getPhysicalDisplayId(displayId);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    *mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(displayId);
     if (*mirrorRoot == nullptr) {
         ALOGE("Failed to create a mirror for screenrecord");
         return UNKNOWN_ERROR;
@@ -486,6 +509,43 @@
 }
 
 /*
+ * Update the display projection if size or orientation have changed.
+ */
+void updateDisplayProjection(const sp<IBinder>& virtualDpy, ui::DisplayState& displayState) {
+    ATRACE_NAME("updateDisplayProjection");
+
+    PhysicalDisplayId displayId;
+    if (getPhysicalDisplayId(displayId) != NO_ERROR) {
+        fprintf(stderr, "ERROR: Failed to get display id\n");
+        return;
+    }
+
+    sp<IBinder> displayToken = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
+    if (!displayToken) {
+        fprintf(stderr, "ERROR: failed to get display token\n");
+        return;
+    }
+
+    ui::DisplayState currentDisplayState;
+    if (SurfaceComposerClient::getDisplayState(displayToken, &currentDisplayState) != NO_ERROR) {
+        ALOGW("ERROR: failed to get display state\n");
+        return;
+    }
+
+    if (currentDisplayState.orientation != displayState.orientation ||
+        currentDisplayState.layerStackSpaceRect != displayState.layerStackSpaceRect) {
+        displayState = currentDisplayState;
+        ALOGD("display state changed, now has orientation %s, size (%d, %d)",
+              toCString(displayState.orientation), displayState.layerStackSpaceRect.getWidth(),
+              displayState.layerStackSpaceRect.getHeight());
+
+        SurfaceComposerClient::Transaction t;
+        setDisplayProjection(t, virtualDpy, currentDisplayState);
+        t.apply();
+    }
+}
+
+/*
  * Runs the MediaCodec encoder, sending the output to the MediaMuxer.  The
  * input frames are coming from the virtual display as fast as SurfaceFlinger
  * wants to send them.
@@ -494,9 +554,8 @@
  *
  * The muxer must *not* have been started before calling.
  */
-static status_t runEncoder(const sp<MediaCodec>& encoder,
-        AMediaMuxer *muxer, FILE* rawFp, const sp<IBinder>& display,
-        const sp<IBinder>& virtualDpy, ui::Rotation orientation) {
+static status_t runEncoder(const sp<MediaCodec>& encoder, AMediaMuxer* muxer, FILE* rawFp,
+                           const sp<IBinder>& virtualDpy, ui::DisplayState displayState) {
     static int kTimeout = 250000;   // be responsive on signal
     status_t err;
     ssize_t trackIdx = -1;
@@ -555,24 +614,7 @@
                 ALOGV("Got data in buffer %zu, size=%zu, pts=%" PRId64,
                         bufIndex, size, ptsUsec);
 
-                { // scope
-                    ATRACE_NAME("orientation");
-                    // Check orientation, update if it has changed.
-                    //
-                    // Polling for changes is inefficient and wrong, but the
-                    // useful stuff is hard to get at without a Dalvik VM.
-                    ui::DisplayState displayState;
-                    err = SurfaceComposerClient::getDisplayState(display, &displayState);
-                    if (err != NO_ERROR) {
-                        ALOGW("getDisplayState() failed: %d", err);
-                    } else if (orientation != displayState.orientation) {
-                        ALOGD("orientation changed, now %s", toCString(displayState.orientation));
-                        SurfaceComposerClient::Transaction t;
-                        setDisplayProjection(t, virtualDpy, displayState);
-                        t.apply();
-                        orientation = displayState.orientation;
-                    }
-                }
+                updateDisplayProjection(virtualDpy, displayState);
 
                 // If the virtual display isn't providing us with timestamps,
                 // use the current time.  This isn't great -- we could get
@@ -764,6 +806,38 @@
 };
 
 /*
+ * Computes the maximum width and height across all physical displays.
+ */
+static ui::Size getMaxDisplaySize() {
+    const std::vector<PhysicalDisplayId> physicalDisplayIds =
+            SurfaceComposerClient::getPhysicalDisplayIds();
+    if (physicalDisplayIds.empty()) {
+        fprintf(stderr, "ERROR: Failed to get physical display ids\n");
+        return {};
+    }
+
+    ui::Size result;
+    for (auto& displayId : physicalDisplayIds) {
+        sp<IBinder> displayToken = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
+        if (!displayToken) {
+            fprintf(stderr, "ERROR: failed to get display token\n");
+            continue;
+        }
+
+        ui::DisplayState displayState;
+        status_t err = SurfaceComposerClient::getDisplayState(displayToken, &displayState);
+        if (err != NO_ERROR) {
+            fprintf(stderr, "ERROR: failed to get display state\n");
+            continue;
+        }
+
+        result.height = std::max(result.height, displayState.layerStackSpaceRect.getHeight());
+        result.width = std::max(result.width, displayState.layerStackSpaceRect.getWidth());
+    }
+    return result;
+}
+
+/*
  * Main "do work" start point.
  *
  * Configures codec, muxer, and virtual display, then starts moving bits
@@ -781,9 +855,15 @@
     sp<ProcessState> self = ProcessState::self();
     self->startThreadPool();
 
+    PhysicalDisplayId displayId;
+    err = getPhysicalDisplayId(displayId);
+    if (err != NO_ERROR) {
+        fprintf(stderr, "ERROR: Failed to get display id\n");
+        return err;
+    }
+
     // Get main display parameters.
-    sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(
-            gPhysicalDisplayId);
+    sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
     if (display == nullptr) {
         fprintf(stderr, "ERROR: no display\n");
         return NAME_NOT_FOUND;
@@ -808,7 +888,8 @@
         return INVALID_OPERATION;
     }
 
-    const ui::Size& layerStackSpaceRect = displayState.layerStackSpaceRect;
+    const ui::Size layerStackSpaceRect =
+        gPhysicalDisplayId ? displayState.layerStackSpaceRect : getMaxDisplaySize();
     if (gVerbose) {
         printf("Display is %dx%d @%.2ffps (orientation=%s), layerStack=%u\n",
                 layerStackSpaceRect.getWidth(), layerStackSpaceRect.getHeight(),
@@ -973,8 +1054,7 @@
         }
     } else {
         // Main encoder loop.
-        err = runEncoder(recordingData.encoder, muxer, rawFp, display, recordingData.dpy,
-                         displayState.orientation);
+        err = runEncoder(recordingData.encoder, muxer, rawFp, recordingData.dpy, displayState);
         if (err != NO_ERROR) {
             fprintf(stderr, "Encoder failed (err=%d)\n", err);
             // fall through to cleanup
@@ -1175,14 +1255,6 @@
         { NULL,                 0,                  NULL, 0 }
     };
 
-    const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
-    if (ids.empty()) {
-        fprintf(stderr, "Failed to get ID for any displays\n");
-        return 1;
-    }
-
-    gPhysicalDisplayId = ids.front();
-
     while (true) {
         int optionIndex = 0;
         int ic = getopt_long(argc, argv, "", longOptions, &optionIndex);
diff --git a/drm/drmserver/Android.bp b/drm/drmserver/Android.bp
index ab25c65..d064123 100644
--- a/drm/drmserver/Android.bp
+++ b/drm/drmserver/Android.bp
@@ -31,7 +31,33 @@
     ],
 }
 
-cc_binary {
+prebuilt_etc {
+    name: "drmserver.zygote64_32.rc",
+    src: "drmserver.zygote64_32.rc",
+    sub_dir: "init/hw",
+}
+
+prebuilt_etc {
+    name: "drmserver.zygote64.rc",
+    src: "drmserver.zygote64.rc",
+    sub_dir: "init/hw",
+}
+
+soong_config_module_type {
+    name: "drmserver_cc_binary",
+    module_type: "cc_binary",
+    config_namespace: "ANDROID",
+    bool_variables: ["TARGET_DYNAMIC_64_32_DRMSERVER"],
+    properties: [
+        "compile_multilib",
+        "init_rc",
+        "multilib.lib32.suffix",
+        "multilib.lib64.suffix",
+        "required",
+    ],
+}
+
+drmserver_cc_binary {
     name: "drmserver",
 
     srcs: [
@@ -61,7 +87,27 @@
 
     compile_multilib: "prefer32",
 
-    init_rc: ["drmserver.rc"],
+    soong_config_variables: {
+        TARGET_DYNAMIC_64_32_DRMSERVER: {
+            compile_multilib: "both",
+            multilib: {
+                lib32: {
+                    suffix: "32",
+                },
+                lib64: {
+                    suffix: "64",
+                },
+            },
+            required: [
+                "drmserver.zygote64_32.rc",
+                "drmserver.zygote64.rc",
+            ],
+            init_rc: ["drmserver_dynamic.rc"],
+            conditions_default: {
+                init_rc: ["drmserver.rc"],
+            },
+        },
+    },
 }
 
 cc_fuzz {
diff --git a/drm/drmserver/drmserver.zygote64.rc b/drm/drmserver/drmserver.zygote64.rc
new file mode 100644
index 0000000..60cd906
--- /dev/null
+++ b/drm/drmserver/drmserver.zygote64.rc
@@ -0,0 +1,6 @@
+service drm /system/bin/drmserver64
+    disabled
+    class main
+    user drm
+    group drm system inet drmrpc readproc
+    task_profiles ProcessCapacityHigh
diff --git a/drm/drmserver/drmserver.zygote64_32.rc b/drm/drmserver/drmserver.zygote64_32.rc
new file mode 100644
index 0000000..c881acf
--- /dev/null
+++ b/drm/drmserver/drmserver.zygote64_32.rc
@@ -0,0 +1,6 @@
+service drm /system/bin/drmserver32
+    disabled
+    class main
+    user drm
+    group drm system inet drmrpc readproc
+    task_profiles ProcessCapacityHigh
diff --git a/drm/drmserver/drmserver_dynamic.rc b/drm/drmserver/drmserver_dynamic.rc
new file mode 100644
index 0000000..bfaada1
--- /dev/null
+++ b/drm/drmserver/drmserver_dynamic.rc
@@ -0,0 +1,7 @@
+import /system/etc/init/hw/drmserver.${ro.zygote}.rc
+
+on property:drm.service.enabled=true
+    start drm
+
+on property:drm.service.enabled=1
+    start drm
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 5b1bd91..cd5d354 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -44,53 +44,5 @@
             ],
             "file_patterns": ["(?i)drm|crypto"]
         }
-    ],
-
-    "platinum-postsubmit": [
-        // runs regularly, independent of changes in this tree.
-        // signals if changes elsewhere break media functionality
-        // @FlakyTest: in staged-postsubmit, but not postsubmit
-        {
-            "name": "CtsMediaCodecTestCases",
-            "options": [
-                {
-                    "include-filter": "android.media.codec.cts.EncodeDecodeTest"
-                }
-            ]
-        },
-        {
-            "name": "CtsMediaCodecTestCases",
-            "options": [
-                {
-                    "include-filter": "android.media.codec.cts.DecodeEditEncodeTest"
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ]
-        }
-    ],
-
-    "staged-platinum-postsubmit": [
-        // runs every four hours
-        {
-            "name": "CtsMediaCodecTestCases",
-            "options": [
-                {
-                    "include-filter": "android.media.codec.cts.EncodeDecodeTest"
-                }
-            ]
-        },
-        {
-            "name": "CtsMediaCodecTestCases",
-            "options": [
-                {
-                    "include-filter": "android.media.codec.cts.DecodeEditEncodeTest"
-                }
-            ]
-        }
     ]
-
-    // TODO (b/229286407) Add EncodeDecodeTest and DecodeEditEncodeTest to
-    // platinum-postsubmit once issues in cuttlefish are fixed
 }
diff --git a/media/audioaidlconversion/AidlConversionNdkCpp.cpp b/media/audioaidlconversion/AidlConversionNdkCpp.cpp
index 36f6128..c64a074 100644
--- a/media/audioaidlconversion/AidlConversionNdkCpp.cpp
+++ b/media/audioaidlconversion/AidlConversionNdkCpp.cpp
@@ -123,6 +123,7 @@
 GENERATE_CONVERTERS(android::media::audio::common, AudioHalEngineConfig);
 GENERATE_CONVERTERS(android::media::audio::common, AudioMMapPolicyInfo);
 GENERATE_ENUM_CONVERTERS(android::media::audio::common, AudioMMapPolicyType);
+GENERATE_ENUM_CONVERTERS(android::media::audio::common, AudioMode);
 GENERATE_CONVERTERS(android::media::audio::common, AudioPort);
 
 }  // namespace android
diff --git a/media/audioaidlconversion/include/media/AidlConversionCppNdk-impl.h b/media/audioaidlconversion/include/media/AidlConversionCppNdk-impl.h
index ec1f75c..bc9d4d5 100644
--- a/media/audioaidlconversion/include/media/AidlConversionCppNdk-impl.h
+++ b/media/audioaidlconversion/include/media/AidlConversionCppNdk-impl.h
@@ -288,6 +288,11 @@
 ConversionResult<media::audio::common::AudioOutputFlags>
 legacy2aidl_audio_output_flags_t_AudioOutputFlags(audio_output_flags_t legacy);
 
+ConversionResult<audio_stream_type_t>
+aidl2legacy_AudioStreamType_audio_stream_type_t(media::audio::common::AudioStreamType aidl);
+ConversionResult<media::audio::common::AudioStreamType>
+legacy2aidl_audio_stream_type_t_AudioStreamType(audio_stream_type_t legacy);
+
 // This type is unnamed in the original definition, thus we name it here.
 using audio_port_config_mix_ext_usecase = decltype(audio_port_config_mix_ext::usecase);
 ConversionResult<audio_port_config_mix_ext_usecase>
diff --git a/media/audioaidlconversion/include/media/AidlConversionNdkCpp.h b/media/audioaidlconversion/include/media/AidlConversionNdkCpp.h
index 09f1c22..f4822aa 100644
--- a/media/audioaidlconversion/include/media/AidlConversionNdkCpp.h
+++ b/media/audioaidlconversion/include/media/AidlConversionNdkCpp.h
@@ -23,11 +23,13 @@
 #include <aidl/android/media/audio/common/AudioHalEngineConfig.h>
 #include <aidl/android/media/audio/common/AudioMMapPolicyInfo.h>
 #include <aidl/android/media/audio/common/AudioMMapPolicyType.h>
+#include <aidl/android/media/audio/common/AudioMode.h>
 #include <aidl/android/media/audio/common/AudioPort.h>
 #include <android/media/audio/common/AudioFormatDescription.h>
 #include <android/media/audio/common/AudioHalEngineConfig.h>
 #include <android/media/audio/common/AudioMMapPolicyInfo.h>
 #include <android/media/audio/common/AudioMMapPolicyType.h>
+#include <android/media/audio/common/AudioMode.h>
 #include <android/media/audio/common/AudioPort.h>
 #include <media/AidlConversionUtil.h>
 
@@ -43,6 +45,7 @@
 DECLARE_CONVERTERS(android::media::audio::common, AudioHalEngineConfig);
 DECLARE_CONVERTERS(android::media::audio::common, AudioMMapPolicyInfo);
 DECLARE_CONVERTERS(android::media::audio::common, AudioMMapPolicyType);
+DECLARE_CONVERTERS(android::media::audio::common, AudioMode);
 DECLARE_CONVERTERS(android::media::audio::common, AudioPort);
 
 #undef DECLARE_CONVERTERS
diff --git a/media/codec2/components/aom/C2SoftAomEnc.cpp b/media/codec2/components/aom/C2SoftAomEnc.cpp
index 59cad9d..e08bf43 100644
--- a/media/codec2/components/aom/C2SoftAomEnc.cpp
+++ b/media/codec2/components/aom/C2SoftAomEnc.cpp
@@ -542,15 +542,15 @@
     mCodecConfiguration->kf_max_dist = 3000;
     // Encoder determines optimal key frame placement automatically.
     mCodecConfiguration->kf_mode = AOM_KF_AUTO;
-    // Initial value of the buffer level in ms.
-    mCodecConfiguration->rc_buf_initial_sz = 500;
-    // Amount of data that the encoder should try to maintain in ms.
-    mCodecConfiguration->rc_buf_optimal_sz = 600;
     // The amount of data that may be buffered by the decoding
     // application in ms.
     mCodecConfiguration->rc_buf_sz = 1000;
 
     if (mBitrateControlMode == AOM_CBR) {
+        // Initial value of the buffer level in ms.
+        mCodecConfiguration->rc_buf_initial_sz = 500;
+        // Amount of data that the encoder should try to maintain in ms.
+        mCodecConfiguration->rc_buf_optimal_sz = 600;
         // Maximum amount of bits that can be subtracted from the target
         // bitrate - expressed as percentage of the target bitrate.
         mCodecConfiguration->rc_undershoot_pct = 100;
@@ -563,7 +563,7 @@
         mCodecConfiguration->rc_undershoot_pct = 100;
         // Maximum amount of bits that can be added to the target
         // bitrate - expressed as percentage of the target bitrate.
-        mCodecConfiguration->rc_overshoot_pct = 25;
+        mCodecConfiguration->rc_overshoot_pct = 100;
     }
 
     if (mIntf->getSyncFramePeriod() >= 0) {
@@ -576,6 +576,12 @@
     }
     if (mMaxQuantizer > 0) {
         mCodecConfiguration->rc_max_quantizer = mMaxQuantizer;
+    } else {
+        if (mBitrateControlMode == AOM_VBR) {
+            // For VBR we are limiting MaxQP to 52 (down 11 steps) to maintain quality
+            // 52 comes from experiments done on libaom standalone app
+            mCodecConfiguration->rc_max_quantizer = 52;
+        }
     }
 
     mCodecContext = new aom_codec_ctx_t;
@@ -630,11 +636,11 @@
         return;
     }
 
-    std::shared_ptr<const C2GraphicView> rView;
+    std::shared_ptr<C2GraphicView> rView;
     std::shared_ptr<C2Buffer> inputBuffer;
     if (!work->input.buffers.empty()) {
         inputBuffer = work->input.buffers[0];
-        rView = std::make_shared<const C2GraphicView>(
+        rView = std::make_shared<C2GraphicView>(
                 inputBuffer->data().graphicBlocks().front().map().get());
         if (rView->error() != C2_OK) {
             ALOGE("graphic view map err = %d", rView->error());
@@ -672,6 +678,10 @@
         return;
     }
 
+    //(b/279387842)
+    //workaround for incorrect crop size in view when using surface mode
+    rView->setCrop_be(C2Rect(mSize->width, mSize->height));
+
     if (!mHeadersReceived) {
         Av1Config av1_config;
         constexpr uint32_t header_length = 2048;
diff --git a/media/codec2/components/gav1/C2SoftGav1Dec.cpp b/media/codec2/components/gav1/C2SoftGav1Dec.cpp
index 77296a4..3e4247b 100644
--- a/media/codec2/components/gav1/C2SoftGav1Dec.cpp
+++ b/media/codec2/components/gav1/C2SoftGav1Dec.cpp
@@ -29,6 +29,14 @@
 #include <media/stagefright/foundation/AUtils.h>
 #include <media/stagefright/foundation/MediaDefs.h>
 
+// libyuv version required for I410ToAB30Matrix and I210ToAB30Matrix.
+#if LIBYUV_VERSION >= 1780
+#include <algorithm>
+#define HAVE_LIBYUV_I410_I210_TO_AB30 1
+#else
+#define HAVE_LIBYUV_I410_I210_TO_AB30 0
+#endif
+
 namespace android {
 
 // codecname set and passed in as a compile flag from Android.bp
@@ -726,6 +734,24 @@
     }
 }
 
+void C2SoftGav1Dec::setError(const std::unique_ptr<C2Work> &work, c2_status_t error) {
+    mSignalledError = true;
+    work->result = error;
+    work->workletsProcessed = 1u;
+}
+
+bool C2SoftGav1Dec::allocTmpFrameBuffer(size_t size) {
+    if (size > mTmpFrameBufferSize) {
+        mTmpFrameBuffer = std::make_unique<uint16_t[]>(size);
+        if (mTmpFrameBuffer == nullptr) {
+            mTmpFrameBufferSize = 0;
+            return false;
+        }
+        mTmpFrameBufferSize = size;
+    }
+    return true;
+}
+
 bool C2SoftGav1Dec::outputBuffer(const std::shared_ptr<C2BlockPool> &pool,
                                  const std::unique_ptr<C2Work> &work) {
   if (!(work && pool)) return false;
@@ -772,6 +798,7 @@
   getHDRStaticParams(buffer, work);
   getHDR10PlusInfoData(buffer, work);
 
+#if LIBYUV_VERSION < 1779
   if (buffer->bitdepth == 10 &&
       !(buffer->image_format == libgav1::kImageFormatYuv420 ||
         buffer->image_format == libgav1::kImageFormatMonochrome400)) {
@@ -781,6 +808,7 @@
     work->result = C2_CORRUPTED;
     return false;
   }
+#endif
 
   const bool isMonochrome =
       buffer->image_format == libgav1::kImageFormatMonochrome400;
@@ -798,6 +826,7 @@
       allowRGBA1010102 = true;
     }
     format = getHalPixelFormatForBitDepth10(allowRGBA1010102);
+#if !HAVE_LIBYUV_I410_I210_TO_AB30
     if ((format == HAL_PIXEL_FORMAT_RGBA_1010102) &&
         (buffer->image_format != libgav1::kImageFormatYuv420)) {
         ALOGE("Only YUV420 output is supported when targeting RGBA_1010102");
@@ -806,6 +835,7 @@
       work->workletsProcessed = 1u;
       return false;
     }
+#endif
   }
 
   if (mHalPixelFormat != format) {
@@ -854,9 +884,6 @@
   uint8_t *dstY = const_cast<uint8_t *>(wView.data()[C2PlanarLayout::PLANE_Y]);
   uint8_t *dstU = const_cast<uint8_t *>(wView.data()[C2PlanarLayout::PLANE_U]);
   uint8_t *dstV = const_cast<uint8_t *>(wView.data()[C2PlanarLayout::PLANE_V]);
-  size_t srcYStride = buffer->stride[0];
-  size_t srcUStride = buffer->stride[1];
-  size_t srcVStride = buffer->stride[2];
 
   C2PlanarLayout layout = wView.layout();
   size_t dstYStride = layout.planes[C2PlanarLayout::PLANE_Y].rowInc;
@@ -867,26 +894,130 @@
     const uint16_t *srcY = (const uint16_t *)buffer->plane[0];
     const uint16_t *srcU = (const uint16_t *)buffer->plane[1];
     const uint16_t *srcV = (const uint16_t *)buffer->plane[2];
+    size_t srcYStride = buffer->stride[0] / 2;
+    size_t srcUStride = buffer->stride[1] / 2;
+    size_t srcVStride = buffer->stride[2] / 2;
 
     if (format == HAL_PIXEL_FORMAT_RGBA_1010102) {
-        convertYUV420Planar16ToY410OrRGBA1010102(
-                (uint32_t *)dstY, srcY, srcU, srcV, srcYStride / 2,
-                srcUStride / 2, srcVStride / 2,
-                dstYStride / sizeof(uint32_t), mWidth, mHeight,
-                std::static_pointer_cast<const C2ColorAspectsStruct>(codedColorAspects));
+        bool processed = false;
+#if HAVE_LIBYUV_I410_I210_TO_AB30
+        if (buffer->image_format == libgav1::kImageFormatYuv444) {
+            libyuv::I410ToAB30Matrix(srcY, srcYStride, srcU, srcUStride, srcV, srcVStride,
+                                     dstY, dstYStride, &libyuv::kYuvV2020Constants,
+                                     mWidth, mHeight);
+            processed = true;
+        } else if (buffer->image_format == libgav1::kImageFormatYuv422) {
+            libyuv::I210ToAB30Matrix(srcY, srcYStride, srcU, srcUStride, srcV, srcVStride,
+                                     dstY, dstYStride, &libyuv::kYuvV2020Constants,
+                                     mWidth, mHeight);
+            processed = true;
+        }
+#endif  // HAVE_LIBYUV_I410_I210_TO_AB30
+        if (!processed) {
+            if (isMonochrome) {
+                const size_t tmpSize = mWidth;
+                const bool needFill = tmpSize > mTmpFrameBufferSize;
+                if (!allocTmpFrameBuffer(tmpSize)) {
+                    ALOGE("Error allocating temp conversion buffer (%zu bytes)", tmpSize);
+                    setError(work, C2_NO_MEMORY);
+                    return false;
+                }
+                srcU = srcV = mTmpFrameBuffer.get();
+                srcUStride = srcVStride = 0;
+                if (needFill) {
+                    std::fill_n(mTmpFrameBuffer.get(), tmpSize, 512);
+                }
+            }
+            convertYUV420Planar16ToY410OrRGBA1010102(
+                    (uint32_t *)dstY, srcY, srcU, srcV, srcYStride,
+                    srcUStride, srcVStride,
+                    dstYStride / sizeof(uint32_t), mWidth, mHeight,
+                    std::static_pointer_cast<const C2ColorAspectsStruct>(codedColorAspects));
+        }
     } else if (format == HAL_PIXEL_FORMAT_YCBCR_P010) {
+        dstYStride /= 2;
+        dstUStride /= 2;
+        dstVStride /= 2;
+#if LIBYUV_VERSION >= 1779
+        if (buffer->image_format == libgav1::kImageFormatYuv444 ||
+            buffer->image_format == libgav1::kImageFormatYuv422) {
+            // TODO(https://crbug.com/libyuv/952): replace this block with libyuv::I410ToP010 and
+            // libyuv::I210ToP010 when they are available.
+            // Note it may be safe to alias dstY in I010ToP010, but the libyuv API doesn't make any
+            // guarantees.
+            const size_t tmpSize = dstYStride * mHeight + dstUStride * align(mHeight, 2);
+            if (!allocTmpFrameBuffer(tmpSize)) {
+                ALOGE("Error allocating temp conversion buffer (%zu bytes)", tmpSize);
+                setError(work, C2_NO_MEMORY);
+                return false;
+            }
+            uint16_t *const tmpY = mTmpFrameBuffer.get();
+            uint16_t *const tmpU = tmpY + dstYStride * mHeight;
+            uint16_t *const tmpV = tmpU + dstUStride * align(mHeight, 2) / 2;
+            if (buffer->image_format == libgav1::kImageFormatYuv444) {
+                libyuv::I410ToI010(srcY, srcYStride, srcU, srcUStride, srcV, srcVStride,
+                                   tmpY, dstYStride, tmpU, dstUStride, tmpV, dstUStride,
+                                   mWidth, mHeight);
+            } else {
+                libyuv::I210ToI010(srcY, srcYStride, srcU, srcUStride, srcV, srcVStride,
+                                   tmpY, dstYStride, tmpU, dstUStride, tmpV, dstUStride,
+                                   mWidth, mHeight);
+            }
+            libyuv::I010ToP010(tmpY, dstYStride, tmpU, dstUStride, tmpV, dstVStride,
+                               (uint16_t*)dstY, dstYStride, (uint16_t*)dstU, dstUStride,
+                               mWidth, mHeight);
+        } else {
+            convertYUV420Planar16ToP010((uint16_t *)dstY, (uint16_t *)dstU, srcY, srcU, srcV,
+                                        srcYStride, srcUStride, srcVStride, dstYStride,
+                                        dstUStride, mWidth, mHeight, isMonochrome);
+        }
+#else  // LIBYUV_VERSION < 1779
         convertYUV420Planar16ToP010((uint16_t *)dstY, (uint16_t *)dstU, srcY, srcU, srcV,
-                                    srcYStride / 2, srcUStride / 2, srcVStride / 2, dstYStride / 2,
-                                    dstUStride / 2, mWidth, mHeight, isMonochrome);
+                                    srcYStride, srcUStride, srcVStride, dstYStride,
+                                    dstUStride, mWidth, mHeight, isMonochrome);
+#endif  // LIBYUV_VERSION >= 1779
     } else {
-        convertYUV420Planar16ToYV12(dstY, dstU, dstV, srcY, srcU, srcV, srcYStride / 2,
-                                    srcUStride / 2, srcVStride / 2, dstYStride, dstUStride, mWidth,
-                                    mHeight, isMonochrome);
+#if LIBYUV_VERSION >= 1779
+        if (buffer->image_format == libgav1::kImageFormatYuv444) {
+            // TODO(https://crbug.com/libyuv/950): replace this block with libyuv::I410ToI420 when
+            // it's available.
+            const size_t tmpSize = dstYStride * mHeight + dstUStride * align(mHeight, 2);
+            if (!allocTmpFrameBuffer(tmpSize)) {
+                ALOGE("Error allocating temp conversion buffer (%zu bytes)", tmpSize);
+                setError(work, C2_NO_MEMORY);
+                return false;
+            }
+            uint16_t *const tmpY = mTmpFrameBuffer.get();
+            uint16_t *const tmpU = tmpY + dstYStride * mHeight;
+            uint16_t *const tmpV = tmpU + dstUStride * align(mHeight, 2) / 2;
+            libyuv::I410ToI010(srcY, srcYStride, srcU, srcUStride, srcV, srcVStride,
+                               tmpY, dstYStride, tmpU, dstUStride, tmpV, dstVStride,
+                               mWidth, mHeight);
+            libyuv::I010ToI420(tmpY, dstYStride, tmpU, dstUStride, tmpV, dstUStride,
+                               dstY, dstYStride, dstU, dstUStride, dstV, dstVStride,
+                               mWidth, mHeight);
+        } else if (buffer->image_format == libgav1::kImageFormatYuv422) {
+            libyuv::I210ToI420(srcY, srcYStride, srcU, srcUStride, srcV, srcVStride,
+                               dstY, dstYStride, dstU, dstUStride, dstV, dstVStride,
+                               mWidth, mHeight);
+        } else {
+            convertYUV420Planar16ToYV12(dstY, dstU, dstV, srcY, srcU, srcV, srcYStride,
+                                        srcUStride, srcVStride, dstYStride, dstUStride,
+                                        mWidth, mHeight, isMonochrome);
+        }
+#else  // LIBYUV_VERSION < 1779
+        convertYUV420Planar16ToYV12(dstY, dstU, dstV, srcY, srcU, srcV, srcYStride,
+                                    srcUStride, srcVStride, dstYStride, dstUStride,
+                                    mWidth, mHeight, isMonochrome);
+#endif  // LIBYUV_VERSION >= 1779
     }
   } else {
     const uint8_t *srcY = (const uint8_t *)buffer->plane[0];
     const uint8_t *srcU = (const uint8_t *)buffer->plane[1];
     const uint8_t *srcV = (const uint8_t *)buffer->plane[2];
+    size_t srcYStride = buffer->stride[0];
+    size_t srcUStride = buffer->stride[1];
+    size_t srcVStride = buffer->stride[2];
 
     if (buffer->image_format == libgav1::kImageFormatYuv444) {
         libyuv::I444ToI420(srcY, srcYStride, srcU, srcUStride, srcV, srcVStride,
diff --git a/media/codec2/components/gav1/C2SoftGav1Dec.h b/media/codec2/components/gav1/C2SoftGav1Dec.h
index f0e14d7..c3b27ea 100644
--- a/media/codec2/components/gav1/C2SoftGav1Dec.h
+++ b/media/codec2/components/gav1/C2SoftGav1Dec.h
@@ -19,6 +19,8 @@
 
 #include <inttypes.h>
 
+#include <memory>
+
 #include <media/stagefright/foundation/ColorUtils.h>
 
 #include <SimpleC2Component.h>
@@ -60,6 +62,9 @@
   uint32_t mHeight;
   bool mSignalledOutputEos;
   bool mSignalledError;
+  // Used during 10-bit I444/I422 to 10-bit P010 & 8-bit I420 conversions.
+  std::unique_ptr<uint16_t[]> mTmpFrameBuffer;
+  size_t mTmpFrameBufferSize = 0;
 
   C2StreamHdrStaticMetadataInfo::output mHdrStaticMetadataInfo;
   std::unique_ptr<C2StreamHdr10PlusInfo::output> mHdr10PlusInfo = nullptr;
@@ -97,6 +102,9 @@
   void destroyDecoder();
   void finishWork(uint64_t index, const std::unique_ptr<C2Work>& work,
                   const std::shared_ptr<C2GraphicBlock>& block);
+  // Sets |work->result| and mSignalledError. Returns false.
+  void setError(const std::unique_ptr<C2Work> &work, c2_status_t error);
+  bool allocTmpFrameBuffer(size_t size);
   bool outputBuffer(const std::shared_ptr<C2BlockPool>& pool,
                     const std::unique_ptr<C2Work>& work);
   c2_status_t drainInternal(uint32_t drainMode,
diff --git a/media/codec2/core/include/C2Config.h b/media/codec2/core/include/C2Config.h
index 417b261..9a3399d 100644
--- a/media/codec2/core/include/C2Config.h
+++ b/media/codec2/core/include/C2Config.h
@@ -2016,7 +2016,8 @@
 constexpr char C2_PARAMKEY_MAX_CODED_CHANNEL_COUNT[] = "coded.max-channel-count";
 
 /**
- * Audio channel mask. Used by decoder to express audio channel mask of decoded content.
+ * Audio channel mask. Used by decoder to express audio channel mask of decoded content,
+ * or by encoder for the channel mask of the encoded content once decoded.
  * Channel representation is specified according to the Java android.media.AudioFormat
  * CHANNEL_OUT_* constants.
  */
diff --git a/media/codec2/hal/client/client.cpp b/media/codec2/hal/client/client.cpp
index 9359e29..97c0806 100644
--- a/media/codec2/hal/client/client.cpp
+++ b/media/codec2/hal/client/client.cpp
@@ -610,16 +610,9 @@
 
 // Codec2Client
 Codec2Client::Codec2Client(sp<Base> const& base,
+                           sp<IConfigurable> const& configurable,
                            size_t serviceIndex)
-      : Configurable{
-            [base]() -> sp<IConfigurable> {
-                Return<sp<IConfigurable>> transResult =
-                        base->getConfigurable();
-                return transResult.isOk() ?
-                        static_cast<sp<IConfigurable>>(transResult) :
-                        nullptr;
-            }()
-        },
+      : Configurable{configurable},
         mBase1_0{base},
         mBase1_1{Base1_1::castFrom(base)},
         mBase1_2{Base1_2::castFrom(base)},
@@ -1003,7 +996,11 @@
     CHECK(baseStore) << "Codec2 service \"" << name << "\""
                         " inaccessible for unknown reasons.";
     LOG(VERBOSE) << "Client to Codec2 service \"" << name << "\" created";
-    return std::make_shared<Codec2Client>(baseStore, index);
+    Return<sp<IConfigurable>> transResult = baseStore->getConfigurable();
+    CHECK(transResult.isOk()) << "Codec2 service \"" << name << "\""
+                                "does not have IConfigurable.";
+    sp<IConfigurable> configurable = static_cast<sp<IConfigurable>>(transResult);
+    return std::make_shared<Codec2Client>(baseStore, configurable, index);
 }
 
 c2_status_t Codec2Client::ForAllServices(
@@ -1523,8 +1520,8 @@
     uint64_t consumerUsage = kDefaultConsumerUsage;
     {
         if (surface) {
-            int usage = 0;
-            status_t err = surface->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &usage);
+            uint64_t usage = 0;
+            status_t err = surface->getConsumerUsage(&usage);
             if (err != NO_ERROR) {
                 ALOGD("setOutputSurface -- failed to get consumer usage bits (%d/%s). ignoring",
                         err, asString(err));
@@ -1537,8 +1534,7 @@
                 // they do not exist inside of C2 scope. Any buffer usage shall be communicated
                 // through the sideband channel.
 
-                // do an unsigned conversion as bit-31 may be 1
-                consumerUsage = (uint32_t)usage | kDefaultConsumerUsage;
+                consumerUsage = usage | kDefaultConsumerUsage;
             }
         }
 
@@ -1562,6 +1558,8 @@
                     static_cast<uint64_t>(blockPoolId),
                     bqId == 0 ? nullHgbp : igbp);
 
+    mOutputBufferQueue->expireOldWaiters();
+
     if (!transStatus.isOk()) {
         LOG(ERROR) << "setOutputSurface -- transaction failed.";
         return C2_TRANSACTION_FAILED;
@@ -1607,6 +1605,7 @@
                        << status << ".";
         }
     }
+    mOutputBufferQueue->expireOldWaiters();
 }
 
 c2_status_t Codec2Client::Component::connectToInputSurface(
diff --git a/media/codec2/hal/client/include/codec2/hidl/client.h b/media/codec2/hal/client/include/codec2/hidl/client.h
index efbf179..5267394 100644
--- a/media/codec2/hal/client/include/codec2/hidl/client.h
+++ b/media/codec2/hal/client/include/codec2/hidl/client.h
@@ -146,6 +146,8 @@
     typedef ::android::hardware::media::c2::V1_2::IComponentStore Base1_2;
     typedef Base1_0 Base;
 
+    typedef ::android::hardware::media::c2::V1_0::IConfigurable IConfigurable;
+
     struct Listener;
 
     typedef Codec2ConfigurableClient Configurable;
@@ -230,8 +232,11 @@
     static std::shared_ptr<InputSurface> CreateInputSurface(
             char const* serviceName = nullptr);
 
-    // base cannot be null.
-    Codec2Client(sp<Base> const& base, size_t serviceIndex);
+    // base and/or configurable cannot be null.
+    Codec2Client(
+            sp<Base> const& base,
+            sp<IConfigurable> const& configurable,
+            size_t serviceIndex);
 
 protected:
     sp<Base1_0> mBase1_0;
diff --git a/media/codec2/hal/client/include/codec2/hidl/output.h b/media/codec2/hal/client/include/codec2/hidl/output.h
index 35a0224..2e89c3b 100644
--- a/media/codec2/hal/client/include/codec2/hidl/output.h
+++ b/media/codec2/hal/client/include/codec2/hidl/output.h
@@ -51,6 +51,10 @@
                    int maxDequeueBufferCount,
                    std::shared_ptr<V1_2::SurfaceSyncObj> *syncObj);
 
+    // If there are waiters to allocate from the old surface, wake up and expire
+    // them.
+    void expireOldWaiters();
+
     // Stop using the current output surface. Pending buffer opeations will not
     // perform anymore.
     void stop();
@@ -90,6 +94,8 @@
     std::weak_ptr<_C2BlockPoolData> mPoolDatas[BufferQueueDefs::NUM_BUFFER_SLOTS];
     std::shared_ptr<C2SurfaceSyncMemory> mSyncMem;
     bool mStopped;
+    std::mutex mOldMutex;
+    std::shared_ptr<C2SurfaceSyncMemory> mOldMem;
 
     bool registerBuffer(const C2ConstGraphicBlock& block);
 };
diff --git a/media/codec2/hal/client/output.cpp b/media/codec2/hal/client/output.cpp
index ce706cc..4eebd1c 100644
--- a/media/codec2/hal/client/output.cpp
+++ b/media/codec2/hal/client/output.cpp
@@ -217,6 +217,7 @@
     sp<GraphicBuffer> buffers[BufferQueueDefs::NUM_BUFFER_SLOTS];
     std::weak_ptr<_C2BlockPoolData>
             poolDatas[BufferQueueDefs::NUM_BUFFER_SLOTS];
+    std::shared_ptr<C2SurfaceSyncMemory> oldMem;
     {
         std::scoped_lock<std::mutex> l(mMutex);
         bool stopped = mStopped;
@@ -238,7 +239,7 @@
             }
             return false;
         }
-        std::shared_ptr<C2SurfaceSyncMemory> oldMem = mSyncMem;
+        oldMem = mSyncMem;
         C2SyncVariables *oldSync = mSyncMem ? mSyncMem->mem() : nullptr;
         if (oldSync) {
             oldSync->lock();
@@ -314,11 +315,26 @@
             newSync->unlock();
         }
     }
+    {
+        std::scoped_lock<std::mutex> l(mOldMutex);
+        mOldMem = oldMem;
+    }
     ALOGD("remote graphic buffer migration %zu/%zu",
           success, tryNum);
     return true;
 }
 
+void OutputBufferQueue::expireOldWaiters() {
+    std::scoped_lock<std::mutex> l(mOldMutex);
+    if (mOldMem) {
+        C2SyncVariables *oldSync = mOldMem->mem();
+        if (oldSync) {
+            oldSync->notifyAll();
+        }
+        mOldMem.reset();
+    }
+}
+
 void OutputBufferQueue::stop() {
     std::scoped_lock<std::mutex> l(mMutex);
     mStopped = true;
diff --git a/media/codec2/hal/plugin/samples/SampleFilterPlugin.cpp b/media/codec2/hal/plugin/samples/SampleFilterPlugin.cpp
index c77eb22..b5383ad 100644
--- a/media/codec2/hal/plugin/samples/SampleFilterPlugin.cpp
+++ b/media/codec2/hal/plugin/samples/SampleFilterPlugin.cpp
@@ -710,10 +710,6 @@
                 layerSettings.source.buffer.fence = Fence::NO_FENCE;
                 layerSettings.source.buffer.textureName = textureName;
                 layerSettings.source.buffer.usePremultipliedAlpha = false;
-                layerSettings.source.buffer.isY410BT2020 =
-                    (layerSettings.sourceDataspace == ui::Dataspace::BT2020_ITU_PQ ||
-                     layerSettings.sourceDataspace == ui::Dataspace::BT2020_ITU_HLG) &&
-                    format == HAL_PIXEL_FORMAT_RGBA_1010102;
                 layerSettings.source.buffer.maxMasteringLuminance =
                     (hdrStaticInfo && *hdrStaticInfo &&
                      hdrStaticInfo->mastering.maxLuminance > 0 &&
diff --git a/media/codec2/sfplugin/Android.bp b/media/codec2/sfplugin/Android.bp
index ecd5463..e4daf5c 100644
--- a/media/codec2/sfplugin/Android.bp
+++ b/media/codec2/sfplugin/Android.bp
@@ -71,10 +71,11 @@
         "libstagefright_codecbase",
         "libstagefright_foundation",
         "libstagefright_omx",
-	"libstagefright_surface_utils",
+        "libstagefright_surface_utils",
         "libstagefright_xmlparser",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
 
     export_shared_lib_headers: [
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index eb1b4b5..5e53acc 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -1828,6 +1828,7 @@
         mCallback->onError(err2, ACTION_CODE_FATAL);
         return;
     }
+
     err2 = mChannel->start(inputFormat, outputFormat, buffersBoundToCodec);
     if (err2 != OK) {
         mCallback->onError(err2, ACTION_CODE_FATAL);
@@ -2131,6 +2132,25 @@
         RevertOutputFormatIfNeeded(outputFormat, config->mOutputFormat);
     }
 
+    std::map<size_t, sp<MediaCodecBuffer>> clientInputBuffers;
+    status_t err = mChannel->prepareInitialInputBuffers(&clientInputBuffers);
+    if (err != OK) {
+        if (err == NO_MEMORY) {
+            // NO_MEMORY happens here when all the buffers are still
+            // with the codec. That is not an error as it is momentarily
+            // and the buffers are send to the client as soon as the codec
+            // releases them
+            ALOGI("Resuming with all input buffers still with codec");
+        } else {
+            ALOGE("Resume request for Input Buffers failed");
+            mCallback->onError(err, ACTION_CODE_FATAL);
+            return;
+        }
+    }
+
+    // channel start should be called after prepareInitialBuffers
+    // Calling before can cause a failure during prepare when
+    // buffers are sent to the client before preparation from onWorkDone
     (void)mChannel->start(nullptr, nullptr, [&]{
         Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
         const std::unique_ptr<Config> &config = *configLocked;
@@ -2148,14 +2168,6 @@
         state->set(RUNNING);
     }
 
-    std::map<size_t, sp<MediaCodecBuffer>> clientInputBuffers;
-    status_t err = mChannel->prepareInitialInputBuffers(&clientInputBuffers);
-    // FIXME(b/237656746)
-    if (err != OK && err != NO_MEMORY) {
-        ALOGE("Resume request for Input Buffers failed");
-        mCallback->onError(err, ACTION_CODE_FATAL);
-        return;
-    }
     mChannel->requestInitialInputBuffers(std::move(clientInputBuffers));
 }
 
@@ -2557,43 +2569,6 @@
 }
 
 void CCodec::initiateReleaseIfStuck() {
-    bool tunneled = false;
-    bool isMediaTypeKnown = false;
-    {
-        static const std::set<std::string> kKnownMediaTypes{
-            MIMETYPE_VIDEO_VP8,
-            MIMETYPE_VIDEO_VP9,
-            MIMETYPE_VIDEO_AV1,
-            MIMETYPE_VIDEO_AVC,
-            MIMETYPE_VIDEO_HEVC,
-            MIMETYPE_VIDEO_MPEG4,
-            MIMETYPE_VIDEO_H263,
-            MIMETYPE_VIDEO_MPEG2,
-            MIMETYPE_VIDEO_RAW,
-            MIMETYPE_VIDEO_DOLBY_VISION,
-
-            MIMETYPE_AUDIO_AMR_NB,
-            MIMETYPE_AUDIO_AMR_WB,
-            MIMETYPE_AUDIO_MPEG,
-            MIMETYPE_AUDIO_AAC,
-            MIMETYPE_AUDIO_QCELP,
-            MIMETYPE_AUDIO_VORBIS,
-            MIMETYPE_AUDIO_OPUS,
-            MIMETYPE_AUDIO_G711_ALAW,
-            MIMETYPE_AUDIO_G711_MLAW,
-            MIMETYPE_AUDIO_RAW,
-            MIMETYPE_AUDIO_FLAC,
-            MIMETYPE_AUDIO_MSGSM,
-            MIMETYPE_AUDIO_AC3,
-            MIMETYPE_AUDIO_EAC3,
-
-            MIMETYPE_IMAGE_ANDROID_HEIC,
-        };
-        Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
-        const std::unique_ptr<Config> &config = *configLocked;
-        tunneled = config->mTunneled;
-        isMediaTypeKnown = (kKnownMediaTypes.count(config->mCodingMediaType) != 0);
-    }
     std::string name;
     bool pendingDeadline = false;
     {
@@ -2605,16 +2580,6 @@
             pendingDeadline = true;
         }
     }
-    if (!tunneled && isMediaTypeKnown && name.empty()) {
-        constexpr std::chrono::steady_clock::duration kWorkDurationThreshold = 3s;
-        std::chrono::steady_clock::duration elapsed = mChannel->elapsed();
-        if (elapsed >= kWorkDurationThreshold) {
-            name = "queue";
-        }
-        if (elapsed > 0s) {
-            pendingDeadline = true;
-        }
-    }
     if (name.empty()) {
         // We're not stuck.
         if (pendingDeadline) {
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index da33b0d..d72d228 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -34,6 +34,7 @@
 
 #include <android/hardware/cas/native/1.0/IDescrambler.h>
 #include <android/hardware/drm/1.0/types.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/MemoryBase.h>
@@ -52,6 +53,7 @@
 #include <media/stagefright/SurfaceUtils.h>
 #include <media/MediaCodecBuffer.h>
 #include <mediadrm/ICrypto.h>
+#include <server_configurable_flags/get_flags.h>
 #include <system/window.h>
 
 #include "CCodecBufferChannel.h"
@@ -75,7 +77,6 @@
 namespace {
 
 constexpr size_t kSmoothnessFactor = 4;
-constexpr size_t kRenderingDepth = 3;
 
 // This is for keeping IGBP's buffer dropping logic in legacy mode other
 // than making it non-blocking. Do not change this value.
@@ -149,10 +150,10 @@
       mFirstValidFrameIndex(0u),
       mIsSurfaceToDisplay(false),
       mHasPresentFenceTimes(false),
+      mRenderingDepth(3u),
       mMetaMode(MODE_NONE),
       mInputMetEos(false),
       mSendEncryptedInfoBuffer(false) {
-    mOutputSurface.lock()->maxDequeueBuffers = kSmoothnessFactor + kRenderingDepth;
     {
         Mutexed<Input>::Locked input(mInput);
         input->buffers.reset(new DummyInputBuffers(""));
@@ -167,11 +168,16 @@
         Mutexed<Output>::Locked output(mOutput);
         output->outputDelay = 0u;
         output->numSlots = kSmoothnessFactor;
+        output->bounded = false;
     }
     {
         Mutexed<BlockPools>::Locked pools(mBlockPools);
         pools->outputPoolId = C2BlockPool::BASIC_LINEAR;
     }
+    std::string value = server_configurable_flags::GetServerConfigurableFlag(
+            "media_native", "ccodec_rendering_depth", "3");
+    android::base::ParseInt(value, &mRenderingDepth);
+    mOutputSurface.lock()->maxDequeueBuffers = kSmoothnessFactor + mRenderingDepth;
 }
 
 CCodecBufferChannel::~CCodecBufferChannel() {
@@ -727,7 +733,7 @@
         Mutexed<Output>::Locked output(mOutput);
         if (!output->buffers ||
                 output->buffers->hasPending() ||
-                output->buffers->numActiveSlots() >= output->numSlots) {
+                (!output->bounded && output->buffers->numActiveSlots() >= output->numSlots)) {
             return;
         }
     }
@@ -1017,7 +1023,7 @@
     int hasPresentFenceTimes = 0;
     window->query(window, NATIVE_WINDOW_FRAME_TIMESTAMPS_SUPPORTS_PRESENT, &hasPresentFenceTimes);
     mHasPresentFenceTimes = hasPresentFenceTimes == 1;
-    if (mHasPresentFenceTimes) {
+    if (!mHasPresentFenceTimes) {
         ALOGI("Using latch times for frame rendered signals - present fences not supported");
     }
 }
@@ -1386,7 +1392,7 @@
         {
             Mutexed<OutputSurface>::Locked output(mOutputSurface);
             maxDequeueCount = output->maxDequeueBuffers = numOutputSlots +
-                    reorderDepth.value + kRenderingDepth;
+                    reorderDepth.value + mRenderingDepth;
             outputSurface = output->surface ?
                     output->surface->getIGraphicBufferProducer() : nullptr;
             if (outputSurface) {
@@ -1509,6 +1515,7 @@
         Mutexed<Output>::Locked output(mOutput);
         output->outputDelay = outputDelayValue;
         output->numSlots = numOutputSlots;
+        output->bounded = bool(outputSurface);
         if (graphic) {
             if (outputSurface || !buffersBoundToCodec) {
                 output->buffers.reset(new GraphicOutputBuffers(mName));
@@ -1801,8 +1808,6 @@
             output->buffers->flushStash();
         }
     }
-    // reset the frames that are being tracked for onFrameRendered callbacks
-    mTrackedFrames.clear();
 }
 
 void CCodecBufferChannel::onWorkDone(
@@ -2055,7 +2060,7 @@
         {
             Mutexed<OutputSurface>::Locked output(mOutputSurface);
             maxDequeueCount = output->maxDequeueBuffers =
-                    numOutputSlots + reorderDepth + kRenderingDepth;
+                    numOutputSlots + reorderDepth + mRenderingDepth;
             if (output->surface) {
                 output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
             }
@@ -2113,7 +2118,10 @@
             outBuffer->meta()->setInt32("flags", BUFFER_FLAG_CODEC_CONFIG);
             ALOGV("[%s] onWorkDone: csd index = %zu [%p]", mName, index, outBuffer.get());
 
-            output.unlock();
+            // TRICKY: we want popped buffers reported in order, so sending
+            // the callback while holding the lock here. This assumes that
+            // onOutputBufferAvailable() does not block. onOutputBufferAvailable()
+            // callbacks are always sent with the Output lock held.
             mCallback->onOutputBufferAvailable(index, outBuffer);
         } else {
             ALOGD("[%s] onWorkDone: unable to register csd", mName);
@@ -2203,7 +2211,10 @@
         case OutputBuffers::DISCARD:
             break;
         case OutputBuffers::NOTIFY_CLIENT:
-            output.unlock();
+            // TRICKY: we want popped buffers reported in order, so sending
+            // the callback while holding the lock here. This assumes that
+            // onOutputBufferAvailable() does not block. onOutputBufferAvailable()
+            // callbacks are always sent with the Output lock held.
             mCallback->onOutputBufferAvailable(index, outBuffer);
             break;
         case OutputBuffers::REALLOCATE:
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.h b/media/codec2/sfplugin/CCodecBufferChannel.h
index 0d25d6d..2d87aa9 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.h
+++ b/media/codec2/sfplugin/CCodecBufferChannel.h
@@ -321,6 +321,9 @@
         std::unique_ptr<OutputBuffers> buffers;
         size_t numSlots;
         uint32_t outputDelay;
+        // true iff the underlying block pool is bounded --- for example,
+        // a BufferQueue-based block pool would be bounded by the BufferQueue.
+        bool bounded;
     };
     Mutexed<Output> mOutput;
     Mutexed<std::list<std::unique_ptr<C2Work>>> mFlushedConfigs;
@@ -341,6 +344,7 @@
         std::map<uint64_t, int> rotation;
     };
     Mutexed<OutputSurface> mOutputSurface;
+    int mRenderingDepth;
 
     struct BlockPools {
         C2Allocator::id_t inputAllocatorId;
diff --git a/media/codec2/sfplugin/CCodecConfig.cpp b/media/codec2/sfplugin/CCodecConfig.cpp
index a893bc0..6d49fa8 100644
--- a/media/codec2/sfplugin/CCodecConfig.cpp
+++ b/media/codec2/sfplugin/CCodecConfig.cpp
@@ -625,30 +625,30 @@
         .limitTo(D::OUTPUT & D::READ));
 
     deprecated(ConfigMapper(KEY_HDR10_PLUS_INFO, C2_PARAMKEY_INPUT_HDR10_PLUS_INFO, "value")
-        .limitTo(D::VIDEO & D::PARAM & D::INPUT));
+        .limitTo(D::VIDEO & D::PARAM & D::INPUT & (D::CONFIG | D::PARAM)));
 
     deprecated(ConfigMapper(KEY_HDR10_PLUS_INFO, C2_PARAMKEY_OUTPUT_HDR10_PLUS_INFO, "value")
-        .limitTo(D::VIDEO & D::OUTPUT));
+        .limitTo(D::VIDEO & D::OUTPUT & D::READ));
 
     add(ConfigMapper(
             std::string(C2_PARAMKEY_INPUT_HDR_DYNAMIC_INFO) + ".type",
             C2_PARAMKEY_INPUT_HDR_DYNAMIC_INFO, "type")
-        .limitTo(D::VIDEO & D::PARAM & D::INPUT));
+        .limitTo(D::VIDEO & D::PARAM & D::INPUT & (D::CONFIG | D::PARAM)));
 
     add(ConfigMapper(
             std::string(C2_PARAMKEY_INPUT_HDR_DYNAMIC_INFO) + ".data",
             C2_PARAMKEY_INPUT_HDR_DYNAMIC_INFO, "data")
-        .limitTo(D::VIDEO & D::PARAM & D::INPUT));
+        .limitTo(D::VIDEO & D::PARAM & D::INPUT & (D::CONFIG | D::PARAM)));
 
     add(ConfigMapper(
             std::string(C2_PARAMKEY_OUTPUT_HDR_DYNAMIC_INFO) + ".type",
             C2_PARAMKEY_OUTPUT_HDR_DYNAMIC_INFO, "type")
-        .limitTo(D::VIDEO & D::OUTPUT));
+        .limitTo(D::VIDEO & D::OUTPUT & D::READ));
 
     add(ConfigMapper(
             std::string(C2_PARAMKEY_OUTPUT_HDR_DYNAMIC_INFO) + ".data",
             C2_PARAMKEY_OUTPUT_HDR_DYNAMIC_INFO, "data")
-        .limitTo(D::VIDEO & D::OUTPUT));
+        .limitTo(D::VIDEO & D::OUTPUT & D::READ));
 
     add(ConfigMapper(C2_PARAMKEY_TEMPORAL_LAYERING, C2_PARAMKEY_TEMPORAL_LAYERING, "")
         .limitTo(D::ENCODER & D::VIDEO & D::OUTPUT));
@@ -940,6 +940,9 @@
     add(ConfigMapper(KEY_CHANNEL_MASK, C2_PARAMKEY_CHANNEL_MASK, "value")
         .limitTo(D::AUDIO & D::DECODER & D::READ));
 
+    add(ConfigMapper(KEY_CHANNEL_MASK, C2_PARAMKEY_CHANNEL_MASK, "value")
+        .limitTo(D::AUDIO & D::ENCODER & D::CONFIG));
+
     add(ConfigMapper(KEY_AAC_SBR_MODE, C2_PARAMKEY_AAC_SBR_MODE, "value")
         .limitTo(D::AUDIO & D::ENCODER & (D::CONFIG | D::PARAM | D::READ))
         .withMapper([](C2Value v) -> C2Value {
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp
index d5bbb52..7122af1 100644
--- a/media/libaudioclient/Android.bp
+++ b/media/libaudioclient/Android.bp
@@ -287,9 +287,11 @@
         "aidl/android/media/AudioFlag.aidl",
         "aidl/android/media/AudioGainSys.aidl",
         "aidl/android/media/AudioHalVersion.aidl",
+        "aidl/android/media/AudioHwModule.aidl",
         "aidl/android/media/AudioIoConfigEvent.aidl",
         "aidl/android/media/AudioIoDescriptor.aidl",
         "aidl/android/media/AudioPatchFw.aidl",
+        "aidl/android/media/AudioPolicyConfig.aidl",
         "aidl/android/media/AudioPortFw.aidl",
         "aidl/android/media/AudioPortSys.aidl",
         "aidl/android/media/AudioPortConfigFw.aidl",
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index b731702..4527e0f 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -2529,6 +2529,14 @@
     return af->supportsBluetoothVariableLatency(support);
 }
 
+status_t AudioSystem::getAudioPolicyConfig(media::AudioPolicyConfig *config) {
+    const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
+    if (af == nullptr) {
+        return PERMISSION_DENIED;
+    }
+    return af->getAudioPolicyConfig(config);
+}
+
 class CaptureStateListenerImpl : public media::BnCaptureStateListener,
                                  public IBinder::DeathRecipient {
 public:
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index 4384f97..7b33a00 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -887,6 +887,16 @@
     return statusTFromBinderStatus(mDelegate->invalidateTracks(portIdsAidl));
 }
 
+status_t AudioFlingerClientAdapter::getAudioPolicyConfig(media::AudioPolicyConfig *config) {
+    if (config == nullptr) {
+        return BAD_VALUE;
+    }
+
+    RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mDelegate->getAudioPolicyConfig(config)));
+
+    return NO_ERROR;
+}
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // AudioFlingerServerAdapter
 AudioFlingerServerAdapter::AudioFlingerServerAdapter(
@@ -1429,4 +1439,8 @@
     return Status::ok();
 }
 
+Status AudioFlingerServerAdapter::getAudioPolicyConfig(media::AudioPolicyConfig* _aidl_return) {
+    return Status::fromStatusT(mDelegate->getAudioPolicyConfig(_aidl_return));
+}
+
 } // namespace android
diff --git a/media/libaudioclient/ToneGenerator.cpp b/media/libaudioclient/ToneGenerator.cpp
index 7c0ce57..9c4ccb8 100644
--- a/media/libaudioclient/ToneGenerator.cpp
+++ b/media/libaudioclient/ToneGenerator.cpp
@@ -1306,7 +1306,7 @@
         streamType = AUDIO_STREAM_DTMF;
     }
     attr = AudioSystem::streamTypeToAttributes(streamType);
-    attr.flags = AUDIO_FLAG_LOW_LATENCY;
+    attr.flags = static_cast<audio_flags_mask_t>(attr.flags | AUDIO_FLAG_LOW_LATENCY);
 
     const size_t frameCount = mProcessSize;
     status_t status = mpAudioTrack->set(
diff --git a/media/libaudioclient/aidl/android/media/AudioHwModule.aidl b/media/libaudioclient/aidl/android/media/AudioHwModule.aidl
new file mode 100644
index 0000000..9251400
--- /dev/null
+++ b/media/libaudioclient/aidl/android/media/AudioHwModule.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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 android.media;
+
+import android.media.audio.common.AudioPort;
+import android.media.AudioRoute;
+
+/*
+ * A representation of a HAL module configuration.
+ * {@hide}
+ */
+parcelable AudioHwModule {
+    int /* audio_module_handle_t */ handle;
+    @utf8InCpp String name;
+    AudioPort[] ports;
+    AudioRoute[] routes;
+}
diff --git a/media/libaudioclient/aidl/android/media/AudioPolicyConfig.aidl b/media/libaudioclient/aidl/android/media/AudioPolicyConfig.aidl
new file mode 100644
index 0000000..87767c2
--- /dev/null
+++ b/media/libaudioclient/aidl/android/media/AudioPolicyConfig.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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 android.media;
+
+import android.media.AudioHwModule;
+import android.media.SurroundSoundConfig;
+import android.media.audio.common.AudioHalEngineConfig;
+import android.media.audio.common.AudioMode;
+
+/*
+ * Audio policy configuration. Functionally replaces the APM XML file.
+ * {@hide}
+ */
+parcelable AudioPolicyConfig {
+    AudioHwModule[] modules;
+    AudioMode[] supportedModes;
+    SurroundSoundConfig surroundSoundConfig;
+    AudioHalEngineConfig engineConfig;
+}
diff --git a/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl b/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl
index 884cd30..6412810 100644
--- a/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl
+++ b/media/libaudioclient/aidl/android/media/IAudioFlingerService.aidl
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.media.AudioPatchFw;
+import android.media.AudioPolicyConfig;
 import android.media.AudioPortFw;
 import android.media.AudioPortConfigFw;
 import android.media.AudioUniqueIdUse;
@@ -281,6 +282,12 @@
      */
     void invalidateTracks(in int[] /* audio_port_handle_t[] */ portIds);
 
+    /**
+     * Only implemented for AIDL. Provides the APM configuration which
+     * used to be in the XML file.
+     */
+    AudioPolicyConfig getAudioPolicyConfig();
+
     // When adding a new method, please review and update
     // IAudioFlinger.h AudioFlingerServerAdapter::Delegate::TransactionCode
     // AudioFlinger.cpp AudioFlinger::onTransactWrapper()
diff --git a/media/libaudioclient/include/media/AidlConversion.h b/media/libaudioclient/include/media/AidlConversion.h
index 5bd0114..b0d48b7 100644
--- a/media/libaudioclient/include/media/AidlConversion.h
+++ b/media/libaudioclient/include/media/AidlConversion.h
@@ -72,11 +72,6 @@
         media::audio::common::AudioPortDeviceExt* aidl,
         media::AudioPortDeviceExtSys* aidlDeviceExt);
 
-ConversionResult<audio_stream_type_t> aidl2legacy_AudioStreamType_audio_stream_type_t(
-        media::audio::common::AudioStreamType aidl);
-ConversionResult<media::audio::common::AudioStreamType>
-legacy2aidl_audio_stream_type_t_AudioStreamType(audio_stream_type_t legacy);
-
 ConversionResult<audio_port_config_mix_ext> aidl2legacy_AudioPortMixExt(
         const media::audio::common::AudioPortMixExt& aidl, media::AudioPortRole role,
         const media::AudioPortMixExtSys& aidlMixExt);
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h
index 1bfe34d..8f8c9dd 100644
--- a/media/libaudioclient/include/media/AudioSystem.h
+++ b/media/libaudioclient/include/media/AudioSystem.h
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include <android/content/AttributionSourceState.h>
+#include <android/media/AudioPolicyConfig.h>
 #include <android/media/AudioPortFw.h>
 #include <android/media/AudioVibratorInfo.h>
 #include <android/media/BnAudioFlingerClient.h>
@@ -663,6 +664,8 @@
                                                   audio_port_handle_t portId,
                                                   uid_t uid);
 
+    static status_t getAudioPolicyConfig(media::AudioPolicyConfig *config);
+
     // A listener for capture state changes.
     class CaptureStateListener : public virtual RefBase {
     public:
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h
index 6e28d51..2e2ef65 100644
--- a/media/libaudioclient/include/media/IAudioFlinger.h
+++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -382,6 +382,8 @@
     virtual status_t isBluetoothVariableLatencyEnabled(bool* enabled) = 0;
 
     virtual status_t supportsBluetoothVariableLatency(bool* support) = 0;
+
+    virtual status_t getAudioPolicyConfig(media::AudioPolicyConfig* output) = 0;
 };
 
 /**
@@ -495,6 +497,7 @@
     status_t getSoundDoseInterface(const sp<media::ISoundDoseCallback>& callback,
                                    sp<media::ISoundDose>* soundDose) override;
     status_t invalidateTracks(const std::vector<audio_port_handle_t>& portIds) override;
+    status_t getAudioPolicyConfig(media::AudioPolicyConfig* output) override;
 
 private:
     const sp<media::IAudioFlingerService> mDelegate;
@@ -594,6 +597,8 @@
                     media::BnAudioFlingerService::TRANSACTION_supportsBluetoothVariableLatency,
             GET_SOUND_DOSE_INTERFACE = media::BnAudioFlingerService::TRANSACTION_getSoundDoseInterface,
             INVALIDATE_TRACKS = media::BnAudioFlingerService::TRANSACTION_invalidateTracks,
+            GET_AUDIO_POLICY_CONFIG =
+                    media::BnAudioFlingerService::TRANSACTION_getAudioPolicyConfig,
         };
 
     protected:
@@ -726,6 +731,7 @@
     Status getSoundDoseInterface(const sp<media::ISoundDoseCallback>& callback,
                                  sp<media::ISoundDose>* _aidl_return) override;
     Status invalidateTracks(const std::vector<int32_t>& portIds) override;
+    Status getAudioPolicyConfig(media::AudioPolicyConfig* _aidl_return) override;
 private:
     const sp<AudioFlingerServerAdapter::Delegate> mDelegate;
 };
diff --git a/media/libaudiohal/impl/ConversionHelperAidl.h b/media/libaudiohal/impl/ConversionHelperAidl.h
index db6b6cf..5534d13 100644
--- a/media/libaudiohal/impl/ConversionHelperAidl.h
+++ b/media/libaudiohal/impl/ConversionHelperAidl.h
@@ -20,6 +20,9 @@
 #include <string_view>
 #include <vector>
 
+#include <android-base/expected.h>
+#include <error/Result.h>
+#include <media/AudioParameter.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
 
@@ -51,4 +54,24 @@
     const std::string mClassName;
 };
 
+// 'action' must accept a value of type 'T' and return 'status_t'.
+// The function returns 'true' if the parameter was found, and the action has succeeded.
+// The function returns 'false' if the parameter was not found.
+// Any errors get propagated, if there are errors it means the parameter was found.
+template<typename T, typename F>
+error::Result<bool> filterOutAndProcessParameter(
+        AudioParameter& parameters, const String8& key, const F& action) {
+    if (parameters.containsKey(key)) {
+        T value;
+        status_t status = parameters.get(key, value);
+        if (status == OK) {
+            parameters.remove(key);
+            status = action(value);
+            if (status == OK) return true;
+        }
+        return base::unexpected(status);
+    }
+    return false;
+}
+
 }  // namespace android
diff --git a/media/libaudiohal/impl/DeviceHalAidl.cpp b/media/libaudiohal/impl/DeviceHalAidl.cpp
index f993907..3125e311 100644
--- a/media/libaudiohal/impl/DeviceHalAidl.cpp
+++ b/media/libaudiohal/impl/DeviceHalAidl.cpp
@@ -35,6 +35,7 @@
 #include "StreamHalAidl.h"
 
 using aidl::android::aidl_utils::statusTFromBinderStatus;
+using aidl::android::media::audio::common::Boolean;
 using aidl::android::media::audio::common::AudioChannelLayout;
 using aidl::android::media::audio::common::AudioConfig;
 using aidl::android::media::audio::common::AudioDevice;
@@ -68,6 +69,9 @@
 using aidl::android::hardware::audio::common::RecordTrackMetadata;
 using aidl::android::hardware::audio::core::AudioPatch;
 using aidl::android::hardware::audio::core::AudioRoute;
+using aidl::android::hardware::audio::core::IBluetooth;
+using aidl::android::hardware::audio::core::IBluetoothA2dp;
+using aidl::android::hardware::audio::core::IBluetoothLe;
 using aidl::android::hardware::audio::core::IModule;
 using aidl::android::hardware::audio::core::ITelephony;
 using aidl::android::hardware::audio::core::ModuleDebug;
@@ -107,8 +111,29 @@
     return cpp;
 }
 
+template<typename T>
+std::shared_ptr<T> retrieveSubInterface(const std::shared_ptr<IModule>& module,
+        ::ndk::ScopedAStatus (IModule::*getT)(std::shared_ptr<T>*)) {
+    if (module != nullptr) {
+        std::shared_ptr<T> instance;
+        if (auto status = (module.get()->*getT)(&instance); status.isOk()) {
+            return instance;
+        }
+    }
+    return nullptr;
+}
+
 }  // namespace
 
+DeviceHalAidl::DeviceHalAidl(const std::string& instance, const std::shared_ptr<IModule>& module)
+        : ConversionHelperAidl("DeviceHalAidl"),
+          mInstance(instance), mModule(module),
+          mTelephony(retrieveSubInterface<ITelephony>(module, &IModule::getTelephony)),
+          mBluetooth(retrieveSubInterface<IBluetooth>(module, &IModule::getBluetooth)),
+          mBluetoothA2dp(retrieveSubInterface<IBluetoothA2dp>(module, &IModule::getBluetoothA2dp)),
+          mBluetoothLe(retrieveSubInterface<IBluetoothLe>(module, &IModule::getBluetoothLe)) {
+}
+
 status_t DeviceHalAidl::getAudioPorts(std::vector<media::audio::common::AudioPort> *ports) {
     auto convertAudioPortFromMap = [](const Ports::value_type& pair) {
         return ndk2cpp_AudioPort(pair.second);
@@ -124,6 +149,22 @@
     return OK;
 }
 
+status_t DeviceHalAidl::getSupportedModes(std::vector<media::audio::common::AudioMode> *modes) {
+    TIME_CHECK();
+    if (modes == nullptr) {
+        return BAD_VALUE;
+    }
+    if (mModule == nullptr) return NO_INIT;
+    if (mTelephony == nullptr) return INVALID_OPERATION;
+    std::vector<AudioMode> aidlModes;
+    RETURN_STATUS_IF_ERROR(
+            statusTFromBinderStatus(mTelephony->getSupportedAudioModes(&aidlModes)));
+    *modes = VALUE_OR_RETURN_STATUS(
+            ::aidl::android::convertContainer<std::vector<media::audio::common::AudioMode>>(
+                    aidlModes, ndk2cpp_AudioMode));
+    return OK;
+}
+
 status_t DeviceHalAidl::getSupportedDevices(uint32_t*) {
     // Obsolete.
     return INVALID_OPERATION;
@@ -175,17 +216,14 @@
 status_t DeviceHalAidl::setVoiceVolume(float volume) {
     TIME_CHECK();
     if (!mModule) return NO_INIT;
-    std::shared_ptr<ITelephony> telephony;
-    if (ndk::ScopedAStatus status = mModule->getTelephony(&telephony);
-            status.isOk() && telephony != nullptr) {
-        ITelephony::TelecomConfig inConfig{ .voiceVolume = Float{volume} }, outConfig;
-        RETURN_STATUS_IF_ERROR(
-                statusTFromBinderStatus(telephony->setTelecomConfig(inConfig, &outConfig)));
-        ALOGW_IF(outConfig.voiceVolume.has_value() && volume != outConfig.voiceVolume.value().value,
-                "%s: the resulting voice volume %f is not the same as requested %f",
-                __func__, outConfig.voiceVolume.value().value, volume);
-    }
-    return INVALID_OPERATION;
+    if (mTelephony == nullptr) return INVALID_OPERATION;
+    ITelephony::TelecomConfig inConfig{ .voiceVolume = Float{volume} }, outConfig;
+    RETURN_STATUS_IF_ERROR(
+            statusTFromBinderStatus(mTelephony->setTelecomConfig(inConfig, &outConfig)));
+    ALOGW_IF(outConfig.voiceVolume.has_value() && volume != outConfig.voiceVolume.value().value,
+            "%s: the resulting voice volume %f is not the same as requested %f",
+            __func__, outConfig.voiceVolume.value().value, volume);
+    return OK;
 }
 
 status_t DeviceHalAidl::setMasterVolume(float volume) {
@@ -204,10 +242,8 @@
     TIME_CHECK();
     if (!mModule) return NO_INIT;
     AudioMode audioMode = VALUE_OR_FATAL(::aidl::android::legacy2aidl_audio_mode_t_AudioMode(mode));
-    std::shared_ptr<ITelephony> telephony;
-    if (ndk::ScopedAStatus status = mModule->getTelephony(&telephony);
-            status.isOk() && telephony != nullptr) {
-        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(telephony->switchAudioMode(audioMode)));
+    if (mTelephony != nullptr) {
+        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mTelephony->switchAudioMode(audioMode)));
     }
     return statusTFromBinderStatus(mModule->updateAudioMode(audioMode));
 }
@@ -236,15 +272,32 @@
     return statusTFromBinderStatus(mModule->getMasterMute(state));
 }
 
-status_t DeviceHalAidl::setParameters(const String8& kvPairs __unused) {
-    TIME_CHECK();
+status_t DeviceHalAidl::setParameters(const String8& kvPairs) {
     if (!mModule) return NO_INIT;
-    ALOGE("%s not implemented yet", __func__);
+    AudioParameter parameters(kvPairs);
+    ALOGD("%s: parameters: \"%s\"", __func__, parameters.toString().c_str());
+
+    if (status_t status = filterAndUpdateBtA2dpParameters(parameters); status != OK) {
+        ALOGW("%s: filtering or updating BT A2DP parameters failed: %d", __func__, status);
+    }
+    if (status_t status = filterAndUpdateBtHfpParameters(parameters); status != OK) {
+        ALOGW("%s: filtering or updating BT HFP parameters failed: %d", __func__, status);
+    }
+    if (status_t status = filterAndUpdateBtLeParameters(parameters); status != OK) {
+        ALOGW("%s: filtering or updating BT LE parameters failed: %d", __func__, status);
+    }
+    if (status_t status = filterAndUpdateBtScoParameters(parameters); status != OK) {
+        ALOGW("%s: filtering or updating BT SCO parameters failed: %d", __func__, status);
+    }
+
+    ALOGW_IF(parameters.size() != 0, "%s: unknown parameters, ignored: \"%s\"",
+            __func__, parameters.toString().c_str());
     return OK;
 }
 
 status_t DeviceHalAidl::getParameters(const String8& keys __unused, String8 *values) {
     TIME_CHECK();
+    // FIXME(b/278976019): Support keyReconfigA2dpSupported via vendor plugin
     values->clear();
     if (!mModule) return NO_INIT;
     ALOGE("%s not implemented yet", __func__);
@@ -1059,6 +1112,150 @@
     return OK;
 }
 
+status_t DeviceHalAidl::filterAndUpdateBtA2dpParameters(AudioParameter &parameters) {
+    TIME_CHECK();
+    std::optional<bool> a2dpEnabled;
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<String8>(
+                    parameters, String8(AudioParameter::keyBtA2dpSuspended),
+                    [&a2dpEnabled](const String8& trueOrFalse) {
+                        if (trueOrFalse == AudioParameter::valueTrue) {
+                            a2dpEnabled = false;  // 'suspended' == true
+                            return OK;
+                        } else if (trueOrFalse == AudioParameter::valueFalse) {
+                            a2dpEnabled = true;  // 'suspended' == false
+                            return OK;
+                        }
+                        ALOGE("setParameters: parameter key \"%s\" has invalid value \"%s\"",
+                                AudioParameter::keyBtA2dpSuspended, trueOrFalse.c_str());
+                        return BAD_VALUE;
+                    }));
+    // FIXME(b/278976019): Support keyReconfigA2dp via vendor plugin
+    if (mBluetoothA2dp != nullptr && a2dpEnabled.has_value()) {
+        return statusTFromBinderStatus(mBluetoothA2dp->setEnabled(a2dpEnabled.value()));
+    }
+    return OK;
+}
+
+status_t DeviceHalAidl::filterAndUpdateBtHfpParameters(AudioParameter &parameters) {
+    TIME_CHECK();
+    IBluetooth::HfpConfig hfpConfig;
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<String8>(
+                    parameters, String8(AudioParameter::keyBtHfpEnable),
+                    [&hfpConfig](const String8& trueOrFalse) {
+                        if (trueOrFalse == AudioParameter::valueTrue) {
+                            hfpConfig.isEnabled = Boolean{ .value = true };
+                            return OK;
+                        } else if (trueOrFalse == AudioParameter::valueFalse) {
+                            hfpConfig.isEnabled = Boolean{ .value = false };
+                            return OK;
+                        }
+                        ALOGE("setParameters: parameter key \"%s\" has invalid value \"%s\"",
+                                AudioParameter::keyBtHfpEnable, trueOrFalse.c_str());
+                        return BAD_VALUE;
+                    }));
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<int>(
+                    parameters, String8(AudioParameter::keyBtHfpSamplingRate),
+                    [&hfpConfig](int sampleRate) {
+                        return sampleRate > 0 ?
+                                hfpConfig.sampleRate = Int{ .value = sampleRate }, OK : BAD_VALUE;
+                    }));
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<int>(
+                    parameters, String8(AudioParameter::keyBtHfpVolume),
+                    [&hfpConfig](int volume0to15) {
+                        if (volume0to15 >= 0 && volume0to15 <= 15) {
+                            hfpConfig.volume = Float{ .value = volume0to15 / 15.0f };
+                            return OK;
+                        }
+                        return BAD_VALUE;
+                    }));
+    if (mBluetooth != nullptr && hfpConfig != IBluetooth::HfpConfig{}) {
+        IBluetooth::HfpConfig newHfpConfig;
+        return statusTFromBinderStatus(mBluetooth->setHfpConfig(hfpConfig, &newHfpConfig));
+    }
+    return OK;
+}
+
+status_t DeviceHalAidl::filterAndUpdateBtLeParameters(AudioParameter &parameters) {
+    TIME_CHECK();
+    std::optional<bool> leEnabled;
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<String8>(
+                    parameters, String8(AudioParameter::keyBtLeSuspended),
+                    [&leEnabled](const String8& trueOrFalse) {
+                        if (trueOrFalse == AudioParameter::valueTrue) {
+                            leEnabled = false;  // 'suspended' == true
+                            return OK;
+                        } else if (trueOrFalse == AudioParameter::valueFalse) {
+                            leEnabled = true;  // 'suspended' == false
+                            return OK;
+                        }
+                        ALOGE("setParameters: parameter key \"%s\" has invalid value \"%s\"",
+                                AudioParameter::keyBtLeSuspended, trueOrFalse.c_str());
+                        return BAD_VALUE;
+                    }));
+    if (mBluetoothLe != nullptr && leEnabled.has_value()) {
+        return statusTFromBinderStatus(mBluetoothLe->setEnabled(leEnabled.value()));
+    }
+    return OK;
+}
+
+status_t DeviceHalAidl::filterAndUpdateBtScoParameters(AudioParameter &parameters) {
+    TIME_CHECK();
+    IBluetooth::ScoConfig scoConfig;
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<String8>(
+                    parameters, String8(AudioParameter::keyBtSco),
+                    [&scoConfig](const String8& onOrOff) {
+                        if (onOrOff == AudioParameter::valueOn) {
+                            scoConfig.isEnabled = Boolean{ .value = true };
+                            return OK;
+                        } else if (onOrOff == AudioParameter::valueOff) {
+                            scoConfig.isEnabled = Boolean{ .value = false };
+                            return OK;
+                        }
+                        ALOGE("setParameters: parameter key \"%s\" has invalid value \"%s\"",
+                                AudioParameter::keyBtSco, onOrOff.c_str());
+                        return BAD_VALUE;
+                    }));
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<String8>(
+                    parameters, String8(AudioParameter::keyBtScoHeadsetName),
+                    [&scoConfig](const String8& name) {
+                        scoConfig.debugName = name;
+                        return OK;
+                    }));
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<String8>(
+                    parameters, String8(AudioParameter::keyBtNrec),
+                    [&scoConfig](const String8& onOrOff) {
+                        if (onOrOff == AudioParameter::valueOn) {
+                            scoConfig.isNrecEnabled = Boolean{ .value = true };
+                            return OK;
+                        } else if (onOrOff == AudioParameter::valueOff) {
+                            scoConfig.isNrecEnabled = Boolean{ .value = false };
+                            return OK;
+                        }
+                        ALOGE("setParameters: parameter key \"%s\" has invalid value \"%s\"",
+                                AudioParameter::keyBtNrec, onOrOff.c_str());
+                        return BAD_VALUE;
+                    }));
+    (void)VALUE_OR_RETURN_STATUS(filterOutAndProcessParameter<String8>(
+                    parameters, String8(AudioParameter::keyBtScoWb),
+                    [&scoConfig](const String8& onOrOff) {
+                        if (onOrOff == AudioParameter::valueOn) {
+                            scoConfig.mode = IBluetooth::ScoConfig::Mode::SCO_WB;
+                            return OK;
+                        } else if (onOrOff == AudioParameter::valueOff) {
+                            scoConfig.mode = IBluetooth::ScoConfig::Mode::SCO;
+                            return OK;
+                        }
+                        ALOGE("setParameters: parameter key \"%s\" has invalid value \"%s\"",
+                                AudioParameter::keyBtScoWb, onOrOff.c_str());
+                        return BAD_VALUE;
+                    }));
+    if (mBluetooth != nullptr && scoConfig != IBluetooth::ScoConfig{}) {
+        IBluetooth::ScoConfig newScoConfig;
+        return statusTFromBinderStatus(mBluetooth->setScoConfig(scoConfig, &newScoConfig));
+    }
+    return OK;
+}
+
 status_t DeviceHalAidl::findOrCreatePatch(
         const AudioPatch& requestedPatch, AudioPatch* patch, bool* created) {
     std::set<int32_t> sourcePortConfigIds(requestedPatch.sourcePortConfigIds.begin(),
diff --git a/media/libaudiohal/impl/DeviceHalAidl.h b/media/libaudiohal/impl/DeviceHalAidl.h
index f2c64de..37d800b 100644
--- a/media/libaudiohal/impl/DeviceHalAidl.h
+++ b/media/libaudiohal/impl/DeviceHalAidl.h
@@ -73,6 +73,8 @@
 
     status_t getAudioRoutes(std::vector<media::AudioRoute> *routes) override;
 
+    status_t getSupportedModes(std::vector<media::audio::common::AudioMode> *modes) override;
+
     // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t.
     status_t getSupportedDevices(uint32_t *devices) override;
 
@@ -201,8 +203,7 @@
     // Must not be constructed directly by clients.
     DeviceHalAidl(
             const std::string& instance,
-            const std::shared_ptr<::aidl::android::hardware::audio::core::IModule>& module)
-            : ConversionHelperAidl("DeviceHalAidl"), mInstance(instance), mModule(module) {}
+            const std::shared_ptr<::aidl::android::hardware::audio::core::IModule>& module);
 
     ~DeviceHalAidl() override = default;
 
@@ -213,6 +214,10 @@
     status_t createOrUpdatePortConfig(
             const ::aidl::android::media::audio::common::AudioPortConfig& requestedPortConfig,
             PortConfigs::iterator* result, bool *created);
+    status_t filterAndUpdateBtA2dpParameters(AudioParameter &parameters);
+    status_t filterAndUpdateBtHfpParameters(AudioParameter &parameters);
+    status_t filterAndUpdateBtLeParameters(AudioParameter &parameters);
+    status_t filterAndUpdateBtScoParameters(AudioParameter &parameters);
     status_t findOrCreatePatch(
         const std::set<int32_t>& sourcePortConfigIds,
         const std::set<int32_t>& sinkPortConfigIds,
@@ -286,6 +291,10 @@
 
     const std::string mInstance;
     const std::shared_ptr<::aidl::android::hardware::audio::core::IModule> mModule;
+    const std::shared_ptr<::aidl::android::hardware::audio::core::ITelephony> mTelephony;
+    const std::shared_ptr<::aidl::android::hardware::audio::core::IBluetooth> mBluetooth;
+    const std::shared_ptr<::aidl::android::hardware::audio::core::IBluetoothA2dp> mBluetoothA2dp;
+    const std::shared_ptr<::aidl::android::hardware::audio::core::IBluetoothLe> mBluetoothLe;
     std::shared_ptr<::aidl::android::hardware::audio::core::sounddose::ISoundDose>
         mSoundDose = nullptr;
     Ports mPorts;
diff --git a/media/libaudiohal/impl/DeviceHalHidl.cpp b/media/libaudiohal/impl/DeviceHalHidl.cpp
index d67cbdc..826461f 100644
--- a/media/libaudiohal/impl/DeviceHalHidl.cpp
+++ b/media/libaudiohal/impl/DeviceHalHidl.cpp
@@ -113,6 +113,11 @@
     return INVALID_OPERATION;
 }
 
+status_t DeviceHalHidl::getSupportedModes(
+        std::vector<media::audio::common::AudioMode> *modes __unused) {
+    return INVALID_OPERATION;
+}
+
 status_t DeviceHalHidl::getSupportedDevices(uint32_t*) {
     // Obsolete.
     return INVALID_OPERATION;
diff --git a/media/libaudiohal/impl/DeviceHalHidl.h b/media/libaudiohal/impl/DeviceHalHidl.h
index 7222f03..c5addde 100644
--- a/media/libaudiohal/impl/DeviceHalHidl.h
+++ b/media/libaudiohal/impl/DeviceHalHidl.h
@@ -33,6 +33,8 @@
 
     status_t getAudioRoutes(std::vector<media::AudioRoute> *routes) override;
 
+    status_t getSupportedModes(std::vector<media::audio::common::AudioMode> *modes) override;
+
     // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t.
     status_t getSupportedDevices(uint32_t *devices) override;
 
diff --git a/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp b/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp
index 0dcb8ee..7b9088e 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp
+++ b/media/libaudiohal/impl/EffectsFactoryHalAidl.cpp
@@ -40,6 +40,8 @@
 using ::aidl::android::hardware::audio::effect::Descriptor;
 using ::aidl::android::hardware::audio::effect::IFactory;
 using ::aidl::android::hardware::audio::effect::Processing;
+using ::aidl::android::media::audio::common::AudioSource;
+using ::aidl::android::media::audio::common::AudioStreamType;
 using ::aidl::android::media::audio::common::AudioUuid;
 using ::android::base::unexpected;
 using ::android::detail::AudioHalVersionInfo;
@@ -96,7 +98,13 @@
           return list;
       }()),
       mEffectCount(mNonProxyDescList.size() + mProxyDescList.size()),
-      mEffectProcessings(nullptr /* TODO: add AIDL implementation */) {
+      mAidlProcessings([this]() -> std::vector<Processing> {
+          std::vector<Processing> processings;
+          if (!mFactory || !mFactory->queryProcessing(std::nullopt, &processings).isOk()) {
+              ALOGE("%s queryProcessing failed", __func__);
+          }
+          return processings;
+      }()) {
     ALOG_ASSERT(mFactory != nullptr, "Provided IEffectsFactory service is NULL");
     ALOGI("%s with %zu nonProxyEffects and %zu proxyEffects", __func__, mNonProxyDescList.size(),
           mProxyDescList.size());
@@ -274,15 +282,79 @@
 }
 
 std::shared_ptr<const effectsConfig::Processings> EffectsFactoryHalAidl::getProcessings() const {
-    return mEffectProcessings;
+
+    auto getConfigEffectWithDescriptor =
+            [](const auto& desc) -> std::shared_ptr<const effectsConfig::Effect> {
+        effectsConfig::Effect effect = {.name = desc.common.name, .isProxy = false};
+        if (const auto uuid =
+                    ::aidl::android::aidl2legacy_AudioUuid_audio_uuid_t(desc.common.id.uuid);
+            uuid.ok()) {
+            static_cast<effectsConfig::EffectImpl>(effect).uuid = uuid.value();
+            return std::make_shared<const effectsConfig::Effect>(effect);
+        } else {
+            return nullptr;
+        }
+    };
+
+    auto getConfigProcessingWithAidlProcessing =
+            [&](const auto& aidlProcess, std::vector<effectsConfig::InputStream>& preprocess,
+                std::vector<effectsConfig::OutputStream>& postprocess) {
+                if (aidlProcess.type.getTag() == Processing::Type::streamType) {
+                    AudioStreamType aidlType =
+                            aidlProcess.type.template get<Processing::Type::streamType>();
+                    const auto type =
+                            ::aidl::android::aidl2legacy_AudioStreamType_audio_stream_type_t(
+                                    aidlType);
+                    if (!type.ok()) {
+                        return;
+                    }
+
+                    std::vector<std::shared_ptr<const effectsConfig::Effect>> effects;
+                    std::transform(aidlProcess.ids.begin(), aidlProcess.ids.end(),
+                                   std::back_inserter(effects), getConfigEffectWithDescriptor);
+                    effectsConfig::OutputStream stream = {.type = type.value(),
+                                                          .effects = std::move(effects)};
+                    postprocess.emplace_back(stream);
+                } else if (aidlProcess.type.getTag() == Processing::Type::source) {
+                    AudioSource aidlType =
+                            aidlProcess.type.template get<Processing::Type::source>();
+                    const auto type =
+                            ::aidl::android::aidl2legacy_AudioSource_audio_source_t(aidlType);
+                    if (!type.ok()) {
+                        return;
+                    }
+
+                    std::vector<std::shared_ptr<const effectsConfig::Effect>> effects;
+                    std::transform(aidlProcess.ids.begin(), aidlProcess.ids.end(),
+                                   std::back_inserter(effects), getConfigEffectWithDescriptor);
+                    effectsConfig::InputStream stream = {.type = type.value(),
+                                                         .effects = std::move(effects)};
+                    preprocess.emplace_back(stream);
+                }
+            };
+
+    static std::shared_ptr<const effectsConfig::Processings> processings(
+            [&]() -> std::shared_ptr<const effectsConfig::Processings> {
+                std::vector<effectsConfig::InputStream> preprocess;
+                std::vector<effectsConfig::OutputStream> postprocess;
+                for (const auto& processing : mAidlProcessings) {
+                    getConfigProcessingWithAidlProcessing(processing, preprocess, postprocess);
+                }
+
+                if (0 == preprocess.size() && 0 == postprocess.size()) {
+                    return nullptr;
+                }
+
+                return std::make_shared<const effectsConfig::Processings>(
+                        effectsConfig::Processings({.preprocess = std::move(preprocess),
+                                                    .postprocess = std::move(postprocess)}));
+            }());
+
+    return processings;
 }
 
+// Return 0 for AIDL, as the AIDL interface is not aware of the configuration file.
 ::android::error::Result<size_t> EffectsFactoryHalAidl::getSkippedElements() const {
-    if (!mEffectProcessings) {
-        return ::android::base::unexpected(BAD_VALUE);
-    }
-
-    // Only return 0 for AIDL, because the AIDL interface doesn't aware of configuration file
     return 0;
 }
 
diff --git a/media/libaudiohal/impl/EffectsFactoryHalAidl.h b/media/libaudiohal/impl/EffectsFactoryHalAidl.h
index 70a7012..39beea2 100644
--- a/media/libaudiohal/impl/EffectsFactoryHalAidl.h
+++ b/media/libaudiohal/impl/EffectsFactoryHalAidl.h
@@ -21,6 +21,7 @@
 #include <mutex>
 
 #include <aidl/android/hardware/audio/effect/IFactory.h>
+#include <aidl/android/hardware/audio/effect/Processing.h>
 #include <android-base/thread_annotations.h>
 #include <media/audiohal/EffectsFactoryHalInterface.h>
 #include <system/thread_defs.h>
@@ -82,7 +83,7 @@
     // total number of effects including proxy effects
     const size_t mEffectCount;
     // Query result of pre and post processing from effect factory
-    const std::shared_ptr<const effectsConfig::Processings> mEffectProcessings;
+    const std::vector<Processing> mAidlProcessings;
 
     std::mutex mLock;
     uint64_t mEffectIdCounter GUARDED_BY(mLock) = 0;  // Align with HIDL (0 is INVALID_ID)
diff --git a/media/libaudiohal/impl/StreamHalAidl.cpp b/media/libaudiohal/impl/StreamHalAidl.cpp
index eccdfe8..d1044dc 100644
--- a/media/libaudiohal/impl/StreamHalAidl.cpp
+++ b/media/libaudiohal/impl/StreamHalAidl.cpp
@@ -122,30 +122,6 @@
     return OK;
 }
 
-namespace {
-
-// 'action' must accept a value of type 'T' and return 'status_t'.
-// The function returns 'true' if the parameter was found, and the action has succeeded.
-// The function returns 'false' if the parameter was not found.
-// Any errors get propagated, if there are errors it means the parameter was found.
-template<typename T, typename F>
-error::Result<bool> filterOutAndProcessParameter(
-        AudioParameter& parameters, const String8& key, const F& action) {
-    if (parameters.containsKey(key)) {
-        T value;
-        status_t status = parameters.get(key, value);
-        if (status == OK) {
-            parameters.remove(key);
-            status = action(value);
-            if (status == OK) return true;
-        }
-        return base::unexpected(status);
-    }
-    return false;
-}
-
-}  // namespace
-
 status_t StreamHalAidl::setParameters(const String8& kvPairs) {
     TIME_CHECK();
     if (!mStream) return NO_INIT;
@@ -579,10 +555,10 @@
     if (!mStream) return NO_INIT;
 
     AudioParameter parameters(kvPairs);
-    ALOGD("%s parameters: %s", __func__, parameters.toString().c_str());
+    ALOGD("%s: parameters: \"%s\"", __func__, parameters.toString().c_str());
 
     if (status_t status = filterAndUpdateOffloadMetadata(parameters); status != OK) {
-        ALOGW("%s filtering or updating offload metadata failed: %d", __func__, status);
+        ALOGW("%s: filtering or updating offload metadata failed: %d", __func__, status);
     }
 
     return StreamHalAidl::setParameters(parameters.toString());
diff --git a/media/libaudiohal/include/media/audiohal/AudioHalVersionInfo.h b/media/libaudiohal/include/media/audiohal/AudioHalVersionInfo.h
index 6e09463..2323ed6 100644
--- a/media/libaudiohal/include/media/audiohal/AudioHalVersionInfo.h
+++ b/media/libaudiohal/include/media/audiohal/AudioHalVersionInfo.h
@@ -30,6 +30,8 @@
         minor = halMinor;
     }
 
+    bool isHidl() const { return type == Type::HIDL; }
+
     Type getType() const { return type; }
 
     int getMajorVersion() const { return major; }
diff --git a/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h b/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
index 45f2922..71e5e7a 100644
--- a/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
+++ b/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
@@ -19,6 +19,7 @@
 
 #include <android/media/audio/common/AudioMMapPolicyInfo.h>
 #include <android/media/audio/common/AudioMMapPolicyType.h>
+#include <android/media/audio/common/AudioMode.h>
 #include <android/media/audio/common/AudioPort.h>
 #include <android/media/AudioRoute.h>
 #include <error/Result.h>
@@ -44,6 +45,8 @@
 
     virtual status_t getAudioRoutes(std::vector<media::AudioRoute> *routes) = 0;
 
+    virtual status_t getSupportedModes(std::vector<media::audio::common::AudioMode> *modes) = 0;
+
     // Sets the value of 'devices' to a bitmask of 1 or more values of audio_devices_t.
     virtual status_t getSupportedDevices(uint32_t *devices) = 0;
 
diff --git a/media/libaudiohal/tests/EffectsFactoryHalInterface_test.cpp b/media/libaudiohal/tests/EffectsFactoryHalInterface_test.cpp
index c076ccc..63f895f 100644
--- a/media/libaudiohal/tests/EffectsFactoryHalInterface_test.cpp
+++ b/media/libaudiohal/tests/EffectsFactoryHalInterface_test.cpp
@@ -15,6 +15,7 @@
  */
 
 //#define LOG_NDEBUG 0
+#include <algorithm>
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
@@ -92,6 +93,47 @@
     }
 }
 
+TEST(libAudioHalTest, getProcessings) {
+    auto factory = EffectsFactoryHalInterface::create();
+    ASSERT_NE(nullptr, factory);
+
+    const auto &processings = factory->getProcessings();
+    if (processings) {
+        EXPECT_NE(0UL, processings->preprocess.size() + processings->postprocess.size() +
+                               processings->deviceprocess.size());
+
+        auto processingChecker = [](const auto& processings) {
+            if (processings.size() != 0) {
+                // any process need at least 1 effect inside
+                std::for_each(processings.begin(), processings.end(), [](const auto& process) {
+                    EXPECT_NE(0ul, process.effects.size());
+                    // any effect should have a valid name string, and not proxy
+                    for (const auto& effect : process.effects) {
+                        SCOPED_TRACE("Effect: {" +
+                                     (effect == nullptr
+                                              ? "NULL}"
+                                              : ("{name: " + effect->name + ", isproxy: " +
+                                                 (effect->isProxy ? "true" : "false") + ", sw: " +
+                                                 (effect->libSw ? "non-null" : "null") + ", hw: " +
+                                                 (effect->libHw ? "non-null" : "null") + "}")));
+                        EXPECT_NE(nullptr, effect);
+                        EXPECT_NE("", effect->name);
+                        EXPECT_EQ(false, effect->isProxy);
+                        EXPECT_EQ(nullptr, effect->libSw);
+                        EXPECT_EQ(nullptr, effect->libHw);
+                    }
+                });
+            }
+        };
+
+        processingChecker(processings->preprocess);
+        processingChecker(processings->postprocess);
+        processingChecker(processings->deviceprocess);
+    } else {
+        GTEST_SKIP() << "no processing found, skipping the test";
+    }
+}
+
 TEST(libAudioHalTest, getHalVersion) {
     auto factory = EffectsFactoryHalInterface::create();
     ASSERT_NE(nullptr, factory);
diff --git a/media/libaudioprocessing/AudioMixerBase.cpp b/media/libaudioprocessing/AudioMixerBase.cpp
index 427bd55..3d11d92 100644
--- a/media/libaudioprocessing/AudioMixerBase.cpp
+++ b/media/libaudioprocessing/AudioMixerBase.cpp
@@ -471,9 +471,9 @@
                         &track->mVolume[param - VOLUME0],
                         &track->mPrevVolume[param - VOLUME0],
                         &track->mVolumeInc[param - VOLUME0])) {
-                    ALOGV("setParameter(%s, VOLUME%d: %04x)",
-                            target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
-                                    track->volume[param - VOLUME0]);
+                    ALOGV("setParameter(%s, VOLUME%d: %f)",
+                          target == VOLUME ? "VOLUME" : "RAMP_VOLUME", param - VOLUME0,
+                          track->mVolume[param - VOLUME0]);
                     invalidate();
                 }
             } else {
@@ -648,7 +648,7 @@
 
         if (t->volumeInc[0]|t->volumeInc[1]) {
             volumeRamp = true;
-        } else if (!t->doesResample() && t->volumeRL == 0) {
+        } else if (!t->doesResample() && t->isVolumeMuted()) {
             n |= NEEDS_MUTE;
         }
         t->needs = n;
@@ -748,7 +748,7 @@
 
         for (const int name : mEnabled) {
             const std::shared_ptr<TrackBase> &t = mTracks[name];
-            if (!t->doesResample() && t->volumeRL == 0) {
+            if (!t->doesResample() && t->isVolumeMuted()) {
                 t->needs |= NEEDS_MUTE;
                 t->hook = &TrackBase::track__nop;
             } else {
diff --git a/media/libaudioprocessing/include/media/AudioMixerBase.h b/media/libaudioprocessing/include/media/AudioMixerBase.h
index 4bd85d8..b44ff20 100644
--- a/media/libaudioprocessing/include/media/AudioMixerBase.h
+++ b/media/libaudioprocessing/include/media/AudioMixerBase.h
@@ -299,6 +299,16 @@
 
         uint32_t       mInputFrameSize; // The track input frame size, used for tee buffer
 
+        // consider volume muted only if all channel volume (floating point) is 0.f
+        inline bool isVolumeMuted() const {
+            for (const auto volume : mVolume) {
+                if (volume != 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
       protected:
 
         // hooks
diff --git a/media/libaudiousecasevalidation/UsecaseValidator.cpp b/media/libaudiousecasevalidation/UsecaseValidator.cpp
index d62df3a..bf532de 100644
--- a/media/libaudiousecasevalidation/UsecaseValidator.cpp
+++ b/media/libaudiousecasevalidation/UsecaseValidator.cpp
@@ -142,6 +142,9 @@
 
     bool areFlagsValid(audio_flags_mask_t flags) {
         ALOGV("areFlagsValid flags: %#x", flags);
+        if ((flags & (AUDIO_FLAG_SCO|AUDIO_FLAG_AUDIBILITY_ENFORCED|AUDIO_FLAG_BEACON)) != 0) {
+            return false;
+        }
         if ((flags & AUDIO_FLAG_LOW_LATENCY) != 0) {
             return true;
         }
diff --git a/media/libeffects/config/include/media/EffectsConfig.h b/media/libeffects/config/include/media/EffectsConfig.h
index a9730e5..09a060d 100644
--- a/media/libeffects/config/include/media/EffectsConfig.h
+++ b/media/libeffects/config/include/media/EffectsConfig.h
@@ -49,26 +49,27 @@
     std::string name;
     std::string path;
 };
-using Libraries = std::vector<Library>;
+using Libraries = std::vector<std::shared_ptr<const Library>>;
 
 struct EffectImpl {
-    Library* library; //< Only valid as long as the associated library vector is unmodified
+    //< Only valid as long as the associated library vector is unmodified
+    std::shared_ptr<const Library> library;
     effect_uuid_t uuid;
 };
 
 struct Effect : public EffectImpl {
     std::string name;
     bool isProxy;
-    EffectImpl libSw; //< Only valid if isProxy
-    EffectImpl libHw; //< Only valid if isProxy
+    std::shared_ptr<EffectImpl> libSw; //< Only valid if isProxy
+    std::shared_ptr<EffectImpl> libHw; //< Only valid if isProxy
 };
 
-using Effects = std::vector<Effect>;
+using Effects = std::vector<std::shared_ptr<const Effect>>;
 
 template <class Type>
 struct Stream {
     Type type;
-    std::vector<std::reference_wrapper<Effect>> effects;
+    Effects effects;
 };
 using OutputStream = Stream<audio_stream_type_t>;
 using InputStream = Stream<audio_source_t>;
diff --git a/media/libeffects/config/src/EffectsConfig.cpp b/media/libeffects/config/src/EffectsConfig.cpp
index 3096659..2ff057e 100644
--- a/media/libeffects/config/src/EffectsConfig.cpp
+++ b/media/libeffects/config/src/EffectsConfig.cpp
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <cstdint>
 #include <functional>
+#include <memory>
 #include <string>
 #include <unistd.h>
 
@@ -149,7 +150,10 @@
         ALOGE("library must have a name and a path: %s", dump(xmlLibrary));
         return false;
     }
-    libraries->push_back({name, path});
+
+    // need this temp variable because `struct Library` doesn't have a constructor
+    Library lib({.name = name, .path = path});
+    libraries->push_back(std::make_shared<const Library>(lib));
     return true;
 }
 
@@ -157,10 +161,10 @@
  * @return nullptr if not found, the element address if found.
  */
 template <class T>
-T* findByName(const char* name, std::vector<T>& collection) {
+T findByName(const char* name, std::vector<T>& collection) {
     auto it = find_if(begin(collection), end(collection),
-                         [name] (auto& item) { return item.name == name; });
-    return it != end(collection) ? &*it : nullptr;
+                      [name](auto& item) { return item && item->name == name; });
+    return it != end(collection) ? *it : nullptr;
 }
 
 /** Parse an effect from an xml element describing it.
@@ -187,7 +191,7 @@
         }
 
         // Convert library name to a pointer to the previously loaded library
-        auto* library = findByName(libraryName, libraries);
+        auto library = findByName(libraryName, libraries);
         if (library == nullptr) {
             ALOGE("Could not find library referenced in: %s", dump(xmlImpl));
             return false;
@@ -211,20 +215,25 @@
         effect.isProxy = true;
 
         // Function to parse libhw and libsw
-        auto parseProxy = [&xmlEffect, &parseImpl](const char* tag, EffectImpl& proxyLib) {
+        auto parseProxy = [&xmlEffect, &parseImpl](const char* tag,
+                                                   const std::shared_ptr<EffectImpl>& proxyLib) {
             auto* xmlProxyLib = xmlEffect.FirstChildElement(tag);
             if (xmlProxyLib == nullptr) {
                 ALOGE("effectProxy must contain a <%s>: %s", tag, dump(xmlEffect));
                 return false;
             }
-            return parseImpl(*xmlProxyLib, proxyLib);
+            return parseImpl(*xmlProxyLib, *proxyLib);
         };
+        effect.libSw = std::make_shared<EffectImpl>();
+        effect.libHw = std::make_shared<EffectImpl>();
         if (!parseProxy("libhw", effect.libHw) || !parseProxy("libsw", effect.libSw)) {
+            effect.libSw.reset();
+            effect.libHw.reset();
             return false;
         }
     }
 
-    effects->push_back(std::move(effect));
+    effects->push_back(std::make_shared<const Effect>(effect));
     return true;
 }
 
@@ -250,12 +259,12 @@
             ALOGE("<stream|device>/apply must have reference an effect: %s", dump(xmlApply));
             return false;
         }
-        auto* effect = findByName(effectName, effects);
+        auto effect = findByName(effectName, effects);
         if (effect == nullptr) {
             ALOGE("Could not find effect referenced in: %s", dump(xmlApply));
             return false;
         }
-        stream.effects.emplace_back(*effect);
+        stream.effects.emplace_back(effect);
     }
     streams->push_back(std::move(stream));
     return true;
diff --git a/media/libeffects/factory/EffectsXmlConfigLoader.cpp b/media/libeffects/factory/EffectsXmlConfigLoader.cpp
index 30a9007..9bff136 100644
--- a/media/libeffects/factory/EffectsXmlConfigLoader.cpp
+++ b/media/libeffects/factory/EffectsXmlConfigLoader.cpp
@@ -64,7 +64,7 @@
 
     std::string absolutePath;
     if (!resolveLibrary(relativePath, &absolutePath)) {
-        ALOGE("Could not find library in effect directories: %s", relativePath);
+        ALOGE("%s Could not find library in effect directories: %s", __func__, relativePath);
         libEntry->path = strdup(relativePath);
         return false;
     }
@@ -75,20 +75,20 @@
     std::unique_ptr<void, decltype(dlclose)*> libHandle(dlopen(path, RTLD_NOW),
                                                        dlclose);
     if (libHandle == nullptr) {
-        ALOGE("Could not dlopen library %s: %s", path, dlerror());
+        ALOGE("%s Could not dlopen library %s: %s", __func__, path, dlerror());
         return false;
     }
 
     auto* description = static_cast<audio_effect_library_t*>(
           dlsym(libHandle.get(), AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR));
     if (description == nullptr) {
-        ALOGE("Invalid effect library, failed not find symbol '%s' in %s: %s",
+        ALOGE("%s Invalid effect library, failed not find symbol '%s' in %s: %s", __func__,
               AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR, path, dlerror());
         return false;
     }
 
     if (description->tag != AUDIO_EFFECT_LIBRARY_TAG) {
-        ALOGE("Bad tag %#08x in description structure, expected %#08x for library %s",
+        ALOGE("%s Bad tag %#08x in description structure, expected %#08x for library %s", __func__,
               description->tag, AUDIO_EFFECT_LIBRARY_TAG, path);
         return false;
     }
@@ -96,8 +96,8 @@
     uint32_t majorVersion = EFFECT_API_VERSION_MAJOR(description->version);
     uint32_t expectedMajorVersion = EFFECT_API_VERSION_MAJOR(EFFECT_LIBRARY_API_VERSION_CURRENT);
     if (majorVersion != expectedMajorVersion) {
-        ALOGE("Unsupported major version %#08x, expected %#08x for library %s",
-              majorVersion, expectedMajorVersion, path);
+        ALOGE("%s Unsupported major version %#08x, expected %#08x for library %s",
+              __func__, majorVersion, expectedMajorVersion, path);
         return false;
     }
 
@@ -155,14 +155,13 @@
 {
     size_t nbSkippedElement = 0;
     for (auto& library : libs) {
-
         // Construct a lib entry
         auto libEntry = makeUniqueC<lib_entry_t>();
-        libEntry->name = strdup(library.name.c_str());
+        libEntry->name = strdup(library->name.c_str());
         libEntry->effects = nullptr;
         pthread_mutex_init(&libEntry->lock, nullptr);
 
-        if (!loadLibrary(library.path.c_str(), libEntry.get())) {
+        if (!loadLibrary(library->path.c_str(), libEntry.get())) {
             // Register library load failure
             listPush(std::move(libEntry), libFailedList);
             ++nbSkippedElement;
@@ -209,24 +208,24 @@
     UniqueCPtr<effect_descriptor_t> effectDesc;
 };
 
-LoadEffectResult loadEffect(const EffectImpl& effect, const std::string& name,
-                            list_elem_t* libList) {
+LoadEffectResult loadEffect(const std::shared_ptr<const EffectImpl>& effect,
+                            const std::string& name, list_elem_t* libList) {
     LoadEffectResult result;
 
     // Find the effect library
-    result.lib = findLibrary(effect.library->name.c_str(), libList);
+    result.lib = findLibrary(effect->library->name.c_str(), libList);
     if (result.lib == nullptr) {
-        ALOGE("Could not find library %s to load effect %s",
-              effect.library->name.c_str(), name.c_str());
+        ALOGE("%s Could not find library %s to load effect %s",
+              __func__, effect->library->name.c_str(), name.c_str());
         return result;
     }
 
     result.effectDesc = makeUniqueC<effect_descriptor_t>();
 
     // Get the effect descriptor
-    if (result.lib->desc->get_descriptor(&effect.uuid, result.effectDesc.get()) != 0) {
+    if (result.lib->desc->get_descriptor(&effect->uuid, result.effectDesc.get()) != 0) {
         ALOGE("Error querying effect %s on lib %s",
-              uuidToString(effect.uuid), result.lib->name);
+              uuidToString(effect->uuid), result.lib->name);
         result.effectDesc.reset();
         return result;
     }
@@ -241,14 +240,15 @@
     // Check effect is supported
     uint32_t expectedMajorVersion = EFFECT_API_VERSION_MAJOR(EFFECT_CONTROL_API_VERSION);
     if (EFFECT_API_VERSION_MAJOR(result.effectDesc->apiVersion) != expectedMajorVersion) {
-        ALOGE("Bad API version %#08x for effect %s in lib %s, expected major %#08x",
+        ALOGE("%s Bad API version %#08x for effect %s in lib %s, expected major %#08x", __func__,
               result.effectDesc->apiVersion, name.c_str(), result.lib->name, expectedMajorVersion);
         return result;
     }
 
     lib_entry_t *_;
-    if (findEffect(nullptr, &effect.uuid, &_, nullptr) == 0) {
-        ALOGE("Effect %s uuid %s already exist", uuidToString(effect.uuid), name.c_str());
+    if (findEffect(nullptr, &effect->uuid, &_, nullptr) == 0) {
+        ALOGE("%s Effect %s uuid %s already exist", __func__, uuidToString(effect->uuid),
+              name.c_str());
         return result;
     }
 
@@ -261,8 +261,11 @@
     size_t nbSkippedElement = 0;
 
     for (auto& effect : effects) {
+        if (!effect) {
+            continue;
+        }
 
-        auto effectLoadResult = loadEffect(effect, effect.name, libList);
+        auto effectLoadResult = loadEffect(effect, effect->name, libList);
         if (!effectLoadResult.success) {
             if (effectLoadResult.effectDesc != nullptr) {
                 listPush(std::move(effectLoadResult.effectDesc), skippedEffects);
@@ -271,9 +274,9 @@
             continue;
         }
 
-        if (effect.isProxy) {
-            auto swEffectLoadResult = loadEffect(effect.libSw, effect.name + " libsw", libList);
-            auto hwEffectLoadResult = loadEffect(effect.libHw, effect.name + " libhw", libList);
+        if (effect->isProxy) {
+            auto swEffectLoadResult = loadEffect(effect->libSw, effect->name + " libsw", libList);
+            auto hwEffectLoadResult = loadEffect(effect->libHw, effect->name + " libhw", libList);
             if (!swEffectLoadResult.success || !hwEffectLoadResult.success) {
                 // Push the main effect in the skipped list even if only a subeffect is invalid
                 // as the main effect is not usable without its subeffects.
@@ -287,7 +290,7 @@
             // get_descriptor call, we replace it with the corresponding
             // sw effect descriptor, but keep the Proxy UUID
             *effectLoadResult.effectDesc = *swEffectLoadResult.effectDesc;
-            effectLoadResult.effectDesc->uuid = effect.uuid;
+            effectLoadResult.effectDesc->uuid = effect->uuid;
 
             effectLoadResult.effectDesc->flags |= EFFECT_FLAG_OFFLOAD_SUPPORTED;
 
@@ -326,8 +329,8 @@
                                loadEffects(result.parsedConfig->effects, gLibraryList,
                                            &gSkippedEffects, &gSubEffectList);
 
-    ALOGE_IF(result.nbSkippedElement != 0, "%zu errors during loading of configuration: %s",
-             result.nbSkippedElement,
+    ALOGE_IF(result.nbSkippedElement != 0, "%s %zu errors during loading of configuration: %s",
+             __func__, result.nbSkippedElement,
              result.configPath.empty() ? "No config file found" : result.configPath.c_str());
 
     return result.nbSkippedElement;
diff --git a/media/libmediahelper/Android.bp b/media/libmediahelper/Android.bp
index c66861b..649f813 100644
--- a/media/libmediahelper/Android.bp
+++ b/media/libmediahelper/Android.bp
@@ -49,8 +49,9 @@
         "liblog",
     ],
     header_libs: [
-        "libmedia_helper_headers",
         "libaudio_system_headers",
+        "libhardware_headers",
+        "libmedia_helper_headers",
     ],
     export_header_lib_headers: [
         "libmedia_helper_headers",
diff --git a/media/libmediahelper/AudioParameter.cpp b/media/libmediahelper/AudioParameter.cpp
index e25f9b7..a61a1bc 100644
--- a/media/libmediahelper/AudioParameter.cpp
+++ b/media/libmediahelper/AudioParameter.cpp
@@ -20,6 +20,7 @@
 #include <utils/Log.h>
 
 #include <media/AudioParameter.h>
+#include <hardware/audio.h>
 #include <system/audio.h>
 
 namespace android {
@@ -34,7 +35,13 @@
 const char * const AudioParameter::keyScreenState = AUDIO_PARAMETER_KEY_SCREEN_STATE;
 const char * const AudioParameter::keyClosing = AUDIO_PARAMETER_KEY_CLOSING;
 const char * const AudioParameter::keyExiting = AUDIO_PARAMETER_KEY_EXITING;
+const char * const AudioParameter::keyBtSco = AUDIO_PARAMETER_KEY_BT_SCO;
+const char * const AudioParameter::keyBtScoHeadsetName = AUDIO_PARAMETER_KEY_BT_SCO_HEADSET_NAME;
 const char * const AudioParameter::keyBtNrec = AUDIO_PARAMETER_KEY_BT_NREC;
+const char * const AudioParameter::keyBtScoWb = AUDIO_PARAMETER_KEY_BT_SCO_WB;
+const char * const AudioParameter::keyBtHfpEnable = AUDIO_PARAMETER_KEY_HFP_ENABLE;
+const char * const AudioParameter::keyBtHfpSamplingRate = AUDIO_PARAMETER_KEY_HFP_SET_SAMPLING_RATE;
+const char * const AudioParameter::keyBtHfpVolume = AUDIO_PARAMETER_KEY_HFP_VOLUME;
 const char * const AudioParameter::keyHwAvSync = AUDIO_PARAMETER_HW_AV_SYNC;
 const char * const AudioParameter::keyPresentationId = AUDIO_PARAMETER_STREAM_PRESENTATION_ID;
 const char * const AudioParameter::keyProgramId = AUDIO_PARAMETER_STREAM_PROGRAM_ID;
@@ -52,9 +59,13 @@
         AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES;
 const char * const AudioParameter::valueOn = AUDIO_PARAMETER_VALUE_ON;
 const char * const AudioParameter::valueOff = AUDIO_PARAMETER_VALUE_OFF;
+const char * const AudioParameter::valueTrue = AUDIO_PARAMETER_VALUE_TRUE;
+const char * const AudioParameter::valueFalse = AUDIO_PARAMETER_VALUE_FALSE;
 const char * const AudioParameter::valueListSeparator = AUDIO_PARAMETER_VALUE_LIST_SEPARATOR;
+const char * const AudioParameter::keyBtA2dpSuspended = AUDIO_PARAMETER_KEY_BT_A2DP_SUSPENDED;
 const char * const AudioParameter::keyReconfigA2dp = AUDIO_PARAMETER_RECONFIG_A2DP;
 const char * const AudioParameter::keyReconfigA2dpSupported = AUDIO_PARAMETER_A2DP_RECONFIG_SUPPORTED;
+const char * const AudioParameter::keyBtLeSuspended = AUDIO_PARAMETER_KEY_BT_LE_SUSPENDED;
 // const char * const AudioParameter::keyDeviceSupportedEncapsulationModes =
 //        AUDIO_PARAMETER_DEVICE_SUP_ENCAPSULATION_MODES;
 // const char * const AudioParameter::keyDeviceSupportedEncapsulationMetadataTypes =
diff --git a/media/libmediahelper/include/media/AudioParameter.h b/media/libmediahelper/include/media/AudioParameter.h
index 6c34a4f..70f8af3 100644
--- a/media/libmediahelper/include/media/AudioParameter.h
+++ b/media/libmediahelper/include/media/AudioParameter.h
@@ -55,11 +55,22 @@
     static const char * const keyClosing;
     static const char * const keyExiting;
 
+    //  keyBtSco: Whether BT SCO is 'on' or 'off'
+    //  keyBtScoHeadsetName: BT SCO headset name (for debugging)
     //  keyBtNrec: BT SCO Noise Reduction + Echo Cancellation parameters
+    //  keyBtScoWb: BT SCO NR wideband mode
+    //  keyHfp...: Parameters of the Hands-Free Profile
+    static const char * const keyBtSco;
+    static const char * const keyBtScoHeadsetName;
+    static const char * const keyBtNrec;
+    static const char * const keyBtScoWb;
+    static const char * const keyBtHfpEnable;
+    static const char * const keyBtHfpSamplingRate;
+    static const char * const keyBtHfpVolume;
+
     //  keyHwAvSync: get HW synchronization source identifier from a device
     //  keyMonoOutput: Enable mono audio playback
     //  keyStreamHwAvSync: set HW synchronization source identifier on a stream
-    static const char * const keyBtNrec;
     static const char * const keyHwAvSync;
     static const char * const keyMonoOutput;
     static const char * const keyStreamHwAvSync;
@@ -90,13 +101,19 @@
 
     static const char * const valueOn;
     static const char * const valueOff;
+    static const char * const valueTrue;
+    static const char * const valueFalse;
 
     static const char * const valueListSeparator;
 
+    // keyBtA2dpSuspended: 'true' or 'false'
     // keyReconfigA2dp: Ask HwModule to reconfigure A2DP offloaded codec
     // keyReconfigA2dpSupported: Query if HwModule supports A2DP offload codec config
+    // keyBtLeSuspended: 'true' or 'false'
+    static const char * const keyBtA2dpSuspended;
     static const char * const keyReconfigA2dp;
     static const char * const keyReconfigA2dpSupported;
+    static const char * const keyBtLeSuspended;
 
     // For querying device supported encapsulation capabilities. All returned values are integer,
     // which are bit fields composed from using encapsulation capability values as position bits.
diff --git a/media/libmediametrics/MediaMetrics.cpp b/media/libmediametrics/MediaMetrics.cpp
index a3c2f1a..2240223 100644
--- a/media/libmediametrics/MediaMetrics.cpp
+++ b/media/libmediametrics/MediaMetrics.cpp
@@ -86,6 +86,11 @@
     if (item != NULL) item->setRate(attr, count, duration);
 }
 
+void mediametrics_setString(mediametrics_handle_t handle, attr_t attr,
+                                 const std::string &string) {
+    mediametrics_setCString(handle, attr, string.c_str());
+}
+
 void mediametrics_setCString(mediametrics_handle_t handle, attr_t attr,
                                  const char *value) {
     Item *item = (Item *) handle;
@@ -152,6 +157,14 @@
     return item->getRate(attr, count, duration, rate);
 }
 
+bool mediametrics_getString(mediametrics_handle_t handle, attr_t attr,
+                                 std::string *string) {
+    Item *item = (Item *) handle;
+    if (item == NULL) return false;
+
+    return item->getString(attr, string);
+}
+
 // NB: caller owns the string that comes back, is responsible for freeing it
 bool mediametrics_getCString(mediametrics_handle_t handle, attr_t attr,
                                  char **value) {
diff --git a/media/libmediametrics/include/media/MediaMetrics.h b/media/libmediametrics/include/media/MediaMetrics.h
index 76abe86..58612a3 100644
--- a/media/libmediametrics/include/media/MediaMetrics.h
+++ b/media/libmediametrics/include/media/MediaMetrics.h
@@ -50,7 +50,7 @@
 void mediametrics_setRate(mediametrics_handle_t handle, attr_t attr,
                           int64_t count, int64_t duration);
 void mediametrics_setCString(mediametrics_handle_t handle, attr_t attr,
-                            const char * value);
+                             const char * value);
 
 // fused get/add/set; if attr wasn't there, it's a simple set.
 // these do not provide atomicity or mutual exclusion, only simpler code sequences.
@@ -95,4 +95,11 @@
 
 __END_DECLS
 
+#ifdef __cplusplus
+#include <string>
+void mediametrics_setString(mediametrics_handle_t handle, attr_t attr,
+                            const std::string &value);
+bool mediametrics_getString(mediametrics_handle_t handle, attr_t attr, std::string *value);
+#endif // __cplusplus
+
 #endif
diff --git a/media/libmediametrics/include/media/MediaMetricsItem.h b/media/libmediametrics/include/media/MediaMetricsItem.h
index de56665..03834d4 100644
--- a/media/libmediametrics/include/media/MediaMetricsItem.h
+++ b/media/libmediametrics/include/media/MediaMetricsItem.h
@@ -1048,6 +1048,9 @@
         }
         return true;
     }
+    bool getString(const char *key, std::string *value) const {
+        return get(key, value);
+    }
     // Caller owns the returned string
     bool getCString(const char *key, char **value) const {
         std::string s;
@@ -1057,9 +1060,6 @@
         }
         return false;
     }
-    bool getString(const char *key, std::string *value) const {
-        return get(key, value);
-    }
 
     const Prop::Elem* get(const char *key) const {
         const Prop *prop = findProp(key);
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index ec79b99..0365085 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1986,6 +1986,10 @@
             format->setString("mime", MEDIA_MIMETYPE_VIDEO_DOLBY_VISION);
             break;
 
+        case VIDEO_ENCODER_AV1:
+            format->setString("mime", MEDIA_MIMETYPE_VIDEO_AV1);
+            break;
+
         default:
             CHECK(!"Should not be here, unsupported video encoding.");
             break;
diff --git a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
index 366956c..e8556dd 100644
--- a/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
+++ b/media/libmediaplayerservice/nuplayer/AWakeLock.cpp
@@ -28,11 +28,13 @@
 
 namespace android {
 
+const int32_t INVALID_DISPLAY_ID = -1;
+
 AWakeLock::AWakeLock() :
     mPowerManager(NULL),
     mWakeLockToken(NULL),
     mWakeLockCount(0),
-    mDeathRecipient(new PMDeathRecipient(this)) {}
+    mDeathRecipient(new PMDeathRecipient(this)){}
 
 AWakeLock::~AWakeLock() {
     if (mPowerManager != NULL) {
@@ -59,10 +61,15 @@
         if (mPowerManager != NULL) {
             sp<IBinder> binder = new BBinder();
             int64_t token = IPCThreadState::self()->clearCallingIdentity();
-            binder::Status status = mPowerManager->acquireWakeLockAsync(
-                    binder, POWERMANAGER_PARTIAL_WAKE_LOCK,
-                    String16("AWakeLock"), String16("media"),
-                    {} /* workSource */, {} /* historyTag */);
+            binder::Status status = mPowerManager->acquireWakeLock(
+                binder,
+                /*flags= */ POWERMANAGER_PARTIAL_WAKE_LOCK,
+                /*tag=*/ String16("AWakeLock"),
+                /*packageName=*/ String16("media"),
+                /*ws=*/ {},
+                /*historyTag=*/ {},
+                /*displayId=*/ INVALID_DISPLAY_ID,
+                /*callback=*/NULL);
             IPCThreadState::self()->restoreCallingIdentity(token);
             if (status.isOk()) {
                 mWakeLockToken = binder;
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index 569a25f..a26fcbe 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -270,6 +270,7 @@
         "SurfaceUtils.cpp",
         "ThrottledSource.cpp",
         "Utils.cpp",
+        "VideoRenderQualityTracker.cpp",
         "VideoFrameSchedulerBase.cpp",
         "VideoFrameScheduler.cpp",
     ],
@@ -313,6 +314,7 @@
         "framework-permission-aidl-cpp",
         "libaudioclient_aidl_conversion",
         "packagemanager_aidl-cpp",
+        "server_configurable_flags",
     ],
 
     static_libs: [
diff --git a/media/libstagefright/FrameCaptureLayer.cpp b/media/libstagefright/FrameCaptureLayer.cpp
index d2cfd41..4e71943 100644
--- a/media/libstagefright/FrameCaptureLayer.cpp
+++ b/media/libstagefright/FrameCaptureLayer.cpp
@@ -64,14 +64,6 @@
     return updatedDataspace;
 }
 
-bool isHdrY410(const BufferItem &bi) {
-    ui::Dataspace dataspace = translateDataspace(static_cast<ui::Dataspace>(bi.mDataSpace));
-    // pixel format is HDR Y410 masquerading as RGBA_1010102
-    return ((dataspace == ui::Dataspace::BT2020_ITU_PQ ||
-            dataspace == ui::Dataspace::BT2020_ITU_HLG) &&
-            bi.mGraphicBuffer->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102);
-}
-
 struct FrameCaptureLayer::BufferLayer : public FrameCaptureProcessor::Layer {
     BufferLayer(const BufferItem &bi) : mBufferItem(bi) {}
     void getLayerSettings(
@@ -95,7 +87,6 @@
     layerSettings->source.buffer.fence = mBufferItem.mFence;
     layerSettings->source.buffer.textureName = textureName;
     layerSettings->source.buffer.usePremultipliedAlpha = false;
-    layerSettings->source.buffer.isY410BT2020 = isHdrY410(mBufferItem);
     bool hasSmpte2086 = mBufferItem.mHdrMetadata.validTypes & HdrMetadata::SMPTE2086;
     bool hasCta861_3 = mBufferItem.mHdrMetadata.validTypes & HdrMetadata::CTA861_3;
     layerSettings->source.buffer.maxMasteringLuminance = hasSmpte2086
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index c93d033..b84dc27 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -3581,7 +3581,7 @@
                             (const uint8_t *)buffer->data()
                                 + buffer->range_offset(),
                             buffer->range_length());
-                } else if (mIsMPEG4) {
+                } else if (mIsMPEG4 || mIsAv1) {
                     err = copyCodecSpecificData((const uint8_t *)buffer->data() + buffer->range_offset(),
                             buffer->range_length());
                 }
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 080c3d0..e0ebc11 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -19,19 +19,18 @@
 #define LOG_TAG "MediaCodec"
 #include <utils/Log.h>
 
-#include <set>
-#include <random>
-#include <stdlib.h>
-
-#include <inttypes.h>
-#include <stdlib.h>
 #include <dlfcn.h>
+#include <inttypes.h>
+#include <random>
+#include <set>
+#include <stdlib.h>
+#include <string>
 
 #include <C2Buffer.h>
 
 #include "include/SoftwareRenderer.h"
-#include "PlaybackDurationAccumulator.h"
 
+#include <android/api-level.h>
 #include <android/binder_manager.h>
 #include <android/content/pm/IPackageManagerNative.h>
 #include <android/hardware/cas/native/1.0/IDescrambler.h>
@@ -83,6 +82,7 @@
 #include <media/stagefright/SurfaceUtils.h>
 #include <nativeloader/dlext_namespaces.h>
 #include <private/android_filesystem_config.h>
+#include <server_configurable_flags/get_flags.h>
 #include <utils/Singleton.h>
 
 namespace android {
@@ -92,6 +92,8 @@
 using aidl::android::media::IResourceManagerClient;
 using aidl::android::media::IResourceManagerService;
 using aidl::android::media::ClientInfoParcel;
+using FreezeEvent = VideoRenderQualityTracker::FreezeEvent;
+using JudderEvent = VideoRenderQualityTracker::JudderEvent;
 
 // key for media statistics
 static const char *kCodecKeyName = "codec";
@@ -109,7 +111,9 @@
 static const char *kCodecModeImage = "image";
 static const char *kCodecModeUnknown = "unknown";
 static const char *kCodecEncoder = "android.media.mediacodec.encoder"; /* 0,1 */
+static const char *kCodecHardware = "android.media.mediacodec.hardware"; /* 0,1 */
 static const char *kCodecSecure = "android.media.mediacodec.secure";   /* 0, 1 */
+static const char *kCodecTunneled = "android.media.mediacodec.tunneled"; /* 0,1 */
 static const char *kCodecWidth = "android.media.mediacodec.width";     /* 0..n */
 static const char *kCodecHeight = "android.media.mediacodec.height";   /* 0..n */
 static const char *kCodecRotation = "android.media.mediacodec.rotation-degrees";  /* 0/90/180/270 */
@@ -155,6 +159,7 @@
 static const char *kCodecLatencyUnknown = "android.media.mediacodec.latency.unknown";
 static const char *kCodecQueueSecureInputBufferError = "android.media.mediacodec.queueSecureInputBufferError";
 static const char *kCodecQueueInputBufferError = "android.media.mediacodec.queueInputBufferError";
+static const char *kCodecComponentColorFormat = "android.media.mediacodec.component-color-format";
 
 static const char *kCodecNumLowLatencyModeOn = "android.media.mediacodec.low-latency.on";  /* 0..n */
 static const char *kCodecNumLowLatencyModeOff = "android.media.mediacodec.low-latency.off";  /* 0..n */
@@ -173,9 +178,9 @@
 static const char *kCodecParsedColorStandard = "android.media.mediacodec.parsed-color-standard";
 static const char *kCodecParsedColorRange = "android.media.mediacodec.parsed-color-range";
 static const char *kCodecParsedColorTransfer = "android.media.mediacodec.parsed-color-transfer";
-static const char *kCodecHDRStaticInfo = "android.media.mediacodec.hdr-static-info";
-static const char *kCodecHDR10PlusInfo = "android.media.mediacodec.hdr10-plus-info";
-static const char *kCodecHDRFormat = "android.media.mediacodec.hdr-format";
+static const char *kCodecHdrStaticInfo = "android.media.mediacodec.hdr-static-info";
+static const char *kCodecHdr10PlusInfo = "android.media.mediacodec.hdr10-plus-info";
+static const char *kCodecHdrFormat = "android.media.mediacodec.hdr-format";
 // array/sync/async/block modes
 static const char *kCodecArrayMode = "android.media.mediacodec.array-mode";
 static const char *kCodecOperationMode = "android.media.mediacodec.operation-mode";
@@ -196,13 +201,72 @@
 static const char *kCodecRecentLatencyAvg = "android.media.mediacodec.recent.avg";      /* in us */
 static const char *kCodecRecentLatencyCount = "android.media.mediacodec.recent.n";
 static const char *kCodecRecentLatencyHist = "android.media.mediacodec.recent.hist";    /* in us */
-static const char *kCodecPlaybackDurationSec =
-        "android.media.mediacodec.playback-duration-sec"; /* in sec */
 
 /* -1: shaper disabled
    >=0: number of fields changed */
 static const char *kCodecShapingEnhanced = "android.media.mediacodec.shaped";
 
+// Render metrics
+static const char *kCodecPlaybackDurationSec = "android.media.mediacodec.playback-duration-sec";
+static const char *kCodecFirstRenderTimeUs = "android.media.mediacodec.first-render-time-us";
+static const char *kCodecFramesReleased = "android.media.mediacodec.frames-released";
+static const char *kCodecFramesRendered = "android.media.mediacodec.frames-rendered";
+static const char *kCodecFramesDropped = "android.media.mediacodec.frames-dropped";
+static const char *kCodecFramesSkipped = "android.media.mediacodec.frames-skipped";
+static const char *kCodecFramerateContent = "android.media.mediacodec.framerate-content";
+static const char *kCodecFramerateDesired = "android.media.mediacodec.framerate-desired";
+static const char *kCodecFramerateActual = "android.media.mediacodec.framerate-actual";
+// Freeze
+static const char *kCodecFreezeCount = "android.media.mediacodec.freeze-count";
+static const char *kCodecFreezeScore = "android.media.mediacodec.freeze-score";
+static const char *kCodecFreezeRate = "android.media.mediacodec.freeze-rate";
+static const char *kCodecFreezeDurationMsAvg = "android.media.mediacodec.freeze-duration-ms-avg";
+static const char *kCodecFreezeDurationMsMax = "android.media.mediacodec.freeze-duration-ms-max";
+static const char *kCodecFreezeDurationMsHistogram =
+        "android.media.mediacodec.freeze-duration-ms-histogram";
+static const char *kCodecFreezeDurationMsHistogramBuckets =
+        "android.media.mediacodec.freeze-duration-ms-histogram-buckets";
+static const char *kCodecFreezeDistanceMsAvg = "android.media.mediacodec.freeze-distance-ms-avg";
+static const char *kCodecFreezeDistanceMsHistogram =
+        "android.media.mediacodec.freeze-distance-ms-histogram";
+static const char *kCodecFreezeDistanceMsHistogramBuckets =
+        "android.media.mediacodec.freeze-distance-ms-histogram-buckets";
+// Judder
+static const char *kCodecJudderCount = "android.media.mediacodec.judder-count";
+static const char *kCodecJudderScore = "android.media.mediacodec.judder-score";
+static const char *kCodecJudderRate = "android.media.mediacodec.judder-rate";
+static const char *kCodecJudderScoreAvg = "android.media.mediacodec.judder-score-avg";
+static const char *kCodecJudderScoreMax = "android.media.mediacodec.judder-score-max";
+static const char *kCodecJudderScoreHistogram = "android.media.mediacodec.judder-score-histogram";
+static const char *kCodecJudderScoreHistogramBuckets =
+        "android.media.mediacodec.judder-score-histogram-buckets";
+// Freeze event
+static const char *kCodecFreezeEventCount = "android.media.mediacodec.freeze-event-count";
+static const char *kFreezeEventKeyName = "freeze";
+static const char *kFreezeEventInitialTimeUs = "android.media.mediacodec.freeze.initial-time-us";
+static const char *kFreezeEventDurationMs = "android.media.mediacodec.freeze.duration-ms";
+static const char *kFreezeEventCount = "android.media.mediacodec.freeze.count";
+static const char *kFreezeEventAvgDurationMs = "android.media.mediacodec.freeze.avg-duration-ms";
+static const char *kFreezeEventAvgDistanceMs = "android.media.mediacodec.freeze.avg-distance-ms";
+static const char *kFreezeEventDetailsDurationMs =
+        "android.media.mediacodec.freeze.details-duration-ms";
+static const char *kFreezeEventDetailsDistanceMs =
+        "android.media.mediacodec.freeze.details-distance-ms";
+// Judder event
+static const char *kCodecJudderEventCount = "android.media.mediacodec.judder-event-count";
+static const char *kJudderEventKeyName = "judder";
+static const char *kJudderEventInitialTimeUs = "android.media.mediacodec.judder.initial-time-us";
+static const char *kJudderEventDurationMs = "android.media.mediacodec.judder.duration-ms";
+static const char *kJudderEventCount = "android.media.mediacodec.judder.count";
+static const char *kJudderEventAvgScore = "android.media.mediacodec.judder.avg-score";
+static const char *kJudderEventAvgDistanceMs = "android.media.mediacodec.judder.avg-distance-ms";
+static const char *kJudderEventDetailsActualDurationUs =
+        "android.media.mediacodec.judder.details-actual-duration-us";
+static const char *kJudderEventDetailsContentDurationUs =
+        "android.media.mediacodec.judder.details-content-duration-us";
+static const char *kJudderEventDetailsDistanceMs =
+        "android.media.mediacodec.judder.details-distance-ms";
+
 // XXX suppress until we get our representation right
 static bool kEmitHistogram = false;
 
@@ -960,8 +1024,10 @@
       mHaveInputSurface(false),
       mHavePendingInputBuffers(false),
       mCpuBoostRequested(false),
-      mPlaybackDurationAccumulator(new PlaybackDurationAccumulator()),
-      mIsSurfaceToScreen(false),
+      mIsSurfaceToDisplay(false),
+      mVideoRenderQualityTracker(
+              VideoRenderQualityTracker::Configuration::getFromServerConfigurableFlags(
+                      server_configurable_flags::GetServerConfigurableFlag)),
       mLatencyUnknown(0),
       mBytesEncoded(0),
       mEarliestEncodedPtsUs(INT64_MAX),
@@ -1096,6 +1162,56 @@
     mediametrics_setInt32(mMetricsHandle, kCodecResolutionChangeCount,
             mReliabilityContextMetrics.resolutionChangeCount);
 
+    // Video rendering quality metrics
+    {
+        const VideoRenderQualityMetrics &m = mVideoRenderQualityTracker.getMetrics();
+        if (m.frameReleasedCount > 0) {
+            mediametrics_setInt64(mMetricsHandle, kCodecFirstRenderTimeUs, m.firstRenderTimeUs);
+            mediametrics_setInt64(mMetricsHandle, kCodecFramesReleased, m.frameReleasedCount);
+            mediametrics_setInt64(mMetricsHandle, kCodecFramesRendered, m.frameRenderedCount);
+            mediametrics_setInt64(mMetricsHandle, kCodecFramesSkipped, m.frameSkippedCount);
+            mediametrics_setInt64(mMetricsHandle, kCodecFramesDropped, m.frameDroppedCount);
+            mediametrics_setDouble(mMetricsHandle, kCodecFramerateContent, m.contentFrameRate);
+            mediametrics_setDouble(mMetricsHandle, kCodecFramerateDesired, m.desiredFrameRate);
+            mediametrics_setDouble(mMetricsHandle, kCodecFramerateActual, m.actualFrameRate);
+        }
+        if (m.freezeDurationMsHistogram.getCount() >= 1) {
+            const MediaHistogram<int32_t> &h = m.freezeDurationMsHistogram;
+            mediametrics_setInt64(mMetricsHandle, kCodecFreezeScore, m.freezeScore);
+            mediametrics_setDouble(mMetricsHandle, kCodecFreezeRate, m.freezeRate);
+            mediametrics_setInt64(mMetricsHandle, kCodecFreezeCount, h.getCount());
+            mediametrics_setInt32(mMetricsHandle, kCodecFreezeDurationMsAvg, h.getAvg());
+            mediametrics_setInt32(mMetricsHandle, kCodecFreezeDurationMsMax, h.getMax());
+            mediametrics_setString(mMetricsHandle, kCodecFreezeDurationMsHistogram, h.emit());
+            mediametrics_setString(mMetricsHandle, kCodecFreezeDurationMsHistogramBuckets,
+                                   h.emitBuckets());
+        }
+        if (m.freezeDistanceMsHistogram.getCount() >= 1) {
+            const MediaHistogram<int32_t> &h = m.freezeDistanceMsHistogram;
+            mediametrics_setInt32(mMetricsHandle, kCodecFreezeDistanceMsAvg, h.getAvg());
+            mediametrics_setString(mMetricsHandle, kCodecFreezeDistanceMsHistogram, h.emit());
+            mediametrics_setString(mMetricsHandle, kCodecFreezeDistanceMsHistogramBuckets,
+                                   h.emitBuckets());
+        }
+        if (m.judderScoreHistogram.getCount() >= 1) {
+            const MediaHistogram<int32_t> &h = m.judderScoreHistogram;
+            mediametrics_setInt64(mMetricsHandle, kCodecJudderScore, m.judderScore);
+            mediametrics_setDouble(mMetricsHandle, kCodecJudderRate, m.judderRate);
+            mediametrics_setInt64(mMetricsHandle, kCodecJudderCount, h.getCount());
+            mediametrics_setInt32(mMetricsHandle, kCodecJudderScoreAvg, h.getAvg());
+            mediametrics_setInt32(mMetricsHandle, kCodecJudderScoreMax, h.getMax());
+            mediametrics_setString(mMetricsHandle, kCodecJudderScoreHistogram, h.emit());
+            mediametrics_setString(mMetricsHandle, kCodecJudderScoreHistogramBuckets,
+                                   h.emitBuckets());
+        }
+        if (m.freezeEventCount != 0) {
+            mediametrics_setInt32(mMetricsHandle, kCodecFreezeEventCount, m.freezeEventCount);
+        }
+        if (m.judderEventCount != 0) {
+            mediametrics_setInt32(mMetricsHandle, kCodecJudderEventCount, m.judderEventCount);
+        }
+    }
+
     if (mLatencyHist.getCount() != 0 ) {
         mediametrics_setInt64(mMetricsHandle, kCodecLatencyMax, mLatencyHist.getMax());
         mediametrics_setInt64(mMetricsHandle, kCodecLatencyMin, mLatencyHist.getMin());
@@ -1111,7 +1227,7 @@
     if (mLatencyUnknown > 0) {
         mediametrics_setInt64(mMetricsHandle, kCodecLatencyUnknown, mLatencyUnknown);
     }
-    int64_t playbackDurationSec = mPlaybackDurationAccumulator->getDurationInSeconds();
+    int64_t playbackDurationSec = mPlaybackDurationAccumulator.getDurationInSeconds();
     if (playbackDurationSec > 0) {
         mediametrics_setInt64(mMetricsHandle, kCodecPlaybackDurationSec, playbackDurationSec);
     }
@@ -1174,14 +1290,14 @@
             && ColorUtils::isHDRStaticInfoValid(&info)) {
         mHdrInfoFlags |= kFlagHasHdrStaticInfo;
     }
-    mediametrics_setInt32(mMetricsHandle, kCodecHDRStaticInfo,
+    mediametrics_setInt32(mMetricsHandle, kCodecHdrStaticInfo,
             (mHdrInfoFlags & kFlagHasHdrStaticInfo) ? 1 : 0);
     sp<ABuffer> hdr10PlusInfo;
     if (mOutputFormat->findBuffer("hdr10-plus-info", &hdr10PlusInfo)
             && hdr10PlusInfo != nullptr && hdr10PlusInfo->size() > 0) {
         mHdrInfoFlags |= kFlagHasHdr10PlusInfo;
     }
-    mediametrics_setInt32(mMetricsHandle, kCodecHDR10PlusInfo,
+    mediametrics_setInt32(mMetricsHandle, kCodecHdr10PlusInfo,
             (mHdrInfoFlags & kFlagHasHdr10PlusInfo) ? 1 : 0);
 
     // hdr format
@@ -1194,7 +1310,7 @@
             && codedFormat->findInt32(KEY_PROFILE, &profile)
             && colorTransfer != -1) {
         hdr_format hdrFormat = getHdrFormat(mime, profile, colorTransfer);
-        mediametrics_setInt32(mMetricsHandle, kCodecHDRFormat, static_cast<int>(hdrFormat));
+        mediametrics_setInt32(mMetricsHandle, kCodecHdrFormat, static_cast<int>(hdrFormat));
     }
 }
 
@@ -1302,16 +1418,15 @@
         return;
     }
 
-    Histogram recentHist;
-
     // build an empty histogram
+    MediaHistogram<int64_t> recentHist;
     recentHist.setup(kLatencyHistBuckets, kLatencyHistWidth, kLatencyHistFloor);
 
     // stuff it with the samples in the ring buffer
     {
         Mutex::Autolock al(mRecentLock);
 
-        for (int i=0; i<kRecentLatencyFrames; i++) {
+        for (int i = 0; i < kRecentLatencyFrames; i++) {
             if (mRecentSamples[i] != kRecentSampleInvalid) {
                 recentHist.insert(mRecentSamples[i]);
             }
@@ -1319,7 +1434,7 @@
     }
 
     // spit the data (if any) into the supplied analytics record
-    if (recentHist.getCount()!= 0 ) {
+    if (recentHist.getCount() != 0 ) {
         mediametrics_setInt64(item, kCodecRecentLatencyMax, recentHist.getMax());
         mediametrics_setInt64(item, kCodecRecentLatencyMin, recentHist.getMin());
         mediametrics_setInt64(item, kCodecRecentLatencyAvg, recentHist.getAvg());
@@ -1333,6 +1448,53 @@
     }
 }
 
+static std::string emitVector(std::vector<int32_t> vector) {
+    std::ostringstream sstr;
+    for (size_t i = 0; i < vector.size(); ++i) {
+        if (i != 0) {
+            sstr << ',';
+        }
+        sstr << vector[i];
+    }
+    return sstr.str();
+}
+
+static void reportToMediaMetricsIfValid(const FreezeEvent &e) {
+    if (e.valid) {
+        mediametrics_handle_t handle = mediametrics_create(kFreezeEventKeyName);
+        mediametrics_setInt64(handle, kFreezeEventInitialTimeUs, e.initialTimeUs);
+        mediametrics_setInt32(handle, kFreezeEventDurationMs, e.durationMs);
+        mediametrics_setInt64(handle, kFreezeEventCount, e.count);
+        mediametrics_setInt32(handle, kFreezeEventAvgDurationMs, e.sumDurationMs / e.count);
+        mediametrics_setInt32(handle, kFreezeEventAvgDistanceMs, e.sumDistanceMs / e.count);
+        mediametrics_setString(handle, kFreezeEventDetailsDurationMs,
+                               emitVector(e.details.durationMs));
+        mediametrics_setString(handle, kFreezeEventDetailsDistanceMs,
+                               emitVector(e.details.distanceMs));
+        mediametrics_selfRecord(handle);
+        mediametrics_delete(handle);
+    }
+}
+
+static void reportToMediaMetricsIfValid(const JudderEvent &e) {
+    if (e.valid) {
+        mediametrics_handle_t handle = mediametrics_create(kJudderEventKeyName);
+        mediametrics_setInt64(handle, kJudderEventInitialTimeUs, e.initialTimeUs);
+        mediametrics_setInt32(handle, kJudderEventDurationMs, e.durationMs);
+        mediametrics_setInt64(handle, kJudderEventCount, e.count);
+        mediametrics_setInt32(handle, kJudderEventAvgScore, e.sumScore / e.count);
+        mediametrics_setInt32(handle, kJudderEventAvgDistanceMs, e.sumDistanceMs / e.count);
+        mediametrics_setString(handle, kJudderEventDetailsActualDurationUs,
+                               emitVector(e.details.actualRenderDurationUs));
+        mediametrics_setString(handle, kJudderEventDetailsContentDurationUs,
+                               emitVector(e.details.contentRenderDurationUs));
+        mediametrics_setString(handle, kJudderEventDetailsDistanceMs,
+                               emitVector(e.details.distanceMs));
+        mediametrics_selfRecord(handle);
+        mediametrics_delete(handle);
+    }
+}
+
 void MediaCodec::flushMediametrics() {
     ALOGD("flushMediametrics");
 
@@ -1351,6 +1513,10 @@
     }
     // we no longer have anything pending upload
     mMetricsToUpload = false;
+
+    // Freeze and judder events are reported separately
+    reportToMediaMetricsIfValid(mVideoRenderQualityTracker.getAndResetFreezeEvent());
+    reportToMediaMetricsIfValid(mVideoRenderQualityTracker.getAndResetJudderEvent());
 }
 
 void MediaCodec::updateLowLatency(const sp<AMessage> &msg) {
@@ -1436,116 +1602,43 @@
     ALOGV("TunnelPeekState: %s -> %s", asString(previousState), asString(mTunnelPeekState));
 }
 
-void MediaCodec::updatePlaybackDuration(const sp<AMessage> &msg) {
+void MediaCodec::processRenderedFrames(const sp<AMessage> &msg) {
     int what = 0;
     msg->findInt32("what", &what);
     if (msg->what() != kWhatCodecNotify && what != kWhatOutputFramesRendered) {
         static bool logged = false;
         if (!logged) {
             logged = true;
-            ALOGE("updatePlaybackDuration: expected kWhatOuputFramesRendered (%d)", msg->what());
+            ALOGE("processRenderedFrames: expected kWhatOutputFramesRendered (%d)", msg->what());
         }
         return;
     }
-    // Playback duration only counts if the buffers are going to the screen.
-    if (!mIsSurfaceToScreen) {
-        return;
-    }
-    int64_t renderTimeNs;
-    size_t index = 0;
-    while (msg->findInt64(AStringPrintf("%zu-system-nano", index++).c_str(), &renderTimeNs)) {
-        mPlaybackDurationAccumulator->processRenderTime(renderTimeNs);
-    }
-}
-
-bool MediaCodec::Histogram::setup(int nbuckets, int64_t width, int64_t floor)
-{
-    if (nbuckets <= 0 || width <= 0) {
-        return false;
-    }
-
-    // get histogram buckets
-    if (nbuckets == mBucketCount && mBuckets != NULL) {
-        // reuse our existing buffer
-        memset(mBuckets, 0, sizeof(*mBuckets) * mBucketCount);
-    } else {
-        // get a new pre-zeroed buffer
-        int64_t *newbuckets = (int64_t *)calloc(nbuckets, sizeof (*mBuckets));
-        if (newbuckets == NULL) {
-            goto bad;
+    // Rendered frames only matter if they're being sent to the display
+    if (mIsSurfaceToDisplay) {
+        int64_t renderTimeNs;
+        for (size_t index = 0;
+            msg->findInt64(AStringPrintf("%zu-system-nano", index).c_str(), &renderTimeNs);
+            index++) {
+            // Capture metrics for playback duration
+            mPlaybackDurationAccumulator.onFrameRendered(renderTimeNs);
+            // Capture metrics for quality
+            int64_t mediaTimeUs = 0;
+            if (!msg->findInt64(AStringPrintf("%zu-media-time-us", index).c_str(), &mediaTimeUs)) {
+                ALOGE("processRenderedFrames: no media time found");
+                continue;
+            }
+            // Tunneled frames use INT64_MAX to indicate end-of-stream, so don't report it as a
+            // rendered frame.
+            if (!mTunneled || mediaTimeUs != INT64_MAX) {
+                FreezeEvent freezeEvent;
+                JudderEvent judderEvent;
+                mVideoRenderQualityTracker.onFrameRendered(mediaTimeUs, renderTimeNs, &freezeEvent,
+                                                           &judderEvent);
+                reportToMediaMetricsIfValid(freezeEvent);
+                reportToMediaMetricsIfValid(judderEvent);
+            }
         }
-        if (mBuckets != NULL)
-            free(mBuckets);
-        mBuckets = newbuckets;
     }
-
-    mWidth = width;
-    mFloor = floor;
-    mCeiling = floor + nbuckets * width;
-    mBucketCount = nbuckets;
-
-    mMin = INT64_MAX;
-    mMax = INT64_MIN;
-    mSum = 0;
-    mCount = 0;
-    mBelow = mAbove = 0;
-
-    return true;
-
-  bad:
-    if (mBuckets != NULL) {
-        free(mBuckets);
-        mBuckets = NULL;
-    }
-
-    return false;
-}
-
-void MediaCodec::Histogram::insert(int64_t sample)
-{
-    // histogram is not set up
-    if (mBuckets == NULL) {
-        return;
-    }
-
-    mCount++;
-    mSum += sample;
-    if (mMin > sample) mMin = sample;
-    if (mMax < sample) mMax = sample;
-
-    if (sample < mFloor) {
-        mBelow++;
-    } else if (sample >= mCeiling) {
-        mAbove++;
-    } else {
-        int64_t slot = (sample - mFloor) / mWidth;
-        CHECK(slot < mBucketCount);
-        mBuckets[slot]++;
-    }
-    return;
-}
-
-std::string MediaCodec::Histogram::emit()
-{
-    std::string value;
-    char buffer[64];
-
-    // emits:  width,Below{bucket0,bucket1,...., bucketN}above
-    // unconfigured will emit: 0,0{}0
-    // XXX: is this best representation?
-    snprintf(buffer, sizeof(buffer), "%" PRId64 ",%" PRId64 ",%" PRId64 "{",
-             mFloor, mWidth, mBelow);
-    value = buffer;
-    for (int i = 0; i < mBucketCount; i++) {
-        if (i != 0) {
-            value = value + ",";
-        }
-        snprintf(buffer, sizeof(buffer), "%" PRId64, mBuckets[i]);
-        value = value + buffer;
-    }
-    snprintf(buffer, sizeof(buffer), "}%" PRId64 , mAbove);
-    value = value + buffer;
-    return value;
 }
 
 // when we send a buffer to the codec;
@@ -3622,8 +3715,7 @@
 
                                 setState(UNINITIALIZED);
                             } else {
-                                setState(
-                                        (mFlags & kFlagIsAsync) ? FLUSHED : STARTED);
+                                setState((mFlags & kFlagIsAsync) ? FLUSHED : STARTED);
                             }
                             break;
                         }
@@ -3748,6 +3840,9 @@
                         mediametrics_setInt32(mMetricsHandle, kCodecSecure, 0);
                     }
 
+                    mediametrics_setInt32(mMetricsHandle, kCodecHardware,
+                                          MediaCodecList::isSoftwareCodec(mComponentName) ? 0 : 1);
+
                     mResourceManagerProxy->addResource(MediaResource::CodecResource(
                             mFlags & kFlagIsSecure, toMediaResourceSubType(mDomain)));
 
@@ -3814,6 +3909,14 @@
                         if (interestingFormat->findInt32("level", &level)) {
                             mediametrics_setInt32(mMetricsHandle, kCodecLevel, level);
                         }
+                        sp<AMessage> uncompressedFormat =
+                                (mFlags & kFlagIsEncoder) ? mInputFormat : mOutputFormat;
+                        int32_t componentColorFormat  = -1;
+                        if (uncompressedFormat->findInt32("android._color-format",
+                                &componentColorFormat)) {
+                            mediametrics_setInt32(mMetricsHandle,
+                                    kCodecComponentColorFormat, componentColorFormat);
+                        }
                         updateHdrMetrics(true /* isConfig */);
                         int32_t codecMaxInputSize = -1;
                         if (mInputFormat->findInt32(KEY_MAX_INPUT_SIZE, &codecMaxInputSize)) {
@@ -3964,7 +4067,7 @@
                                 asString(previousState),
                                 asString(TunnelPeekState::kBufferRendered));
                     }
-                    updatePlaybackDuration(msg);
+                    processRenderedFrames(msg);
                     // check that we have a notification set
                     if (mOnFrameRenderedNotification != NULL) {
                         sp<AMessage> notify = mOnFrameRenderedNotification->dup();
@@ -4158,6 +4261,11 @@
                               mState, stateString(mState).c_str());
                         break;
                     }
+
+                    if (mIsSurfaceToDisplay) {
+                        mVideoRenderQualityTracker.resetForDiscontinuity();
+                    }
+
                     // Notify the RM that the codec has been stopped.
                     ClientConfigParcel clientConfig;
                     initClientConfigParcel(clientConfig);
@@ -4213,6 +4321,10 @@
                         break;
                     }
 
+                    if (mIsSurfaceToDisplay) {
+                        mVideoRenderQualityTracker.resetForDiscontinuity();
+                    }
+
                     if (mFlags & kFlagIsAsync) {
                         setState(FLUSHED);
                     } else {
@@ -4464,6 +4576,7 @@
             } else {
                 mTunneled = false;
             }
+            mediametrics_setInt32(mMetricsHandle, kCodecTunneled, mTunneled ? 1 : 0);
 
             int32_t background = 0;
             if (format->findInt32("android._background-mode", &background) && background) {
@@ -5815,6 +5928,10 @@
     }
 
     if (err == OK) {
+        if (mTunneled && (flags & (BUFFER_FLAG_DECODE_ONLY | BUFFER_FLAG_END_OF_STREAM)) == 0) {
+            mVideoRenderQualityTracker.onTunnelFrameQueued(timeUs);
+        }
+
         // synchronization boundary for getBufferAndFormat
         Mutex::Autolock al(mBufferLock);
         info->mOwnedByClient = false;
@@ -5897,7 +6014,7 @@
     }
 
     if (render && buffer->size() != 0) {
-        int64_t mediaTimeUs = -1;
+        int64_t mediaTimeUs = INT64_MIN;
         buffer->meta()->findInt64("timeUs", &mediaTimeUs);
 
         bool noRenderTime = false;
@@ -5927,7 +6044,12 @@
 
         // If rendering to the screen, then schedule a time in the future to poll to see if this
         // frame was ever rendered to seed onFrameRendered callbacks.
-        if (mIsSurfaceToScreen) {
+        if (mIsSurfaceToDisplay) {
+            if (mediaTimeUs != INT64_MIN) {
+                noRenderTime ? mVideoRenderQualityTracker.onFrameReleased(mediaTimeUs)
+                             : mVideoRenderQualityTracker.onFrameReleased(mediaTimeUs,
+                                                                          renderTimeNs);
+            }
             // can't initialize this in the constructor because the Looper parent class needs to be
             // initialized first
             if (mMsgPollForRenderedBuffers == nullptr) {
@@ -5957,6 +6079,12 @@
             ALOGI("rendring output error %d", err);
         }
     } else {
+        if (mIsSurfaceToDisplay && buffer->size() != 0) {
+            int64_t mediaTimeUs = INT64_MIN;
+            if (buffer->meta()->findInt64("timeUs", &mediaTimeUs)) {
+                mVideoRenderQualityTracker.onFrameSkipped(mediaTimeUs);
+            }
+        }
         mBufferChannel->discardBuffer(buffer);
     }
 
@@ -6023,7 +6151,7 @@
 
         // in case we don't connect, ensure that we don't signal the surface is
         // connected to the screen
-        mIsSurfaceToScreen = false;
+        mIsSurfaceToDisplay = false;
 
         err = nativeWindowConnect(surface.get(), "connectToSurface");
         if (err == OK) {
@@ -6053,7 +6181,7 @@
             // keep track whether or not the buffers of the connected surface go to the screen
             int result = 0;
             surface->query(NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER, &result);
-            mIsSurfaceToScreen = result != 0;
+            mIsSurfaceToDisplay = result != 0;
         }
     }
     // do not return ALREADY_EXISTS unless surfaces are the same
@@ -6071,7 +6199,7 @@
         }
         // assume disconnected even on error
         mSurface.clear();
-        mIsSurfaceToScreen = false;
+        mIsSurfaceToDisplay = false;
     }
     return err;
 }
diff --git a/media/libstagefright/VideoRenderQualityTracker.cpp b/media/libstagefright/VideoRenderQualityTracker.cpp
new file mode 100644
index 0000000..4f12a37
--- /dev/null
+++ b/media/libstagefright/VideoRenderQualityTracker.cpp
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2023 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_TAG "VideoRenderQualityTracker"
+#include <utils/Log.h>
+
+#include <media/stagefright/VideoRenderQualityTracker.h>
+
+#include <assert.h>
+#include <charconv>
+#include <cmath>
+#include <stdio.h>
+#include <sys/time.h>
+
+#include <android-base/parsebool.h>
+#include <android-base/parseint.h>
+
+namespace android {
+
+using android::base::ParseBoolResult;
+
+static constexpr float FRAME_RATE_UNDETERMINED = VideoRenderQualityMetrics::FRAME_RATE_UNDETERMINED;
+static constexpr float FRAME_RATE_24_3_2_PULLDOWN =
+        VideoRenderQualityMetrics::FRAME_RATE_24_3_2_PULLDOWN;
+
+typedef VideoRenderQualityTracker::Configuration::GetServerConfigurableFlagFn
+        GetServerConfigurableFlagFn;
+
+static void getServerConfigurableFlag(GetServerConfigurableFlagFn getServerConfigurableFlagFn,
+                                      char const *flagNameSuffix, bool *value) {
+    std::string flagName("render_metrics_");
+    flagName.append(flagNameSuffix);
+    std::string valueStr = (*getServerConfigurableFlagFn)("media_native", flagName,
+                                                          *value ? "true" : "false");
+    switch (android::base::ParseBool(valueStr)) {
+    case ParseBoolResult::kTrue: *value = true; break;
+    case ParseBoolResult::kFalse: *value = false; break;
+    case ParseBoolResult::kError:
+        ALOGW("failed to parse server-configurable flag '%s' from '%s'", flagNameSuffix,
+              valueStr.c_str());
+        break;
+    }
+}
+
+static void getServerConfigurableFlag(GetServerConfigurableFlagFn getServerConfigurableFlagFn,
+                                      char const *flagNameSuffix, int32_t *value) {
+    char defaultStr[11];
+    sprintf(defaultStr, "%d", int(*value));
+    std::string flagName("render_metrics_");
+    flagName.append(flagNameSuffix);
+    std::string valueStr = (*getServerConfigurableFlagFn)("media_native", flagName, defaultStr);
+    if (!android::base::ParseInt(valueStr.c_str(), value) || valueStr.size() == 0) {
+        ALOGW("failed to parse server-configurable flag '%s' from '%s'", flagNameSuffix,
+              valueStr.c_str());
+        return;
+    }
+}
+
+template<typename T>
+static void getServerConfigurableFlag(GetServerConfigurableFlagFn getServerConfigurableFlagFn,
+                                      char const *flagNameSuffix, std::vector<T> *value) {
+    std::stringstream sstr;
+    for (int i = 0; i < value->size(); ++i) {
+        if (i != 0) {
+            sstr << ",";
+        }
+        sstr << (*value)[i];
+    }
+    std::string flagName("render_metrics_");
+    flagName.append(flagNameSuffix);
+    std::string valueStr = (*getServerConfigurableFlagFn)("media_native", flagName, sstr.str());
+    if (valueStr.size() == 0) {
+        return;
+    }
+    // note: using android::base::Tokenize fails to catch parsing failures for values ending in ','
+    std::vector<T> newValues;
+    const char *p = valueStr.c_str();
+    const char *last = p + valueStr.size();
+    while (p != last) {
+        if (*p == ',') {
+            p++;
+        }
+        T value = -1;
+        auto [ptr, error] = std::from_chars(p, last, value);
+        if (error == std::errc::invalid_argument || error == std::errc::result_out_of_range) {
+            ALOGW("failed to parse server-configurable flag '%s' from '%s'", flagNameSuffix,
+                  valueStr.c_str());
+            return;
+        }
+        p = ptr;
+        newValues.push_back(value);
+    }
+    *value = std::move(newValues);
+}
+
+VideoRenderQualityMetrics::VideoRenderQualityMetrics() {
+    clear();
+}
+
+void VideoRenderQualityMetrics::clear() {
+    firstRenderTimeUs = 0;
+    frameReleasedCount = 0;
+    frameRenderedCount = 0;
+    frameDroppedCount = 0;
+    frameSkippedCount = 0;
+    contentFrameRate = FRAME_RATE_UNDETERMINED;
+    desiredFrameRate = FRAME_RATE_UNDETERMINED;
+    actualFrameRate = FRAME_RATE_UNDETERMINED;
+    freezeEventCount = 0;
+    freezeDurationMsHistogram.clear();
+    freezeDistanceMsHistogram.clear();
+    judderEventCount = 0;
+    judderScoreHistogram.clear();
+}
+
+VideoRenderQualityTracker::Configuration
+        VideoRenderQualityTracker::Configuration::getFromServerConfigurableFlags(
+            GetServerConfigurableFlagFn getServerConfigurableFlagFn) {
+    VideoRenderQualityTracker::Configuration c;
+#define getFlag(FIELDNAME, FLAGNAME) \
+    getServerConfigurableFlag(getServerConfigurableFlagFn, FLAGNAME, &c.FIELDNAME)
+    getFlag(enabled, "enabled");
+    getFlag(areSkippedFramesDropped, "are_skipped_frames_dropped");
+    getFlag(maxExpectedContentFrameDurationUs, "max_expected_content_frame_duration_us");
+    getFlag(frameRateDetectionToleranceUs, "frame_rate_detection_tolerance_us");
+    getFlag(liveContentFrameDropToleranceUs, "live_content_frame_drop_tolerance_us");
+    getFlag(freezeDurationMsHistogramBuckets, "freeze_duration_ms_histogram_buckets");
+    getFlag(freezeDurationMsHistogramToScore, "freeze_duration_ms_histogram_to_score");
+    getFlag(freezeDistanceMsHistogramBuckets, "freeze_distance_ms_histogram_buckets");
+    getFlag(freezeEventMax, "freeze_event_max");
+    getFlag(freezeEventDetailsMax, "freeze_event_details_max");
+    getFlag(freezeEventDistanceToleranceMs, "freeze_event_distance_tolerance_ms");
+    getFlag(judderErrorToleranceUs, "judder_error_tolerance_us");
+    getFlag(judderScoreHistogramBuckets, "judder_score_histogram_buckets");
+    getFlag(judderScoreHistogramToScore, "judder_score_histogram_to_score");
+    getFlag(judderEventMax, "judder_event_max");
+    getFlag(judderEventDetailsMax, "judder_event_details_max");
+    getFlag(judderEventDistanceToleranceMs, "judder_event_distance_tolerance_ms");
+#undef getFlag
+    return c;
+}
+
+VideoRenderQualityTracker::Configuration::Configuration() {
+    enabled = true;
+
+    // Assume that the app is skipping frames because it's detected that the frame couldn't be
+    // rendered in time.
+    areSkippedFramesDropped = true;
+
+    // 400ms is 8 frames at 20 frames per second and 24 frames at 60 frames per second
+    maxExpectedContentFrameDurationUs = 400 * 1000;
+
+    // Allow for 2 milliseconds of deviation when detecting frame rates
+    frameRateDetectionToleranceUs = 2 * 1000;
+
+    // Allow for a tolerance of 200 milliseconds for determining if we moved forward in content time
+    // because of frame drops for live content, or because the user is seeking.
+    liveContentFrameDropToleranceUs = 200 * 1000;
+
+    // Freeze configuration
+    freezeDurationMsHistogramBuckets = {1, 20, 40, 60, 80, 100, 120, 150, 175, 225, 300, 400, 500};
+    freezeDurationMsHistogramToScore = {1,  1,  1,  1,  1,   1,   1,   1,   1,   1,   1,   1,   1};
+    freezeDistanceMsHistogramBuckets = {0, 20, 100, 400, 1000, 2000, 3000, 4000, 8000, 15000, 30000,
+                                        60000};
+    freezeEventMax = 0; // enabled only when debugging
+    freezeEventDetailsMax = 20;
+    freezeEventDistanceToleranceMs = 60000; // lump freeze occurrences together when 60s or less
+
+    // Judder configuration
+    judderErrorToleranceUs = 2000;
+    judderScoreHistogramBuckets = {1, 4, 5, 9, 11, 20, 30, 40, 50, 60, 70, 80};
+    judderScoreHistogramToScore = {1, 1, 1, 1,  1,  1,  1,  1,  1,  1,  1,  1};
+    judderEventMax = 0; // enabled only when debugging
+    judderEventDetailsMax = 20;
+    judderEventDistanceToleranceMs = 5000; // lump judder occurrences together when 5s or less
+}
+
+VideoRenderQualityTracker::VideoRenderQualityTracker() : mConfiguration(Configuration()) {
+    configureHistograms(mMetrics, mConfiguration);
+    clear();
+}
+
+VideoRenderQualityTracker::VideoRenderQualityTracker(const Configuration &configuration) :
+        mConfiguration(configuration) {
+    configureHistograms(mMetrics, mConfiguration);
+    clear();
+}
+
+void VideoRenderQualityTracker::onTunnelFrameQueued(int64_t contentTimeUs) {
+    if (!mConfiguration.enabled) {
+        return;
+    }
+
+    // Since P-frames are queued out of order, hold onto the P-frame until we can track it in
+    // render order. This only works because it depends on today's encoding algorithms that only
+    // allow B-frames to refer to ONE P-frame that comes after it. If the cardinality of P-frames
+    // in a single mini-GOP is increased, this algorithm breaks down.
+    if (mTunnelFrameQueuedContentTimeUs == -1) {
+        mTunnelFrameQueuedContentTimeUs = contentTimeUs;
+    } else if (contentTimeUs < mTunnelFrameQueuedContentTimeUs) {
+        onFrameReleased(contentTimeUs, 0);
+    } else {
+        onFrameReleased(mTunnelFrameQueuedContentTimeUs, 0);
+        mTunnelFrameQueuedContentTimeUs = contentTimeUs;
+    }
+}
+
+void VideoRenderQualityTracker::onFrameSkipped(int64_t contentTimeUs) {
+    if (!mConfiguration.enabled) {
+        return;
+    }
+
+    // Frames skipped at the beginning shouldn't really be counted as skipped frames, since the
+    // app might be seeking to a starting point that isn't the first key frame.
+    if (mLastRenderTimeUs == -1) {
+        return;
+    }
+
+    resetIfDiscontinuity(contentTimeUs, -1);
+
+    // Frames skipped at the end of playback shouldn't be counted as skipped frames, since the
+    // app could be terminating the playback. The pending count will be added to the metrics if and
+    // when the next frame is rendered.
+    mPendingSkippedFrameContentTimeUsList.push_back(contentTimeUs);
+}
+
+void VideoRenderQualityTracker::onFrameReleased(int64_t contentTimeUs) {
+    onFrameReleased(contentTimeUs, nowUs() * 1000);
+}
+
+void VideoRenderQualityTracker::onFrameReleased(int64_t contentTimeUs,
+                                                int64_t desiredRenderTimeNs) {
+    if (!mConfiguration.enabled) {
+        return;
+    }
+
+    int64_t desiredRenderTimeUs = desiredRenderTimeNs / 1000;
+    resetIfDiscontinuity(contentTimeUs, desiredRenderTimeUs);
+    mMetrics.frameReleasedCount++;
+    mNextExpectedRenderedFrameQueue.push({contentTimeUs, desiredRenderTimeUs});
+    mLastContentTimeUs = contentTimeUs;
+}
+
+void VideoRenderQualityTracker::onFrameRendered(int64_t contentTimeUs, int64_t actualRenderTimeNs,
+                                                FreezeEvent *freezeEventOut,
+                                                JudderEvent *judderEventOut) {
+    if (!mConfiguration.enabled) {
+        return;
+    }
+
+    int64_t actualRenderTimeUs = actualRenderTimeNs / 1000;
+
+    if (mLastRenderTimeUs != -1) {
+        mRenderDurationMs += (actualRenderTimeUs - mLastRenderTimeUs) / 1000;
+    }
+    // Now that a frame has been rendered, the previously skipped frames can be processed as skipped
+    // frames since the app is not skipping them to terminate playback.
+    for (int64_t contentTimeUs : mPendingSkippedFrameContentTimeUsList) {
+        processMetricsForSkippedFrame(contentTimeUs);
+    }
+    mPendingSkippedFrameContentTimeUsList = {};
+
+    // We can render a pending queued frame if it's the last frame of the video, so release it
+    // immediately.
+    if (contentTimeUs == mTunnelFrameQueuedContentTimeUs && mTunnelFrameQueuedContentTimeUs != -1) {
+        onFrameReleased(mTunnelFrameQueuedContentTimeUs, 0);
+        mTunnelFrameQueuedContentTimeUs = -1;
+    }
+
+    static const FrameInfo noFrame = {-1, -1};
+    FrameInfo nextExpectedFrame = noFrame;
+    while (!mNextExpectedRenderedFrameQueue.empty()) {
+        nextExpectedFrame = mNextExpectedRenderedFrameQueue.front();
+        mNextExpectedRenderedFrameQueue.pop();
+        // Happy path - the rendered frame is what we expected it to be
+        if (contentTimeUs == nextExpectedFrame.contentTimeUs) {
+            break;
+        }
+        // This isn't really supposed to happen - the next rendered frame should be the expected
+        // frame, or, if there's frame drops, it will be a frame later in the content stream
+        if (contentTimeUs < nextExpectedFrame.contentTimeUs) {
+            ALOGW("Rendered frame is earlier than the next expected frame (%lld, %lld)",
+                  (long long) contentTimeUs, (long long) nextExpectedFrame.contentTimeUs);
+            break;
+        }
+        processMetricsForDroppedFrame(nextExpectedFrame.contentTimeUs,
+                                      nextExpectedFrame.desiredRenderTimeUs);
+    }
+    processMetricsForRenderedFrame(nextExpectedFrame.contentTimeUs,
+                                   nextExpectedFrame.desiredRenderTimeUs, actualRenderTimeUs,
+                                   freezeEventOut, judderEventOut);
+    mLastRenderTimeUs = actualRenderTimeUs;
+}
+
+VideoRenderQualityTracker::FreezeEvent VideoRenderQualityTracker::getAndResetFreezeEvent() {
+    FreezeEvent event = std::move(mFreezeEvent);
+    mFreezeEvent.valid = false;
+    return event;
+}
+
+VideoRenderQualityTracker::JudderEvent VideoRenderQualityTracker::getAndResetJudderEvent() {
+    JudderEvent event = std::move(mJudderEvent);
+    mJudderEvent.valid = false;
+    return event;
+}
+
+const VideoRenderQualityMetrics &VideoRenderQualityTracker::getMetrics() {
+    if (!mConfiguration.enabled) {
+        return mMetrics;
+    }
+
+    mMetrics.freezeScore = 0;
+    if (mConfiguration.freezeDurationMsHistogramToScore.size() ==
+        mMetrics.freezeDurationMsHistogram.size()) {
+        for (int i = 0; i < mMetrics.freezeDurationMsHistogram.size(); ++i) {
+            mMetrics.freezeScore += mMetrics.freezeDurationMsHistogram[i] *
+                    mConfiguration.freezeDurationMsHistogramToScore[i];
+        }
+    }
+    mMetrics.freezeRate = float(double(mMetrics.freezeDurationMsHistogram.getSum()) /
+            mRenderDurationMs);
+
+    mMetrics.judderScore = 0;
+    if (mConfiguration.judderScoreHistogramToScore.size() == mMetrics.judderScoreHistogram.size()) {
+        for (int i = 0; i < mMetrics.judderScoreHistogram.size(); ++i) {
+            mMetrics.judderScore += mMetrics.judderScoreHistogram[i] *
+                    mConfiguration.judderScoreHistogramToScore[i];
+        }
+    }
+    mMetrics.judderRate = float(double(mMetrics.judderScoreHistogram.getCount()) /
+            (mMetrics.frameReleasedCount + mMetrics.frameSkippedCount));
+
+    return mMetrics;
+}
+
+void VideoRenderQualityTracker::clear() {
+    mRenderDurationMs = 0;
+    mMetrics.clear();
+    resetForDiscontinuity();
+}
+
+void VideoRenderQualityTracker::resetForDiscontinuity() {
+    mLastContentTimeUs = -1;
+    mLastRenderTimeUs = -1;
+    mLastFreezeEndTimeUs = -1;
+    mLastJudderEndTimeUs = -1;
+    mWasPreviousFrameDropped = false;
+    mFreezeEvent.valid = false;
+    mJudderEvent.valid = false;
+
+    // Don't worry about tracking frame rendering times from now up until playback catches up to the
+    // discontinuity. While stuttering or freezing could be found in the next few frames, the impact
+    // to the user is is minimal, so better to just keep things simple and don't bother.
+    mNextExpectedRenderedFrameQueue = {};
+    mTunnelFrameQueuedContentTimeUs = -1;
+
+    // Ignore any frames that were skipped just prior to the discontinuity.
+    mPendingSkippedFrameContentTimeUsList = {};
+
+    // All frame durations can be now ignored since all bets are off now on what the render
+    // durations should be after the discontinuity.
+    for (int i = 0; i < FrameDurationUs::SIZE; ++i) {
+        mActualFrameDurationUs[i] = -1;
+        mDesiredFrameDurationUs[i] = -1;
+        mContentFrameDurationUs[i] = -1;
+    }
+    mActualFrameDurationUs.priorTimestampUs = -1;
+    mDesiredFrameDurationUs.priorTimestampUs = -1;
+    mContentFrameDurationUs.priorTimestampUs = -1;
+}
+
+bool VideoRenderQualityTracker::resetIfDiscontinuity(int64_t contentTimeUs,
+                                                     int64_t desiredRenderTimeUs) {
+    if (mLastContentTimeUs == -1) {
+        resetForDiscontinuity();
+        return true;
+    }
+    if (contentTimeUs < mLastContentTimeUs) {
+        ALOGI("Video playback jumped %d ms backwards in content time (%d -> %d)",
+              int((mLastContentTimeUs - contentTimeUs) / 1000), int(mLastContentTimeUs / 1000),
+              int(contentTimeUs / 1000));
+        resetForDiscontinuity();
+        return true;
+    }
+    if (contentTimeUs - mLastContentTimeUs > mConfiguration.maxExpectedContentFrameDurationUs) {
+        // The content frame duration could be long due to frame drops for live content. This can be
+        // detected by looking at the app's desired rendering duration. If the app's rendered frame
+        // duration is roughly the same as the content's frame duration, then it is assumed that
+        // the forward discontinuity is due to frame drops for live content. A false positive can
+        // occur if the time the user spends seeking is equal to the duration of the seek. This is
+        // very unlikely to occur in practice but CAN occur - the user starts seeking forward, gets
+        // distracted, and then returns to seeking forward.
+        bool skippedForwardDueToLiveContentFrameDrops = false;
+        if (desiredRenderTimeUs != -1) {
+            int64_t contentFrameDurationUs = contentTimeUs - mLastContentTimeUs;
+            int64_t desiredFrameDurationUs = desiredRenderTimeUs - mLastRenderTimeUs;
+            skippedForwardDueToLiveContentFrameDrops =
+                    abs(contentFrameDurationUs - desiredFrameDurationUs) <
+                    mConfiguration.liveContentFrameDropToleranceUs;
+        }
+        if (!skippedForwardDueToLiveContentFrameDrops) {
+            ALOGI("Video playback jumped %d ms forward in content time (%d -> %d) ",
+                int((contentTimeUs - mLastContentTimeUs) / 1000), int(mLastContentTimeUs / 1000),
+                int(contentTimeUs / 1000));
+            resetForDiscontinuity();
+            return true;
+        }
+    }
+    return false;
+}
+
+void VideoRenderQualityTracker::processMetricsForSkippedFrame(int64_t contentTimeUs) {
+    mMetrics.frameSkippedCount++;
+    if (mConfiguration.areSkippedFramesDropped) {
+        processMetricsForDroppedFrame(contentTimeUs, -1);
+        return;
+    }
+    updateFrameDurations(mContentFrameDurationUs, contentTimeUs);
+    updateFrameDurations(mDesiredFrameDurationUs, -1);
+    updateFrameDurations(mActualFrameDurationUs, -1);
+    updateFrameRate(mMetrics.contentFrameRate, mContentFrameDurationUs, mConfiguration);
+    mWasPreviousFrameDropped = false;
+}
+
+void VideoRenderQualityTracker::processMetricsForDroppedFrame(int64_t contentTimeUs,
+                                                              int64_t desiredRenderTimeUs) {
+    mMetrics.frameDroppedCount++;
+    updateFrameDurations(mContentFrameDurationUs, contentTimeUs);
+    updateFrameDurations(mDesiredFrameDurationUs, desiredRenderTimeUs);
+    updateFrameDurations(mActualFrameDurationUs, -1);
+    updateFrameRate(mMetrics.contentFrameRate, mContentFrameDurationUs, mConfiguration);
+    updateFrameRate(mMetrics.desiredFrameRate, mDesiredFrameDurationUs, mConfiguration);
+    mWasPreviousFrameDropped = true;
+}
+
+void VideoRenderQualityTracker::processMetricsForRenderedFrame(int64_t contentTimeUs,
+                                                               int64_t desiredRenderTimeUs,
+                                                               int64_t actualRenderTimeUs,
+                                                               FreezeEvent *freezeEventOut,
+                                                               JudderEvent *judderEventOut) {
+    // Capture the timestamp at which the first frame was rendered
+    if (mMetrics.firstRenderTimeUs == 0) {
+        mMetrics.firstRenderTimeUs = actualRenderTimeUs;
+    }
+
+    mMetrics.frameRenderedCount++;
+
+    // The content time is -1 when it was rendered after a discontinuity (e.g. seek) was detected.
+    // So, even though a frame was rendered, it's impact on the user is insignificant, so don't do
+    // anything other than count it as a rendered frame.
+    if (contentTimeUs == -1) {
+        return;
+    }
+    updateFrameDurations(mContentFrameDurationUs, contentTimeUs);
+    updateFrameDurations(mDesiredFrameDurationUs, desiredRenderTimeUs);
+    updateFrameDurations(mActualFrameDurationUs, actualRenderTimeUs);
+    updateFrameRate(mMetrics.contentFrameRate, mContentFrameDurationUs, mConfiguration);
+    updateFrameRate(mMetrics.desiredFrameRate, mDesiredFrameDurationUs, mConfiguration);
+    updateFrameRate(mMetrics.actualFrameRate, mActualFrameDurationUs, mConfiguration);
+
+    // If the previous frame was dropped, there was a freeze if we've already rendered a frame
+    if (mWasPreviousFrameDropped && mLastRenderTimeUs != -1) {
+        processFreeze(actualRenderTimeUs, mLastRenderTimeUs, mLastFreezeEndTimeUs, mFreezeEvent,
+                      mMetrics, mConfiguration);
+        mLastFreezeEndTimeUs = actualRenderTimeUs;
+    }
+    maybeCaptureFreezeEvent(actualRenderTimeUs, mLastFreezeEndTimeUs, mFreezeEvent, mMetrics,
+                            mConfiguration, freezeEventOut);
+
+    // Judder is computed on the prior video frame, not the current video frame
+    int64_t judderScore = computePreviousJudderScore(mActualFrameDurationUs,
+                                                     mContentFrameDurationUs,
+                                                     mConfiguration);
+    if (judderScore != 0) {
+        int64_t judderTimeUs = actualRenderTimeUs - mActualFrameDurationUs[0] -
+                mActualFrameDurationUs[1];
+        processJudder(judderScore, judderTimeUs, mLastJudderEndTimeUs, mActualFrameDurationUs,
+                      mContentFrameDurationUs, mJudderEvent, mMetrics, mConfiguration);
+        mLastJudderEndTimeUs = judderTimeUs + mActualFrameDurationUs[1];
+    }
+    maybeCaptureJudderEvent(actualRenderTimeUs, mLastJudderEndTimeUs, mJudderEvent, mMetrics,
+                            mConfiguration, judderEventOut);
+
+    mWasPreviousFrameDropped = false;
+}
+
+void VideoRenderQualityTracker::processFreeze(int64_t actualRenderTimeUs, int64_t lastRenderTimeUs,
+                                              int64_t lastFreezeEndTimeUs, FreezeEvent &e,
+                                              VideoRenderQualityMetrics &m,
+                                              const Configuration &c) {
+    int32_t durationMs = int32_t((actualRenderTimeUs - lastRenderTimeUs) / 1000);
+    m.freezeDurationMsHistogram.insert(durationMs);
+    int32_t distanceMs = -1;
+    if (lastFreezeEndTimeUs != -1) {
+        // The distance to the last freeze is measured from the end of the last freze to the start
+        // of this freeze.
+        distanceMs = int32_t((lastRenderTimeUs - lastFreezeEndTimeUs) / 1000);
+        m.freezeDistanceMsHistogram.insert(distanceMs);
+    }
+    if (c.freezeEventMax > 0) {
+        if (e.valid == false) {
+            m.freezeEventCount++;
+            e.valid = true;
+            e.initialTimeUs = lastRenderTimeUs;
+            e.durationMs = 0;
+            e.sumDurationMs = 0;
+            e.sumDistanceMs = 0;
+            e.count = 0;
+            e.details.durationMs.clear();
+            e.details.distanceMs.clear();
+        // The first occurrence in the event should not have the distance recorded as part of the
+        // event, because it belongs in a vacuum between two events. However we still want the
+        // distance recorded in the details to calculate times in all details in all events.
+        } else if (distanceMs != -1) {
+            e.durationMs += distanceMs;
+            e.sumDistanceMs += distanceMs;
+        }
+        e.durationMs += durationMs;
+        e.count++;
+        e.sumDurationMs += durationMs;
+        if (e.details.durationMs.size() < c.freezeEventDetailsMax) {
+            e.details.durationMs.push_back(durationMs);
+            e.details.distanceMs.push_back(distanceMs); // -1 for first detail in the first event
+        }
+    }
+}
+
+void VideoRenderQualityTracker::maybeCaptureFreezeEvent(int64_t actualRenderTimeUs,
+                                                        int64_t lastFreezeEndTimeUs, FreezeEvent &e,
+                                                        const VideoRenderQualityMetrics & m,
+                                                        const Configuration &c,
+                                                        FreezeEvent *freezeEventOut) {
+    if (lastFreezeEndTimeUs == -1 || !e.valid) {
+        return;
+    }
+    // Future freeze occurrences are still pulled into the current freeze event if under tolerance
+    int64_t distanceMs = (actualRenderTimeUs - lastFreezeEndTimeUs) / 1000;
+    if (distanceMs < c.freezeEventDistanceToleranceMs) {
+        return;
+    }
+    if (freezeEventOut != nullptr && m.freezeEventCount <= c.freezeEventMax) {
+        *freezeEventOut = std::move(e);
+    }
+    // start recording a new freeze event after pushing the current one back to the caller
+    e.valid = false;
+}
+
+int64_t VideoRenderQualityTracker::computePreviousJudderScore(
+        const FrameDurationUs &actualFrameDurationUs,
+        const FrameDurationUs &contentFrameDurationUs,
+        const Configuration &c) {
+    // If the frame before or after was dropped, then don't generate a judder score, since any
+    // problems with frame drops are scored as a freeze instead.
+    if (actualFrameDurationUs[0] == -1 || actualFrameDurationUs[1] == -1 ||
+        actualFrameDurationUs[2] == -1) {
+        return 0;
+    }
+
+    // Don't score judder for when playback is paused or rebuffering (long frame duration), or if
+    // the player is intentionally playing each frame at a slow rate (e.g. half-rate). If the long
+    // frame duration was unintentional, it is assumed that this will be coupled with a later frame
+    // drop, and be scored as a freeze instead of judder.
+    if (actualFrameDurationUs[1] >= 2 * contentFrameDurationUs[1]) {
+        return 0;
+    }
+
+    // The judder score is based on the error of this frame
+    int64_t errorUs = actualFrameDurationUs[1] - contentFrameDurationUs[1];
+    // Don't score judder if the previous frame has high error, but this frame has low error
+    if (abs(errorUs) < c.judderErrorToleranceUs) {
+        return 0;
+    }
+
+    // Add a penalty if this frame has judder that amplifies the problem introduced by previous
+    // judder, instead of catching up for the previous judder (50, 16, 16, 50) vs (50, 16, 50, 16)
+    int64_t previousErrorUs = actualFrameDurationUs[2] - contentFrameDurationUs[2];
+    // Don't add the pentalty for errors from the previous frame if the previous frame has low error
+    if (abs(previousErrorUs) >= c.judderErrorToleranceUs) {
+        errorUs = abs(errorUs) + abs(errorUs + previousErrorUs);
+    }
+
+    // Avoid scoring judder for 3:2 pulldown or other minimally-small frame duration errors
+    if (abs(errorUs) < contentFrameDurationUs[1] / 4) {
+        return 0;
+    }
+
+    return abs(errorUs) / 1000; // error in millis to keep numbers small
+}
+
+void VideoRenderQualityTracker::processJudder(int32_t judderScore, int64_t judderTimeUs,
+                                              int64_t lastJudderEndTime,
+                                              const FrameDurationUs &actualDurationUs,
+                                              const FrameDurationUs &contentDurationUs,
+                                              JudderEvent &e, VideoRenderQualityMetrics &m,
+                                              const Configuration &c) {
+    int32_t distanceMs = -1;
+    if (lastJudderEndTime != -1) {
+        distanceMs = int32_t((judderTimeUs - lastJudderEndTime) / 1000);
+    }
+    m.judderScoreHistogram.insert(judderScore);
+    if (c.judderEventMax > 0) {
+        if (!e.valid) {
+            m.judderEventCount++;
+            e.valid = true;
+            e.initialTimeUs = judderTimeUs;
+            e.durationMs = 0;
+            e.sumScore = 0;
+            e.sumDistanceMs = 0;
+            e.count = 0;
+            e.details.contentRenderDurationUs.clear();
+            e.details.actualRenderDurationUs.clear();
+            e.details.distanceMs.clear();
+        // The first occurrence in the event should not have the distance recorded as part of the
+        // event, because it belongs in a vacuum between two events. However we still want the
+        // distance recorded in the details to calculate the times using all details in all events.
+        } else if (distanceMs != -1) {
+            e.durationMs += distanceMs;
+            e.sumDistanceMs += distanceMs;
+        }
+        e.durationMs += actualDurationUs[1] / 1000;
+        e.count++;
+        e.sumScore += judderScore;
+        if (e.details.contentRenderDurationUs.size() < c.judderEventDetailsMax) {
+            e.details.actualRenderDurationUs.push_back(actualDurationUs[1]);
+            e.details.contentRenderDurationUs.push_back(contentDurationUs[1]);
+            e.details.distanceMs.push_back(distanceMs); // -1 for first detail in the first event
+        }
+    }
+}
+
+void VideoRenderQualityTracker::maybeCaptureJudderEvent(int64_t actualRenderTimeUs,
+                                                        int64_t lastJudderEndTimeUs, JudderEvent &e,
+                                                        const VideoRenderQualityMetrics &m,
+                                                        const Configuration &c,
+                                                        JudderEvent *judderEventOut) {
+    if (lastJudderEndTimeUs == -1 || !e.valid) {
+        return;
+    }
+    // Future judder occurrences are still pulled into the current judder event if under tolerance
+    int64_t distanceMs = (actualRenderTimeUs - lastJudderEndTimeUs) / 1000;
+    if (distanceMs < c.judderEventDistanceToleranceMs) {
+        return;
+    }
+    if (judderEventOut != nullptr && m.judderEventCount <= c.judderEventMax) {
+        *judderEventOut = std::move(e);
+    }
+    // start recording a new judder event after pushing the current one back to the caller
+    e.valid = false;
+}
+
+void VideoRenderQualityTracker::configureHistograms(VideoRenderQualityMetrics &m,
+                                                    const Configuration &c) {
+    m.freezeDurationMsHistogram.setup(c.freezeDurationMsHistogramBuckets);
+    m.freezeDistanceMsHistogram.setup(c.freezeDistanceMsHistogramBuckets);
+    m.judderScoreHistogram.setup(c.judderScoreHistogramBuckets);
+}
+
+int64_t VideoRenderQualityTracker::nowUs() {
+    struct timespec t;
+    t.tv_sec = t.tv_nsec = 0;
+    clock_gettime(CLOCK_MONOTONIC, &t);
+    return (t.tv_sec * 1000000000LL + t.tv_nsec) / 1000LL;
+}
+
+void VideoRenderQualityTracker::updateFrameDurations(FrameDurationUs &durationUs,
+                                                     int64_t newTimestampUs) {
+    for (int i = FrameDurationUs::SIZE - 1; i > 0; --i ) {
+        durationUs[i] = durationUs[i - 1];
+    }
+    if (newTimestampUs == -1) {
+        durationUs[0] = -1;
+    } else {
+        durationUs[0] = durationUs.priorTimestampUs == -1 ? -1 :
+                newTimestampUs - durationUs.priorTimestampUs;
+        durationUs.priorTimestampUs = newTimestampUs;
+    }
+}
+
+void VideoRenderQualityTracker::updateFrameRate(float &frameRate, const FrameDurationUs &durationUs,
+                                                const Configuration &c) {
+    float newFrameRate = detectFrameRate(durationUs, c);
+    if (newFrameRate != FRAME_RATE_UNDETERMINED) {
+        frameRate = newFrameRate;
+    }
+}
+
+float VideoRenderQualityTracker::detectFrameRate(const FrameDurationUs &durationUs,
+                                                 const Configuration &c) {
+    // At least 3 frames are necessary to detect stable frame rates
+    assert(FrameDurationUs::SIZE >= 3);
+    if (durationUs[0] == -1 || durationUs[1] == -1 || durationUs[2] == -1) {
+        return FRAME_RATE_UNDETERMINED;
+    }
+    // Only determine frame rate if the render durations are stable across 3 frames
+    if (abs(durationUs[0] - durationUs[1]) > c.frameRateDetectionToleranceUs ||
+        abs(durationUs[0] - durationUs[2]) > c.frameRateDetectionToleranceUs) {
+        return is32pulldown(durationUs, c) ? FRAME_RATE_24_3_2_PULLDOWN : FRAME_RATE_UNDETERMINED;
+    }
+    return 1000.0 * 1000.0 / durationUs[0];
+}
+
+bool VideoRenderQualityTracker::is32pulldown(const FrameDurationUs &durationUs,
+                                             const Configuration &c) {
+    // At least 5 frames are necessary to detect stable 3:2 pulldown
+    assert(FrameDurationUs::SIZE >= 5);
+    if (durationUs[0] == -1 || durationUs[1] == -1 || durationUs[2] == -1 || durationUs[3] == -1 ||
+        durationUs[4] == -1) {
+        return false;
+    }
+    // 3:2 pulldown expects that every other frame has identical duration...
+    if (abs(durationUs[0] - durationUs[2]) > c.frameRateDetectionToleranceUs ||
+        abs(durationUs[1] - durationUs[3]) > c.frameRateDetectionToleranceUs ||
+        abs(durationUs[0] - durationUs[4]) > c.frameRateDetectionToleranceUs) {
+        return false;
+    }
+    // ... for either 2 vsysncs or 3 vsyncs
+    if ((abs(durationUs[0] - 33333) < c.frameRateDetectionToleranceUs &&
+         abs(durationUs[1] - 50000) < c.frameRateDetectionToleranceUs) ||
+        (abs(durationUs[0] - 50000) < c.frameRateDetectionToleranceUs &&
+         abs(durationUs[1] - 33333) < c.frameRateDetectionToleranceUs)) {
+        return true;
+    }
+    return false;
+}
+
+} // namespace android
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 3d4b6f8..52d7d3d 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -30,6 +30,9 @@
 #include <media/stagefright/foundation/AHandler.h>
 #include <media/stagefright/CodecErrorLog.h>
 #include <media/stagefright/FrameRenderTracker.h>
+#include <media/stagefright/MediaHistogram.h>
+#include <media/stagefright/PlaybackDurationAccumulator.h>
+#include <media/stagefright/VideoRenderQualityTracker.h>
 #include <utils/Vector.h>
 
 class C2Buffer;
@@ -63,7 +66,6 @@
 struct PersistentSurface;
 class SoftwareRenderer;
 class Surface;
-class PlaybackDurationAccumulator;
 namespace hardware {
 namespace cas {
 namespace native {
@@ -459,7 +461,7 @@
     void onGetMetrics(const sp<AMessage>& msg);
     constexpr const char *asString(TunnelPeekState state, const char *default_string="?");
     void updateTunnelPeek(const sp<AMessage> &msg);
-    void updatePlaybackDuration(const sp<AMessage> &msg);
+    void processRenderedFrames(const sp<AMessage> &msg);
 
     inline void initClientConfigParcel(ClientConfigParcel& clientConfig);
 
@@ -569,8 +571,9 @@
     sp<CryptoAsync> mCryptoAsync;
     sp<ALooper> mCryptoLooper;
 
-    std::unique_ptr<PlaybackDurationAccumulator> mPlaybackDurationAccumulator;
-    bool mIsSurfaceToScreen;
+    bool mIsSurfaceToDisplay;
+    PlaybackDurationAccumulator mPlaybackDurationAccumulator;
+    VideoRenderQualityTracker mVideoRenderQualityTracker;
 
     MediaCodec(
             const sp<ALooper> &looper, pid_t pid, uid_t uid,
@@ -712,31 +715,8 @@
     int mRecentHead;
     Mutex mRecentLock;
 
-    class Histogram {
-      public:
-        Histogram() : mFloor(0), mWidth(0), mBelow(0), mAbove(0),
-                      mMin(INT64_MAX), mMax(INT64_MIN), mSum(0), mCount(0),
-                      mBucketCount(0), mBuckets(NULL) {};
-        ~Histogram() { clear(); };
-        void clear() { if (mBuckets != NULL) free(mBuckets); mBuckets = NULL; };
-        bool setup(int nbuckets, int64_t width, int64_t floor = 0);
-        void insert(int64_t sample);
-        int64_t getMin() const { return mMin; }
-        int64_t getMax() const { return mMax; }
-        int64_t getCount() const { return mCount; }
-        int64_t getSum() const { return mSum; }
-        int64_t getAvg() const { return mSum / (mCount == 0 ? 1 : mCount); }
-        std::string emit();
-      private:
-        int64_t mFloor, mCeiling, mWidth;
-        int64_t mBelow, mAbove;
-        int64_t mMin, mMax, mSum, mCount;
+    MediaHistogram<int64_t> mLatencyHist;
 
-        int mBucketCount;
-        int64_t *mBuckets;
-    };
-
-    Histogram mLatencyHist;
     // An unique ID for the codec - Used by the metrics.
     uint64_t mCodecId = 0;
 
diff --git a/media/libstagefright/include/media/stagefright/MediaHistogram.h b/media/libstagefright/include/media/stagefright/MediaHistogram.h
new file mode 100644
index 0000000..50fa258
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/MediaHistogram.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2023, 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 MEDIA_HISTOGRAM_H_
+#define MEDIA_HISTOGRAM_H_
+
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace android {
+
+template<typename T>
+class MediaHistogram {
+public:
+    MediaHistogram();
+    void clear();
+    bool setup(int bucketCount, T width, T floor = 0);
+    bool setup(const std::vector<T> &bucketLimits);
+    void insert(T sample);
+    size_t size();
+    int64_t operator[](int);
+    T getMin() const { return mMin; }
+    T getMax() const { return mMax; }
+    T getCount() const { return mCount; }
+    T getSum() const { return mSum; }
+    T getAvg() const { return mSum / (mCount == 0 ? 1 : mCount); }
+    T getPercentile(int) const;
+    std::string emit() const;
+    std::string emitBuckets() const;
+private:
+    MediaHistogram(const MediaHistogram &); // disallow
+
+    bool allocate(int bucketCount, bool withBucketLimits);
+
+    T mFloor, mCeiling, mWidth;
+    T mMin, mMax, mSum;
+    int64_t mBelow, mAbove, mCount;
+    std::vector<T> mBuckets;
+    std::vector<T> mBucketLimits;
+};
+
+template<typename T>
+MediaHistogram<T>::MediaHistogram() {
+    mWidth = mCeiling = mFloor = -1;
+    clear();
+}
+
+template<typename T>
+void MediaHistogram<T>::clear() {
+    for (int i = 0; i < mBuckets.size(); ++i) {
+        mBuckets[i] = 0;
+    }
+    mMin = std::numeric_limits<T>::max();
+    mMax = std::numeric_limits<T>::min();
+    mSum = 0;
+    mCount = 0;
+    mBelow = mAbove = 0;
+}
+
+template<typename T>
+bool MediaHistogram<T>::setup(int bucketCount, T width, T floor) {
+    if (bucketCount <= 0 || width <= 0) {
+        return false;
+    }
+    if (!allocate(bucketCount, false)) {
+        return false;
+    }
+    mWidth = width;
+    mFloor = floor;
+    mCeiling = floor + bucketCount * width;
+    clear();
+    return true;
+}
+
+template<typename T>
+bool MediaHistogram<T>::setup(const std::vector<T> &bucketLimits) {
+    if (bucketLimits.size() <= 1) {
+        return false;
+    }
+    int bucketCount = bucketLimits.size() - 1;
+    if (!allocate(bucketCount, true)) {
+        return false;
+    }
+
+    mWidth = -1;
+    mFloor = bucketLimits[0];
+    for (int i = 0; i < bucketCount; ++i) {
+        mBucketLimits[i] = bucketLimits[i + 1];
+    }
+    mCeiling = bucketLimits[bucketCount];
+    clear();
+    return true;
+}
+
+template<typename T>
+bool MediaHistogram<T>::allocate(int bucketCount, bool withBucketLimits) {
+    assert(bucketCount > 0);
+    if (bucketCount != mBuckets.size()) {
+        mBuckets = std::vector<T>(bucketCount, 0);
+    }
+    if (withBucketLimits && mBucketLimits.size() != bucketCount) {
+        mBucketLimits = std::vector<T>(bucketCount, 0);
+    }
+    return true;
+}
+
+template<typename T>
+void MediaHistogram<T>::insert(T sample) {
+    // histogram is not set up
+    if (mBuckets.size() == 0) {
+        return;
+    }
+
+    mCount++;
+    mSum += sample;
+    if (mMin > sample) mMin = sample;
+    if (mMax < sample) mMax = sample;
+
+    if (sample < mFloor) {
+        mBelow++;
+    } else if (sample >= mCeiling) {
+        mAbove++;
+    } else if (mWidth == -1) {
+        // A binary search might be more efficient for large number of buckets, but it is expected
+        // that there will never be a large amount of buckets, so keep the code simple.
+        for (int slot = 0; slot < mBucketLimits.size(); ++slot) {
+            if (sample < mBucketLimits[slot]) {
+                mBuckets[slot]++;
+                break;
+            }
+        }
+    } else {
+        int64_t slot = (sample - mFloor) / mWidth;
+        assert(slot < mBuckets.size());
+        mBuckets[slot]++;
+    }
+    return;
+}
+
+template<typename T>
+size_t MediaHistogram<T>::size() {
+    return mBuckets.size() + 1;
+}
+
+template<typename T>
+int64_t MediaHistogram<T>::operator[](int i) {
+    assert(i >= 0);
+    assert(i <= mBuckets.size());
+    if (i == mBuckets.size()) {
+        return mAbove;
+    }
+    return mBuckets[i];
+}
+
+template<typename T>
+std::string MediaHistogram<T>::emit() const {
+    // emits:  floor,width,below{bucket0,bucket1,...., bucketN}above
+    // or.. emits:  below{bucket0,bucket1,...., bucketN}above
+    // unconfigured will emit: 0{}0
+    // XXX: is this best representation?
+    std::stringstream ss("");
+    if (mWidth == -1) {
+        ss << mBelow << "{";
+    } else {
+        ss << mFloor << "," << mWidth << "," << mBelow << "{";
+    }
+    for (int i = 0; i < mBuckets.size(); i++) {
+        if (i != 0) {
+            ss << ",";
+        }
+        ss << mBuckets[i];
+    }
+    ss << "}" << mAbove;
+    return ss.str();
+}
+
+template<typename T>
+std::string MediaHistogram<T>::emitBuckets() const {
+    std::stringstream ss("");
+    if (mWidth == -1) {
+        ss << mFloor;
+        for (int i = 0; i < mBucketLimits.size(); ++i) {
+            ss << ',' << mBucketLimits[i];
+        }
+    } else {
+        ss << mFloor;
+        for (int i = 1; i <= mBuckets.size(); ++i) {
+            ss << ',' << (mFloor + i * mWidth);
+        }
+    }
+    return ss.str();
+}
+
+} // android
+
+#endif // MEDIA_HISTOGRAM_H_
\ No newline at end of file
diff --git a/media/libstagefright/PlaybackDurationAccumulator.h b/media/libstagefright/include/media/stagefright/PlaybackDurationAccumulator.h
similarity index 95%
rename from media/libstagefright/PlaybackDurationAccumulator.h
rename to media/libstagefright/include/media/stagefright/PlaybackDurationAccumulator.h
index cb5f0c4..bdf1171 100644
--- a/media/libstagefright/PlaybackDurationAccumulator.h
+++ b/media/libstagefright/include/media/stagefright/PlaybackDurationAccumulator.h
@@ -33,7 +33,7 @@
     }
 
     // Process a render time expressed in nanoseconds.
-    void processRenderTime(int64_t newRenderTimeNs) {
+    void onFrameRendered(int64_t newRenderTimeNs) {
         // If we detect wrap-around or out of order frames, just ignore the duration for this
         // and the next frame.
         if (newRenderTimeNs < mPreviousRenderTimeNs) {
@@ -59,7 +59,7 @@
     int64_t mPreviousRenderTimeNs;
 };
 
-}
+} // android
 
-#endif
+#endif // PLAYBACK_DURATION_ACCUMULATOR_H_
 
diff --git a/media/libstagefright/include/media/stagefright/VideoRenderQualityTracker.h b/media/libstagefright/include/media/stagefright/VideoRenderQualityTracker.h
new file mode 100644
index 0000000..82ba81c
--- /dev/null
+++ b/media/libstagefright/include/media/stagefright/VideoRenderQualityTracker.h
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2023, 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 VIDEO_RENDER_QUALITY_TRACKER_H_
+
+#define VIDEO_RENDER_QUALITY_TRACKER_H_
+
+#include <assert.h>
+#include <list>
+#include <queue>
+
+#include <media/stagefright/MediaHistogram.h>
+
+namespace android {
+
+// A variety of video rendering quality metrics.
+struct VideoRenderQualityMetrics {
+    static constexpr float FRAME_RATE_UNDETERMINED = -1.0f;
+    static constexpr float FRAME_RATE_24_3_2_PULLDOWN = -2.0f;
+
+    VideoRenderQualityMetrics();
+
+    void clear();
+
+    // The render time of the first video frame.
+    int64_t firstRenderTimeUs;
+
+    // The number of frames released to be rendered.
+    int64_t frameReleasedCount;
+
+    // The number of frames actually rendered.
+    int64_t frameRenderedCount;
+
+    // The number of frames dropped - frames that were released but never rendered.
+    int64_t frameDroppedCount;
+
+    // The number of frames that were intentionally dropped/skipped by the app.
+    int64_t frameSkippedCount;
+
+    // The frame rate as detected by looking at the position timestamp from the content stream.
+    float contentFrameRate;
+
+    // The frame rate as detected by looking at the desired render time passed in by the app.
+    float desiredFrameRate;
+
+    // The frame rate as detected by looking at the actual render time, as returned by the system
+    // post-render.
+    float actualFrameRate;
+
+    // A histogram of the durations of freezes due to dropped/skipped frames.
+    MediaHistogram<int32_t> freezeDurationMsHistogram;
+    // The computed overall freeze score using the above histogram and score conversion table. The
+    // score is based on counts in the histogram bucket, multiplied by the value in the score
+    // conversion table for that bucket. For example, the impact of a short freeze may be minimal,
+    // but the impact of long freeze may be disproportionally worse. Therefore, the score
+    // multipliers for each bucket might increase exponentially instead of linearly. A score
+    // multiplier of zero would reflect that small freeze durations have near-zero impact to the
+    // user experience.
+    int32_t freezeScore;
+    // The computed percentage of total playback duration that was frozen.
+    float freezeRate;
+    // The number of freeze events.
+    int32_t freezeEventCount;
+
+    // A histogram of the durations between each freeze.
+    MediaHistogram<int32_t> freezeDistanceMsHistogram;
+
+    // A histogram of the judder scores - based on the error tolerance between actual render
+    // duration of each frame and the ideal render duration.
+    MediaHistogram<int32_t> judderScoreHistogram;
+    // The computed overall judder score using the above histogram and score conversion table. The
+    // score is based on counts in the histogram bucket, multiplied by the value in the score
+    // conversion table for that bucket. For example, the impact of minimal judder may be small,
+    // but the impact of large judder may be disproportionally worse. Therefore, the score
+    // multipliers for each bucket might increase exponentially instead of linearly. A score
+    // multiplier of zero would reflect that small judder errors have near-zero impact to the user
+    // experience.
+    int32_t judderScore;
+    // The computed percentage of total frames that had judder.
+    float judderRate;
+    // The number of judder events.
+    int32_t judderEventCount;
+};
+
+///////////////////////////////////////////////////////
+// This class analyzes various timestamps related to video rendering to compute a set of metrics
+// that attempt to capture the quality of the user experience during video playback.
+//
+// The following timestamps (in microseconds) are analyzed to compute these metrics:
+//   * The content timestamp found in the content stream, indicating the position of each video
+//     frame.
+//   * The desired timestamp passed in by the app, indicating at what point in time in the future
+//     the app would like the frame to be rendered.
+//   * The actual timestamp passed in by the display subsystem, indicating the point in time at
+//     which the frame was actually rendered.
+//
+// Core to the algorithms are deriving frame durations based on these timestamps and determining
+// the result of each video frame in the content stream:
+//   * skipped: the app didn't want to render the frame
+//   * dropped: the display subsystem could not render the frame in time
+//   * rendered: the display subsystem rendered the frame
+//
+class VideoRenderQualityTracker {
+public:
+    // Configurable elements of the metrics algorithms
+    class Configuration {
+    public:
+        // system/server_configurable_flags/libflags/include/get_flags.h:GetServerConfigurableFlag
+        typedef std::string (*GetServerConfigurableFlagFn)(
+                const std::string& experiment_category_name,
+                const std::string& experiment_flag_name,
+                const std::string& default_value);
+
+        static Configuration getFromServerConfigurableFlags(
+                GetServerConfigurableFlagFn getServerConfigurableFlagFn);
+
+        Configuration();
+
+        // Whether or not frame render quality is tracked.
+        bool enabled;
+
+        // Whether or not frames that are intentionally not rendered by the app should be considered
+        // as dropped.
+        bool areSkippedFramesDropped;
+
+        // How large of a jump forward in content time is allowed before it is considered a
+        // discontinuity (seek/playlist) and various internal states are reset.
+        int32_t maxExpectedContentFrameDurationUs;
+
+        // How much tolerance in frame duration when considering whether or not two frames have the
+        // same frame rate.
+        int32_t frameRateDetectionToleranceUs;
+
+        // A skip forward in content time could occur during frame drops of live content. Therefore
+        // the content frame duration and the app-desired frame duration are compared using this
+        // tolerance to determine whether the app is intentionally seeking forward or whether the
+        // skip forward in content time is due to frame drops. If the app-desired frame duration is
+        // short, but the content frame duration is large, it is assumed the app is intentionally
+        // seeking forward.
+        int32_t liveContentFrameDropToleranceUs;
+
+        // Freeze configuration
+        //
+        // The values used to distribute freeze durations across a histogram.
+        std::vector<int32_t> freezeDurationMsHistogramBuckets;
+        //
+        // The values used to multiply the counts in the histogram buckets above to compute an
+        // overall score. This allows the score to reflect disproportionate impact as freeze
+        // durations increase.
+        std::vector<int64_t> freezeDurationMsHistogramToScore;
+        //
+        // The values used to distribute distances between freezes across a histogram.
+        std::vector<int32_t> freezeDistanceMsHistogramBuckets;
+        //
+        // The maximum number of freeze events to send back to the caller.
+        int32_t freezeEventMax;
+        //
+        // The maximum number of detail entries tracked per freeze event.
+        int32_t freezeEventDetailsMax;
+        //
+        // The maximum distance in time between two freeze occurrences such that both will be
+        // lumped into the same freeze event.
+        int32_t freezeEventDistanceToleranceMs;
+
+        // Judder configuration
+        //
+        // A judder error lower than this value is not scored as judder.
+        int32_t judderErrorToleranceUs;
+        //
+        // The values used to distribute judder scores across a histogram.
+        std::vector<int32_t> judderScoreHistogramBuckets;
+        //
+        // The values used to multiply the counts in the histogram buckets above to compute an
+        // overall score. This allows the score to reflect disproportionate impact as judder scores
+        // increase.
+        std::vector<int64_t> judderScoreHistogramToScore;
+        //
+        // The maximum number of judder events to send back to the caller.
+        int32_t judderEventMax;
+        //
+        // The maximum number of detail entries tracked per judder event.
+        int32_t judderEventDetailsMax;
+        //
+        // The maximum distance in time between two judder occurrences such that both will be
+        // lumped into the same judder event.
+        int32_t judderEventDistanceToleranceMs;
+    };
+
+    struct FreezeEvent {
+        // Details are captured for each freeze up to a limited number. The arrays are guaranteed to
+        // have the same size.
+        struct Details {
+            /// The duration of the freeze.
+            std::vector<int32_t> durationMs;
+            // The distance between the beginning of this freeze and the end of the previous freeze.
+            std::vector<int32_t> distanceMs;
+        };
+        // Whether or not the data in this structure is valid.
+        bool valid = false;
+        // The time at which the first freeze for this event was detected.
+        int64_t initialTimeUs;
+        // The total duration from the beginning of the first freeze to the end of the last freeze
+        // in this event.
+        int32_t durationMs;
+        // The number of freezes in this event.
+        int64_t count;
+        // The sum of all durations of all freezes in this event.
+        int64_t sumDurationMs;
+        // The sum of all distances between each freeze in this event.
+        int64_t sumDistanceMs;
+        // Detailed information for the first N freezes in this event.
+        Details details;
+    };
+
+    struct JudderEvent {
+        // Details are captured for each frame judder up to a limited number. The arrays are
+        // guaranteed to have the same size.
+        struct Details {
+            // The actual render duration of the frame for this judder occurrence.
+            std::vector<int32_t> actualRenderDurationUs;
+            // The content render duration of the frame for this judder occurrence.
+            std::vector<int32_t> contentRenderDurationUs;
+            // The distance from this judder occurrence and the previous judder occurrence.
+            std::vector<int32_t> distanceMs;
+        };
+        // Whether or not the data in this structure is valid.
+        bool valid = false;
+        // The time at which the first judder occurrence for this event was detected.
+        int64_t initialTimeUs;
+        // The total duration from the first judder occurrence to the last judder occurrence in this
+        // event.
+        int32_t durationMs;
+        // The number of judder occurrences in this event.
+        int64_t count;
+        // The sum of all judder scores in this event.
+        int64_t sumScore;
+        // The sum of all distances between each judder occurrence in this event.
+        int64_t sumDistanceMs;
+        // Detailed information for the first N judder occurrences in this event.
+        Details details;
+    };
+
+    VideoRenderQualityTracker();
+    VideoRenderQualityTracker(const Configuration &configuration);
+
+    // Called when a tunnel mode frame has been queued.
+    void onTunnelFrameQueued(int64_t contentTimeUs);
+
+    // Called when the app has intentionally decided not to render this frame.
+    void onFrameSkipped(int64_t contentTimeUs);
+
+    // Called when the app has requested the frame to be rendered as soon as possible.
+    void onFrameReleased(int64_t contentTimeUs);
+
+    // Called when the app has requested the frame to be rendered at a specific point in time in the
+    // future.
+    void onFrameReleased(int64_t contentTimeUs, int64_t desiredRenderTimeNs);
+
+    // Called when the system has detected that the frame has actually been rendered to the display.
+    // Returns any freeze events or judder events that were detected.
+    void onFrameRendered(int64_t contentTimeUs, int64_t actualRenderTimeNs,
+                         FreezeEvent *freezeEventOut = nullptr,
+                         JudderEvent *judderEventOut = nullptr);
+
+    // Gets and resets data for the current freeze event.
+    FreezeEvent getAndResetFreezeEvent();
+
+    // Gets and resets data for the current judder event.
+    JudderEvent getAndResetJudderEvent();
+
+    // Retrieve the metrics.
+    const VideoRenderQualityMetrics &getMetrics();
+
+    // Called when a change in codec state will result in a content discontinuity - e.g. flush.
+    void resetForDiscontinuity();
+
+    // Clear out all metrics and tracking - e.g. codec reconfigured.
+    void clear();
+
+private:
+    // Tracking of frames that are pending to be rendered to the display.
+    struct FrameInfo {
+        int64_t contentTimeUs;
+        int64_t desiredRenderTimeUs;
+    };
+
+    // Historic tracking of frame durations
+    struct FrameDurationUs {
+        static const int SIZE = 5;
+
+        FrameDurationUs() {
+            for (int i = 0; i < SIZE; ++i) {
+                durationUs[i] = -1;
+            }
+            priorTimestampUs = -1;
+        }
+
+        int32_t &operator[](int index) {
+            assert(index < SIZE);
+            return durationUs[index];
+        }
+
+        const int32_t &operator[](int index) const {
+            assert(index < SIZE);
+            return durationUs[index];
+        }
+
+        // The duration of the past N frames.
+        int32_t durationUs[SIZE];
+
+        // The timestamp of the previous frame.
+        int64_t priorTimestampUs;
+    };
+
+    // Configure histograms for the metrics.
+    static void configureHistograms(VideoRenderQualityMetrics &m, const Configuration &c);
+
+    // The current time in microseconds.
+    static int64_t nowUs();
+
+    // A new frame has been processed, so update the frame durations based on the new frame
+    // timestamp.
+    static void updateFrameDurations(FrameDurationUs &durationUs, int64_t newTimestampUs);
+
+    // Update a frame rate if, and only if, one can be detected.
+    static void updateFrameRate(float &frameRate, const FrameDurationUs &durationUs,
+                                const Configuration &c);
+
+    // Examine the past few frames to detect the frame rate based on each frame's render duration.
+    static float detectFrameRate(const FrameDurationUs &durationUs, const Configuration &c);
+
+    // Determine whether or not 3:2 pulldowng for displaying 24fps content on 60Hz displays is
+    // occurring.
+    static bool is32pulldown(const FrameDurationUs &durationUs, const Configuration &c);
+
+    // Process a frame freeze.
+    static void processFreeze(int64_t actualRenderTimeUs, int64_t lastRenderTimeUs,
+                              int64_t lastFreezeEndTimeUs, FreezeEvent &e,
+                              VideoRenderQualityMetrics &m, const Configuration &c);
+
+    // Retrieve a freeze event if an event just finished.
+    static void maybeCaptureFreezeEvent(int64_t actualRenderTimeUs, int64_t lastFreezeEndTimeUs,
+                                        FreezeEvent &e, const VideoRenderQualityMetrics & m,
+                                        const Configuration &c, FreezeEvent *freezeEventOut);
+
+    // Compute a judder score for the previously-rendered frame.
+    static int64_t computePreviousJudderScore(const FrameDurationUs &actualRenderDurationUs,
+                                              const FrameDurationUs &contentRenderDurationUs,
+                                              const Configuration &c);
+
+    // Process a frame judder.
+    static void processJudder(int32_t judderScore, int64_t judderTimeUs,
+                              int64_t lastJudderEndTimeUs,
+                              const FrameDurationUs &contentDurationUs,
+                              const FrameDurationUs &actualDurationUs, JudderEvent &e,
+                              VideoRenderQualityMetrics &m, const Configuration &c);
+
+    // Retrieve a judder event if an event just finished.
+    static void maybeCaptureJudderEvent(int64_t actualRenderTimeUs, int64_t lastJudderEndTimeUs,
+                                        JudderEvent &e, const VideoRenderQualityMetrics & m,
+                                        const Configuration &c, JudderEvent *judderEventOut);
+
+    // Check to see if a discontinuity has occurred by examining the content time and the
+    // app-desired render time. If so, reset some internal state.
+    bool resetIfDiscontinuity(int64_t contentTimeUs, int64_t desiredRenderTimeUs);
+
+    // Update the metrics because a skipped frame was detected.
+    void processMetricsForSkippedFrame(int64_t contentTimeUs);
+
+    // Update the metrics because a dropped frame was detected.
+    void processMetricsForDroppedFrame(int64_t contentTimeUs, int64_t desiredRenderTimeUs);
+
+    // Update the metrics because a rendered frame was detected.
+    void processMetricsForRenderedFrame(int64_t contentTimeUs, int64_t desiredRenderTimeUs,
+                                        int64_t actualRenderTimeUs,
+                                        FreezeEvent *freezeEventOut, JudderEvent *judderEventOut);
+
+    // Configurable elements of the metrics algorithms.
+    const Configuration mConfiguration;
+
+    // Metrics are updated every time a frame event occurs - skipped, dropped, rendered.
+    VideoRenderQualityMetrics mMetrics;
+
+    // The most recently processed timestamp referring to the position in the content stream.
+    int64_t mLastContentTimeUs;
+
+    // The most recently processed timestamp referring to the wall clock time a frame was rendered.
+    int64_t mLastRenderTimeUs;
+
+    // The most recent timestamp of the first frame rendered after the freeze.
+    int64_t mLastFreezeEndTimeUs;
+
+    // The most recent timestamp of frame judder.
+    int64_t mLastJudderEndTimeUs;
+
+    // The render duration of the playback.
+    int64_t mRenderDurationMs;
+
+    // True if the previous frame was dropped.
+    bool mWasPreviousFrameDropped;
+
+    // The freeze event that's currently being tracked.
+    FreezeEvent mFreezeEvent;
+
+    // The judder event that's currently being tracked.
+    JudderEvent mJudderEvent;
+
+    // Frames skipped at the end of playback shouldn't really be considered skipped, therefore keep
+    // a list of the frames, and process them as skipped frames the next time a frame is rendered.
+    std::list<int64_t> mPendingSkippedFrameContentTimeUsList;
+
+    // Since the system only signals when a frame is rendered, dropped frames are detected by
+    // checking to see if the next expected frame is rendered. If not, it is considered dropped.
+    std::queue<FrameInfo> mNextExpectedRenderedFrameQueue;
+
+    // When B-frames are present in the stream, a P-frame will be queued before the B-frame even
+    // though it is rendered after. Therefore, the P-frame is held here and not inserted into
+    // mNextExpectedRenderedFrameQueue until it should be inserted to maintain render order.
+    int64_t mTunnelFrameQueuedContentTimeUs;
+
+    // Frame durations derived from timestamps encoded into the content stream. These are the
+    // durations that each frame is supposed to be rendered for.
+    FrameDurationUs mContentFrameDurationUs;
+
+    // Frame durations derived from timestamps passed in by the app, indicating the wall clock time
+    // at which the app would like to have the frame rendered.
+    FrameDurationUs mDesiredFrameDurationUs;
+
+    // Frame durations derived from timestamps captured by the display subsystem, indicating the
+    // wall clock atime at which the frame is actually rendered.
+    FrameDurationUs mActualFrameDurationUs;
+};
+
+}  // namespace android
+
+#endif  // VIDEO_RENDER_QUALITY_TRACKER_H_
diff --git a/media/libstagefright/tests/Android.bp b/media/libstagefright/tests/Android.bp
index e6b67ce..581292e 100644
--- a/media/libstagefright/tests/Android.bp
+++ b/media/libstagefright/tests/Android.bp
@@ -55,3 +55,20 @@
         "-Wall",
     ],
 }
+
+cc_test {
+    name: "VideoRenderQualityTracker_test",
+    srcs: ["VideoRenderQualityTracker_test.cpp"],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libstagefright",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+}
diff --git a/media/libstagefright/tests/VideoRenderQualityTracker_test.cpp b/media/libstagefright/tests/VideoRenderQualityTracker_test.cpp
new file mode 100644
index 0000000..7823922
--- /dev/null
+++ b/media/libstagefright/tests/VideoRenderQualityTracker_test.cpp
@@ -0,0 +1,1027 @@
+/*
+ * Copyright 2023 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 "VideoRenderQualityTracker_test"
+#include <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include <media/stagefright/VideoRenderQualityTracker.h>
+
+namespace android {
+
+using Metrics = VideoRenderQualityMetrics;
+using Configuration = VideoRenderQualityTracker::Configuration;
+using FreezeEvent = VideoRenderQualityTracker::FreezeEvent;
+using JudderEvent = VideoRenderQualityTracker::JudderEvent;
+
+static constexpr float FRAME_RATE_UNDETERMINED = VideoRenderQualityMetrics::FRAME_RATE_UNDETERMINED;
+static constexpr float FRAME_RATE_24_3_2_PULLDOWN =
+        VideoRenderQualityMetrics::FRAME_RATE_24_3_2_PULLDOWN;
+
+class Helper {
+public:
+    Helper(double contentFrameDurationMs, const Configuration &configuration) :
+            mVideoRenderQualityTracker(configuration) {
+        mContentFrameDurationUs = int64_t(contentFrameDurationMs * 1000);
+        mMediaTimeUs = 0;
+        mClockTimeNs = 0;
+    }
+
+    void changeContentFrameDuration(double contentFrameDurationMs) {
+        mContentFrameDurationUs = int64_t(contentFrameDurationMs * 1000);
+    }
+
+    template<typename T>
+    void render(std::initializer_list<T> renderDurationMsList) {
+        for (auto renderDurationMs : renderDurationMsList) {
+            mVideoRenderQualityTracker.onFrameReleased(mMediaTimeUs);
+            mVideoRenderQualityTracker.onFrameRendered(mMediaTimeUs, mClockTimeNs, &mFreezeEvent,
+                                                       &mJudderEvent);
+            mMediaTimeUs += mContentFrameDurationUs;
+            mClockTimeNs += int64_t(renderDurationMs * 1000 * 1000);
+        }
+    }
+
+    void render(int numFrames, float durationMs = -1) {
+        int64_t durationUs = durationMs < 0 ? mContentFrameDurationUs : durationMs * 1000;
+        for (int i = 0; i < numFrames; ++i) {
+            mVideoRenderQualityTracker.onFrameReleased(mMediaTimeUs);
+            mVideoRenderQualityTracker.onFrameRendered(mMediaTimeUs, mClockTimeNs, &mFreezeEvent,
+                                                       &mJudderEvent);
+            mMediaTimeUs += mContentFrameDurationUs;
+            mClockTimeNs += durationUs * 1000;
+        }
+    }
+
+    void skip(int numFrames) {
+        for (int i = 0; i < numFrames; ++i) {
+            mVideoRenderQualityTracker.onFrameSkipped(mMediaTimeUs);
+            mMediaTimeUs += mContentFrameDurationUs;
+            mClockTimeNs += mContentFrameDurationUs * 1000;
+        }
+    }
+
+    void drop(int numFrames) {
+        for (int i = 0; i < numFrames; ++i) {
+            mVideoRenderQualityTracker.onFrameReleased(mMediaTimeUs);
+            mMediaTimeUs += mContentFrameDurationUs;
+            mClockTimeNs += mContentFrameDurationUs * 1000;
+        }
+    }
+
+    const Metrics & getMetrics() {
+        return mVideoRenderQualityTracker.getMetrics();
+    }
+
+    FreezeEvent getAndClearFreezeEvent() {
+        FreezeEvent e = std::move(mFreezeEvent);
+        mFreezeEvent.valid = false;
+        return e;
+    }
+
+    JudderEvent getAndClearJudderEvent() {
+        JudderEvent e = std::move(mJudderEvent);
+        mJudderEvent.valid = false;
+        return e;
+    }
+
+private:
+    VideoRenderQualityTracker mVideoRenderQualityTracker;
+    int64_t mContentFrameDurationUs;
+    int64_t mMediaTimeUs;
+    int64_t mClockTimeNs;
+    VideoRenderQualityTracker::FreezeEvent mFreezeEvent;
+    VideoRenderQualityTracker::JudderEvent mJudderEvent;
+};
+
+class VideoRenderQualityTrackerTest : public ::testing::Test {
+public:
+    VideoRenderQualityTrackerTest() {}
+};
+
+TEST_F(VideoRenderQualityTrackerTest, getFromServerConfigurableFlags_withDefaults) {
+    Configuration::GetServerConfigurableFlagFn getServerConfigurableFlagFn =
+        [](const std::string &, const std::string &, const std::string &defaultStr) -> std::string {
+            return defaultStr;
+        };
+
+    Configuration c = Configuration::getFromServerConfigurableFlags(getServerConfigurableFlagFn);
+    Configuration d; // default configuration
+    EXPECT_EQ(c.enabled, d.enabled);
+    EXPECT_EQ(c.areSkippedFramesDropped, d.areSkippedFramesDropped);
+    EXPECT_EQ(c.maxExpectedContentFrameDurationUs, d.maxExpectedContentFrameDurationUs);
+    EXPECT_EQ(c.frameRateDetectionToleranceUs, d.frameRateDetectionToleranceUs);
+    EXPECT_EQ(c.liveContentFrameDropToleranceUs, d.liveContentFrameDropToleranceUs);
+    EXPECT_EQ(c.freezeDurationMsHistogramBuckets, d.freezeDurationMsHistogramBuckets);
+    EXPECT_EQ(c.freezeDurationMsHistogramToScore, d.freezeDurationMsHistogramToScore);
+    EXPECT_EQ(c.freezeDistanceMsHistogramBuckets, d.freezeDistanceMsHistogramBuckets);
+    EXPECT_EQ(c.freezeEventMax, d.freezeEventMax);
+    EXPECT_EQ(c.freezeEventDetailsMax, d.freezeEventDetailsMax);
+    EXPECT_EQ(c.freezeEventDistanceToleranceMs, d.freezeEventDistanceToleranceMs);
+    EXPECT_EQ(c.judderErrorToleranceUs, d.judderErrorToleranceUs);
+    EXPECT_EQ(c.judderScoreHistogramBuckets, d.judderScoreHistogramBuckets);
+    EXPECT_EQ(c.judderScoreHistogramToScore, d.judderScoreHistogramToScore);
+    EXPECT_EQ(c.judderEventMax, d.judderEventMax);
+    EXPECT_EQ(c.judderEventDetailsMax, d.judderEventDetailsMax);
+    EXPECT_EQ(c.judderEventDistanceToleranceMs, d.judderEventDistanceToleranceMs);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, getFromServerConfigurableFlags_withEmpty) {
+    Configuration::GetServerConfigurableFlagFn getServerConfigurableFlagFn{
+        [](const std::string &, const std::string &, const std::string &) -> std::string {
+            return "";
+        }
+    };
+    Configuration c = Configuration::getFromServerConfigurableFlags(getServerConfigurableFlagFn);
+    Configuration d; // default configuration
+    EXPECT_EQ(c.enabled, d.enabled);
+    EXPECT_EQ(c.areSkippedFramesDropped, d.areSkippedFramesDropped);
+    EXPECT_EQ(c.maxExpectedContentFrameDurationUs, d.maxExpectedContentFrameDurationUs);
+    EXPECT_EQ(c.frameRateDetectionToleranceUs, d.frameRateDetectionToleranceUs);
+    EXPECT_EQ(c.liveContentFrameDropToleranceUs, d.liveContentFrameDropToleranceUs);
+    EXPECT_EQ(c.freezeDurationMsHistogramBuckets, d.freezeDurationMsHistogramBuckets);
+    EXPECT_EQ(c.freezeDurationMsHistogramToScore, d.freezeDurationMsHistogramToScore);
+    EXPECT_EQ(c.freezeDistanceMsHistogramBuckets, d.freezeDistanceMsHistogramBuckets);
+    EXPECT_EQ(c.freezeEventMax, d.freezeEventMax);
+    EXPECT_EQ(c.freezeEventDetailsMax, d.freezeEventDetailsMax);
+    EXPECT_EQ(c.freezeEventDistanceToleranceMs, d.freezeEventDistanceToleranceMs);
+    EXPECT_EQ(c.judderErrorToleranceUs, d.judderErrorToleranceUs);
+    EXPECT_EQ(c.judderScoreHistogramBuckets, d.judderScoreHistogramBuckets);
+    EXPECT_EQ(c.judderScoreHistogramToScore, d.judderScoreHistogramToScore);
+    EXPECT_EQ(c.judderEventMax, d.judderEventMax);
+    EXPECT_EQ(c.judderEventDetailsMax, d.judderEventDetailsMax);
+    EXPECT_EQ(c.judderEventDistanceToleranceMs, d.judderEventDistanceToleranceMs);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, getFromServerConfigurableFlags_withInvalid) {
+    Configuration::GetServerConfigurableFlagFn getServerConfigurableFlagFn{
+        [](const std::string &, const std::string &, const std::string &) -> std::string {
+            return "abc";
+        }
+    };
+    Configuration c = Configuration::getFromServerConfigurableFlags(getServerConfigurableFlagFn);
+    Configuration d; // default configuration
+    EXPECT_EQ(c.enabled, d.enabled);
+    EXPECT_EQ(c.areSkippedFramesDropped, d.areSkippedFramesDropped);
+    EXPECT_EQ(c.maxExpectedContentFrameDurationUs, d.maxExpectedContentFrameDurationUs);
+    EXPECT_EQ(c.frameRateDetectionToleranceUs, d.frameRateDetectionToleranceUs);
+    EXPECT_EQ(c.liveContentFrameDropToleranceUs, d.liveContentFrameDropToleranceUs);
+    EXPECT_EQ(c.freezeDurationMsHistogramBuckets, d.freezeDurationMsHistogramBuckets);
+    EXPECT_EQ(c.freezeDurationMsHistogramToScore, d.freezeDurationMsHistogramToScore);
+    EXPECT_EQ(c.freezeDistanceMsHistogramBuckets, d.freezeDistanceMsHistogramBuckets);
+    EXPECT_EQ(c.freezeEventMax, d.freezeEventMax);
+    EXPECT_EQ(c.freezeEventDetailsMax, d.freezeEventDetailsMax);
+    EXPECT_EQ(c.freezeEventDistanceToleranceMs, d.freezeEventDistanceToleranceMs);
+    EXPECT_EQ(c.judderErrorToleranceUs, d.judderErrorToleranceUs);
+    EXPECT_EQ(c.judderScoreHistogramBuckets, d.judderScoreHistogramBuckets);
+    EXPECT_EQ(c.judderScoreHistogramToScore, d.judderScoreHistogramToScore);
+    EXPECT_EQ(c.judderEventMax, d.judderEventMax);
+    EXPECT_EQ(c.judderEventDetailsMax, d.judderEventDetailsMax);
+    EXPECT_EQ(c.judderEventDistanceToleranceMs, d.judderEventDistanceToleranceMs);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, getFromServerConfigurableFlags_withAlmostValid) {
+    Configuration::GetServerConfigurableFlagFn getServerConfigurableFlagFn{
+        [](const std::string &, const std::string &flag, const std::string &) -> std::string {
+            if (flag == "render_metrics_enabled") {
+                return "fals";
+            } else if (flag == "render_metrics_are_skipped_frames_dropped") {
+                return "fals";
+            } else if (flag == "render_metrics_max_expected_content_frame_duration_us") {
+                return "100a";
+            } else if (flag == "render_metrics_frame_rate_detection_tolerance_us") {
+                return "10b0";
+            } else if (flag == "render_metrics_live_content_frame_drop_tolerance_us") {
+                return "c100";
+            } else if (flag == "render_metrics_freeze_duration_ms_histogram_buckets") {
+                return "1,5300,3b400,123";
+            } else if (flag == "render_metrics_freeze_duration_ms_histogram_to_score") {
+                return "2,5300*400,132";
+            } else if (flag == "render_metrics_freeze_distance_ms_histogram_buckets") {
+                return "3,12345678901234,5,7";
+            } else if (flag == "render_metrics_freeze_event_max") {
+                return "12345678901234";
+            } else if (flag == "render_metrics_freeze_event_details_max") {
+                return "12345.11321";
+            } else if (flag == "render_metrics_freeze_event_distance_tolerance_ms") {
+                return "*!-";
+            } else if (flag == "render_metrics_judder_error_tolerance_us") {
+                return "10.5";
+            } else if (flag == "render_metrics_judder_score_histogram_buckets") {
+                return "abc";
+            } else if (flag == "render_metrics_judder_score_histogram_to_score") {
+                return "123,";
+            } else if (flag == "render_metrics_judder_event_max") {
+                return ",1234";
+            } else if (flag == "render_metrics_judder_event_details_max") {
+                return "10*10";
+            } else if (flag == "render_metrics_judder_event_distance_tolerance_ms") {
+                return "140-a";
+            }
+            return "";
+        }
+    };
+    Configuration c = Configuration::getFromServerConfigurableFlags(getServerConfigurableFlagFn);
+    Configuration d; // default configuration
+    EXPECT_EQ(c.enabled, d.enabled);
+    EXPECT_EQ(c.areSkippedFramesDropped, d.areSkippedFramesDropped);
+    EXPECT_EQ(c.maxExpectedContentFrameDurationUs, d.maxExpectedContentFrameDurationUs);
+    EXPECT_EQ(c.frameRateDetectionToleranceUs, d.frameRateDetectionToleranceUs);
+    EXPECT_EQ(c.liveContentFrameDropToleranceUs, d.liveContentFrameDropToleranceUs);
+    EXPECT_EQ(c.freezeDurationMsHistogramBuckets, d.freezeDurationMsHistogramBuckets);
+    EXPECT_EQ(c.freezeDurationMsHistogramToScore, d.freezeDurationMsHistogramToScore);
+    EXPECT_EQ(c.freezeDistanceMsHistogramBuckets, d.freezeDistanceMsHistogramBuckets);
+    EXPECT_EQ(c.freezeEventMax, d.freezeEventMax);
+    EXPECT_EQ(c.freezeEventDetailsMax, d.freezeEventDetailsMax);
+    EXPECT_EQ(c.freezeEventDistanceToleranceMs, d.freezeEventDistanceToleranceMs);
+    EXPECT_EQ(c.judderErrorToleranceUs, d.judderErrorToleranceUs);
+    EXPECT_EQ(c.judderScoreHistogramBuckets, d.judderScoreHistogramBuckets);
+    EXPECT_EQ(c.judderScoreHistogramToScore, d.judderScoreHistogramToScore);
+    EXPECT_EQ(c.judderEventMax, d.judderEventMax);
+    EXPECT_EQ(c.judderEventDetailsMax, d.judderEventDetailsMax);
+    EXPECT_EQ(c.judderEventDistanceToleranceMs, d.judderEventDistanceToleranceMs);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, getFromServerConfigurableFlags_withValid) {
+    Configuration::GetServerConfigurableFlagFn getServerConfigurableFlagFn{
+        [](const std::string &, const std::string &flag, const std::string &) -> std::string {
+            if (flag == "render_metrics_enabled") {
+                return "false";
+            } else if (flag == "render_metrics_are_skipped_frames_dropped") {
+                return "false";
+            } else if (flag == "render_metrics_max_expected_content_frame_duration_us") {
+                return "2000";
+            } else if (flag == "render_metrics_frame_rate_detection_tolerance_us") {
+                return "3000";
+            } else if (flag == "render_metrics_live_content_frame_drop_tolerance_us") {
+                return "4000";
+            } else if (flag == "render_metrics_freeze_duration_ms_histogram_buckets") {
+                return "100,200,300,400";
+            } else if (flag == "render_metrics_freeze_duration_ms_histogram_to_score") {
+                return "1234567890120,1234567890121,1234567890122";
+            } else if (flag == "render_metrics_freeze_distance_ms_histogram_buckets") {
+                return "500,600,700,800,900";
+            } else if (flag == "render_metrics_freeze_event_max") {
+                return "5000";
+            } else if (flag == "render_metrics_freeze_event_details_max") {
+                return "6000";
+            } else if (flag == "render_metrics_freeze_event_distance_tolerance_ms") {
+                return "7000";
+            } else if (flag == "render_metrics_judder_error_tolerance_us") {
+                return "8000";
+            } else if (flag == "render_metrics_judder_score_histogram_buckets") {
+                return "1,2,3,4,5";
+            } else if (flag == "render_metrics_judder_score_histogram_to_score") {
+                return "-1,-2,-3,-4,-5";
+            } else if (flag == "render_metrics_judder_event_max") {
+                return "9000";
+            } else if (flag == "render_metrics_judder_event_details_max") {
+                return "10000";
+            } else if (flag == "render_metrics_judder_event_distance_tolerance_ms") {
+                return "11000";
+            }
+            return "";
+        }
+    };
+
+    Configuration c = Configuration::getFromServerConfigurableFlags(getServerConfigurableFlagFn);
+    // The default configuration here used to verify we're not configuring the values to the
+    // default - if we are accidentally configuring to the default then we're not necessarily
+    // testing the parsing.
+    Configuration d;
+    EXPECT_EQ(c.enabled, false);
+    EXPECT_NE(c.enabled, d.enabled);
+    EXPECT_EQ(c.areSkippedFramesDropped, false);
+    EXPECT_NE(c.areSkippedFramesDropped, d.areSkippedFramesDropped);
+    EXPECT_EQ(c.maxExpectedContentFrameDurationUs, 2000);
+    EXPECT_NE(c.maxExpectedContentFrameDurationUs, d.maxExpectedContentFrameDurationUs);
+    EXPECT_EQ(c.frameRateDetectionToleranceUs, 3000);
+    EXPECT_NE(c.frameRateDetectionToleranceUs, d.frameRateDetectionToleranceUs);
+    EXPECT_EQ(c.liveContentFrameDropToleranceUs, 4000);
+    EXPECT_NE(c.liveContentFrameDropToleranceUs, d.liveContentFrameDropToleranceUs);
+    {
+        std::vector<int32_t> expected({100,200,300,400});
+        EXPECT_EQ(c.freezeDurationMsHistogramBuckets, expected);
+        EXPECT_NE(c.freezeDurationMsHistogramBuckets, d.freezeDurationMsHistogramBuckets);
+    }
+    {
+        std::vector<int64_t> expected({1234567890120LL,1234567890121LL,1234567890122LL});
+        EXPECT_EQ(c.freezeDurationMsHistogramToScore, expected);
+        EXPECT_NE(c.freezeDurationMsHistogramToScore, d.freezeDurationMsHistogramToScore);
+    }
+    {
+        std::vector<int32_t> expected({500,600,700,800,900});
+        EXPECT_EQ(c.freezeDistanceMsHistogramBuckets, expected);
+        EXPECT_NE(c.freezeDistanceMsHistogramBuckets, d.freezeDistanceMsHistogramBuckets);
+    }
+    EXPECT_EQ(c.freezeEventMax, 5000);
+    EXPECT_NE(c.freezeEventMax, d.freezeEventMax);
+    EXPECT_EQ(c.freezeEventDetailsMax, 6000);
+    EXPECT_NE(c.freezeEventDetailsMax, d.freezeEventDetailsMax);
+    EXPECT_EQ(c.freezeEventDistanceToleranceMs, 7000);
+    EXPECT_NE(c.freezeEventDistanceToleranceMs, d.freezeEventDistanceToleranceMs);
+    EXPECT_EQ(c.judderErrorToleranceUs, 8000);
+    EXPECT_NE(c.judderErrorToleranceUs, d.judderErrorToleranceUs);
+    {
+        std::vector<int32_t> expected({1,2,3,4,5});
+        EXPECT_EQ(c.judderScoreHistogramBuckets, expected);
+        EXPECT_NE(c.judderScoreHistogramBuckets, d.judderScoreHistogramBuckets);
+    }
+    {
+        std::vector<int64_t> expected({-1,-2,-3,-4,-5});
+        EXPECT_EQ(c.judderScoreHistogramToScore, expected);
+        EXPECT_NE(c.judderScoreHistogramToScore, d.judderScoreHistogramToScore);
+    }
+    EXPECT_EQ(c.judderEventMax, 9000);
+    EXPECT_NE(c.judderEventMax, d.judderEventMax);
+    EXPECT_EQ(c.judderEventDetailsMax, 10000);
+    EXPECT_NE(c.judderEventDetailsMax, d.judderEventDetailsMax);
+    EXPECT_EQ(c.judderEventDistanceToleranceMs, 11000);
+    EXPECT_NE(c.judderEventDistanceToleranceMs, d.judderEventDistanceToleranceMs);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, countsReleasedFrames) {
+    Configuration c;
+    Helper h(16.66, c);
+    h.drop(10);
+    h.render({16.66, 16.66, 16.66});
+    h.skip(10); // skipped frames aren't released so they are not counted
+    h.render({16.66, 16.66, 16.66, 16.66});
+    h.drop(10);
+    EXPECT_EQ(27, h.getMetrics().frameReleasedCount);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, countsSkippedFrames) {
+    Configuration c;
+    Helper h(16.66, c);
+    h.drop(10); // dropped frames are not counted
+    h.skip(10); // frames skipped before rendering a frame are not counted
+    h.render({16.66, 16.66, 16.66}); // rendered frames are not counted
+    h.drop(10); // dropped frames are not counted
+    h.skip(10);
+    h.render({16.66, 16.66, 16.66, 16.66}); // rendered frames are not counted
+    h.skip(10); // frames skipped at the end of playback are not counted
+    h.drop(10); // dropped frames are not counted
+    EXPECT_EQ(10, h.getMetrics().frameSkippedCount);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenSkippedFramesAreDropped_countsDroppedFrames) {
+    Configuration c;
+    c.areSkippedFramesDropped = true;
+    Helper h(16.66, c);
+    h.skip(10); // skipped frames at the beginning of playback are not counted
+    h.drop(10);
+    h.skip(10); // skipped frames at the beginning of playback after dropped frames are not counted
+    h.render({16.66, 16.66, 16.66});  // rendered frames are not counted
+    h.drop(10);
+    h.skip(10);
+    h.render({16.66, 16.66, 16.66, 16.66}); // rendered frames are not counted
+    h.drop(10); // dropped frames at the end of playback are not counted
+    h.skip(10); // skipped frames at the end of playback are not counted
+    EXPECT_EQ(30, h.getMetrics().frameDroppedCount);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenNotSkippedFramesAreDropped_countsDroppedFrames) {
+    Configuration c;
+    c.areSkippedFramesDropped = false;
+    Helper h(16.66, c);
+    h.skip(10); // skipped frames at the beginning of playback are not counted
+    h.drop(10);
+    h.skip(10); // skipped frames at the beginning of playback after dropped frames are not coutned
+    h.render({16.66, 16.66, 16.66}); // rendered frames are not counted
+    h.drop(10);
+    h.skip(10); // skipped frames are not counted
+    h.render({16.66, 16.66, 16.66, 16.66}); // rendered frames are not counted
+    h.drop(10); // dropped frames at the end of playback are not counted
+    h.skip(10); // skipped frames at the end of playback are not counted
+    EXPECT_EQ(20, h.getMetrics().frameDroppedCount);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, countsRenderedFrames) {
+    Configuration c;
+    Helper h(16.66, c);
+    h.drop(10); // dropped frames are not counted
+    h.render({16.66, 16.66, 16.66});
+    h.skip(10); // skipped frames are not counted
+    h.render({16.66, 16.66, 16.66, 16.66});
+    h.drop(10); // dropped frames are not counted
+    EXPECT_EQ(7, h.getMetrics().frameRenderedCount);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, detectsFrameRate) {
+    Configuration c;
+    c.frameRateDetectionToleranceUs = 2 * 1000; // 2 ms
+    Helper h(16.66, c);
+    h.render({16.6, 16.7, 16.6, 16.7});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 60.0, 0.5);
+    EXPECT_NEAR(h.getMetrics().actualFrameRate, 60.0, 0.5);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, handlesSeeking) {
+    Configuration c;
+    c.maxExpectedContentFrameDurationUs = 30;
+    VideoRenderQualityTracker v(c);
+    v.onFrameReleased(0, 0);
+    v.onFrameRendered(0, 0);
+    v.onFrameReleased(20, 20);
+    v.onFrameRendered(20, 20);
+    v.onFrameReleased(40, 40);
+    v.onFrameRendered(40, 40);
+    v.onFrameReleased(60, 60);
+    v.onFrameRendered(60, 60);
+    v.onFrameReleased(80, 80);
+    v.onFrameRendered(80, 80);
+    v.onFrameReleased(7200000000, 100);
+    v.onFrameRendered(7200000000, 100);
+    v.onFrameReleased(7200000020, 120);
+    v.onFrameRendered(7200000020, 120);
+    v.onFrameReleased(7200000040, 140);
+    v.onFrameRendered(7200000040, 140);
+    v.onFrameReleased(7200000060, 160);
+    v.onFrameRendered(7200000060, 160);
+    v.onFrameReleased(7200000080, 180);
+    v.onFrameRendered(7200000080, 180);
+    v.onFrameReleased(0, 200);
+    v.onFrameRendered(0, 200);
+    v.onFrameReleased(20, 220);
+    v.onFrameRendered(20, 220);
+    v.onFrameReleased(40, 240);
+    v.onFrameRendered(40, 240);
+    v.onFrameReleased(60, 260);
+    v.onFrameRendered(60, 260);
+    const VideoRenderQualityMetrics &m = v.getMetrics();
+    EXPECT_EQ(m.judderRate, 0); // frame durations can get messed up during discontinuities so if
+                                // the discontinuity is not detected, judder is expected
+    EXPECT_NE(m.contentFrameRate, FRAME_RATE_UNDETERMINED);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, withSkipping_handlesSeeking) {
+    Configuration c;
+    c.maxExpectedContentFrameDurationUs = 30;
+    VideoRenderQualityTracker v(c);
+    v.onFrameReleased(0, 0);
+    v.onFrameRendered(0, 0);
+    v.onFrameReleased(20, 20);
+    v.onFrameRendered(20, 20);
+    v.onFrameReleased(40, 40);
+    v.onFrameRendered(40, 40);
+    v.onFrameReleased(60, 60);
+    v.onFrameRendered(60, 60);
+    v.onFrameReleased(80, 80);
+    v.onFrameRendered(80, 80);
+    v.onFrameSkipped(7200000000);
+    v.onFrameSkipped(7200000020);
+    v.onFrameReleased(7200000040, 100);
+    v.onFrameRendered(7200000040, 100);
+    v.onFrameReleased(7200000060, 120);
+    v.onFrameRendered(7200000060, 120);
+    v.onFrameReleased(7200000080, 140);
+    v.onFrameSkipped(0);
+    v.onFrameRendered(7200000080, 140);
+    v.onFrameSkipped(20);
+    v.onFrameReleased(40, 160);
+    v.onFrameRendered(40, 160);
+    v.onFrameReleased(60, 180);
+    v.onFrameRendered(60, 180);
+    v.onFrameReleased(80, 200);
+    v.onFrameRendered(80, 200);
+    const VideoRenderQualityMetrics &m = v.getMetrics();
+    EXPECT_EQ(m.judderRate, 0); // frame durations can get messed up during discontinuities so if
+                                // the discontinuity is not detected, judder is expected
+    EXPECT_NE(m.contentFrameRate, FRAME_RATE_UNDETERMINED);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenLowTolerance_doesntDetectFrameRate) {
+    Configuration c;
+    c.frameRateDetectionToleranceUs = 0;
+    Helper h(16.66, c);
+    h.render({16.6, 16.7, 16.6, 16.7});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 60.0, 0.5);
+    EXPECT_EQ(h.getMetrics().actualFrameRate, FRAME_RATE_UNDETERMINED);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenFrameRateDestabilizes_detectsFrameRate) {
+    Configuration c;
+    c.frameRateDetectionToleranceUs = 2 * 1000; // 2 ms
+    Helper h(16.66, c);
+    h.render({16.6, 16.7, 16.6, 16.7});
+    h.render({30.0, 16.6, 30.0, 16.6});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 60.0, 0.5);
+    EXPECT_NEAR(h.getMetrics().actualFrameRate, 60.0, 0.5);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, detects32Pulldown) {
+    Configuration c;
+    c.frameRateDetectionToleranceUs = 2 * 1000; // 2 ms
+    Helper h(41.66, c);
+    h.render({49.9, 33.2, 50.0, 33.4, 50.1, 33.2});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 24.0, 0.5);
+    EXPECT_EQ(h.getMetrics().actualFrameRate, FRAME_RATE_24_3_2_PULLDOWN);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenBad32Pulldown_doesntDetect32Pulldown) {
+    Configuration c;
+    c.frameRateDetectionToleranceUs = 2 * 1000; // 2 ms
+    Helper h(41.66, c);
+    h.render({50.0, 33.33, 33.33, 50.00, 33.33, 50.00});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 24.0, 0.5);
+    EXPECT_EQ(h.getMetrics().actualFrameRate, FRAME_RATE_UNDETERMINED);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenFrameRateChanges_detectsMostRecentFrameRate) {
+    Configuration c;
+    c.frameRateDetectionToleranceUs = 2 * 1000; // 2 ms
+    Helper h(16.66, c);
+    h.render({16.6, 16.7, 16.6, 16.7});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 60.0, 0.5);
+    EXPECT_NEAR(h.getMetrics().actualFrameRate, 60.0, 0.5);
+    h.changeContentFrameDuration(41.66);
+    h.render({50.0, 33.33, 50.0, 33.33, 50.0, 33.33});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 24.0, 0.5);
+    EXPECT_EQ(h.getMetrics().actualFrameRate, FRAME_RATE_24_3_2_PULLDOWN);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenFrameRateIsUnstable_doesntDetectFrameRate) {
+    Configuration c;
+    c.frameRateDetectionToleranceUs = 2 * 1000; // 2 ms
+    Helper h(16.66, c);
+    h.render({16.66, 30.0, 16.66, 30.0, 16.66});
+    EXPECT_NEAR(h.getMetrics().contentFrameRate, 60.0, 0.5);
+    EXPECT_EQ(h.getMetrics().actualFrameRate, FRAME_RATE_UNDETERMINED);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesFreezeRate) {
+    Configuration c;
+    Helper h(20, c);
+    h.render(3);
+    EXPECT_EQ(h.getMetrics().freezeRate, 0);
+    h.drop(3);
+    h.render(3);
+    // +1 because the first frame before drops is considered frozen
+    // and then -1 because the last frame has an unknown render duration
+    EXPECT_EQ(h.getMetrics().freezeRate, 4.0 / 8.0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesFreezeDurationHistogram) {
+    Configuration c;
+    // +17 because freeze durations include the render time of the previous frame
+    c.freezeDurationMsHistogramBuckets = {2 * 17 + 17, 3 * 17 + 17, 6 * 17 + 17};
+    Helper h(17, c);
+    h.render(1);
+    h.drop(1); // below
+    h.render(1);
+    h.drop(3); // bucket 1
+    h.render(1);
+    h.drop(2); // bucket 0
+    h.render(1);
+    h.drop(4); // bucket 1
+    h.render(1);
+    h.drop(2); // bucket 0
+    h.render(1);
+    h.drop(5); // bucket 1
+    h.render(1);
+    h.drop(10); // above
+    h.render(1);
+    h.drop(15); // above
+    h.render(1);
+    EXPECT_EQ(h.getMetrics().freezeDurationMsHistogram.emit(), "1{2,3}2");
+    EXPECT_EQ(h.getMetrics().freezeDurationMsHistogram.getCount(), 8);
+    // the smallest frame drop was 1, +17 because it includes the previous frame render time
+    EXPECT_EQ(h.getMetrics().freezeDurationMsHistogram.getMin(), 1 * 17 + 17);
+    // the largest frame drop was 10, +17 because it includes the previous frame render time
+    EXPECT_EQ(h.getMetrics().freezeDurationMsHistogram.getMax(), 15 * 17 + 17);
+    // total frame drop count, multiplied by 17, plus 17 for each occurrence, divided by occurrences
+    EXPECT_EQ(h.getMetrics().freezeDurationMsHistogram.getAvg(), ((1 + 3 + 2 + 4 + 2 + 5 + 10 + 15)
+                                                                   * 17 + 8 * 17) / 8);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesFreezeDistanceHistogram) {
+    Configuration c;
+    c.freezeDistanceMsHistogramBuckets = {1 * 17, 5 * 17, 6 * 17};
+    Helper h(17, c);
+    h.render(1);
+    h.drop(1);
+    h.render(5); // bucket 0
+    h.drop(3);
+    h.render(3); // bucket 0
+    h.drop(2);
+    h.render(9); // above
+    h.drop(5);
+    h.render(1); // below
+    h.drop(2);
+    h.render(6); // bucket 1
+    h.drop(4);
+    h.render(12); // above
+    h.drop(2);
+    h.render(1);
+    EXPECT_EQ(h.getMetrics().freezeDistanceMsHistogram.emit(), "1{2,1}2");
+    EXPECT_EQ(h.getMetrics().freezeDistanceMsHistogram.getCount(), 6);
+    // the smallest render between drops was 1, -17 because the last frame rendered also froze
+    EXPECT_EQ(h.getMetrics().freezeDistanceMsHistogram.getMin(), 1 * 17 - 17);
+    // the largest render between drops was 12, -17 because the last frame rendered also froze
+    EXPECT_EQ(h.getMetrics().freezeDistanceMsHistogram.getMax(), 12 * 17 - 17);
+    // total render count between, multiplied by 17, minus 17 for each occurrence, divided by
+    // occurrences
+    EXPECT_EQ(h.getMetrics().freezeDistanceMsHistogram.getAvg(), ((5 + 3 + 9 + 1 + 6 + 12) * 17 -
+                                                                  6 * 17) / 6);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, when60hz_hasNoJudder) {
+    Configuration c;
+    Helper h(16.66, c); // ~24Hz
+    h.render({16.66, 16.66, 16.66, 16.66, 16.66, 16.66, 16.66});
+    EXPECT_LE(h.getMetrics().judderScoreHistogram.getMax(), 0);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenSmallVariance60hz_hasNoJudder) {
+    Configuration c;
+    Helper h(16.66, c); // ~24Hz
+    h.render({14, 18, 14, 18, 14, 18, 14, 18});
+    EXPECT_LE(h.getMetrics().judderScoreHistogram.getMax(), 0);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenBadSmallVariance60Hz_hasJudder) {
+    Configuration c;
+    Helper h(16.66, c); // ~24Hz
+    h.render({14, 18, 14, /* no 18 between 14s */ 14, 18, 14, 18});
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 1);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, when30Hz_hasNoJudder) {
+    Configuration c;
+    Helper h(33.33, c);
+    h.render({33.33, 33.33, 33.33, 33.33, 33.33, 33.33});
+    EXPECT_LE(h.getMetrics().judderScoreHistogram.getMax(), 0);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenSmallVariance30Hz_hasNoJudder) {
+    Configuration c;
+    Helper h(33.33, c);
+    h.render({29.0, 35.0, 29.0, 35.0, 29.0, 35.0});
+    EXPECT_LE(h.getMetrics().judderScoreHistogram.getMax(), 0);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenBadSmallVariance30Hz_hasJudder) {
+    Configuration c;
+    Helper h(33.33, c);
+    h.render({29.0, 35.0, 29.0, /* no 35 between 29s */ 29.0, 35.0, 29.0, 35.0});
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 1);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenBad30HzTo60Hz_hasJudder) {
+    Configuration c;
+    Helper h(33.33, c);
+    h.render({33.33, 33.33, 50.0, /* frame stayed 1 vsync too long */ 16.66, 33.33, 33.33});
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 2); // note: 2 counts of judder
+}
+
+TEST_F(VideoRenderQualityTrackerTest, when24HzTo60Hz_hasNoJudder) {
+    Configuration c;
+    Helper h(41.66, c);
+    h.render({50.0, 33.33, 50.0, 33.33, 50.0, 33.33});
+    EXPECT_LE(h.getMetrics().judderScoreHistogram.getMax(), 0);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, when25HzTo60Hz_hasJudder) {
+    Configuration c;
+    Helper h(40, c);
+    h.render({33.33, 33.33, 50.0});
+    h.render({33.33, 33.33, 50.0});
+    h.render({33.33, 33.33, 50.0});
+    h.render({33.33, 33.33, 50.0});
+    h.render({33.33, 33.33, 50.0});
+    h.render({33.33, 33.33, 50.0});
+    EXPECT_GT(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, when50HzTo60Hz_hasJudder) {
+    Configuration c;
+    Helper h(20, c);
+    h.render({16.66, 16.66, 16.66, 33.33});
+    h.render({16.66, 16.66, 16.66, 33.33});
+    h.render({16.66, 16.66, 16.66, 33.33});
+    h.render({16.66, 16.66, 16.66, 33.33});
+    h.render({16.66, 16.66, 16.66, 33.33});
+    h.render({16.66, 16.66, 16.66, 33.33});
+    EXPECT_GT(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, when30HzTo50Hz_hasJudder) {
+    Configuration c;
+    Helper h(33.33, c);
+    h.render({40.0, 40.0, 40.0, 60.0});
+    h.render({40.0, 40.0, 40.0, 60.0});
+    h.render({40.0, 40.0, 40.0, 60.0});
+    h.render({40.0, 40.0, 40.0, 60.0});
+    h.render({40.0, 40.0, 40.0, 60.0});
+    EXPECT_GT(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenSmallVariancePulldown24HzTo60Hz_hasNoJudder) {
+    Configuration c;
+    Helper h(41.66, c);
+    h.render({52.0, 31.33, 52.0, 31.33, 52.0, 31.33});
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 0);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, whenBad24HzTo60Hz_hasJudder) {
+    Configuration c;
+    Helper h(41.66, c);
+    h.render({50.0, 33.33, 50.0, 33.33, /* no 50 between 33s */ 33.33, 50.0, 33.33});
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 1);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesJudderScoreHistogram) {
+    Configuration c;
+    c.judderErrorToleranceUs = 2000;
+    c.judderScoreHistogramBuckets = {1, 5, 8};
+    Helper h(16, c);
+    h.render({16, 16, 23, 16, 16, 10, 16, 4, 16, 20, 16, 16});
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.emit(), "0{1,2}1");
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getCount(), 4);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getMin(), 4);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getMax(), 12);
+    EXPECT_EQ(h.getMetrics().judderScoreHistogram.getAvg(), (7 + 6 + 12 + 4) / 4);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, ranksJudderScoresInOrder) {
+    // Each rendering is ranked from best to worst from a user experience
+    Configuration c;
+    c.judderErrorToleranceUs = 2000;
+    c.judderScoreHistogramBuckets = {0, 1000};
+    int64_t previousScore = 0;
+
+    // 30fps poorly displayed at 60Hz
+    {
+        Helper h(33.33, c);
+        h.render({33.33, 33.33, 16.66, 50.0, 33.33, 33.33});
+        int64_t scoreBad30fpsTo60Hz = h.getMetrics().judderScoreHistogram.getMax();
+        EXPECT_GT(scoreBad30fpsTo60Hz, previousScore);
+        previousScore = scoreBad30fpsTo60Hz;
+    }
+
+    // 25fps displayed at 60hz
+    {
+        Helper h(40, c);
+        h.render({33.33, 33.33, 50.0});
+        h.render({33.33, 33.33, 50.0});
+        h.render({33.33, 33.33, 50.0});
+        h.render({33.33, 33.33, 50.0});
+        h.render({33.33, 33.33, 50.0});
+        h.render({33.33, 33.33, 50.0});
+        int64_t score25fpsTo60hz = h.getMetrics().judderScoreHistogram.getMax();
+        EXPECT_GT(score25fpsTo60hz, previousScore);
+        previousScore = score25fpsTo60hz;
+    }
+
+    // 50fps displayed at 60hz
+    {
+        Helper h(20, c);
+        h.render({16.66, 16.66, 16.66, 33.33});
+        h.render({16.66, 16.66, 16.66, 33.33});
+        h.render({16.66, 16.66, 16.66, 33.33});
+        h.render({16.66, 16.66, 16.66, 33.33});
+        h.render({16.66, 16.66, 16.66, 33.33});
+        h.render({16.66, 16.66, 16.66, 33.33});
+        int64_t score50fpsTo60hz = h.getMetrics().judderScoreHistogram.getMax();
+        EXPECT_GT(score50fpsTo60hz, previousScore);
+        previousScore = score50fpsTo60hz;
+    }
+
+    // 24fps poorly displayed at 60Hz
+    {
+        Helper h(41.66, c);
+        h.render({50.0, 33.33, 50.0, 33.33, 33.33, 50.0, 33.33});
+        int64_t scoreBad24HzTo60Hz = h.getMetrics().judderScoreHistogram.getMax();
+        EXPECT_GT(scoreBad24HzTo60Hz, previousScore);
+        previousScore = scoreBad24HzTo60Hz;
+    }
+
+    // 30fps displayed at 50hz
+    {
+        Helper h(33.33, c);
+        h.render({40.0, 40.0, 40.0, 60.0});
+        h.render({40.0, 40.0, 40.0, 60.0});
+        h.render({40.0, 40.0, 40.0, 60.0});
+        h.render({40.0, 40.0, 40.0, 60.0});
+        h.render({40.0, 40.0, 40.0, 60.0});
+        int64_t score30fpsTo50hz = h.getMetrics().judderScoreHistogram.getMax();
+        EXPECT_GT(score30fpsTo50hz, previousScore);
+        previousScore = score30fpsTo50hz;
+    }
+
+    // 24fps displayed at 50Hz
+    {
+        Helper h(41.66, c);
+        h.render(40.0, 11);
+        h.render(60.0, 1);
+        h.render(40.0, 11);
+        h.render(60.0, 1);
+        h.render(40.0, 11);
+        int64_t score24HzTo50Hz = h.getMetrics().judderScoreHistogram.getMax();
+        EXPECT_GT(score24HzTo50Hz, previousScore);
+        previousScore = score24HzTo50Hz;
+    }
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesFreezeEvents) {
+    Configuration c;
+    c.freezeEventMax = 5;
+    c.freezeEventDetailsMax = 4;
+    c.freezeEventDistanceToleranceMs = 1000;
+    Helper h(20, c);
+    h.render(10);
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, false);
+    h.drop(3);
+    h.render(1000 / 20); // +1 because it's unclear if the current frame is frozen
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, false);
+    h.drop(1);
+    h.render(10);
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, false);
+    h.drop(6);
+    h.render(12);
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, false);
+    h.drop(10);
+    h.render(1000 / 20 + 1); // +1 because it's unclear if the current frame is frozen
+    EXPECT_EQ(h.getMetrics().freezeEventCount, 1);
+    FreezeEvent e = h.getAndClearFreezeEvent();
+    EXPECT_EQ(e.valid, true); // freeze event
+    // -1 because the last rendered frame is considered frozen
+    EXPECT_EQ(e.initialTimeUs, 9 * 20 * 1000);
+    // only count the last frame of the first group of rendered frames
+    EXPECT_EQ(e.durationMs, (1 + 3 + 1000 / 20 + 1 + 10 + 6 + 12 + 10) * 20);
+    EXPECT_EQ(e.count, 4);
+    // number of dropped frames
+    // +1 because the last rendered frame is considered frozen
+    EXPECT_EQ(e.sumDurationMs, (4 + 2 + 7 + 11) * 20);
+    // number of rendered frames between dropped frames
+    // -1 because the last rendered frame is considered frozen
+    EXPECT_EQ(e.sumDistanceMs, ((1000 / 20) - 1 + 9 + 11) * 20);
+    // +1 for each since the last rendered frame is considered frozen
+    ASSERT_EQ(e.details.durationMs.size(), 4);
+    EXPECT_EQ(e.details.durationMs[0], 4 * 20);
+    EXPECT_EQ(e.details.durationMs[1], 2 * 20);
+    EXPECT_EQ(e.details.durationMs[2], 7 * 20);
+    EXPECT_EQ(e.details.durationMs[3], 11 * 20);
+    // -1 for each since the last rendered frame is considered frozen
+    ASSERT_EQ(e.details.distanceMs.size(), 4);
+    EXPECT_EQ(e.details.distanceMs[0], -1);
+    EXPECT_EQ(e.details.distanceMs[1], 1000 - 20);
+    EXPECT_EQ(e.details.distanceMs[2], 9 * 20);
+    EXPECT_EQ(e.details.distanceMs[3], 11 * 20);
+    int64_t previousEventEndTimeUs = e.initialTimeUs + e.durationMs * 1000;
+    h.drop(1);
+    h.render(4);
+    h.drop(1);
+    h.render(4);
+    h.drop(1);
+    h.render(4);
+    h.drop(1);
+    h.render(4);
+    h.drop(1);
+    h.render(1000 / 20 + 1);
+    EXPECT_EQ(h.getMetrics().freezeEventCount, 2);
+    e = h.getAndClearFreezeEvent();
+    EXPECT_EQ(e.valid, true);
+    // 1000ms tolerance means 1000ms from the end of the last event to the beginning of this event
+    EXPECT_EQ(e.initialTimeUs, previousEventEndTimeUs + 1000 * 1000);
+    EXPECT_EQ(e.count, 5);
+    // 5 freezes captured in the freeze event, but only 4 details are recorded
+    EXPECT_EQ(e.details.durationMs.size(), 4);
+    EXPECT_EQ(e.details.distanceMs.size(), 4);
+    EXPECT_EQ(e.details.distanceMs[0], 1000); // same as the tolerance
+    // The duration across the entire series f freezes is captured, with only 4 details captured
+    // +1 because the first rendered frame is considered frozen (not the 1st dropped frame)
+    EXPECT_EQ(e.durationMs, (1 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 4 + 1) * 20);
+    // The duration of all 5 freeze events are captured, with only 4 details captured
+    EXPECT_EQ(e.sumDurationMs, (2 + 2 + 2 + 2 + 2) * 20);
+    // The distance of all 5 freeze events are captured, with only 4 details captured
+    EXPECT_EQ(e.sumDistanceMs, (3 + 3 + 3 + 3) * 20);
+    h.drop(1);
+    h.render(1000 / 20 + 1);
+    EXPECT_EQ(h.getMetrics().freezeEventCount, 3);
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, true);
+    h.drop(1);
+    h.render(1000 / 20 + 1);
+    EXPECT_EQ(h.getMetrics().freezeEventCount, 4);
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, true);
+    h.drop(1);
+    h.render(1000 / 20 + 1);
+    EXPECT_EQ(h.getMetrics().freezeEventCount, 5);
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, true);
+    h.drop(1);
+    h.render(1000 / 20 + 1);
+    // The 6th event isn't captured because it exceeds the configured limit
+    EXPECT_EQ(h.getMetrics().freezeEventCount, 6);
+    EXPECT_EQ(h.getAndClearFreezeEvent().valid, false);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesJudderEvents) {
+    Configuration c;
+    c.judderEventMax = 4;
+    c.judderEventDetailsMax = 3;
+    c.judderEventDistanceToleranceMs = 100;
+    Helper h(20, c);
+    h.render({19, 20, 19});
+    EXPECT_EQ(h.getAndClearJudderEvent().valid, false);
+    h.render({15, 19, 20, 19});
+    EXPECT_EQ(h.getAndClearJudderEvent().valid, false);
+    h.render({28, 20, 19});
+    EXPECT_EQ(h.getAndClearJudderEvent().valid, false);
+    h.render({13, 20, 20, 20, 20});
+    EXPECT_EQ(h.getAndClearJudderEvent().valid, false);
+    // Start with judder for the next event at the end of the sequence, because judder is scored
+    // one frame behind, and for combining judder occurrences into events, it's not clear yet if
+    // the current frame has judder or not.
+    h.render({15, 20, 20, 20, 20, 20, 15});
+    JudderEvent e = h.getAndClearJudderEvent();
+    EXPECT_EQ(e.valid, true);
+    EXPECT_EQ(e.initialTimeUs, (19 + 20 + 19) * 1000);
+    EXPECT_EQ(e.durationMs, 15 + 19 + 20 + 19 /**/ + 28 + 20 + 19 /**/ + 13 + 20 * 4 /**/ + 15);
+    EXPECT_EQ(e.count, 4);
+    EXPECT_EQ(e.sumScore, (20 - 15) + (28 - 20) + (20 - 13) + (20 - 15));
+    EXPECT_EQ(e.sumDistanceMs, 19 + 20 + 19 /**/ + 20 + 19 /**/ + 20 * 4);
+    ASSERT_EQ(e.details.actualRenderDurationUs.size(), 3); // 3 details per configured maximum
+    EXPECT_EQ(e.details.actualRenderDurationUs[0], 15 * 1000);
+    EXPECT_EQ(e.details.actualRenderDurationUs[1], 28 * 1000);
+    EXPECT_EQ(e.details.actualRenderDurationUs[2], 13 * 1000);
+    ASSERT_EQ(e.details.contentRenderDurationUs.size(), 3);
+    EXPECT_EQ(e.details.contentRenderDurationUs[0], 20 * 1000);
+    EXPECT_EQ(e.details.contentRenderDurationUs[1], 20 * 1000);
+    EXPECT_EQ(e.details.contentRenderDurationUs[2], 20 * 1000);
+    ASSERT_EQ(e.details.distanceMs.size(), 3);
+    EXPECT_EQ(e.details.distanceMs[0], -1);
+    EXPECT_EQ(e.details.distanceMs[1], 19 + 20 + 19);
+    EXPECT_EQ(e.details.distanceMs[2], 20 + 19);
+    h.render({20, 20, 20, 20, 20, 15});
+    e = h.getAndClearJudderEvent();
+    EXPECT_EQ(e.valid, true);
+    ASSERT_EQ(e.details.distanceMs.size(), 1);
+    EXPECT_EQ(e.details.distanceMs[0], 100); // same as the tolerance
+    h.render({20, 20, 20, 20, 20, 15});
+    EXPECT_EQ(h.getAndClearJudderEvent().valid, true);
+    h.render({20, 20, 20, 20, 20, 15});
+    EXPECT_EQ(h.getAndClearJudderEvent().valid, true);
+    h.render({20, 20, 20, 20, 20, 20});
+    EXPECT_EQ(h.getAndClearJudderEvent().valid, false); // max number of judder events exceeded
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesOverallFreezeScore) {
+    Configuration c;
+    // # drops * 20ms + 20ms because current frame is frozen + 1 for bucket threshold
+    c.freezeDurationMsHistogramBuckets = {1 * 20 + 21, 5 * 20 + 21, 10 * 20 + 21};
+    c.freezeDurationMsHistogramToScore = {10, 100, 1000};
+    Helper h(20, c);
+    h.render(5);
+    h.drop(2); // bucket = 0, bucket count = 1, bucket score = 10
+    h.render(5);
+    h.drop(11); // bucket = 2, bucket count = 1, bucket score = 1000
+    h.render(5);
+    h.drop(6); // bucket = 1, bucket count = 1, bucket score = 100
+    h.render(5);
+    h.drop(1); // bucket = null
+    h.render(5);
+    h.drop(3); // bucket = 0, bucket count = 2, bucket score = 20
+    h.render(5);
+    h.drop(10); // bucket = 1, bucket count = 2, bucket score = 200
+    h.render(5);
+    h.drop(7); // bucket = 1, bucket count = 3, bucket score = 300
+    h.render(5);
+    EXPECT_EQ(h.getMetrics().freezeScore, 20 + 300 + 1000);
+}
+
+TEST_F(VideoRenderQualityTrackerTest, capturesOverallJudderScore) {
+    Configuration c;
+    c.judderScoreHistogramBuckets = {0, 6, 10};
+    c.judderScoreHistogramToScore = {10, 100, 1000};
+    Helper h(20, c);
+    h.render({20, 20, 15, 20, 20}); // bucket = 0, bucket count = 1, bucket score = 10
+    h.render({20, 20, 11, 20, 20}); // bucket = 1, bucket count = 1, bucket score = 100
+    h.render({20, 20, 13, 20, 20}); // bucket = 1, bucket count = 2, bucket score = 200
+    h.render({20, 20,  5, 20, 20}); // bucket = 2, bucket count = 1, bucket score = 1000
+    h.render({20, 20, 14, 20, 20}); // bucket = 1, bucket count = 3, bucket score = 300
+    h.render({20, 20, 10, 20, 20}); // bucket = 2, bucket count = 2, bucket score = 2000
+    EXPECT_EQ(h.getMetrics().judderScore, 10 + 300 + 2000);
+}
+
+} // android
diff --git a/media/libstagefright/tests/mediacodec/Android.bp b/media/libstagefright/tests/mediacodec/Android.bp
index 9cdc6d4..23882ea 100644
--- a/media/libstagefright/tests/mediacodec/Android.bp
+++ b/media/libstagefright/tests/mediacodec/Android.bp
@@ -70,4 +70,4 @@
     test_suites: [
         "general-tests",
     ],
-}
+}
\ No newline at end of file
diff --git a/media/ndk/include/media/NdkImage.h b/media/ndk/include/media/NdkImage.h
index 814a327..76270d3 100644
--- a/media/ndk/include/media/NdkImage.h
+++ b/media/ndk/include/media/NdkImage.h
@@ -829,7 +829,7 @@
 /**
  * Query the dataspace of the input {@link AImage}.
  *
- * Available since API level 33.
+ * Available since API level 34.
  *
  * @param image the {@link AImage} of interest.
  * @param dataSpace the dataspace of the image will be filled here if the method call succeeds.
@@ -843,7 +843,7 @@
  *                 image has been deleted.</li></ul>
  */
 media_status_t AImage_getDataSpace(const AImage* image,
-                                   /*out*/int32_t* dataSpace) __INTRODUCED_IN(33);
+                                   /*out*/int32_t* dataSpace) __INTRODUCED_IN(34);
 
 __END_DECLS
 
diff --git a/media/ndk/include/media/NdkImageReader.h b/media/ndk/include/media/NdkImageReader.h
index 992955b..b6dcaae 100644
--- a/media/ndk/include/media/NdkImageReader.h
+++ b/media/ndk/include/media/NdkImageReader.h
@@ -395,7 +395,7 @@
  * the combination of {@code hardwareBufferFormat} and {@code dataSpace} for the
  * format of the Image that the reader will produce.</p>
  *
- * Available since API level 33.
+ * Available since API level 34.
  *
  * @param width The default width in pixels of the Images that this reader will produce.
  * @param height The default height in pixels of the Images that this reader will produce.
@@ -422,7 +422,7 @@
  */
 media_status_t AImageReader_newWithDataSpace(int32_t width, int32_t height, uint64_t usage,
         int32_t maxImages, uint32_t hardwareBufferFormat, int32_t dataSpace,
-        /*out*/ AImageReader** reader) __INTRODUCED_IN(33);
+        /*out*/ AImageReader** reader) __INTRODUCED_IN(34);
 
 /**
  * Acquire the next {@link AImage} from the image reader's queue asynchronously.
diff --git a/media/ndk/libmediandk.map.txt b/media/ndk/libmediandk.map.txt
index 4dd81ab..4f045fd 100644
--- a/media/ndk/libmediandk.map.txt
+++ b/media/ndk/libmediandk.map.txt
@@ -13,13 +13,13 @@
     AImageReader_getWindow; # introduced=24
     AImageReader_new; # introduced=24
     AImageReader_newWithUsage; # introduced=26
-    AImageReader_newWithDataSpace; # introduced=Tiramisu
+    AImageReader_newWithDataSpace; # introduced=UpsideDownCake
     AImageReader_setBufferRemovedListener; # introduced=26
     AImageReader_setImageListener; # introduced=24
     AImage_delete; # introduced=24
     AImage_deleteAsync; # introduced=26
     AImage_getCropRect; # introduced=24
-    AImage_getDataSpace; # introduced=Tiramisu
+    AImage_getDataSpace; # introduced=UpsideDownCake
     AImage_getFormat; # introduced=24
     AImage_getHardwareBuffer; # introduced=26
     AImage_getHeight; # introduced=24
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index f69307b..325adfa 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -58,6 +58,7 @@
 #include <audiomanager/IAudioManager.h>
 
 #include "AudioFlinger.h"
+#include "EffectConfiguration.h"
 #include "NBAIO_Tee.h"
 #include "PropertyUtils.h"
 
@@ -111,6 +112,7 @@
 using media::IEffectClient;
 using media::audio::common::AudioMMapPolicyInfo;
 using media::audio::common::AudioMMapPolicyType;
+using media::audio::common::AudioMode;
 using android::content::AttributionSourceState;
 using android::detail::AudioHalVersionInfo;
 
@@ -236,6 +238,7 @@
 BINDER_METHOD_ENTRY(isBluetoothVariableLatencyEnabled) \
 BINDER_METHOD_ENTRY(supportsBluetoothVariableLatency) \
 BINDER_METHOD_ENTRY(getSoundDoseInterface) \
+BINDER_METHOD_ENTRY(getAudioPolicyConfig) \
 
 // singleton for Binder Method Statistics for IAudioFlinger
 static auto& getIAudioFlingerStatistics() {
@@ -370,7 +373,7 @@
     BatteryNotifier::getInstance().noteResetAudio();
 
     mDevicesFactoryHal = DevicesFactoryHalInterface::create();
-    mEffectsFactoryHal = EffectsFactoryHalInterface::create();
+    mEffectsFactoryHal = audioflinger::EffectConfiguration::getEffectsFactoryHal();
 
     mMediaLogNotifier->run("MediaLogNotifier");
     std::vector<pid_t> halPids;
@@ -839,6 +842,8 @@
     for (const auto& vibratorInfo : mAudioVibratorInfos) {
         dprintf(fd, "  - %s\n", vibratorInfo.toString().c_str());
     }
+    dprintf(fd, "Bluetooth latency modes are %senabled\n",
+            mBluetoothLatencyModesEnabled ? "" : "not ");
 }
 
 void AudioFlinger::dumpPermissionDenial(int fd, const Vector<String16>& args __unused)
@@ -2573,6 +2578,47 @@
 
 // ----------------------------------------------------------------------------
 
+status_t AudioFlinger::getAudioPolicyConfig(media::AudioPolicyConfig *config)
+{
+    if (config == nullptr) {
+        return BAD_VALUE;
+    }
+    Mutex::Autolock _l(mLock);
+    AutoMutex lock(mHardwareLock);
+    RETURN_STATUS_IF_ERROR(
+            mDevicesFactoryHal->getSurroundSoundConfig(&config->surroundSoundConfig));
+    RETURN_STATUS_IF_ERROR(mDevicesFactoryHal->getEngineConfig(&config->engineConfig));
+    std::vector<std::string> hwModuleNames;
+    RETURN_STATUS_IF_ERROR(mDevicesFactoryHal->getDeviceNames(&hwModuleNames));
+    std::set<AudioMode> allSupportedModes;
+    for (const auto& name : hwModuleNames) {
+        AudioHwDevice* module = loadHwModule_l(name.c_str());
+        if (module == nullptr) continue;
+        media::AudioHwModule aidlModule;
+        if (module->hwDevice()->getAudioPorts(&aidlModule.ports) == OK &&
+                module->hwDevice()->getAudioRoutes(&aidlModule.routes) == OK) {
+            aidlModule.handle = module->handle();
+            aidlModule.name = module->moduleName();
+            config->modules.push_back(std::move(aidlModule));
+        }
+        std::vector<AudioMode> supportedModes;
+        if (module->hwDevice()->getSupportedModes(&supportedModes) == OK) {
+            allSupportedModes.insert(supportedModes.begin(), supportedModes.end());
+        }
+    }
+    if (!allSupportedModes.empty()) {
+        config->supportedModes.insert(config->supportedModes.end(),
+                allSupportedModes.begin(), allSupportedModes.end());
+    } else {
+        ALOGW("%s: The HAL does not provide telephony functionality", __func__);
+        config->supportedModes = { media::audio::common::AudioMode::NORMAL,
+            media::audio::common::AudioMode::RINGTONE,
+            media::audio::common::AudioMode::IN_CALL,
+            media::audio::common::AudioMode::IN_COMMUNICATION };
+    }
+    return OK;
+}
+
 audio_module_handle_t AudioFlinger::loadHwModule(const char *name)
 {
     if (name == NULL) {
@@ -2583,16 +2629,17 @@
     }
     Mutex::Autolock _l(mLock);
     AutoMutex lock(mHardwareLock);
-    return loadHwModule_l(name);
+    AudioHwDevice* module = loadHwModule_l(name);
+    return module != nullptr ? module->handle() : AUDIO_MODULE_HANDLE_NONE;
 }
 
 // loadHwModule_l() must be called with AudioFlinger::mLock and AudioFlinger::mHardwareLock held
-audio_module_handle_t AudioFlinger::loadHwModule_l(const char *name)
+AudioHwDevice* AudioFlinger::loadHwModule_l(const char *name)
 {
     for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
         if (strncmp(mAudioHwDevs.valueAt(i)->moduleName(), name, strlen(name)) == 0) {
             ALOGW("loadHwModule() module %s already loaded", name);
-            return mAudioHwDevs.keyAt(i);
+            return mAudioHwDevs.valueAt(i);
         }
     }
 
@@ -2601,7 +2648,7 @@
     int rc = mDevicesFactoryHal->openDevice(name, &dev);
     if (rc) {
         ALOGE("loadHwModule() error %d loading module %s", rc, name);
-        return AUDIO_MODULE_HANDLE_NONE;
+        return nullptr;
     }
     if (!mMelReporter->activateHalSoundDoseComputation(name, dev)) {
         ALOGW("loadHwModule() sound dose reporting is not available");
@@ -2612,7 +2659,7 @@
     mHardwareStatus = AUDIO_HW_IDLE;
     if (rc) {
         ALOGE("loadHwModule() init check error %d for module %s", rc, name);
-        return AUDIO_MODULE_HANDLE_NONE;
+        return nullptr;
     }
 
     // Check and cache this HAL's level of support for master mute and master
@@ -2686,8 +2733,7 @@
 
     ALOGI("loadHwModule() Loaded %s audio interface, handle %d", name, handle);
 
-    return handle;
-
+    return audioDevice;
 }
 
 // ----------------------------------------------------------------------------
@@ -4729,6 +4775,7 @@
         case TransactionCode::SET_REQUESTED_LATENCY_MODE:
         case TransactionCode::GET_SUPPORTED_LATENCY_MODES:
         case TransactionCode::INVALIDATE_TRACKS:
+        case TransactionCode::GET_AUDIO_POLICY_CONFIG:
             ALOGW("%s: transaction %d received from PID %d",
                   __func__, code, IPCThreadState::self()->getCallingPid());
             // return status only for non void methods
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 47fa74f..10bfdb9 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -319,6 +319,8 @@
 
     status_t invalidateTracks(const std::vector<audio_port_handle_t>& portIds) override;
 
+    virtual status_t getAudioPolicyConfig(media::AudioPolicyConfig* config);
+
     status_t onTransactWrapper(TransactionCode code, const Parcel& data, uint32_t flags,
         const std::function<status_t()>& delegate) override;
 
@@ -990,7 +992,7 @@
                 float       masterVolume_l() const;
                 float       getMasterBalance_l() const;
                 bool        masterMute_l() const;
-                audio_module_handle_t loadHwModule_l(const char *name);
+                AudioHwDevice* loadHwModule_l(const char *name);
 
                 Vector < sp<SyncEvent> > mPendingSyncEvents; // sync events awaiting for a session
                                                              // to be created
diff --git a/services/audioflinger/DeviceEffectManager.cpp b/services/audioflinger/DeviceEffectManager.cpp
index 2f61a01..4fb6138 100644
--- a/services/audioflinger/DeviceEffectManager.cpp
+++ b/services/audioflinger/DeviceEffectManager.cpp
@@ -23,6 +23,7 @@
 #include <audio_utils/primitives.h>
 
 #include "AudioFlinger.h"
+#include "EffectConfiguration.h"
 #include <media/audiohal/EffectsFactoryHalInterface.h>
 
 // ----------------------------------------------------------------------------
@@ -111,14 +112,16 @@
 
 status_t AudioFlinger::DeviceEffectManager::checkEffectCompatibility(
         const effect_descriptor_t *desc) {
-    sp<EffectsFactoryHalInterface> effectsFactory = mAudioFlinger.getEffectsFactory();
+    const sp<EffectsFactoryHalInterface> effectsFactory =
+            audioflinger::EffectConfiguration::getEffectsFactoryHal();
     if (effectsFactory == nullptr) {
         return BAD_VALUE;
     }
 
-    static AudioHalVersionInfo sMinDeviceEffectHalVersion =
+    static const AudioHalVersionInfo sMinDeviceEffectHalVersion =
             AudioHalVersionInfo(AudioHalVersionInfo::Type::HIDL, 6, 0);
-    AudioHalVersionInfo halVersion = effectsFactory->getHalVersion();
+    static const AudioHalVersionInfo halVersion =
+            audioflinger::EffectConfiguration::getAudioHalVersionInfo();
 
     // We can trust AIDL generated AudioHalVersionInfo comparison operator (based on std::tie) as
     // long as the type, major and minor sequence doesn't change in the definition.
@@ -137,7 +140,8 @@
         const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t deviceId,
         sp<EffectHalInterface> *effect) {
     status_t status = NO_INIT;
-    sp<EffectsFactoryHalInterface> effectsFactory = mAudioFlinger.getEffectsFactory();
+    const sp<EffectsFactoryHalInterface> effectsFactory =
+            audioflinger::EffectConfiguration::getEffectsFactoryHal();
     if (effectsFactory != 0) {
         status = effectsFactory->createEffect(
                 pEffectUuid, sessionId, AUDIO_IO_HANDLE_NONE, deviceId, effect);
diff --git a/services/audioflinger/EffectConfiguration.h b/services/audioflinger/EffectConfiguration.h
new file mode 100644
index 0000000..2f07fa2
--- /dev/null
+++ b/services/audioflinger/EffectConfiguration.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <media/audiohal/EffectsFactoryHalInterface.h>
+
+namespace android::audioflinger {
+
+/**
+ * Effect Configuration abstraction and helper class.
+ */
+class EffectConfiguration {
+public:
+    static bool isHidl() {
+        static const bool isHidl = getAudioHalVersionInfo().isHidl();
+        return isHidl;
+    }
+
+    static const sp<EffectsFactoryHalInterface>& getEffectsFactoryHal() {
+        static const auto effectsFactoryHal = EffectsFactoryHalInterface::create();
+        return effectsFactoryHal;
+    }
+
+    static const detail::AudioHalVersionInfo& getAudioHalVersionInfo() {
+        static const auto audioHalVersionInfo = getEffectsFactoryHal() ?
+                getEffectsFactoryHal()->getHalVersion() : detail::AudioHalVersionInfo{
+                        detail::AudioHalVersionInfo::Type::HIDL, 0 /* major */, 0 /* minor */ };
+        return audioHalVersionInfo;
+    }
+};
+
+} // namespace android::audioflinger
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index f324408..77aa804 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -44,6 +44,7 @@
 #include <mediautils/TimeCheck.h>
 
 #include "AudioFlinger.h"
+#include "EffectConfiguration.h"
 
 // ----------------------------------------------------------------------------
 
@@ -65,6 +66,7 @@
 namespace android {
 
 using aidl_utils::statusTFromBinderStatus;
+using audioflinger::EffectConfiguration;
 using binder::Status;
 
 namespace {
@@ -982,6 +984,7 @@
 
 #ifdef MULTICHANNEL_EFFECT_CHAIN
     if (status != NO_ERROR &&
+            EffectConfiguration::isHidl() && // only HIDL effects support channel conversion
             mIsOutput &&
             (mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO
                     || mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO)) {
@@ -1012,7 +1015,8 @@
         mSupportsFloat = true;
     }
 
-    if (status != NO_ERROR) {
+    // only HIDL effects support integer conversion.
+    if (status != NO_ERROR && EffectConfiguration::isHidl()) {
         ALOGV("EFFECT_CMD_SET_CONFIG failed with float format, retry with int16_t.");
         mConfig.inputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
         mConfig.outputCfg.format = AUDIO_FORMAT_PCM_16_BIT;
@@ -3032,7 +3036,8 @@
         const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t deviceId,
         sp<EffectHalInterface> *effect) {
     status_t status = NO_INIT;
-    sp<EffectsFactoryHalInterface> effectsFactory = mAudioFlinger.getEffectsFactory();
+    const sp<EffectsFactoryHalInterface> effectsFactory =
+            EffectConfiguration::getEffectsFactoryHal();
     if (effectsFactory != 0) {
         status = effectsFactory->createEffect(pEffectUuid, sessionId, io(), deviceId, effect);
     }
@@ -3375,8 +3380,18 @@
     ALOGV("%s type %d device type %d address %s device ID %d patch.isSoftware() %d",
             __func__, port->type, port->ext.device.type,
             port->ext.device.address, port->id, patch.isSoftware());
-    if (port->type != AUDIO_PORT_TYPE_DEVICE || port->ext.device.type != mDevice.mType
-        || port->ext.device.address != mDevice.address()) {
+    if (port->type != AUDIO_PORT_TYPE_DEVICE || port->ext.device.type != mDevice.mType ||
+        port->ext.device.address != mDevice.address()) {
+        return NAME_NOT_FOUND;
+    }
+    if (((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_POST_PROC) &&
+        (audio_port_config_has_input_direction(port))) {
+        ALOGI("%s don't create postprocessing effect on record port", __func__);
+        return NAME_NOT_FOUND;
+    }
+    if (((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) &&
+        (!audio_port_config_has_input_direction(port))) {
+        ALOGI("%s don't create preprocessing effect on playback port", __func__);
         return NAME_NOT_FOUND;
     }
     status_t status = NAME_NOT_FOUND;
@@ -3407,20 +3422,12 @@
     } else if (patch.isSoftware() || patch.thread().promote() != nullptr) {
         sp <ThreadBase> thread;
         if (audio_port_config_has_input_direction(port)) {
-            if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_POST_PROC) {
-                ALOGI("%s don't create postprocessing effect on record thread", __func__);
-                return NAME_NOT_FOUND;
-            }
             if (patch.isSoftware()) {
                 thread = patch.mRecord.thread();
             } else {
                 thread = patch.thread().promote();
             }
         } else {
-            if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC) {
-                ALOGI("%s don't create preprocessing effect on playback thread", __func__);
-                return NAME_NOT_FOUND;
-            }
             if (patch.isSoftware()) {
                 thread = patch.mPlayback.thread();
             } else {
@@ -3436,6 +3443,7 @@
     } else {
         status = BAD_VALUE;
     }
+
     if (status == NO_ERROR || status == ALREADY_EXISTS) {
         Status bs;
         if (isEnabled()) {
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 8d0c648..700bdd2 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -3571,12 +3571,7 @@
     mIdleSleepTimeUs = idleSleepTimeUs();
 
     mStandbyDelayNs = AudioFlinger::mStandbyTimeInNsecs;
-    // Shorten standby delay on VOIP RX output to avoid delayed routing updates
-    // after a call due to call end tone.
-    if (mOutput != nullptr && (mOutput->flags & AUDIO_OUTPUT_FLAG_VOIP_RX) != 0) {
-        const nsecs_t NS_PER_MS = 1000000;
-        mStandbyDelayNs = std::min(mStandbyDelayNs, latency_l() * NS_PER_MS);
-    }
+
     // make sure standby delay is not too short when connected to an A2DP sink to avoid
     // truncating audio when going to standby.
     if (!Intersection(outDeviceTypes(),  getAudioDeviceOutAllA2dpSet()).empty()) {
@@ -4110,8 +4105,9 @@
 
             // signal actual start of output stream when the render position reported by the kernel
             // starts moving.
-            if (!mStandby && !mHalStarted && mKernelPositionOnStandby !=
-                    mTimestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL]) {
+            if (!mHalStarted && ((isSuspended() && (mBytesWritten != 0)) || (!mStandby
+                    && (mKernelPositionOnStandby
+                            != mTimestamp.mPosition[ExtendedTimestamp::LOCATION_KERNEL])))) {
                 mHalStarted = true;
                 mWaitHalStartCV.broadcast();
             }
@@ -6320,6 +6316,12 @@
     } else {
         dprintf(fd, "  No FastMixer\n");
     }
+
+     dprintf(fd, "Bluetooth latency modes are %senabled\n",
+            mBluetoothLatencyModesEnabled ? "" : "not ");
+     dprintf(fd, "HAL does %ssupport Bluetooth latency modes\n", mOutput != nullptr &&
+             mOutput->audioHwDev->supportsBluetoothVariableLatency() ? "" : "not ");
+     dprintf(fd, "Supported latency modes: %s\n", toString(mSupportedLatencyModes).c_str());
 }
 
 uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() const
@@ -9985,6 +9987,9 @@
     audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
 
     audio_io_handle_t io = mId;
+    AttributionSourceState adjAttributionSource = AudioFlinger::checkAttributionSourcePackage(
+            client.attributionSource);
+
     if (isOutput()) {
         audio_config_t config = AUDIO_CONFIG_INITIALIZER;
         config.sample_rate = mSampleRate;
@@ -10000,7 +10005,7 @@
         ret = AudioSystem::getOutputForAttr(&mAttr, &io,
                                             mSessionId,
                                             &stream,
-                                            client.attributionSource,
+                                            adjAttributionSource,
                                             &config,
                                             flags,
                                             &deviceId,
@@ -10019,7 +10024,7 @@
         ret = AudioSystem::getInputForAttr(&mAttr, &io,
                                               RECORD_RIID_INVALID,
                                               mSessionId,
-                                              client.attributionSource,
+                                              adjAttributionSource,
                                               &config,
                                               AUDIO_INPUT_FLAG_MMAP_NOIRQ,
                                               &deviceId,
diff --git a/services/audiopolicy/AudioPolicyInterface.h b/services/audiopolicy/AudioPolicyInterface.h
index 134d068..da0df5f 100644
--- a/services/audiopolicy/AudioPolicyInterface.h
+++ b/services/audiopolicy/AudioPolicyInterface.h
@@ -438,6 +438,8 @@
 public:
     virtual ~AudioPolicyClientInterface() {}
 
+    virtual status_t getAudioPolicyConfig(media::AudioPolicyConfig *config) = 0;
+
     //
     // Audio HW module functions
     //
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioPolicyConfig.h b/services/audiopolicy/common/managerdefinitions/include/AudioPolicyConfig.h
index b24fce8..32c78a1 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioPolicyConfig.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioPolicyConfig.h
@@ -44,6 +44,8 @@
 
     // The source used to indicate the default fallback configuration.
     static const constexpr char* const kDefaultConfigSource = "AudioPolicyConfig::setDefault";
+    // The suffix of the "engine default" implementation shared library name.
+    static const constexpr char* const kDefaultEngineLibraryNameSuffix = "default";
 
     // Creates the default (fallback) configuration.
     static sp<const AudioPolicyConfig> createDefault();
@@ -114,12 +116,6 @@
         mDefaultOutputDevice = defaultDevice;
     }
 
-    bool isSpeakerDrcEnabled() const { return mIsSpeakerDrcEnabled; }
-    void setSpeakerDrcEnabled(bool isSpeakerDrcEnabled)
-    {
-        mIsSpeakerDrcEnabled = isSpeakerDrcEnabled;
-    }
-
     bool isCallScreenModeSupported() const { return mIsCallScreenModeSupported; }
     void setCallScreenModeSupported(bool isCallScreenModeSupported)
     {
@@ -135,18 +131,12 @@
     {
         mSurroundFormats = surroundFormats;
     }
-    void setSurroundFormats(SurroundFormats &&surroundFormats)
-    {
-        mSurroundFormats = std::move(surroundFormats);
-    }
 
     void setDefault();
 
 private:
     friend class sp<AudioPolicyConfig>;
 
-    static const constexpr char* const kDefaultEngineLibraryNameSuffix = "default";
-
     AudioPolicyConfig() = default;
 
     void augmentData();
@@ -155,13 +145,9 @@
     std::string mSource;  // Not kDefaultConfigSource. Empty source means an empty config.
     std::string mEngineLibraryNameSuffix = kDefaultEngineLibraryNameSuffix;
     HwModuleCollection mHwModules; /**< Collection of Module, with Profiles, i.e. Mix Ports. */
-    DeviceVector mOutputDevices;
-    DeviceVector mInputDevices;
+    DeviceVector mOutputDevices;  // Attached output devices.
+    DeviceVector mInputDevices;   // Attached input devices.
     sp<DeviceDescriptor> mDefaultOutputDevice;
-    // TODO: remove when legacy conf file is removed. true on devices that use DRC on the
-    // DEVICE_CATEGORY_SPEAKER path to boost soft sounds, used to adjust volume curves accordingly.
-    // Note: remove also speaker_drc_enabled from global configuration of XML config file.
-    bool mIsSpeakerDrcEnabled = false;
     bool mIsCallScreenModeSupported = false;
     SurroundFormats mSurroundFormats;
 };
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyConfig.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyConfig.cpp
index 575a6c5..42c76e2 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyConfig.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyConfig.cpp
@@ -41,9 +41,8 @@
     auto config = sp<AudioPolicyConfig>::make();
     if (status_t status = config->loadFromXml(filePath, false /*forVts*/); status == NO_ERROR) {
         return config;
-    } else {
-        return createDefault();
     }
+    return createDefault();
 }
 
 // static
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
index 4cfdaad..5f257f0 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
@@ -183,7 +183,8 @@
     for (size_t i = 0; i < size(); i++) {
         const sp<AudioPolicyMix>& registeredMix = itemAt(i);
         if (mix.mDeviceType == registeredMix->mDeviceType
-                && mix.mDeviceAddress.compare(registeredMix->mDeviceAddress) == 0) {
+                && mix.mDeviceAddress.compare(registeredMix->mDeviceAddress) == 0
+                && is_mix_loopback(mix.mRouteFlags)) {
             ALOGE("registerMix(): mix already registered for dev=0x%x addr=%s",
                     mix.mDeviceType, mix.mDeviceAddress.string());
             return BAD_VALUE;
diff --git a/services/audiopolicy/common/managerdefinitions/src/Serializer.cpp b/services/audiopolicy/common/managerdefinitions/src/Serializer.cpp
index 1b48dc3..3d5c1d2 100644
--- a/services/audiopolicy/common/managerdefinitions/src/Serializer.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/Serializer.cpp
@@ -197,7 +197,6 @@
 
     struct Attributes
     {
-        static constexpr const char *speakerDrcEnabled = "speaker_drc_enabled";
         static constexpr const char *callScreenModeSupported= "call_screen_mode_supported";
         static constexpr const char *engineLibrarySuffix = "engine_library";
     };
@@ -770,12 +769,7 @@
     for (const xmlNode *cur = root->xmlChildrenNode; cur != NULL; cur = cur->next) {
         if (!xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>(GlobalConfigTraits::tag))) {
             bool value;
-            std::string attr = getXmlAttribute(cur, Attributes::speakerDrcEnabled);
-            if (!attr.empty() &&
-                    convertTo<std::string, bool>(attr, value)) {
-                config->setSpeakerDrcEnabled(value);
-            }
-            attr = getXmlAttribute(cur, Attributes::callScreenModeSupported);
+            std::string attr = getXmlAttribute(cur, Attributes::callScreenModeSupported);
             if (!attr.empty() &&
                     convertTo<std::string, bool>(attr, value)) {
                 config->setCallScreenModeSupported(value);
diff --git a/services/audiopolicy/engine/common/include/EngineBase.h b/services/audiopolicy/engine/common/include/EngineBase.h
index 7ff0301..bac51f5 100644
--- a/services/audiopolicy/engine/common/include/EngineBase.h
+++ b/services/audiopolicy/engine/common/include/EngineBase.h
@@ -113,7 +113,7 @@
     status_t getDevicesForRoleAndStrategy(product_strategy_t strategy, device_role_t role,
             AudioDeviceTypeAddrVector &devices) const override;
 
-    engineConfig::ParsingResult loadAudioPolicyEngineConfig();
+    engineConfig::ParsingResult loadAudioPolicyEngineConfig(const std::string& xmlFilePath = "");
 
     const ProductStrategyMap &getProductStrategies() const { return mProductStrategies; }
 
diff --git a/services/audiopolicy/engine/common/src/EngineBase.cpp b/services/audiopolicy/engine/common/src/EngineBase.cpp
index 5769a97..7d6a308 100644
--- a/services/audiopolicy/engine/common/src/EngineBase.cpp
+++ b/services/audiopolicy/engine/common/src/EngineBase.cpp
@@ -115,7 +115,7 @@
     return PRODUCT_STRATEGY_NONE;
 }
 
-engineConfig::ParsingResult EngineBase::loadAudioPolicyEngineConfig()
+engineConfig::ParsingResult EngineBase::loadAudioPolicyEngineConfig(const std::string& xmlFilePath)
 {
     auto loadVolumeConfig = [](auto &volumeGroups, auto &volumeConfig) {
         // Ensure name unicity to prevent duplicate
@@ -163,8 +163,9 @@
         return stat(path, &fileStat) == 0 && S_ISREG(fileStat.st_mode);
     };
 
-    auto result = fileExists(engineConfig::DEFAULT_PATH) ?
-            engineConfig::parse(engineConfig::DEFAULT_PATH) : engineConfig::ParsingResult{};
+    const std::string filePath = xmlFilePath.empty() ? engineConfig::DEFAULT_PATH : xmlFilePath;
+    auto result = fileExists(filePath.c_str()) ?
+            engineConfig::parse(filePath.c_str()) : engineConfig::ParsingResult{};
     if (result.parsedConfig == nullptr) {
         ALOGD("%s: No configuration found, using default matching phone experience.", __FUNCTION__);
         engineConfig::Config config = gDefaultEngineConfig;
diff --git a/services/audiopolicy/engine/interface/EngineInterface.h b/services/audiopolicy/engine/interface/EngineInterface.h
index ea8fc41..dd7ac1a 100644
--- a/services/audiopolicy/engine/interface/EngineInterface.h
+++ b/services/audiopolicy/engine/interface/EngineInterface.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <string>
 #include <utility>
 
 #include <AudioPolicyManagerObserver.h>
@@ -46,6 +47,13 @@
 {
 public:
     /**
+     * Loads the engine configuration from the specified or the default config file.
+     * If loading failed, tries to fall back to some default configuration. If fallback
+     * is impossible, returns an error.
+     */
+    virtual status_t loadFromXmlConfigWithFallback(const std::string& xmlFilePath = "") = 0;
+
+    /**
      * Checks if the engine was correctly initialized.
      *
      * @return NO_ERROR if initialization has been done correctly, error code otherwise..
diff --git a/services/audiopolicy/engineconfigurable/src/Collection.h b/services/audiopolicy/engineconfigurable/src/Collection.h
index 02b41cb..4640515 100644
--- a/services/audiopolicy/engineconfigurable/src/Collection.h
+++ b/services/audiopolicy/engineconfigurable/src/Collection.h
@@ -53,6 +53,10 @@
     {
         collectionSupported();
     }
+    ~Collection()
+    {
+        clear();
+    }
 
     /**
      * Add a policy element to the collection. Policy elements are streams, strategies, input
diff --git a/services/audiopolicy/engineconfigurable/src/Engine.cpp b/services/audiopolicy/engineconfigurable/src/Engine.cpp
index a7f92cd..2eb0177 100644
--- a/services/audiopolicy/engineconfigurable/src/Engine.cpp
+++ b/services/audiopolicy/engineconfigurable/src/Engine.cpp
@@ -68,16 +68,15 @@
 
 Engine::Engine() : mPolicyParameterMgr(new ParameterManagerWrapper())
 {
-    status_t loadResult = loadAudioPolicyEngineConfig();
+}
+
+status_t Engine::loadFromXmlConfigWithFallback(const std::string& xmlFilePath)
+{
+    status_t loadResult = loadAudioPolicyEngineConfig(xmlFilePath);
     if (loadResult < 0) {
         ALOGE("Policy Engine configuration is invalid.");
     }
-}
-
-Engine::~Engine()
-{
-    mStreamCollection.clear();
-    mInputSourceCollection.clear();
+    return loadResult;
 }
 
 status_t Engine::initCheck()
@@ -93,7 +92,7 @@
 template <typename Key>
 Element<Key> *Engine::getFromCollection(const Key &key) const
 {
-    const Collection<Key> collection = getCollection<Key>();
+    const Collection<Key> &collection = getCollection<Key>();
     return collection.get(key);
 }
 
@@ -194,9 +193,9 @@
     return EngineBase::setDeviceConnectionState(device, state);
 }
 
-status_t Engine::loadAudioPolicyEngineConfig()
+status_t Engine::loadAudioPolicyEngineConfig(const std::string& xmlFilePath)
 {
-    auto result = EngineBase::loadAudioPolicyEngineConfig();
+    auto result = EngineBase::loadAudioPolicyEngineConfig(xmlFilePath);
 
     // Custom XML Parsing
     auto loadCriteria= [this](const auto& configCriteria, const auto& configCriterionTypes) {
@@ -531,5 +530,3 @@
 
 } // namespace audio_policy
 } // namespace android
-
-
diff --git a/services/audiopolicy/engineconfigurable/src/Engine.h b/services/audiopolicy/engineconfigurable/src/Engine.h
index 8ea8052..b964cd6 100644
--- a/services/audiopolicy/engineconfigurable/src/Engine.h
+++ b/services/audiopolicy/engineconfigurable/src/Engine.h
@@ -33,12 +33,17 @@
 {
 public:
     Engine();
-    virtual ~Engine();
+    virtual ~Engine() = default;
 
     template <class RequestedInterface>
     RequestedInterface *queryInterface();
 
     ///
+    /// from EngineInterface
+    ///
+    android::status_t loadFromXmlConfigWithFallback(const std::string& xmlFilePath = "") override;
+
+    ///
     /// from EngineBase
     ///
     android::status_t initCheck() override;
@@ -132,7 +137,7 @@
     template <typename Property, typename Key>
     bool setPropertyForKey(const Property &property, const Key &key);
 
-    status_t loadAudioPolicyEngineConfig();
+    status_t loadAudioPolicyEngineConfig(const std::string& xmlFilePath);
 
     DeviceVector getCachedDevices(product_strategy_t ps) const;
 
@@ -150,4 +155,3 @@
 } // namespace audio_policy
 
 } // namespace android
-
diff --git a/services/audiopolicy/enginedefault/src/Engine.cpp b/services/audiopolicy/enginedefault/src/Engine.cpp
index 84e7cb8..88cbb7d 100644
--- a/services/audiopolicy/enginedefault/src/Engine.cpp
+++ b/services/audiopolicy/enginedefault/src/Engine.cpp
@@ -59,9 +59,8 @@
     return legacyStrategy;
 }
 
-Engine::Engine()
-{
-    auto result = EngineBase::loadAudioPolicyEngineConfig();
+status_t Engine::loadFromXmlConfigWithFallback(const std::string& xmlFilePath) {
+    auto result = EngineBase::loadAudioPolicyEngineConfig(xmlFilePath);
     ALOGE_IF(result.nbSkippedElement != 0,
              "Policy Engine configuration is partially invalid, skipped %zu elements",
              result.nbSkippedElement);
@@ -70,6 +69,8 @@
     for (const auto &strategy : legacyStrategy) {
         mLegacyStrategyMap[getProductStrategyByName(strategy.name)] = strategy.id;
     }
+
+    return OK;
 }
 
 status_t Engine::setForceUse(audio_policy_force_use_t usage, audio_policy_forced_cfg_t config)
@@ -845,5 +846,3 @@
 
 } // namespace audio_policy
 } // namespace android
-
-
diff --git a/services/audiopolicy/enginedefault/src/Engine.h b/services/audiopolicy/enginedefault/src/Engine.h
index 2e4decf..8410560 100644
--- a/services/audiopolicy/enginedefault/src/Engine.h
+++ b/services/audiopolicy/enginedefault/src/Engine.h
@@ -45,9 +45,14 @@
 class Engine : public EngineBase
 {
 public:
-    Engine();
+    Engine() = default;
     virtual ~Engine() = default;
 
+    ///
+    /// from EngineInterface
+    ///
+    status_t loadFromXmlConfigWithFallback(const std::string& xmlFilePath = "") override;
+
 private:
     ///
     /// from EngineBase, so from EngineInterface
@@ -105,4 +110,3 @@
 };
 } // namespace audio_policy
 } // namespace android
-
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index b558fed..f093e68 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -4373,6 +4373,11 @@
         ALOGE("%s the requested device is currently unavailable", __func__);
         return BAD_VALUE;
     }
+    if (!audio_is_usb_out_device(deviceDescriptor->type())) {
+        ALOGE("%s the requested device(type=%#x) is not usb device", __func__,
+              deviceDescriptor->type());
+        return BAD_VALUE;
+    }
     for (const auto& hwModule : mHwModules) {
         for (const auto& curProfile : hwModule->getOutputProfiles()) {
             if (curProfile->supportsDevice(deviceDescriptor)) {
@@ -5997,10 +6002,12 @@
 }
 
 AudioPolicyManager::AudioPolicyManager(const sp<const AudioPolicyConfig>& config,
+                                       EngineInstance&& engine,
                                        AudioPolicyClientInterface *clientInterface)
     :
     mUidCached(AID_AUDIOSERVER), // no need to call getuid(), there's only one of us running.
     mConfig(config),
+    mEngine(std::move(engine)),
     mpClientInterface(clientInterface),
     mLimitRingtoneVolume(false), mLastVoiceVolume(-1.0f),
     mA2dpSuspended(false),
@@ -6015,18 +6022,8 @@
 }
 
 status_t AudioPolicyManager::initialize() {
-    {
-        auto engLib = EngineLibrary::load(
-                        "libaudiopolicyengine" + mConfig->getEngineLibraryNameSuffix() + ".so");
-        if (!engLib) {
-            ALOGE("%s: Failed to load the engine library", __FUNCTION__);
-            return NO_INIT;
-        }
-        mEngine = engLib->createEngine();
-        if (mEngine == nullptr) {
-            ALOGE("%s: Failed to instantiate the APM engine", __FUNCTION__);
-            return NO_INIT;
-        }
+    if (mEngine == nullptr) {
+        return NO_INIT;
     }
     mEngine->setObserver(this);
     status_t status = mEngine->initCheck();
@@ -6102,10 +6099,14 @@
         if (std::find(mHwModules.begin(), mHwModules.end(), hwModule) != mHwModules.end()) {
             continue;
         }
-        hwModule->setHandle(mpClientInterface->loadHwModule(hwModule->getName()));
         if (hwModule->getHandle() == AUDIO_MODULE_HANDLE_NONE) {
-            ALOGW("could not open HW module %s", hwModule->getName());
-            continue;
+            if (audio_module_handle_t handle = mpClientInterface->loadHwModule(hwModule->getName());
+                    handle != AUDIO_MODULE_HANDLE_NONE) {
+                hwModule->setHandle(handle);
+            } else {
+                ALOGW("could not load HW module %s", hwModule->getName());
+                continue;
+            }
         }
         mHwModules.push_back(hwModule);
         // open all output streams needed to access attached devices.
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index b2ac181..88bafef 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -95,6 +95,7 @@
 
 public:
         AudioPolicyManager(const sp<const AudioPolicyConfig>& config,
+                           EngineInstance&& engine,
                            AudioPolicyClientInterface *clientInterface);
         virtual ~AudioPolicyManager();
 
@@ -922,6 +923,7 @@
 
         const uid_t mUidCached;                         // AID_AUDIOSERVER
         sp<const AudioPolicyConfig> mConfig;
+        EngineInstance mEngine;                         // Audio Policy Engine instance
         AudioPolicyClientInterface *mpClientInterface;  // audio policy client interface
         sp<SwAudioOutputDescriptor> mPrimaryOutput;     // primary output descriptor
         // list of descriptors for outputs currently opened
@@ -974,9 +976,6 @@
 
         uint32_t nextAudioPortGeneration();
 
-        // Audio Policy Engine Interface.
-        EngineInstance mEngine;
-
         // Surround formats that are enabled manually. Taken into account when
         // "encoded surround" is forced into "manual" mode.
         std::unordered_set<audio_format_t> mManualSurroundFormats;
diff --git a/services/audiopolicy/managerdefault/EngineLibrary.cpp b/services/audiopolicy/managerdefault/EngineLibrary.cpp
index ef699aa..939fbc5 100644
--- a/services/audiopolicy/managerdefault/EngineLibrary.cpp
+++ b/services/audiopolicy/managerdefault/EngineLibrary.cpp
@@ -23,9 +23,27 @@
 
 namespace android {
 
-// static
-std::shared_ptr<EngineLibrary> EngineLibrary::load(std::string libraryPath)
+EngineInstance loadApmEngineLibraryAndCreateEngine(const std::string& librarySuffix,
+        const std::string& configXmlFilePath)
 {
+    auto engLib = EngineLibrary::load(librarySuffix);
+    if (!engLib) {
+        ALOGE("%s: Failed to load the engine library, suffix \"%s\"",
+                __func__, librarySuffix.c_str());
+        return nullptr;
+    }
+    auto engine = engLib->createEngineUsingXmlConfig(configXmlFilePath);
+    if (engine == nullptr) {
+        ALOGE("%s: Failed to instantiate the APM engine", __func__);
+        return nullptr;
+    }
+    return engine;
+}
+
+// static
+std::shared_ptr<EngineLibrary> EngineLibrary::load(const std::string& librarySuffix)
+{
+    std::string libraryPath = "libaudiopolicyengine" + librarySuffix + ".so";
     std::shared_ptr<EngineLibrary> engLib(new EngineLibrary());
     return engLib->init(std::move(libraryPath)) ? engLib : nullptr;
 }
@@ -35,6 +53,20 @@
     close();
 }
 
+EngineInstance EngineLibrary::createEngineUsingXmlConfig(const std::string& xmlFilePath) {
+    auto instance = createEngine();
+    if (instance != nullptr) {
+        if (status_t status = instance->loadFromXmlConfigWithFallback(xmlFilePath);
+                status == OK) {
+            return instance;
+        } else {
+            ALOGE("%s: loading of the engine config with XML configuration file \"%s\" failed: %d",
+                    __func__, xmlFilePath.empty() ? "default" : xmlFilePath.c_str(), status);
+        }
+    }
+    return nullptr;
+}
+
 bool EngineLibrary::init(std::string libraryPath)
 {
     mLibraryHandle = dlopen(libraryPath.c_str(), 0);
diff --git a/services/audiopolicy/managerdefault/EngineLibrary.h b/services/audiopolicy/managerdefault/EngineLibrary.h
index f143916..dc138a1 100644
--- a/services/audiopolicy/managerdefault/EngineLibrary.h
+++ b/services/audiopolicy/managerdefault/EngineLibrary.h
@@ -26,9 +26,12 @@
 
 using EngineInstance = std::unique_ptr<EngineInterface, std::function<void (EngineInterface*)>>;
 
+EngineInstance loadApmEngineLibraryAndCreateEngine(const std::string& librarySuffix,
+        const std::string& configXmlFilePath = "");
+
 class EngineLibrary : public std::enable_shared_from_this<EngineLibrary> {
 public:
-    static std::shared_ptr<EngineLibrary> load(std::string libraryPath);
+    static std::shared_ptr<EngineLibrary> load(const std::string& librarySuffix);
     ~EngineLibrary();
 
     EngineLibrary(const EngineLibrary&) = delete;
@@ -36,11 +39,12 @@
     EngineLibrary& operator=(const EngineLibrary&) = delete;
     EngineLibrary& operator=(EngineLibrary&&) = delete;
 
-    EngineInstance createEngine();
+    EngineInstance createEngineUsingXmlConfig(const std::string& xmlFilePath);
 
 private:
     EngineLibrary() = default;
     bool init(std::string libraryPath);
+    EngineInstance createEngine();
     void close();
 
     void *mLibraryHandle = nullptr;
diff --git a/services/audiopolicy/service/AudioPolicyClientImpl.cpp b/services/audiopolicy/service/AudioPolicyClientImpl.cpp
index 887f210..13e682b 100644
--- a/services/audiopolicy/service/AudioPolicyClientImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyClientImpl.cpp
@@ -27,6 +27,18 @@
 
 /* implementation of the client interface from the policy manager */
 
+status_t AudioPolicyService::AudioPolicyClient::getAudioPolicyConfig(
+        media::AudioPolicyConfig *config)
+{
+    sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
+    if (af == 0) {
+        ALOGW("%s: could not get AudioFlinger", __func__);
+        return AUDIO_MODULE_HANDLE_NONE;
+    }
+
+    return af->getAudioPolicyConfig(config);
+}
+
 audio_module_handle_t AudioPolicyService::AudioPolicyClient::loadHwModule(const char *name)
 {
     sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
diff --git a/services/audiopolicy/service/AudioPolicyEffects.cpp b/services/audiopolicy/service/AudioPolicyEffects.cpp
index b111865..70a1785 100644
--- a/services/audiopolicy/service/AudioPolicyEffects.cpp
+++ b/services/audiopolicy/service/AudioPolicyEffects.cpp
@@ -924,8 +924,7 @@
         for (auto& stream : processingChain) {
             auto effectDescs = std::make_unique<EffectDescVector>();
             for (auto& effect : stream.effects) {
-                effectDescs->mEffects.add(
-                        new EffectDesc{effect.get().name.c_str(), effect.get().uuid});
+                effectDescs->mEffects.add(new EffectDesc{effect->name.c_str(), effect->uuid});
             }
             streams.add(stream.type, effectDescs.release());
         }
@@ -935,8 +934,7 @@
         for (auto& deviceProcess : processingChain) {
             auto effectDescs = std::make_unique<EffectDescVector>();
             for (auto& effect : deviceProcess.effects) {
-                effectDescs->mEffects.add(
-                        new EffectDesc{effect.get().name.c_str(), effect.get().uuid});
+                effectDescs->mEffects.add(new EffectDesc{effect->name.c_str(), effect->uuid});
             }
             auto deviceEffects = std::make_unique<DeviceEffects>(
                         std::move(effectDescs), deviceProcess.type, deviceProcess.address);
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 0d12060..2e7b3ff 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -818,8 +818,29 @@
 
     Mutex::Autolock _l(mLock);
 
+    ALOGW_IF(client->silenced, "startInput on silenced input for port %d, uid %d. Unsilencing.",
+            portIdAidl,
+            client->attributionSource.uid);
+
+    if (client->active) {
+        ALOGE("Client should never be active before startInput. Uid %d port %d",
+                client->attributionSource.uid, portId);
+        finishRecording(client->attributionSource, client->attributes.source);
+        return binderStatusFromStatusT(INVALID_OPERATION);
+    }
+
+    // Force the possibly silenced client to be unsilenced since we just called
+    // startRecording (i.e. we have assumed it is unsilenced).
+    // At this point in time, the client is inactive, so no calls to appops are sent in
+    // setAppState_l.
+    // This ensures existing clients have the same behavior as new clients (starting unsilenced).
+    // TODO(b/282076713)
+    setAppState_l(client, APP_STATE_TOP);
+
     client->active = true;
     client->startTimeNs = systemTime();
+    // This call updates the silenced state, and since we are active, appropriately notifies appops
+    // if we silence the track.
     updateUidStates_l();
 
     status_t status;
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index 7757f37..9367949 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -186,8 +186,10 @@
 
 static AudioPolicyInterface* createAudioPolicyManager(AudioPolicyClientInterface *clientInterface)
 {
+    auto config = AudioPolicyConfig::loadFromApmXmlConfigWithFallback();  // This can't fail.
     AudioPolicyManager *apm = new AudioPolicyManager(
-            AudioPolicyConfig::loadFromApmXmlConfigWithFallback(), clientInterface);
+            config, loadApmEngineLibraryAndCreateEngine(config->getEngineLibraryNameSuffix()),
+            clientInterface);
     status_t status = apm->initialize();
     if (status != NO_ERROR) {
         delete apm;
@@ -1841,14 +1843,12 @@
 void AudioPolicyService::SensorPrivacyPolicy::registerSelf() {
     SensorPrivacyManager spm;
     mSensorPrivacyEnabled = spm.isSensorPrivacyEnabled();
-    (void)spm.addToggleSensorPrivacyListener(this);
     spm.addSensorPrivacyListener(this);
 }
 
 void AudioPolicyService::SensorPrivacyPolicy::unregisterSelf() {
     SensorPrivacyManager spm;
     spm.removeSensorPrivacyListener(this);
-    spm.removeToggleSensorPrivacyListener(this);
 }
 
 bool AudioPolicyService::SensorPrivacyPolicy::isSensorPrivacyEnabled() {
@@ -1917,6 +1917,7 @@
     // since it controls the mic permission for legacy apps.
     mAppOpsManager.startWatchingMode(mAppOp, VALUE_OR_FATAL(aidl2legacy_string_view_String16(
         mAttributionSource.packageName.value_or(""))),
+        AppOpsManager::WATCH_FOREGROUND_CHANGES,
         mOpCallback);
 }
 
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index 981951a..4710a8a 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -735,6 +735,8 @@
         explicit AudioPolicyClient(AudioPolicyService *service) : mAudioPolicyService(service) {}
         virtual ~AudioPolicyClient() {}
 
+        virtual status_t getAudioPolicyConfig(media::AudioPolicyConfig *config);
+
         //
         // Audio HW module functions
         //
diff --git a/services/audiopolicy/tests/AudioPolicyTestClient.h b/services/audiopolicy/tests/AudioPolicyTestClient.h
index 71e34e3..2ae0e97 100644
--- a/services/audiopolicy/tests/AudioPolicyTestClient.h
+++ b/services/audiopolicy/tests/AudioPolicyTestClient.h
@@ -25,6 +25,9 @@
     virtual ~AudioPolicyTestClient() = default;
 
     // AudioPolicyClientInterface Implementation
+    status_t getAudioPolicyConfig(media::AudioPolicyConfig* /*config*/) override {
+        return INVALID_OPERATION;
+    }
     audio_module_handle_t loadHwModule(const char* /*name*/) override {
         return AUDIO_MODULE_HANDLE_NONE;
     }
diff --git a/services/audiopolicy/tests/AudioPolicyTestManager.h b/services/audiopolicy/tests/AudioPolicyTestManager.h
index 9ad9849..31ee252 100644
--- a/services/audiopolicy/tests/AudioPolicyTestManager.h
+++ b/services/audiopolicy/tests/AudioPolicyTestManager.h
@@ -22,10 +22,12 @@
 class AudioPolicyTestManager : public AudioPolicyManager {
   public:
     explicit AudioPolicyTestManager(AudioPolicyClientInterface *clientInterface)
-            : AudioPolicyManager(AudioPolicyConfig::createDefault(), clientInterface) {}
+            : AudioPolicyTestManager(AudioPolicyConfig::createDefault(), clientInterface) {}
     AudioPolicyTestManager(const sp<const AudioPolicyConfig>& config,
             AudioPolicyClientInterface *clientInterface)
-            : AudioPolicyManager(config, clientInterface) {}
+            : AudioPolicyManager(config,
+                    loadApmEngineLibraryAndCreateEngine(config->getEngineLibraryNameSuffix()),
+                    clientInterface) {}
     using AudioPolicyManager::getConfig;
     using AudioPolicyManager::initialize;
     using AudioPolicyManager::getOutputs;
diff --git a/services/audiopolicy/tests/audiopolicymanager_tests.cpp b/services/audiopolicy/tests/audiopolicymanager_tests.cpp
index 97a1e0d..34c3749 100644
--- a/services/audiopolicy/tests/audiopolicymanager_tests.cpp
+++ b/services/audiopolicy/tests/audiopolicymanager_tests.cpp
@@ -1337,13 +1337,14 @@
             AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "", audioConfig);
     ASSERT_EQ(INVALID_OPERATION, ret);
 
-    // The first time to register valid policy mixes should succeed.
+    // The first time to register valid loopback policy mix should succeed.
     clearPolicyMix();
-    ret = addPolicyMix(MIX_TYPE_PLAYERS, MIX_ROUTE_FLAG_RENDER,
-            AUDIO_DEVICE_OUT_SPEAKER, "", audioConfig);
+    ret = addPolicyMix(MIX_TYPE_PLAYERS, MIX_ROUTE_FLAG_LOOP_BACK,
+            AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "addr", audioConfig);
     ASSERT_EQ(NO_ERROR, ret);
-    // Registering the same policy mixes should fail.
-    ret = mManager->registerPolicyMixes(mAudioMixes);
+    // Registering the render policy for the loopback address should succeed.
+    ret = addPolicyMix(MIX_TYPE_PLAYERS, MIX_ROUTE_FLAG_RENDER,
+            AUDIO_DEVICE_OUT_REMOTE_SUBMIX, "addr", audioConfig);
     ASSERT_EQ(INVALID_OPERATION, ret);
 }
 
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index cecced4..668a51a 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -3074,6 +3074,13 @@
     return binder::Status::ok();
 }
 
+Status CameraService::reportExtensionSessionStats(
+        const hardware::CameraExtensionSessionStats& stats, String16* sessionKey /*out*/) {
+    ALOGV("%s: reported %s", __FUNCTION__, stats.toString().c_str());
+    *sessionKey = mCameraServiceProxyWrapper->updateExtensionStats(stats);
+    return Status::ok();
+}
+
 void CameraService::removeByClient(const BasicClient* client) {
     Mutex::Autolock lock(mServiceLock);
     for (auto& i : mActiveClientManager.getAll()) {
@@ -3910,18 +3917,21 @@
 
 void CameraService::UidPolicy::registerWithActivityManager() {
     Mutex::Autolock _l(mUidLock);
+    int32_t emptyUidArray[] = { };
 
     if (mRegistered) return;
     status_t res = mAm.linkToDeath(this);
-    mAm.registerUidObserver(this, ActivityManager::UID_OBSERVER_GONE
+    mAm.registerUidObserverForUids(this, ActivityManager::UID_OBSERVER_GONE
             | ActivityManager::UID_OBSERVER_IDLE
             | ActivityManager::UID_OBSERVER_ACTIVE | ActivityManager::UID_OBSERVER_PROCSTATE
             | ActivityManager::UID_OBSERVER_PROC_OOM_ADJ,
             ActivityManager::PROCESS_STATE_UNKNOWN,
-            String16("cameraserver"));
+            String16("cameraserver"), emptyUidArray, 0, mObserverToken);
     if (res == OK) {
         mRegistered = true;
         ALOGV("UidPolicy: Registered with ActivityManager");
+    } else {
+        ALOGE("UidPolicy: Failed to register with ActivityManager: 0x%08x", res);
     }
 }
 
@@ -4019,13 +4029,16 @@
             if (it->second.hasCamera) {
                 for (auto &monitoredUid : mMonitoredUids) {
                     if (monitoredUid.first != uid && adj > monitoredUid.second.procAdj) {
+                        ALOGV("%s: notify uid %d", __FUNCTION__, monitoredUid.first);
                         notifyUidSet.emplace(monitoredUid.first);
                     }
                 }
+                ALOGV("%s: notify uid %d", __FUNCTION__, uid);
                 notifyUidSet.emplace(uid);
             } else {
                 for (auto &monitoredUid : mMonitoredUids) {
                     if (monitoredUid.second.hasCamera && adj < monitoredUid.second.procAdj) {
+                        ALOGV("%s: notify uid %d", __FUNCTION__, uid);
                         notifyUidSet.emplace(uid);
                     }
                 }
@@ -4056,6 +4069,10 @@
         monitoredUid.procAdj = resource_policy::UNKNOWN_ADJ;
         monitoredUid.refCount = 1;
         it = mMonitoredUids.emplace(std::pair<uid_t, MonitoredUid>(uid, monitoredUid)).first;
+        status_t res = mAm.addUidToObserver(mObserverToken, String16("cameraserver"), uid);
+        if (res != OK) {
+            ALOGE("UidPolicy: Failed to add uid to observer: 0x%08x", res);
+        }
     }
 
     if (openCamera) {
@@ -4073,6 +4090,10 @@
         it->second.refCount--;
         if (it->second.refCount == 0) {
             mMonitoredUids.erase(it);
+            status_t res = mAm.removeUidFromObserver(mObserverToken, String16("cameraserver"), uid);
+            if (res != OK) {
+                ALOGE("UidPolicy: Failed to remove uid from observer: 0x%08x", res);
+            }
         } else if (closeCamera) {
             it->second.hasCamera = false;
         }
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 8c57d26..3214d4c 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -218,6 +218,9 @@
             /*out*/
             sp<hardware::camera2::ICameraInjectionSession>* cameraInjectionSession);
 
+    virtual binder::Status reportExtensionSessionStats(
+            const hardware::CameraExtensionSessionStats& stats, String16* sessionKey /*out*/);
+
     // Extra permissions checks
     virtual status_t    onTransact(uint32_t code, const Parcel& data,
                                    Parcel* reply, uint32_t flags);
@@ -798,6 +801,7 @@
         // Monitored uid map
         std::unordered_map<uid_t, MonitoredUid> mMonitoredUids;
         std::unordered_map<uid_t, bool> mOverrideUids;
+        sp<IBinder> mObserverToken;
     }; // class UidPolicy
 
     // If sensor privacy is enabled then all apps, including those that are active, should be
diff --git a/services/camera/libcameraservice/CameraServiceWatchdog.cpp b/services/camera/libcameraservice/CameraServiceWatchdog.cpp
index 28dff7d..1c1bd24 100644
--- a/services/camera/libcameraservice/CameraServiceWatchdog.cpp
+++ b/services/camera/libcameraservice/CameraServiceWatchdog.cpp
@@ -43,8 +43,7 @@
             mTidMap[currentThreadId].cycles++;
 
             if (mTidMap[currentThreadId].cycles >= mMaxCycles) {
-                std::string abortMessage = getAbortMessage(getpid(), currentThreadId,
-                        mTidMap[currentThreadId].functionName);
+                std::string abortMessage = getAbortMessage(mTidMap[currentThreadId].functionName);
                 android_set_abort_message(abortMessage.c_str());
                 ALOGW("CameraServiceWatchdog triggering abort for pid: %d tid: %d", getpid(),
                         currentThreadId);
@@ -60,10 +59,9 @@
     return true;
 }
 
-std::string CameraServiceWatchdog::getAbortMessage(int pid, int tid, std::string functionName) {
+std::string CameraServiceWatchdog::getAbortMessage(const std::string& functionName) {
     std::string res = "CameraServiceWatchdog triggering abort during "
-            + functionName + " | pid: " + std::to_string(pid)
-            + " tid: " + std::to_string(tid);
+            + functionName;
     return res;
 }
 
diff --git a/services/camera/libcameraservice/CameraServiceWatchdog.h b/services/camera/libcameraservice/CameraServiceWatchdog.h
index 6f8858a..de6ac9e 100644
--- a/services/camera/libcameraservice/CameraServiceWatchdog.h
+++ b/services/camera/libcameraservice/CameraServiceWatchdog.h
@@ -144,7 +144,7 @@
      */
     void stop(uint32_t tid);
 
-    std::string getAbortMessage(int pid, int tid, std::string functionName);
+    std::string getAbortMessage(const std::string& functionName);
 
     virtual bool    threadLoop();
 
diff --git a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
index e652546..694aff3 100644
--- a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
@@ -120,7 +120,7 @@
         camera_stream_rotation_t rotation, int *id, const String8& physicalCameraId,
         const std::unordered_set<int32_t> &sensorPixelModesUsed,
         std::vector<int> *surfaceIds,
-        int /*streamSetId*/, bool /*isShared*/, int32_t /*colorSpace*/,
+        int /*streamSetId*/, bool /*isShared*/, int32_t colorSpace,
         int64_t /*dynamicProfile*/, int64_t /*streamUseCase*/, bool useReadoutTimestamp) {
     sp<CameraDeviceBase> device = mDevice.promote();
     if (!device.get()) {
@@ -153,7 +153,7 @@
             ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT,
             OutputConfiguration::TIMESTAMP_BASE_DEFAULT,
             OutputConfiguration::MIRROR_MODE_AUTO,
-            ANDROID_REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED,
+            colorSpace,
             useReadoutTimestamp);
     if (res == OK) {
         mAppSegmentSurfaceId = (*surfaceIds)[0];
@@ -196,7 +196,7 @@
             ANDROID_SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT,
             OutputConfiguration::TIMESTAMP_BASE_DEFAULT,
             OutputConfiguration::MIRROR_MODE_AUTO,
-            ANDROID_REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED,
+            colorSpace,
             useReadoutTimestamp);
     if (res == OK) {
         mMainImageSurfaceId = sourceSurfaceId[0];
diff --git a/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp b/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp
index 8223371..6588470 100644
--- a/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/JpegRCompositeStream.cpp
@@ -658,6 +658,13 @@
         return res;
     }
 
+    if ((res = native_window_set_usage(mOutputSurface.get(),
+            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN)) != OK) {
+        ALOGE("%s: Unable to configure stream buffer usage for stream %d", __FUNCTION__,
+                mP010StreamId);
+        return res;
+    }
+
     int maxProducerBuffers;
     ANativeWindow *anw = mP010Surface.get();
     if ((res = anw->query(anw, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &maxProducerBuffers)) != OK) {
diff --git a/services/camera/libcameraservice/common/Camera2ClientBase.cpp b/services/camera/libcameraservice/common/Camera2ClientBase.cpp
index f1fc815..6e10f30 100644
--- a/services/camera/libcameraservice/common/Camera2ClientBase.cpp
+++ b/services/camera/libcameraservice/common/Camera2ClientBase.cpp
@@ -145,7 +145,6 @@
     res = TClientBase::startCameraOps();
     if (res != OK) {
         TClientBase::finishCameraOps();
-        mDevice.clear();
         return res;
     }
 
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index 6f4c04a..fac6e4e 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -1127,8 +1127,8 @@
     const int32_t scalerSizesTag =
               SessionConfigurationUtils::getAppropriateModeTag(
                       ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, maxResolution);
-    const int32_t scalerMinFrameDurationsTag =
-            ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS;
+    const int32_t scalerMinFrameDurationsTag = SessionConfigurationUtils::getAppropriateModeTag(
+            ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS, maxResolution);
     const int32_t scalerStallDurationsTag =
                  SessionConfigurationUtils::getAppropriateModeTag(
                         ANDROID_SCALER_AVAILABLE_STALL_DURATIONS, maxResolution);
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 1643212..61c3298 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -265,113 +265,110 @@
 
 status_t Camera3Device::disconnectImpl() {
     ATRACE_CALL();
+    Mutex::Autolock il(mInterfaceLock);
+
     ALOGI("%s: E", __FUNCTION__);
 
     status_t res = OK;
     std::vector<wp<Camera3StreamInterface>> streams;
+    nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
     {
-        Mutex::Autolock il(mInterfaceLock);
-        nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
-        {
-            Mutex::Autolock l(mLock);
-            if (mStatus == STATUS_UNINITIALIZED) return res;
+        Mutex::Autolock l(mLock);
+        if (mStatus == STATUS_UNINITIALIZED) return res;
 
-            if (mRequestThread != NULL) {
-                if (mStatus == STATUS_ACTIVE || mStatus == STATUS_ERROR) {
-                    res = mRequestThread->clear();
+        if (mRequestThread != NULL) {
+            if (mStatus == STATUS_ACTIVE || mStatus == STATUS_ERROR) {
+                res = mRequestThread->clear();
+                if (res != OK) {
+                    SET_ERR_L("Can't stop streaming");
+                    // Continue to close device even in case of error
+                } else {
+                    res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration,
+                                  /*requestThreadInvocation*/ false);
                     if (res != OK) {
-                        SET_ERR_L("Can't stop streaming");
+                        SET_ERR_L("Timeout waiting for HAL to drain (% " PRIi64 " ns)",
+                                maxExpectedDuration);
                         // Continue to close device even in case of error
-                    } else {
-                        res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration);
-                        if (res != OK) {
-                            SET_ERR_L("Timeout waiting for HAL to drain (% " PRIi64 " ns)",
-                                    maxExpectedDuration);
-                            // Continue to close device even in case of error
-                        }
                     }
                 }
-                // Signal to request thread that we're not expecting any
-                // more requests. This will be true since once we're in
-                // disconnect and we've cleared off the request queue, the
-                // request thread can't receive any new requests through
-                // binder calls - since disconnect holds
-                // mBinderSerialization lock.
-                mRequestThread->setRequestClearing();
             }
+            // Signal to request thread that we're not expecting any
+            // more requests. This will be true since once we're in
+            // disconnect and we've cleared off the request queue, the
+            // request thread can't receive any new requests through
+            // binder calls - since disconnect holds
+            // mBinderSerialization lock.
+            mRequestThread->setRequestClearing();
+        }
 
-            if (mStatus == STATUS_ERROR) {
-                CLOGE("Shutting down in an error state");
-            }
+        if (mStatus == STATUS_ERROR) {
+            CLOGE("Shutting down in an error state");
+        }
 
-            if (mStatusTracker != NULL) {
-                mStatusTracker->requestExit();
-            }
+        if (mStatusTracker != NULL) {
+            mStatusTracker->requestExit();
+        }
 
-            if (mRequestThread != NULL) {
-                mRequestThread->requestExit();
-            }
+        if (mRequestThread != NULL) {
+            mRequestThread->requestExit();
+        }
 
-            streams.reserve(mOutputStreams.size() + (mInputStream != nullptr ? 1 : 0));
-            for (size_t i = 0; i < mOutputStreams.size(); i++) {
-                streams.push_back(mOutputStreams[i]);
-            }
-            if (mInputStream != nullptr) {
-                streams.push_back(mInputStream);
-            }
+        streams.reserve(mOutputStreams.size() + (mInputStream != nullptr ? 1 : 0));
+        for (size_t i = 0; i < mOutputStreams.size(); i++) {
+            streams.push_back(mOutputStreams[i]);
+        }
+        if (mInputStream != nullptr) {
+            streams.push_back(mInputStream);
         }
     }
-    // Joining done without holding mLock and mInterfaceLock, otherwise deadlocks may ensue
-    // as the threads try to access parent state (b/143513518)
+
+    // Joining done without holding mLock, otherwise deadlocks may ensue
+    // as the threads try to access parent state
     if (mRequestThread != NULL && mStatus != STATUS_ERROR) {
         // HAL may be in a bad state, so waiting for request thread
         // (which may be stuck in the HAL processCaptureRequest call)
         // could be dangerous.
-        // give up mInterfaceLock here and then lock it again. Could this lead
-        // to other deadlocks
         mRequestThread->join();
     }
+
+    if (mStatusTracker != NULL) {
+        mStatusTracker->join();
+    }
+
+    if (mInjectionMethods->isInjecting()) {
+        mInjectionMethods->stopInjection();
+    }
+
+    HalInterface* interface;
     {
-        Mutex::Autolock il(mInterfaceLock);
-        if (mStatusTracker != NULL) {
-            mStatusTracker->join();
-        }
+        Mutex::Autolock l(mLock);
+        mRequestThread.clear();
+        Mutex::Autolock stLock(mTrackerLock);
+        mStatusTracker.clear();
+        interface = mInterface.get();
+    }
 
-        if (mInjectionMethods->isInjecting()) {
-            mInjectionMethods->stopInjection();
-        }
+    // Call close without internal mutex held, as the HAL close may need to
+    // wait on assorted callbacks,etc, to complete before it can return.
+    mCameraServiceWatchdog->WATCH(interface->close());
 
-        HalInterface* interface;
-        {
-            Mutex::Autolock l(mLock);
-            mRequestThread.clear();
-            Mutex::Autolock stLock(mTrackerLock);
-            mStatusTracker.clear();
-            interface = mInterface.get();
-        }
+    flushInflightRequests();
 
-        // Call close without internal mutex held, as the HAL close may need to
-        // wait on assorted callbacks,etc, to complete before it can return.
-        mCameraServiceWatchdog->WATCH(interface->close());
+    {
+        Mutex::Autolock l(mLock);
+        mInterface->clear();
+        mOutputStreams.clear();
+        mInputStream.clear();
+        mDeletedStreams.clear();
+        mBufferManager.clear();
+        internalUpdateStatusLocked(STATUS_UNINITIALIZED);
+    }
 
-        flushInflightRequests();
-
-        {
-            Mutex::Autolock l(mLock);
-            mInterface->clear();
-            mOutputStreams.clear();
-            mInputStream.clear();
-            mDeletedStreams.clear();
-            mBufferManager.clear();
-            internalUpdateStatusLocked(STATUS_UNINITIALIZED);
-        }
-
-        for (auto& weakStream : streams) {
-              sp<Camera3StreamInterface> stream = weakStream.promote();
-            if (stream != nullptr) {
-                ALOGE("%s: Stream %d leaked! strong reference (%d)!",
-                        __FUNCTION__, stream->getId(), stream->getStrongCount() - 1);
-            }
+    for (auto& weakStream : streams) {
+        sp<Camera3StreamInterface> stream = weakStream.promote();
+        if (stream != nullptr) {
+            ALOGE("%s: Stream %d leaked! strong reference (%d)!",
+                    __FUNCTION__, stream->getId(), stream->getStrongCount() - 1);
         }
     }
     ALOGI("%s: X", __FUNCTION__);
@@ -843,7 +840,7 @@
     }
 
     if (res == OK) {
-        waitUntilStateThenRelock(/*active*/true, kActiveTimeout);
+        waitUntilStateThenRelock(/*active*/true, kActiveTimeout, /*requestThreadInvocation*/false);
         if (res != OK) {
             SET_ERR_L("Can't transition to active in %f seconds!",
                     kActiveTimeout/1e9);
@@ -968,7 +965,8 @@
             break;
         case STATUS_ACTIVE:
             ALOGV("%s: Stopping activity to reconfigure streams", __FUNCTION__);
-            res = internalPauseAndWaitLocked(maxExpectedDuration);
+            res = internalPauseAndWaitLocked(maxExpectedDuration,
+                          /*requestThreadInvocation*/ false);
             if (res != OK) {
                 SET_ERR_L("Can't pause captures to reconfigure streams!");
                 return res;
@@ -1085,7 +1083,8 @@
             break;
         case STATUS_ACTIVE:
             ALOGV("%s: Stopping activity to reconfigure streams", __FUNCTION__);
-            res = internalPauseAndWaitLocked(maxExpectedDuration);
+            res = internalPauseAndWaitLocked(maxExpectedDuration,
+                          /*requestThreadInvocation*/ false);
             if (res != OK) {
                 SET_ERR_L("Can't pause captures to reconfigure streams!");
                 return res;
@@ -1558,7 +1557,8 @@
     }
     ALOGV("%s: Camera %s: Waiting until idle (%" PRIi64 "ns)", __FUNCTION__, mId.string(),
             maxExpectedDuration);
-    status_t res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration);
+    status_t res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration,
+                           /*requestThreadInvocation*/ false);
     if (res != OK) {
         mStatusTracker->dumpActiveComponents();
         SET_ERR_L("Error waiting for HAL to drain: %s (%d)", strerror(-res),
@@ -1569,12 +1569,14 @@
 
 void Camera3Device::internalUpdateStatusLocked(Status status) {
     mStatus = status;
-    mRecentStatusUpdates.add(mStatus);
+    mStatusIsInternal = mPauseStateNotify ? true : false;
+    mRecentStatusUpdates.add({mStatus, mStatusIsInternal});
     mStatusChanged.broadcast();
 }
 
 // Pause to reconfigure
-status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration) {
+status_t Camera3Device::internalPauseAndWaitLocked(nsecs_t maxExpectedDuration,
+        bool requestThreadInvocation) {
     if (mRequestThread.get() != nullptr) {
         mRequestThread->setPaused(true);
     } else {
@@ -1583,7 +1585,8 @@
 
     ALOGV("%s: Camera %s: Internal wait until idle (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
           maxExpectedDuration);
-    status_t res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration);
+    status_t res = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration,
+                           requestThreadInvocation);
     if (res != OK) {
         mStatusTracker->dumpActiveComponents();
         SET_ERR_L("Can't idle device in %f seconds!",
@@ -1601,7 +1604,9 @@
 
     ALOGV("%s: Camera %s: Internal wait until active (% " PRIi64 " ns)", __FUNCTION__, mId.string(),
             kActiveTimeout);
-    res = waitUntilStateThenRelock(/*active*/ true, kActiveTimeout);
+    // internalResumeLocked is always called from a binder thread.
+    res = waitUntilStateThenRelock(/*active*/ true, kActiveTimeout,
+                  /*requestThreadInvocation*/ false);
     if (res != OK) {
         SET_ERR_L("Can't transition to active in %f seconds!",
                 kActiveTimeout/1e9);
@@ -1610,7 +1615,8 @@
     return OK;
 }
 
-status_t Camera3Device::waitUntilStateThenRelock(bool active, nsecs_t timeout) {
+status_t Camera3Device::waitUntilStateThenRelock(bool active, nsecs_t timeout,
+        bool requestThreadInvocation) {
     status_t res = OK;
 
     size_t startIndex = 0;
@@ -1639,7 +1645,12 @@
     bool stateSeen = false;
     nsecs_t startTime = systemTime();
     do {
-        if (active == (mStatus == STATUS_ACTIVE)) {
+        if (mStatus == STATUS_ERROR) {
+            // Device in error state. Return right away.
+            break;
+        }
+        if (active == (mStatus == STATUS_ACTIVE) &&
+            (requestThreadInvocation || !mStatusIsInternal)) {
             // Desired state is current
             break;
         }
@@ -1661,9 +1672,19 @@
                 "%s: Skipping status updates in Camera3Device, may result in deadlock.",
                 __FUNCTION__);
 
-        // Encountered desired state since we began waiting
+        // Encountered desired state since we began waiting. Internal invocations coming from
+        // request threads (such as reconfigureCamera) should be woken up immediately, whereas
+        // invocations from binder threads (such as createInputStream) should only be woken up if
+        // they are not paused. This avoids intermediate pause signals from reconfigureCamera as it
+        // changes the status to active right after.
         for (size_t i = startIndex; i < mRecentStatusUpdates.size(); i++) {
-            if (active == (mRecentStatusUpdates[i] == STATUS_ACTIVE) ) {
+            if (mRecentStatusUpdates[i].status == STATUS_ERROR) {
+                // Device in error state. Return right away.
+                stateSeen = true;
+                break;
+            }
+            if (active == (mRecentStatusUpdates[i].status == STATUS_ACTIVE) &&
+                (requestThreadInvocation || !mRecentStatusUpdates[i].isInternal)) {
                 stateSeen = true;
                 break;
             }
@@ -1894,7 +1915,7 @@
     camera_metadata_entry minDurations =
             mDeviceInfo.find(ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
     for (size_t i = 0; i < minDurations.count; i += 4) {
-        if (minDurations.data.i64[i] == stream->getFormat()
+        if (minDurations.data.i64[i] == stream->getOriginalFormat()
                 && minDurations.data.i64[i+1] == stream->getWidth()
                 && minDurations.data.i64[i+2] == stream->getHeight()) {
             int64_t minFrameDuration = minDurations.data.i64[i+3];
@@ -2310,7 +2331,9 @@
 
     nsecs_t startTime = systemTime();
 
-    Mutex::Autolock il(mInterfaceLock);
+    // We must not hold mInterfaceLock here since this function is called from
+    // RequestThread::threadLoop and holding mInterfaceLock could lead to
+    // deadlocks (http://b/143513518)
     nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
 
     Mutex::Autolock l(mLock);
@@ -2325,8 +2348,18 @@
     if (mStatus == STATUS_ACTIVE) {
         markClientActive = true;
         mPauseStateNotify = true;
+        mStatusTracker->markComponentIdle(clientStatusId, Fence::NO_FENCE);
 
-        rc = internalPauseAndWaitLocked(maxExpectedDuration);
+        // This is essentially the same as calling rc = internalPauseAndWaitLocked(..), except that
+        // we don't want to call setPaused(true) to avoid it interfering with setPaused() called
+        // from createInputStream/createStream.
+        rc = waitUntilStateThenRelock(/*active*/ false, maxExpectedDuration,
+                /*requestThreadInvocation*/ true);
+        if (rc != OK) {
+            mStatusTracker->dumpActiveComponents();
+            SET_ERR_L("Can't idle device in %f seconds!",
+                maxExpectedDuration/1e9);
+        }
     }
 
     if (rc == NO_ERROR) {
@@ -2341,6 +2374,9 @@
             //present streams end up with outstanding buffers that will
             //not get drained.
             internalUpdateStatusLocked(STATUS_ACTIVE);
+
+            mCameraServiceProxyWrapper->logStreamConfigured(mId, mOperatingMode,
+                    true /*internalReconfig*/, ns2ms(systemTime() - startTime));
         } else if (rc == DEAD_OBJECT) {
             // DEAD_OBJECT can be returned if either the consumer surface is
             // abandoned, or the HAL has died.
@@ -2356,9 +2392,6 @@
         ALOGE("%s: Failed to pause streaming: %d", __FUNCTION__, rc);
     }
 
-    mCameraServiceProxyWrapper->logStreamConfigured(mId, mOperatingMode, true /*internalReconfig*/,
-        ns2ms(systemTime() - startTime));
-
     if (markClientActive) {
         mStatusTracker->markComponentActive(clientStatusId);
     }
@@ -3567,13 +3600,8 @@
         if (res == OK) {
             sp<Camera3Device> parent = mParent.promote();
             if (parent != nullptr) {
-                sp<StatusTracker> statusTracker = mStatusTracker.promote();
-                if (statusTracker != nullptr) {
-                    statusTracker->markComponentIdle(mStatusId, Fence::NO_FENCE);
-                }
                 mReconfigured |= parent->reconfigureCamera(mLatestSessionParams, mStatusId);
             }
-            setPaused(false);
 
             if (mNextRequests[0].captureRequest->mInputStream != nullptr) {
                 mNextRequests[0].captureRequest->mInputStream->restoreConfiguredState();
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 526728c..b1dd135 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -366,6 +366,7 @@
 
     // A lock to enforce serialization on the input/configure side
     // of the public interface.
+    // Only locked by public methods inherited from CameraDeviceBase.
     // Not locked by methods guarded by mOutputLock, since they may act
     // concurrently to the input/configure side of the interface.
     // Must be locked before mLock if both will be locked by a method
@@ -571,8 +572,15 @@
         STATUS_ACTIVE
     }                          mStatus;
 
+    struct StatusInfo {
+        Status status;
+        bool isInternal; // status triggered by internal reconfigureCamera.
+    };
+
+    bool                       mStatusIsInternal;
+
     // Only clear mRecentStatusUpdates, mStatusWaiters from waitUntilStateThenRelock
-    Vector<Status>             mRecentStatusUpdates;
+    Vector<StatusInfo>         mRecentStatusUpdates;
     int                        mStatusWaiters;
 
     Condition                  mStatusChanged;
@@ -717,7 +725,8 @@
      * CameraDeviceBase interface we shouldn't need to.
      * Must be called with mLock and mInterfaceLock both held.
      */
-    status_t internalPauseAndWaitLocked(nsecs_t maxExpectedDuration);
+    status_t internalPauseAndWaitLocked(nsecs_t maxExpectedDuration,
+                     bool requestThreadInvocation);
 
     /**
      * Resume work after internalPauseAndWaitLocked()
@@ -736,7 +745,8 @@
      * During the wait mLock is released.
      *
      */
-    status_t waitUntilStateThenRelock(bool active, nsecs_t timeout);
+    status_t waitUntilStateThenRelock(bool active, nsecs_t timeout,
+                     bool requestThreadInvocation);
 
     /**
      * Implementation of waitUntilDrained. On success, will transition to IDLE state.
diff --git a/services/camera/libcameraservice/device3/Camera3DeviceInjectionMethods.cpp b/services/camera/libcameraservice/device3/Camera3DeviceInjectionMethods.cpp
index 031c255..ab772d4 100644
--- a/services/camera/libcameraservice/device3/Camera3DeviceInjectionMethods.cpp
+++ b/services/camera/libcameraservice/device3/Camera3DeviceInjectionMethods.cpp
@@ -58,7 +58,8 @@
     if (parent->mStatus == STATUS_ACTIVE) {
         ALOGV("%s: Let the device be IDLE and the request thread is paused",
                 __FUNCTION__);
-        res = parent->internalPauseAndWaitLocked(maxExpectedDuration);
+        res = parent->internalPauseAndWaitLocked(maxExpectedDuration,
+                                                 /*requestThreadInvocation*/false);
         if (res != OK) {
             ALOGE("%s: Can't pause captures to inject camera!", __FUNCTION__);
             return res;
@@ -117,7 +118,8 @@
     if (parent->mStatus == STATUS_ACTIVE) {
         ALOGV("%s: Let the device be IDLE and the request thread is paused",
                 __FUNCTION__);
-        res = parent->internalPauseAndWaitLocked(maxExpectedDuration);
+        res = parent->internalPauseAndWaitLocked(maxExpectedDuration,
+                                                 /*requestThreadInvocation*/false);
         if (res != OK) {
             ALOGE("%s: Can't pause captures to stop injection!", __FUNCTION__);
             return res;
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
index f2a62fa..a387064 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.cpp
@@ -486,7 +486,7 @@
             bufferDeferred = true;
         } else {
             nsecs_t presentTime = mSyncToDisplay ?
-                    syncTimestampToDisplayLocked(captureTime) : captureTime;
+                    syncTimestampToDisplayLocked(captureTime, releaseFence->dup()) : captureTime;
 
             setTransform(transform, true/*mayChangeMirror*/);
             res = native_window_set_buffers_timestamp(mConsumer.get(), presentTime);
@@ -1410,7 +1410,7 @@
     }
 }
 
-nsecs_t Camera3OutputStream::syncTimestampToDisplayLocked(nsecs_t t) {
+nsecs_t Camera3OutputStream::syncTimestampToDisplayLocked(nsecs_t t, int releaseFence) {
     nsecs_t currentTime = systemTime();
     if (!mFixedFps) {
         mLastCaptureTime = t;
@@ -1453,6 +1453,17 @@
                 mLastCaptureTime = t;
                 mLastPresentTime = presentT;
 
+                // If releaseFence is available, store the fence to check signal
+                // time later.
+                mRefVsyncData = vsyncEventData;
+                mReferenceCaptureTime = t;
+                mReferenceArrivalTime = currentTime;
+                if (releaseFence != -1) {
+                    mReferenceFrameFence = new Fence(releaseFence);
+                } else {
+                    mFenceSignalOffset = 0;
+                }
+
                 // Move the expected presentation time back by 1/3 of frame interval to
                 // mitigate the time drift. Due to time drift, if we directly use the
                 // expected presentation time, often times 2 expected presentation time
@@ -1462,6 +1473,36 @@
         }
     }
 
+    // If there is a reference frame release fence, get the signal time and
+    // update the captureToPresentOffset.
+    if (mReferenceFrameFence != nullptr) {
+        mFenceSignalOffset = 0;
+        nsecs_t signalTime = mReferenceFrameFence->getSignalTime();
+        // Now that the fence has signaled, recalculate the offsets based on
+        // the timeline which was actually latched
+        if (signalTime != INT64_MAX) {
+            for (size_t i = 0; i < mRefVsyncData.frameTimelinesLength; i++) {
+                const auto& timeline = mRefVsyncData.frameTimelines[i];
+                if (timeline.deadlineTimestamp >= signalTime) {
+                    nsecs_t originalOffset = mCaptureToPresentOffset;
+                    mCaptureToPresentOffset = timeline.expectedPresentationTime
+                            - mReferenceCaptureTime;
+                    mLastPresentTime = timeline.expectedPresentationTime;
+                    mFenceSignalOffset = signalTime > mReferenceArrivalTime ?
+                            signalTime - mReferenceArrivalTime : 0;
+
+                    ALOGV("%s: Last deadline %" PRId64 " signalTime %" PRId64
+                            " original offset %" PRId64 " new offset %" PRId64
+                            " fencesignal offset %" PRId64, __FUNCTION__,
+                            timeline.deadlineTimestamp, signalTime, originalOffset,
+                            mCaptureToPresentOffset, mFenceSignalOffset);
+                    break;
+                }
+            }
+            mReferenceFrameFence.clear();
+        }
+    }
+
     nsecs_t idealPresentT = t + mCaptureToPresentOffset;
     nsecs_t expectedPresentT = mLastPresentTime;
     nsecs_t minDiff = INT64_MAX;
@@ -1505,6 +1546,7 @@
 
     // Find best timestamp in the vsync timelines:
     // - Only use at most kMaxTimelines timelines to avoid long latency
+    // - Add an extra timeline if display fence is used
     // - closest to the ideal presentation time,
     // - deadline timestamp is greater than the current time, and
     // - For fixed FPS, if the capture interval doesn't deviate too much from refresh interval,
@@ -1513,7 +1555,9 @@
     // - For variable FPS, or if the capture interval deviates from refresh
     //   interval for more than 5%, find a presentation time closest to the
     //   (lastPresentationTime + captureToPresentOffset) instead.
-    int maxTimelines = std::min(kMaxTimelines, (int)vsyncEventData.frameTimelinesLength);
+    int fenceAdjustment = (mFenceSignalOffset > 0) ? 1 : 0;
+    int maxTimelines = std::min(kMaxTimelines + fenceAdjustment,
+            (int)vsyncEventData.frameTimelinesLength);
     float biasForShortDelay = 1.0f;
     for (int i = 0; i < maxTimelines; i ++) {
         const auto& vsyncTime = vsyncEventData.frameTimelines[i];
@@ -1524,7 +1568,7 @@
             biasForShortDelay = 1.0 - 2.0 * i / (maxTimelines - 1);
         }
         if (std::abs(vsyncTime.expectedPresentationTime - idealPresentT) < minDiff &&
-                vsyncTime.deadlineTimestamp >= currentTime &&
+                vsyncTime.deadlineTimestamp >= currentTime + mFenceSignalOffset &&
                 ((!cameraDisplayInSync && vsyncTime.expectedPresentationTime > minPresentT) ||
                  (cameraDisplayInSync && vsyncTime.expectedPresentationTime >
                 mLastPresentTime + minInterval +
diff --git a/services/camera/libcameraservice/device3/Camera3OutputStream.h b/services/camera/libcameraservice/device3/Camera3OutputStream.h
index 9a08485..1435081 100644
--- a/services/camera/libcameraservice/device3/Camera3OutputStream.h
+++ b/services/camera/libcameraservice/device3/Camera3OutputStream.h
@@ -446,7 +446,14 @@
     static constexpr nsecs_t kTimelineThresholdNs = 1000000LL; // 1 millisecond
     static constexpr float kMaxIntervalRatioDeviation = 0.05f;
     static constexpr int kMaxTimelines = 2;
-    nsecs_t syncTimestampToDisplayLocked(nsecs_t t);
+    nsecs_t syncTimestampToDisplayLocked(nsecs_t t, int releaseFence);
+
+    // In case of fence being used
+    sp<Fence> mReferenceFrameFence;
+    nsecs_t mReferenceCaptureTime = 0;
+    nsecs_t mReferenceArrivalTime = 0;
+    nsecs_t mFenceSignalOffset = 0;
+    VsyncEventData  mRefVsyncData;
 
     // Re-space frames by delaying queueBuffer so that frame delivery has
     // the same cadence as capture. Default is on for SurfaceTexture bound
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
index 4225366..b58975f 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.cpp
@@ -26,8 +26,14 @@
 
 namespace android {
 
-using hardware::ICameraServiceProxy;
+using hardware::CameraExtensionSessionStats;
 using hardware::CameraSessionStats;
+using hardware::ICameraServiceProxy;
+
+namespace {
+// Sentinel value to be returned when extension session with a stale or invalid key is reported.
+const String16 POISON_EXT_STATS_KEY("poisoned_stats");
+} // anonymous namespace
 
 /**
  * CameraSessionStatsWrapper functions
@@ -53,6 +59,7 @@
     mSessionStats.mNewCameraState = CameraSessionStats::CAMERA_STATE_CLOSED;
     mSessionStats.mLatencyMs = latencyMs;
     mSessionStats.mDeviceError = deviceError;
+    mSessionStats.mSessionIndex = 0;
     updateProxyDeviceState(proxyBinder);
 }
 
@@ -74,6 +81,7 @@
 
     mSessionStats.mNewCameraState = CameraSessionStats::CAMERA_STATE_ACTIVE;
     mSessionStats.mMaxPreviewFps = maxPreviewFps;
+    mSessionStats.mSessionIndex++;
     updateProxyDeviceState(proxyBinder);
 
     // Reset mCreationDuration to -1 to distinguish between 1st session
@@ -95,10 +103,12 @@
     mSessionStats.mUserTag = String16(userTag.c_str());
     mSessionStats.mVideoStabilizationMode = videoStabilizationMode;
     mSessionStats.mStreamStats = streamStats;
+
     updateProxyDeviceState(proxyBinder);
 
     mSessionStats.mInternalReconfigure = 0;
     mSessionStats.mStreamStats.clear();
+    mSessionStats.mCameraExtensionSessionStats = {};
 }
 
 int64_t CameraServiceProxyWrapper::CameraSessionStatsWrapper::getLogId() {
@@ -106,6 +116,65 @@
     return mSessionStats.mLogId;
 }
 
+String16 CameraServiceProxyWrapper::CameraSessionStatsWrapper::updateExtensionSessionStats(
+        const hardware::CameraExtensionSessionStats& extStats) {
+    Mutex::Autolock l(mLock);
+    CameraExtensionSessionStats& currStats = mSessionStats.mCameraExtensionSessionStats;
+    if (currStats.key != extStats.key) {
+        // Mismatched keys. Extensions stats likely reported for a closed session
+        ALOGW("%s: mismatched extensions stats key: current='%s' reported='%s'. Dropping stats.",
+              __FUNCTION__, String8(currStats.key).c_str(), String8(extStats.key).c_str());
+        return POISON_EXT_STATS_KEY; // return poisoned key to so future calls are
+                                     // definitely dropped.
+    }
+
+    // Matching keys...
+    if (currStats.key.size()) {
+        // non-empty matching keys. overwrite.
+        ALOGV("%s: Overwriting extension session stats: %s", __FUNCTION__,
+              extStats.toString().c_str());
+        currStats = extStats;
+        return currStats.key;
+    }
+
+    // Matching empty keys...
+    if (mSessionStats.mClientName != extStats.clientName) {
+        ALOGW("%s: extension stats reported for unexpected package: current='%s' reported='%s'. "
+              "Dropping stats.", __FUNCTION__,
+              String8(mSessionStats.mClientName).c_str(),
+              String8(extStats.clientName).c_str());
+        return POISON_EXT_STATS_KEY;
+    }
+
+    // Matching empty keys for the current client...
+    if (mSessionStats.mNewCameraState == CameraSessionStats::CAMERA_STATE_OPEN ||
+        mSessionStats.mNewCameraState == CameraSessionStats::CAMERA_STATE_IDLE) {
+        // Camera is open, but not active. It is possible that the active callback hasn't
+        // occurred yet. Keep the stats, but don't associate it with any session.
+        ALOGV("%s: extension stat reported for an open, but not active camera. "
+              "Saving stats, but not generating key.", __FUNCTION__);
+        currStats = extStats;
+        return {}; // Subsequent calls will handle setting the correct key.
+    }
+
+    if (mSessionStats.mNewCameraState == CameraSessionStats::CAMERA_STATE_ACTIVE) {
+        // camera is active. First call for the session!
+        currStats = extStats;
+
+        // Generate a new key from logId and sessionIndex.
+        std::ostringstream key;
+        key << mSessionStats.mSessionIndex << '/' << mSessionStats.mLogId;
+        currStats.key = String16(key.str().c_str());
+        ALOGV("%s: New extension session stats: %s", __FUNCTION__, currStats.toString().c_str());
+        return currStats.key;
+    }
+
+    // Camera is closed. Probably a stale call.
+    ALOGW("%s: extension stats reported for closed camera id '%s'. Dropping stats.",
+          __FUNCTION__, String8(mSessionStats.mCameraId).c_str());
+    return {};
+}
+
 /**
  * CameraServiceProxyWrapper functions
  */
@@ -174,12 +243,12 @@
     std::shared_ptr<CameraSessionStatsWrapper> sessionStats;
     {
         Mutex::Autolock l(mLock);
-        sessionStats = mSessionStatsMap[id];
-        if (sessionStats == nullptr) {
+        if (mSessionStatsMap.count(id) == 0) {
             ALOGE("%s: SessionStatsMap should contain camera %s",
                     __FUNCTION__, id.c_str());
             return;
         }
+        sessionStats = mSessionStatsMap[id];
     }
 
     ALOGV("%s: id %s, operatingMode %d, internalConfig %d, latencyMs %d",
@@ -191,12 +260,12 @@
     std::shared_ptr<CameraSessionStatsWrapper> sessionStats;
     {
         Mutex::Autolock l(mLock);
-        sessionStats = mSessionStatsMap[id];
-        if (sessionStats == nullptr) {
+        if (mSessionStatsMap.count(id) == 0) {
             ALOGE("%s: SessionStatsMap should contain camera %s when logActive is called",
                     __FUNCTION__, id.c_str());
             return;
         }
+        sessionStats = mSessionStatsMap[id];
     }
 
     ALOGV("%s: id %s", __FUNCTION__, id.c_str());
@@ -211,13 +280,12 @@
     std::shared_ptr<CameraSessionStatsWrapper> sessionStats;
     {
         Mutex::Autolock l(mLock);
-        sessionStats = mSessionStatsMap[id];
-    }
-
-    if (sessionStats == nullptr) {
-        ALOGE("%s: SessionStatsMap should contain camera %s when logIdle is called",
+        if (mSessionStatsMap.count(id) == 0) {
+            ALOGE("%s: SessionStatsMap should contain camera %s when logIdle is called",
                 __FUNCTION__, id.c_str());
-        return;
+            return;
+        }
+        sessionStats = mSessionStatsMap[id];
     }
 
     ALOGV("%s: id %s, requestCount %" PRId64 ", resultErrorCount %" PRId64 ", deviceError %d"
@@ -335,4 +403,21 @@
     return ret;
 }
 
+String16 CameraServiceProxyWrapper::updateExtensionStats(
+        const hardware::CameraExtensionSessionStats& extStats) {
+    std::shared_ptr<CameraSessionStatsWrapper> stats;
+    String8 cameraId = String8(extStats.cameraId);
+    {
+        Mutex::Autolock _l(mLock);
+        if (mSessionStatsMap.count(cameraId) == 0) {
+            ALOGE("%s CameraExtensionSessionStats reported for camera id that isn't open: %s",
+                  __FUNCTION__, cameraId.c_str());
+            return {};
+        }
+
+        stats = mSessionStatsMap[cameraId];
+        return stats->updateExtensionSessionStats(extStats);
+    }
+}
+
 }  // namespace android
diff --git a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
index d47c738..e32580c 100644
--- a/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
+++ b/services/camera/libcameraservice/utils/CameraServiceProxyWrapper.h
@@ -65,6 +65,8 @@
                 const std::string& userTag, int32_t videoStabilizationMode,
                 const std::vector<hardware::CameraStreamStats>& streamStats);
 
+        String16 updateExtensionSessionStats(const hardware::CameraExtensionSessionStats& extStats);
+
         // Returns the logId associated with this event.
         int64_t getLogId();
     };
@@ -127,6 +129,9 @@
     // frameworks/av/camera/include/camera/CameraSessionStats.h for more details about this
     // identifier. Returns a non-0 value on success.
     int64_t getCurrentLogIdForCamera(const String8& cameraId);
+
+    // Update the stored extension stats to the latest values
+    String16 updateExtensionStats(const hardware::CameraExtensionSessionStats& extStats);
 };
 
 } // android
diff --git a/services/camera/libcameraservice/utils/TagMonitor.cpp b/services/camera/libcameraservice/utils/TagMonitor.cpp
index fe87ed6..d1e54ab 100644
--- a/services/camera/libcameraservice/utils/TagMonitor.cpp
+++ b/services/camera/libcameraservice/utils/TagMonitor.cpp
@@ -346,7 +346,7 @@
                         == OK) {
                         returnStr += value_string_tmp;
                     } else {
-                        returnStr.appendFormat("%hhu", *(data_ptr + index));
+                        returnStr.appendFormat("%hhu ", *(data_ptr + index));
                     }
                     break;
                 case TYPE_INT32:
@@ -363,7 +363,7 @@
                     }
                     break;
                 case TYPE_FLOAT:
-                    returnStr.appendFormat("%0.8f", *(float*)(data_ptr + index));
+                    returnStr.appendFormat("%0.8f ", *(float*)(data_ptr + index));
                     break;
                 case TYPE_INT64:
                     returnStr.appendFormat("%" PRId64 " ", *(int64_t*)(data_ptr + index));
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index adb2217..af1372b 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -524,6 +524,8 @@
                                      "audiotrack",
                                      // other media
                                      "codec",
+                                     "freeze",
+                                     "judder",
                                      "extractor",
                                      "mediadrm",
                                      "mediaparser",
diff --git a/services/mediametrics/StringUtils.cpp b/services/mediametrics/StringUtils.cpp
index d1c7a18..5766f1c 100644
--- a/services/mediametrics/StringUtils.cpp
+++ b/services/mediametrics/StringUtils.cpp
@@ -15,11 +15,13 @@
  */
 
 //#define LOG_NDEBUG 0
-#define LOG_TAG "MediaMetricsService::stringutils"
+#define LOG_TAG "mediametrics::stringutils"
 #include <utils/Log.h>
 
 #include "StringUtils.h"
 
+#include <charconv>
+
 #include "AudioTypes.h"
 
 namespace android::mediametrics::stringutils {
@@ -54,6 +56,26 @@
     }
 }
 
+bool parseVector(const std::string &str, std::vector<int32_t> *vector) {
+    std::vector<int32_t> values;
+    const char *p = str.c_str();
+    const char *last = p + str.size();
+    while (p != last) {
+        if (*p == ',' || *p == '{' || *p == '}') {
+            p++;
+        }
+        int32_t value = -1;
+        auto [ptr, error] = std::from_chars(p, last, value);
+        if (error == std::errc::invalid_argument || error == std::errc::result_out_of_range) {
+            return false;
+        }
+        p = ptr;
+        values.push_back(value);
+    }
+    *vector = std::move(values);
+    return true;
+}
+
 std::vector<std::pair<std::string, std::string>> getDeviceAddressPairs(const std::string& devices)
 {
     std::vector<std::pair<std::string, std::string>> result;
diff --git a/services/mediametrics/include/mediametricsservice/StatsdLog.h b/services/mediametrics/include/mediametricsservice/StatsdLog.h
index e207bac..5d5009e 100644
--- a/services/mediametrics/include/mediametricsservice/StatsdLog.h
+++ b/services/mediametrics/include/mediametricsservice/StatsdLog.h
@@ -16,11 +16,13 @@
 
 #pragma once
 
-#include <audio_utils/SimpleLog.h>
 #include <map>
 #include <mutex>
 #include <sstream>
 
+#include <android-base/thread_annotations.h>
+#include <audio_utils/SimpleLog.h>
+
 namespace android::mediametrics {
 
 class StatsdLog {
@@ -61,9 +63,9 @@
    }
 
 private:
+    mutable std::mutex mLock;
     SimpleLog mSimpleLog; // internally locked
     std::map<int /* atom */, size_t /* count */> mCountMap GUARDED_BY(mLock); // sorted
-    mutable std::mutex mLock;
 };
 
 } // namespace android::mediametrics
diff --git a/services/mediametrics/include/mediametricsservice/StringUtils.h b/services/mediametrics/include/mediametricsservice/StringUtils.h
index 78c25ff..ed2cf2e 100644
--- a/services/mediametrics/include/mediametricsservice/StringUtils.h
+++ b/services/mediametrics/include/mediametricsservice/StringUtils.h
@@ -72,6 +72,12 @@
 std::vector<std::string> split(const std::string& flags, const char *delim);
 
 /**
+ * Parses a vector of integers using ',' '{' and '}' as delimeters. Leaves
+ * vector unmodified if the parsing fails.
+ */
+bool parseVector(const std::string &str, std::vector<int32_t> *vector);
+
+/**
  * Parse the devices string and return a vector of device address pairs.
  *
  * A failure to parse returns early with the contents that were able to be parsed.
diff --git a/services/mediametrics/include/mediametricsservice/iface_statsd.h b/services/mediametrics/include/mediametricsservice/iface_statsd.h
index 5bc293b..34d71ba 100644
--- a/services/mediametrics/include/mediametricsservice/iface_statsd.h
+++ b/services/mediametrics/include/mediametricsservice/iface_statsd.h
@@ -15,7 +15,9 @@
  */
 
 #include <memory>
+
 #include <stats_event.h>
+#include <StatsdLog.h>
 
 namespace android {
 namespace mediametrics {
diff --git a/services/mediametrics/statsd_codec.cpp b/services/mediametrics/statsd_codec.cpp
index 158914a..ea76bcd 100644
--- a/services/mediametrics/statsd_codec.cpp
+++ b/services/mediametrics/statsd_codec.cpp
@@ -23,6 +23,7 @@
 #include <pthread.h>
 #include <pwd.h>
 #include <stdint.h>
+#include <string>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/time.h>
@@ -32,14 +33,149 @@
 #include <stats_media_metrics.h>
 #include <stats_event.h>
 
-#include "cleaner.h"
-#include "MediaMetricsService.h"
-#include "ValidateId.h"
-#include "frameworks/proto_logging/stats/message/mediametrics_message.pb.h"
-#include "iface_statsd.h"
+#include <frameworks/proto_logging/stats/message/mediametrics_message.pb.h>
+#include <mediametricsservice/cleaner.h>
+#include <mediametricsservice/iface_statsd.h>
+#include <mediametricsservice/MediaMetricsService.h>
+#include <mediametricsservice/StringUtils.h>
+#include <mediametricsservice/ValidateId.h>
 
 namespace android {
 
+using stats::media_metrics::stats_write;
+using stats::media_metrics::MEDIA_CODEC_RENDERED;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_UNKNOWN;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_INVALID;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_ZERO;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_UNKNOWN;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_UNDETERMINED;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_24_3_2_PULLDOWN;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_NONE;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_HLG;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_HDR10;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_HDR10_PLUS;
+using stats::media_metrics::MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_DOLBY_VISION;
+
+static const int BITRATE_UNKNOWN =
+        stats::media_metrics::MEDIA_CODEC_RENDERED__BITRATE__BITRATE_UNKNOWN;
+
+static const std::pair<char const *, int> CODEC_LOOKUP[] = {
+    { "avc", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_AVC },
+    { "h264", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_AVC },
+    { "hevc", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_HEVC },
+    { "h265", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_HEVC },
+    { "vp8", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_VP8 },
+    { "vp9", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_VP9 },
+    { "av1", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_AV1 },
+    { "av01", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_AV1 },
+    { "dolby-vision", stats::media_metrics::MEDIA_CODEC_RENDERED__CODEC__CODEC_HEVC },
+};
+
+static const int32_t RESOLUTION_LOOKUP[] = {
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_MAX_SIZE,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_32K,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_16K,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_8K_UHD,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_8K_UHD_ALMOST,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_4K_UHD_ALMOST,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_1440X2560,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_1080X2400,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_1080X2340,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_1080P_FHD,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_1080P_FHD_ALMOST,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_720P_HD,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_720P_HD_ALMOST,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_576X1024,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_540X960,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_480X854,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_480X640,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_360X640,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_352X640,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_VERY_LOW,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_SMALLEST,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_ZERO,
+};
+
+static const int32_t FRAMERATE_LOOKUP[] = {
+    stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_24,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_25,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_30,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_50,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_60,
+    stats::media_metrics::MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_120,
+};
+
+static int32_t getMetricsCodecEnum(const std::string &mime, const std::string &componentName) {
+    for (const auto & codecStrAndEnum : CODEC_LOOKUP) {
+        if (strcasestr(mime.c_str(), codecStrAndEnum.first) != nullptr ||
+            strcasestr(componentName.c_str(), codecStrAndEnum.first) != nullptr) {
+            return codecStrAndEnum.second;
+        }
+    }
+    return MEDIA_CODEC_RENDERED__CODEC__CODEC_UNKNOWN;
+}
+
+static int32_t getMetricsResolutionEnum(int32_t width, int32_t height) {
+    if (width == 0 || height == 0) {
+        return MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_ZERO;
+    }
+    int64_t pixels = int64_t(width) * height / 1000;
+    if (width < 0 || height < 0 || pixels > RESOLUTION_LOOKUP[0]) {
+        return MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_INVALID;
+    }
+    for (int32_t resolutionEnum : RESOLUTION_LOOKUP) {
+        if (pixels > resolutionEnum) {
+            return resolutionEnum;
+        }
+    }
+    return MEDIA_CODEC_RENDERED__RESOLUTION__RESOLUTION_ZERO;
+}
+
+static int32_t getMetricsFramerateEnum(float inFramerate) {
+    if (inFramerate == -1.0f) {
+        return MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_UNDETERMINED;
+    }
+    if (inFramerate == -2.0f) {
+        return MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_24_3_2_PULLDOWN;
+    }
+    int framerate = int(inFramerate * 100); // Table is in hundredths of frames per second
+    static const int framerateTolerance = 40; // Tolerance is 0.4 frames per second - table is 100s
+    for (int32_t framerateEnum : FRAMERATE_LOOKUP) {
+        if (abs(framerate - framerateEnum) < framerateTolerance) {
+            return framerateEnum;
+        }
+    }
+    return MEDIA_CODEC_RENDERED__CONTENT_FRAMERATE__FRAMERATE_UNKNOWN;
+}
+
+static int32_t getMetricsHdrFormatEnum(std::string &mime, std::string &componentName,
+                                       int32_t configColorTransfer, int32_t parsedColorTransfer,
+                                       int32_t hdr10StaticInfo, int32_t hdr10PlusInfo) {
+    if (hdr10PlusInfo) {
+        return MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_HDR10_PLUS;
+    }
+    if (hdr10StaticInfo) {
+        return MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_HDR10;
+    }
+    // 7 = COLOR_TRANSFER_HLG in MediaCodecConstants.h
+    if (configColorTransfer == 7 || parsedColorTransfer == 7) {
+        return MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_HLG;
+    }
+    if (strcasestr(mime.c_str(), "dolby-vision") != nullptr ||
+        strcasestr(componentName.c_str(), "dvhe") != nullptr ||
+        strcasestr(componentName.c_str(), "dvav") != nullptr ||
+        strcasestr(componentName.c_str(), "dav1") != nullptr) {
+        return MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_DOLBY_VISION;
+    }
+    return MEDIA_CODEC_RENDERED__HDR_FORMAT__HDR_FORMAT_NONE;
+}
+
+static void parseVector(const std::string &str, std::vector<int32_t> *vector) {
+    if (!mediametrics::stringutils::parseVector(str, vector)) {
+        ALOGE("failed to parse integer vector from '%s'", str.c_str());
+    }
+}
+
 bool statsd_codec(const std::shared_ptr<const mediametrics::Item>& item,
         const std::shared_ptr<mediametrics::StatsdLog>& statsdLog)
 {
@@ -48,17 +184,17 @@
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, stats::media_metrics::MEDIA_CODEC_REPORTED);
 
-    const nsecs_t timestamp_nanos = MediaMetricsService::roundTime(item->getTimestamp());
-    AStatsEvent_writeInt64(event, timestamp_nanos);
+    const nsecs_t timestampNanos = MediaMetricsService::roundTime(item->getTimestamp());
+    AStatsEvent_writeInt64(event, timestampNanos);
 
-    std::string package_name = item->getPkgName();
-    AStatsEvent_writeString(event, package_name.c_str());
+    std::string packageName = item->getPkgName();
+    AStatsEvent_writeString(event, packageName.c_str());
 
-    int64_t package_version_code = item->getPkgVersionCode();
-    AStatsEvent_writeInt64(event, package_version_code);
+    int64_t packageVersionCode = item->getPkgVersionCode();
+    AStatsEvent_writeInt64(event, packageVersionCode);
 
-    int64_t media_apex_version = 0;
-    AStatsEvent_writeInt64(event, media_apex_version);
+    int64_t mediaApexVersion = 0;
+    AStatsEvent_writeInt64(event, mediaApexVersion);
 
     // the rest into our own proto
     //
@@ -84,17 +220,25 @@
     }
     AStatsEvent_writeString(event, mode.c_str());
 
-    int32_t encoder = -1;
-    if (item->getInt32("android.media.mediacodec.encoder", &encoder)) {
-        metrics_proto.set_encoder(encoder);
+    int32_t isEncoder = -1;
+    if (item->getInt32("android.media.mediacodec.encoder", &isEncoder)) {
+        metrics_proto.set_encoder(isEncoder);
     }
-    AStatsEvent_writeInt32(event, encoder);
+    AStatsEvent_writeInt32(event, isEncoder);
 
-    int32_t secure = -1;
-    if (item->getInt32("android.media.mediacodec.secure", &secure)) {
-        metrics_proto.set_secure(secure);
+    int32_t isSecure = -1;
+    if (item->getInt32("android.media.mediacodec.secure", &isSecure)) {
+        metrics_proto.set_secure(isSecure);
     }
-    AStatsEvent_writeInt32(event, secure);
+    AStatsEvent_writeInt32(event, isSecure);
+
+    int32_t isHardware = -1;
+    item->getInt32("android.media.mediacodec.hardware", &isHardware);
+    // not logged to MediaCodecReported or MediametricsCodecReported
+
+    int32_t isTunneled = -1;
+    item->getInt32("android.media.mediacodec.tunneled", &isTunneled);
+    // not logged to MediaCodecReported or MediametricsCodecReported
 
     int32_t width = -1;
     if (item->getInt32("android.media.mediacodec.width", &width)) {
@@ -133,79 +277,78 @@
     AStatsEvent_writeInt32(event, level);
 
 
-    int32_t max_width = -1;
-    if ( item->getInt32("android.media.mediacodec.maxwidth", &max_width)) {
-        metrics_proto.set_max_width(max_width);
+    int32_t maxWidth = -1;
+    if ( item->getInt32("android.media.mediacodec.maxwidth", &maxWidth)) {
+        metrics_proto.set_max_width(maxWidth);
     }
-    AStatsEvent_writeInt32(event, max_width);
+    AStatsEvent_writeInt32(event, maxWidth);
 
-    int32_t max_height = -1;
-    if ( item->getInt32("android.media.mediacodec.maxheight", &max_height)) {
-        metrics_proto.set_max_height(max_height);
+    int32_t maxHeight = -1;
+    if ( item->getInt32("android.media.mediacodec.maxheight", &maxHeight)) {
+        metrics_proto.set_max_height(maxHeight);
     }
-    AStatsEvent_writeInt32(event, max_height);
+    AStatsEvent_writeInt32(event, maxHeight);
 
-    int32_t error_code = -1;
-    if ( item->getInt32("android.media.mediacodec.errcode", &error_code)) {
-        metrics_proto.set_error_code(error_code);
+    int32_t errorCode = -1;
+    if ( item->getInt32("android.media.mediacodec.errcode", &errorCode)) {
+        metrics_proto.set_error_code(errorCode);
     }
-    AStatsEvent_writeInt32(event, error_code);
+    AStatsEvent_writeInt32(event, errorCode);
 
-    std::string error_state;
-    if ( item->getString("android.media.mediacodec.errstate", &error_state)) {
-        metrics_proto.set_error_state(error_state);
+    std::string errorState;
+    if ( item->getString("android.media.mediacodec.errstate", &errorState)) {
+        metrics_proto.set_error_state(errorState);
     }
-    AStatsEvent_writeString(event, error_state.c_str());
+    AStatsEvent_writeString(event, errorState.c_str());
 
-    int64_t latency_max = -1;
-    if (item->getInt64("android.media.mediacodec.latency.max", &latency_max)) {
-        metrics_proto.set_latency_max(latency_max);
+    int64_t latencyMax = -1;
+    if (item->getInt64("android.media.mediacodec.latency.max", &latencyMax)) {
+        metrics_proto.set_latency_max(latencyMax);
     }
-    AStatsEvent_writeInt64(event, latency_max);
+    AStatsEvent_writeInt64(event, latencyMax);
 
-    int64_t latency_min = -1;
-    if (item->getInt64("android.media.mediacodec.latency.min", &latency_min)) {
-        metrics_proto.set_latency_min(latency_min);
+    int64_t latencyMin = -1;
+    if (item->getInt64("android.media.mediacodec.latency.min", &latencyMin)) {
+        metrics_proto.set_latency_min(latencyMin);
     }
-    AStatsEvent_writeInt64(event, latency_min);
+    AStatsEvent_writeInt64(event, latencyMin);
 
-    int64_t latency_avg = -1;
-    if (item->getInt64("android.media.mediacodec.latency.avg", &latency_avg)) {
-        metrics_proto.set_latency_avg(latency_avg);
+    int64_t latencyAvg = -1;
+    if (item->getInt64("android.media.mediacodec.latency.avg", &latencyAvg)) {
+        metrics_proto.set_latency_avg(latencyAvg);
     }
-    AStatsEvent_writeInt64(event, latency_avg);
+    AStatsEvent_writeInt64(event, latencyAvg);
 
-    int64_t latency_count = -1;
-    if (item->getInt64("android.media.mediacodec.latency.n", &latency_count)) {
-        metrics_proto.set_latency_count(latency_count);
+    int64_t latencyCount = -1;
+    if (item->getInt64("android.media.mediacodec.latency.n", &latencyCount)) {
+        metrics_proto.set_latency_count(latencyCount);
     }
-    AStatsEvent_writeInt64(event, latency_count);
+    AStatsEvent_writeInt64(event, latencyCount);
 
-    int64_t latency_unknown = -1;
-    if (item->getInt64("android.media.mediacodec.latency.unknown", &latency_unknown)) {
-        metrics_proto.set_latency_unknown(latency_unknown);
+    int64_t latencyUnknown = -1;
+    if (item->getInt64("android.media.mediacodec.latency.unknown", &latencyUnknown)) {
+        metrics_proto.set_latency_unknown(latencyUnknown);
     }
-    AStatsEvent_writeInt64(event, latency_unknown);
+    AStatsEvent_writeInt64(event, latencyUnknown);
 
-    int32_t queue_secure_input_buffer_error = -1;
+    int32_t queueSecureInputBufferError = -1;
     if (item->getInt32("android.media.mediacodec.queueSecureInputBufferError",
-            &queue_secure_input_buffer_error)) {
-        metrics_proto.set_queue_secure_input_buffer_error(queue_secure_input_buffer_error);
+            &queueSecureInputBufferError)) {
+        metrics_proto.set_queue_secure_input_buffer_error(queueSecureInputBufferError);
     }
-    AStatsEvent_writeInt32(event, queue_secure_input_buffer_error);
+    AStatsEvent_writeInt32(event, queueSecureInputBufferError);
 
-    int32_t queue_input_buffer_error = -1;
-    if (item->getInt32("android.media.mediacodec.queueInputBufferError",
-            &queue_input_buffer_error)) {
-        metrics_proto.set_queue_input_buffer_error(queue_input_buffer_error);
+    int32_t queueInputBufferError = -1;
+    if (item->getInt32("android.media.mediacodec.queueInputBufferError", &queueInputBufferError)) {
+        metrics_proto.set_queue_input_buffer_error(queueInputBufferError);
     }
-    AStatsEvent_writeInt32(event, queue_input_buffer_error);
+    AStatsEvent_writeInt32(event, queueInputBufferError);
 
-    std::string bitrate_mode;
-    if (item->getString("android.media.mediacodec.bitrate_mode", &bitrate_mode)) {
-        metrics_proto.set_bitrate_mode(bitrate_mode);
+    std::string bitrateMode;
+    if (item->getString("android.media.mediacodec.bitrate_mode", &bitrateMode)) {
+        metrics_proto.set_bitrate_mode(bitrateMode);
     }
-    AStatsEvent_writeString(event, bitrate_mode.c_str());
+    AStatsEvent_writeString(event, bitrateMode.c_str());
 
     int32_t bitrate = -1;
     if (item->getInt32("android.media.mediacodec.bitrate", &bitrate)) {
@@ -213,18 +356,18 @@
     }
     AStatsEvent_writeInt32(event, bitrate);
 
-    int64_t lifetime_millis = -1;
-    if (item->getInt64("android.media.mediacodec.lifetimeMs", &lifetime_millis)) {
-        lifetime_millis = mediametrics::bucket_time_minutes(lifetime_millis);
-        metrics_proto.set_lifetime_millis(lifetime_millis);
+    int64_t lifetimeMillis = -1;
+    if (item->getInt64("android.media.mediacodec.lifetimeMs", &lifetimeMillis)) {
+        lifetimeMillis = mediametrics::bucket_time_minutes(lifetimeMillis);
+        metrics_proto.set_lifetime_millis(lifetimeMillis);
     }
-    AStatsEvent_writeInt64(event, lifetime_millis);
+    AStatsEvent_writeInt64(event, lifetimeMillis);
 
-    int64_t playback_duration_sec = -1;
-    item->getInt64("android.media.mediacodec.playback-duration-sec", &playback_duration_sec);
+    int64_t playbackDurationSec = -1;
+    item->getInt64("android.media.mediacodec.playback-duration-sec", &playbackDurationSec);
     // DO NOT record  playback-duration in the metrics_proto - it should only
     // exist in the flattened atom
-    AStatsEvent_writeInt64(event, playback_duration_sec);
+    AStatsEvent_writeInt64(event, playbackDurationSec);
 
     std::string sessionId;
     if (item->getString("android.media.mediacodec.log-session-id", &sessionId)) {
@@ -505,61 +648,188 @@
     }
     AStatsEvent_writeInt32(event, resolutionChangeCount);
 
+    int32_t componentColorFormat = -1;
+    if (item->getInt32("android.media.mediacodec.component-color-format", &componentColorFormat)) {
+        metrics_proto.set_component_color_format(componentColorFormat);
+    }
+    AStatsEvent_writeInt32(event, componentColorFormat);
+
+    int64_t firstRenderTimeUs = -1;
+    item->getInt64("android.media.mediacodec.first-render-time-us", &firstRenderTimeUs);
+    int64_t framesReleased = -1;
+    item->getInt64("android.media.mediacodec.frames-released", &framesReleased);
+    int64_t framesRendered = -1;
+    item->getInt64("android.media.mediacodec.frames-rendered", &framesRendered);
+    int64_t framesDropped = -1;
+    item->getInt64("android.media.mediacodec.frames-dropped", &framesDropped);
+    int64_t framesSkipped = -1;
+    item->getInt64("android.media.mediacodec.frames-skipped", &framesSkipped);
+    double framerateContent = -1;
+    item->getDouble("android.media.mediacodec.framerate-content", &framerateContent);
+    double framerateActual = -1;
+    item->getDouble("android.media.mediacodec.framerate-actual", &framerateActual);
+    int64_t freezeScore = -1;
+    item->getInt64("android.media.mediacodec.freeze-score", &freezeScore);
+    double freezeRate = -1;
+    item->getDouble("android.media.mediacodec.freeze-rate", &freezeRate);
+    std::string freezeScoreHistogramStr;
+    item->getString("android.media.mediacodec.freeze-score-histogram", &freezeScoreHistogramStr);
+    std::string freezeScoreHistogramBucketsStr;
+    item->getString("android.media.mediacodec.freeze-score-histogram-buckets",
+                    &freezeScoreHistogramBucketsStr);
+    std::string freezeDurationMsHistogramStr;
+    item->getString("android.media.mediacodec.freeze-duration-ms-histogram",
+                    &freezeDurationMsHistogramStr);
+    std::string freezeDurationMsHistogramBucketsStr;
+    item->getString("android.media.mediacodec.freeze-duration-ms-histogram-buckets",
+                    &freezeDurationMsHistogramBucketsStr);
+    std::string freezeDistanceMsHistogramStr;
+    item->getString("android.media.mediacodec.freeze-distance-ms-histogram",
+                    &freezeDistanceMsHistogramStr);
+    std::string freezeDistanceMsHistogramBucketsStr;
+    item->getString("android.media.mediacodec.freeze-distance-ms-histogram-buckets",
+                    &freezeDistanceMsHistogramBucketsStr);
+    int64_t judderScore = -1;
+    item->getInt64("android.media.mediacodec.judder-score", &judderScore);
+    double judderRate = -1;
+    item->getDouble("android.media.mediacodec.judder-rate", &judderRate);
+    std::string judderScoreHistogramStr;
+    item->getString("android.media.mediacodec.judder-score-histogram", &judderScoreHistogramStr);
+    std::string judderScoreHistogramBucketsStr;
+    item->getString("android.media.mediacodec.judder-score-histogram-buckets",
+                    &judderScoreHistogramBucketsStr);
+
     int err = AStatsEvent_write(event);
     if (err < 0) {
       ALOGE("Failed to write codec metrics to statsd (%d)", err);
     }
     AStatsEvent_release(event);
 
+    if (framesRendered > 0) {
+        int32_t statsUid = item->getUid();
+        int64_t statsCodecId = codecId;
+        char const *statsLogSessionId = sessionId.c_str();
+        int32_t statsIsHardware = isHardware;
+        int32_t statsIsSecure = isSecure;
+        int32_t statsIsTunneled = isTunneled;
+        int32_t statsCodec = getMetricsCodecEnum(mime, codec);
+        int32_t statsResolution = getMetricsResolutionEnum(width, height);
+        int32_t statsBitrate = BITRATE_UNKNOWN;
+        int32_t statsContentFramerate = getMetricsFramerateEnum(framerateContent);
+        int32_t statsActualFramerate = getMetricsFramerateEnum(framerateActual);
+        int32_t statsHdrFormat = getMetricsHdrFormatEnum(mime, codec, configColorTransfer,
+                                                         parsedColorTransfer, hdrStaticInfo,
+                                                         hdr10PlusInfo);
+        int64_t statsFirstRenderTimeUs = firstRenderTimeUs;
+        int64_t statsPlaybackDurationSeconds = playbackDurationSec;
+        int64_t statsFramesTotal = framesReleased + framesSkipped;
+        int64_t statsFramesReleased = framesReleased;
+        int64_t statsFramesRendered = framesRendered;
+        int64_t statsFramesDropped = framesDropped;
+        int64_t statsFramesSkipped = framesSkipped;
+        float statsFrameDropRate = float(double(framesDropped) / statsFramesTotal);
+        float statsFrameSkipRate = float(double(framesSkipped) / statsFramesTotal);
+        float statsFrameSkipDropRate = float(double(framesSkipped + framesDropped) /
+                                             statsFramesTotal);
+        int64_t statsFreezeScore = freezeScore;
+        float statsFreezeRate = freezeRate;
+        std::vector<int32_t> statsFreezeDurationMsHistogram;
+        parseVector(freezeDurationMsHistogramStr, &statsFreezeDurationMsHistogram);
+        std::vector<int32_t> statsFreezeDurationMsHistogramBuckets;
+        parseVector(freezeDurationMsHistogramBucketsStr, &statsFreezeDurationMsHistogramBuckets);
+        std::vector<int32_t> statsFreezeDistanceMsHistogram;
+        parseVector(freezeDistanceMsHistogramStr, &statsFreezeDistanceMsHistogram);
+        std::vector<int32_t> statsFreezeDistanceMsHistogramBuckets;
+        parseVector(freezeDistanceMsHistogramBucketsStr, &statsFreezeDistanceMsHistogramBuckets);
+        int64_t statsJudderScore = judderScore;
+        float statsJudderRate = judderRate;
+        std::vector<int32_t> statsJudderScoreHistogram;
+        parseVector(judderScoreHistogramStr, &statsJudderScoreHistogram);
+        std::vector<int32_t> statsJudderScoreHistogramBuckets;
+        parseVector(judderScoreHistogramBucketsStr, &statsJudderScoreHistogramBuckets);
+        int result = stats_write(
+            MEDIA_CODEC_RENDERED,
+            statsUid,
+            statsCodecId,
+            statsLogSessionId,
+            statsIsHardware,
+            statsIsSecure,
+            statsIsTunneled,
+            statsCodec,
+            statsResolution,
+            statsBitrate,
+            statsContentFramerate,
+            statsActualFramerate,
+            statsHdrFormat,
+            statsFirstRenderTimeUs,
+            statsPlaybackDurationSeconds,
+            statsFramesTotal,
+            statsFramesReleased,
+            statsFramesRendered,
+            statsFramesDropped,
+            statsFramesSkipped,
+            statsFrameDropRate,
+            statsFrameSkipRate,
+            statsFrameSkipDropRate,
+            statsFreezeScore,
+            statsFreezeRate,
+            statsFreezeDurationMsHistogram,
+            statsFreezeDurationMsHistogramBuckets,
+            statsFreezeDistanceMsHistogram,
+            statsFreezeDistanceMsHistogramBuckets,
+            statsJudderScore,
+            statsJudderRate,
+            statsJudderScoreHistogram,
+            statsJudderScoreHistogramBuckets);
+        ALOGE_IF(result < 0, "Failed to record MEDIA_CODEC_RENDERED atom (%d)", result);
+    }
+
     std::string serialized;
     if (!metrics_proto.SerializeToString(&serialized)) {
         ALOGE("Failed to serialize codec metrics");
         return false;
     }
-    const stats::media_metrics::BytesField bf_serialized( serialized.c_str(), serialized.size());
+    const stats::media_metrics::BytesField bf_serialized(serialized.c_str(), serialized.size());
     const int result = stats::media_metrics::stats_write(stats::media_metrics::MEDIAMETRICS_CODEC_REPORTED,
-                               timestamp_nanos, package_name.c_str(), package_version_code,
-                               media_apex_version,
+                               timestampNanos, packageName.c_str(), packageVersionCode,
+                               mediaApexVersion,
                                bf_serialized);
 
     std::stringstream log;
     log << "result:" << result << " {"
             << " mediametrics_codec_reported:"
             << stats::media_metrics::MEDIAMETRICS_CODEC_REPORTED
-            << " timestamp_nanos:" << timestamp_nanos
-            << " package_name:" << package_name
-            << " package_version_code:" << package_version_code
-            << " media_apex_version:" << media_apex_version
-
+            << " timestamp_nanos:" << timestampNanos
+            << " package_name:" << packageName
+            << " package_version_code:" << packageVersionCode
+            << " media_apex_version:" << mediaApexVersion
             << " codec:" << codec
             << " mime:" << mime
             << " mode:" << mode
-            << " encoder:" << encoder
-            << " secure:" << secure
+            << " encoder:" << isEncoder
+            << " secure:" << isSecure
             << " width:" << width
             << " height:" << height
             << " rotation:" << rotation
             << " crypto:" << crypto
             << " profile:" << profile
-
             << " level:" << level
-            << " max_width:" << max_width
-            << " max_height:" << max_height
-            << " error_code:" << error_code
-            << " error_state:" << error_state
-            << " latency_max:" << latency_max
-            << " latency_min:" << latency_min
-            << " latency_avg:" << latency_avg
-            << " latency_count:" << latency_count
-            << " latency_unknown:" << latency_unknown
-
-            << " queue_input_buffer_error:" << queue_input_buffer_error
-            << " queue_secure_input_buffer_error:" << queue_secure_input_buffer_error
-            << " bitrate_mode:" << bitrate_mode
+            << " max_width:" << maxWidth
+            << " max_height:" << maxHeight
+            << " error_code:" << errorCode
+            << " error_state:" << errorState
+            << " latency_max:" << latencyMax
+            << " latency_min:" << latencyMin
+            << " latency_avg:" << latencyAvg
+            << " latency_count:" << latencyCount
+            << " latency_unknown:" << latencyUnknown
+            << " queue_input_buffer_error:" << queueInputBufferError
+            << " queue_secure_input_buffer_error:" << queueSecureInputBufferError
+            << " bitrate_mode:" << bitrateMode
             << " bitrate:" << bitrate
             << " original_bitrate:" << originalBitrate
-            << " lifetime_millis:" << lifetime_millis
-            << " playback_duration_seconds:" << playback_duration_sec
+            << " lifetime_millis:" << lifetimeMillis
+            << " playback_duration_seconds:" << playbackDurationSec
             << " log_session_id:" << sessionId
             << " channel_count:" << channelCount
             << " sample_rate:" << sampleRate
@@ -572,7 +842,6 @@
             << " operating_rate:" << operatingRate
             << " priority:" << priority
             << " shaping_enhanced:" << shapingEnhanced
-
             << " qp_i_min:" << qpIMin
             << " qp_i_max:" << qpIMax
             << " qp_p_min:" << qpPMin
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index bc7b47b..4a6aee4 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -17,9 +17,10 @@
 #define LOG_TAG "mediametrics_tests"
 #include <utils/Log.h>
 
-
 #include <stdio.h>
+#include <string>
 #include <unordered_set>
+#include <vector>
 
 #include <gtest/gtest.h>
 #include <media/MediaMetricsItem.h>
@@ -30,6 +31,7 @@
 #include <system/audio.h>
 
 using namespace android;
+using android::mediametrics::stringutils::parseVector;
 
 static size_t countNewlines(const char *s) {
     size_t count = 0;
@@ -57,6 +59,35 @@
   ASSERT_EQ(false, android::mediametrics::startsWith(s, std::string("est")));
 }
 
+TEST(mediametrics_tests, parseVector) {
+    {
+        std::vector<int32_t> values;
+        EXPECT_EQ(true, parseVector("0{4,300,0,-112343,350}9", &values));
+        EXPECT_EQ(values, std::vector<int32_t>({0, 4, 300, 0, -112343, 350, 9}));
+    }
+    {
+        std::vector<int32_t> values;
+        EXPECT_EQ(true, parseVector("53", &values));
+        EXPECT_EQ(values, std::vector<int32_t>({53}));
+    }
+    {
+        std::vector<int32_t> values;
+        EXPECT_EQ(false, parseVector("5{3,6*3}3", &values));
+        EXPECT_EQ(values, std::vector<int32_t>({}));
+    }
+    {
+        std::vector<int32_t> values = {1}; // should still be this when parsing fails
+        std::vector<int32_t> expected = {1};
+        EXPECT_EQ(false, parseVector("51342abcd,1232", &values));
+        EXPECT_EQ(values, std::vector<int32_t>({1}));
+    }
+    {
+        std::vector<int32_t> values = {2}; // should still be this when parsing fails
+        EXPECT_EQ(false, parseVector("12345678901234,12345678901234", &values));
+        EXPECT_EQ(values, std::vector<int32_t>({2}));
+    }
+}
+
 TEST(mediametrics_tests, defer) {
   bool check = false;
   {
diff --git a/services/tuner/TunerService.cpp b/services/tuner/TunerService.cpp
index e5bcf1f..9a1e8bb 100644
--- a/services/tuner/TunerService.cpp
+++ b/services/tuner/TunerService.cpp
@@ -100,8 +100,11 @@
 
     if (fallBackToOpenDemux) {
         auto status = mTuner->openDemux(&ids, &demux);
-        return ::ndk::ScopedAStatus::fromServiceSpecificError(
-                static_cast<int32_t>(Result::UNAVAILABLE));
+        if (status.isOk()) {
+            *_aidl_return = ::ndk::SharedRefBase::make<TunerDemux>(demux, ids[0],
+                                                                   this->ref<TunerService>());
+        }
+        return status;
     } else {
         int id = TunerHelper::getResourceIdFromHandle(in_demuxHandle, DEMUX);
         auto status = mTuner->openDemuxById(id, &demux);
diff --git a/services/tuner/hidl/TunerHidlFilter.cpp b/services/tuner/hidl/TunerHidlFilter.cpp
index 1789028..617622d 100644
--- a/services/tuner/hidl/TunerHidlFilter.cpp
+++ b/services/tuner/hidl/TunerHidlFilter.cpp
@@ -368,7 +368,7 @@
     }
 
     hidl_handle handle;
-    handle.setTo(makeFromAidl(in_handle), true);
+    handle.setTo(makeFromAidl(in_handle));
     HidlResult res = mFilter->releaseAvHandle(handle, in_avDataId);
     if (res != HidlResult::SUCCESS) {
         return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(res));