Merge "Add InputFilter rust component as InputListener stage." into main
diff --git a/Android.bp b/Android.bp
index 7f1ef67..8c4dfbb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -97,6 +97,12 @@
     ],
 }
 
+aidl_library {
+    name: "PersistableBundle_aidl",
+    hdrs: ["aidl/binder/android/os/PersistableBundle.aidl"],
+    strip_import_prefix: "aidl/binder",
+}
+
 cc_library_headers {
     name: "libandroid_headers_private",
     export_include_dirs: ["include/private"],
diff --git a/aidl/binder/android/os/PersistableBundle.aidl b/aidl/binder/android/os/PersistableBundle.aidl
index 493ecb4..248e973 100644
--- a/aidl/binder/android/os/PersistableBundle.aidl
+++ b/aidl/binder/android/os/PersistableBundle.aidl
@@ -17,4 +17,4 @@
 
 package android.os;
 
-@JavaOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h" ndk_header "android/persistable_bundle_aidl.h";
diff --git a/cmds/evemu-record/Android.bp b/cmds/evemu-record/Android.bp
new file mode 100644
index 0000000..1edacec
--- /dev/null
+++ b/cmds/evemu-record/Android.bp
@@ -0,0 +1,13 @@
+package {
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_binary {
+    name: "evemu-record",
+    srcs: ["main.rs"],
+    rustlibs: [
+        "libclap",
+        "liblibc",
+        "libnix",
+    ],
+}
diff --git a/cmds/evemu-record/OWNERS b/cmds/evemu-record/OWNERS
new file mode 100644
index 0000000..c88bfe9
--- /dev/null
+++ b/cmds/evemu-record/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/cmds/evemu-record/evdev.rs b/cmds/evemu-record/evdev.rs
new file mode 100644
index 0000000..35feea1
--- /dev/null
+++ b/cmds/evemu-record/evdev.rs
@@ -0,0 +1,299 @@
+/*
+ * 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.
+ */
+
+//! Wrappers for the Linux evdev APIs.
+
+use std::fs::File;
+use std::io;
+use std::mem;
+use std::os::fd::{AsRawFd, OwnedFd};
+use std::path::Path;
+
+use libc::c_int;
+use nix::sys::time::TimeVal;
+
+pub const SYN_CNT: usize = 0x10;
+pub const KEY_CNT: usize = 0x300;
+pub const REL_CNT: usize = 0x10;
+pub const ABS_CNT: usize = 0x40;
+pub const MSC_CNT: usize = 0x08;
+pub const SW_CNT: usize = 0x11;
+pub const LED_CNT: usize = 0x10;
+pub const SND_CNT: usize = 0x08;
+pub const REP_CNT: usize = 0x02;
+
+// Disable naming warnings, as these are supposed to match the EV_ constants in input-event-codes.h.
+#[allow(non_camel_case_types)]
+// Some of these types aren't referenced for evemu purposes, but are included for completeness.
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[repr(u16)]
+pub enum EventType {
+    SYN = 0x00,
+    KEY = 0x01,
+    REL = 0x02,
+    ABS = 0x03,
+    MSC = 0x04,
+    SW = 0x05,
+    LED = 0x11,
+    SND = 0x12,
+    REP = 0x14,
+    FF = 0x15,
+    PWR = 0x16,
+    FF_STATUS = 0x17,
+}
+
+impl EventType {
+    fn code_count(&self) -> usize {
+        match self {
+            EventType::SYN => SYN_CNT,
+            EventType::KEY => KEY_CNT,
+            EventType::REL => REL_CNT,
+            EventType::ABS => ABS_CNT,
+            EventType::MSC => MSC_CNT,
+            EventType::SW => SW_CNT,
+            EventType::LED => LED_CNT,
+            EventType::SND => SND_CNT,
+            EventType::REP => REP_CNT,
+            _ => {
+                panic!("Event type {self:?} does not have a defined code count.");
+            }
+        }
+    }
+}
+
+pub const EVENT_TYPES_WITH_BITMAPS: [EventType; 7] = [
+    EventType::KEY,
+    EventType::REL,
+    EventType::ABS,
+    EventType::MSC,
+    EventType::SW,
+    EventType::LED,
+    EventType::SND,
+];
+
+const INPUT_PROP_CNT: usize = 32;
+
+/// The `ioctl_*!` macros create public functions by default, so this module makes them private.
+mod ioctl {
+    use nix::{ioctl_read, ioctl_read_buf};
+
+    ioctl_read!(eviocgid, b'E', 0x02, super::DeviceId);
+    ioctl_read_buf!(eviocgname, b'E', 0x06, u8);
+    ioctl_read_buf!(eviocgprop, b'E', 0x09, u8);
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct DeviceId {
+    pub bus_type: u16,
+    pub vendor: u16,
+    pub product: u16,
+    pub version: u16,
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct AbsoluteAxisInfo {
+    pub value: i32,
+    pub minimum: i32,
+    pub maximum: i32,
+    pub fuzz: i32,
+    pub flat: i32,
+    pub resolution: i32,
+}
+
+#[repr(C)]
+pub struct InputEvent {
+    pub time: TimeVal,
+    pub type_: u16,
+    pub code: u16,
+    pub value: i32,
+}
+
+impl InputEvent {
+    pub fn offset_time_by(&self, offset: TimeVal) -> InputEvent {
+        InputEvent { time: self.time - offset, ..*self }
+    }
+}
+
+impl Default for InputEvent {
+    fn default() -> Self {
+        InputEvent { time: TimeVal::new(0, 0), type_: 0, code: 0, value: 0 }
+    }
+}
+
+/// An object representing an input device using Linux's evdev protocol.
+pub struct Device {
+    fd: OwnedFd,
+}
+
+/// # Safety
+///
+/// `ioctl` must be safe to call with the given file descriptor and a pointer to a buffer of
+/// `initial_buf_size` `u8`s.
+unsafe fn buf_from_ioctl(
+    ioctl: unsafe fn(c_int, &mut [u8]) -> nix::Result<c_int>,
+    fd: &OwnedFd,
+    initial_buf_size: usize,
+) -> Result<Vec<u8>, nix::errno::Errno> {
+    let mut buf = vec![0; initial_buf_size];
+    // SAFETY:
+    // Here we're relying on the safety guarantees for `ioctl` made by the caller.
+    match unsafe { ioctl(fd.as_raw_fd(), buf.as_mut_slice()) } {
+        Ok(len) if len < 0 => {
+            panic!("ioctl returned invalid length {len}");
+        }
+        Ok(len) => {
+            buf.truncate(len as usize);
+            Ok(buf)
+        }
+        Err(err) => Err(err),
+    }
+}
+
+impl Device {
+    /// Opens a device from the evdev node at the given path.
+    pub fn open(path: &Path) -> io::Result<Device> {
+        Ok(Device { fd: OwnedFd::from(File::open(path)?) })
+    }
+
+    /// Returns the name of the device, as set by the relevant kernel driver.
+    pub fn name(&self) -> Result<String, nix::errno::Errno> {
+        // There's no official maximum length for evdev device names. The Linux HID driver
+        // currently supports names of at most 151 bytes (128 from the device plus a suffix of up
+        // to 23 bytes). 256 seems to be the buffer size most commonly used in evdev bindings, so
+        // we use it here.
+        //
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // The ioctl_read_buf macro prevents the retrieved data from overflowing the buffer created
+        // by buf_from_ioctl by passing in the size to the ioctl, meaning that the kernel's
+        // str_to_user function will truncate the string to that length.
+        let mut buf = unsafe { buf_from_ioctl(ioctl::eviocgname, &self.fd, 256)? };
+        assert!(!buf.is_empty(), "buf is too short for an empty null-terminated string");
+        assert_eq!(buf.pop().unwrap(), 0, "buf is not a null-terminated string");
+        Ok(String::from_utf8_lossy(buf.as_slice()).into_owned())
+    }
+
+    pub fn ids(&self) -> Result<DeviceId, nix::errno::Errno> {
+        let mut ids = DeviceId::default();
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We know that the pointer to ids is valid because we just allocated it.
+        unsafe { ioctl::eviocgid(self.fd.as_raw_fd(), &mut ids) }.map(|_| ids)
+    }
+
+    pub fn properties_bitmap(&self) -> Result<Vec<u8>, nix::errno::Errno> {
+        let buf_size = (INPUT_PROP_CNT + 7) / 8;
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // The ioctl_read_buf macro prevents the retrieved data from overflowing the buffer created
+        // by buf_from_ioctl by passing in the size to the ioctl, meaning that the kernel's
+        // str_to_user function will truncate the string to that length.
+        unsafe { buf_from_ioctl(ioctl::eviocgprop, &self.fd, buf_size) }
+    }
+
+    pub fn bitmap_for_event_type(&self, event_type: EventType) -> nix::Result<Vec<u8>> {
+        let buf_size = (event_type.code_count() + 7) / 8;
+        let mut buf = vec![0; buf_size];
+
+        // The EVIOCGBIT ioctl can't be bound using ioctl_read_buf! like the others, since it uses
+        // part of its ioctl code as an additional parameter, for the event type. Hence this unsafe
+        // block is a manual expansion of ioctl_read_buf!.
+        //
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We prevent the retrieved data from overflowing buf by passing in the size of buf to the
+        // ioctl, meaning that the kernel's str_to_user function will truncate the string to that
+        // length. We also panic if the ioctl returns a length longer than buf, hopefully before the
+        // overflow can do any damage.
+        match nix::errno::Errno::result(unsafe {
+            libc::ioctl(
+                self.fd.as_raw_fd(),
+                nix::request_code_read!(b'E', 0x20 + event_type as u16, buf.len())
+                    as nix::sys::ioctl::ioctl_num_type,
+                buf.as_mut_ptr(),
+            )
+        }) {
+            Ok(len) if len < 0 => {
+                panic!("EVIOCGBIT returned invalid length {len} for event type {event_type:?}");
+            }
+            Ok(len) => {
+                buf.truncate(len as usize);
+                Ok(buf)
+            }
+            Err(err) => Err(err),
+        }
+    }
+
+    pub fn supported_axes_of_type(&self, event_type: EventType) -> nix::Result<Vec<u16>> {
+        let mut axes = Vec::new();
+        for (i, byte_ref) in self.bitmap_for_event_type(event_type)?.iter().enumerate() {
+            let mut byte = *byte_ref;
+            for j in 0..8 {
+                if byte & 1 == 1 {
+                    axes.push((i * 8 + j) as u16);
+                }
+                byte >>= 1;
+            }
+        }
+        Ok(axes)
+    }
+
+    pub fn absolute_axis_info(&self, axis: u16) -> nix::Result<AbsoluteAxisInfo> {
+        let mut info = AbsoluteAxisInfo::default();
+        // The EVIOCGABS ioctl can't be bound using ioctl_read! since it uses part of its ioctl code
+        // as an additional parameter, for the axis code. Hence this unsafe block is a manual
+        // expansion of ioctl_read!.
+        //
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We know that the pointer to info is valid because we just allocated it.
+        nix::errno::Errno::result(unsafe {
+            nix::libc::ioctl(
+                self.fd.as_raw_fd(),
+                nix::request_code_read!(b'E', 0x40 + axis, mem::size_of::<AbsoluteAxisInfo>()),
+                &mut info,
+            )
+        })
+        .map(|_| info)
+    }
+
+    pub fn read_event(&self) -> nix::Result<InputEvent> {
+        let mut event = InputEvent::default();
+
+        // SAFETY:
+        // We know that fd is a valid file descriptor as it comes from a File that we have open.
+        //
+        // We know that the pointer to event is valid because we just allocated it, and that the
+        // data structures match up because InputEvent is repr(C) and all its members are repr(C)
+        // or primitives that support all representations without niches.
+        nix::errno::Errno::result(unsafe {
+            libc::read(
+                self.fd.as_raw_fd(),
+                &mut event as *mut _ as *mut std::ffi::c_void,
+                mem::size_of::<InputEvent>(),
+            )
+        })
+        .map(|_| event)
+    }
+}
diff --git a/cmds/evemu-record/main.rs b/cmds/evemu-record/main.rs
new file mode 100644
index 0000000..6f5deb9
--- /dev/null
+++ b/cmds/evemu-record/main.rs
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+
+//! A Rust implementation of the evemu-record command from the [FreeDesktop evemu suite][evemu] of
+//! tools.
+//!
+//! [evemu]: https://gitlab.freedesktop.org/libevdev/evemu
+
+use std::cmp;
+use std::error::Error;
+use std::fs;
+use std::io;
+use std::io::{BufRead, Write};
+use std::path::PathBuf;
+
+use clap::Parser;
+use nix::sys::time::TimeVal;
+
+mod evdev;
+
+/// Records evdev events from an input device in a format compatible with the FreeDesktop evemu
+/// library.
+#[derive(Parser, Debug)]
+struct Args {
+    /// The path to the input device to record. If omitted, offers a list of devices to choose from.
+    device: Option<PathBuf>,
+    /// The file to save the recording to. Defaults to standard output.
+    output_file: Option<PathBuf>,
+}
+
+fn get_choice(max: u32) -> u32 {
+    fn read_u32() -> Result<u32, std::num::ParseIntError> {
+        io::stdin().lock().lines().next().unwrap().unwrap().parse::<u32>()
+    }
+    let mut choice = read_u32();
+    while choice.is_err() || choice.clone().unwrap() > max {
+        eprint!("Enter a number between 0 and {max} inclusive: ");
+        choice = read_u32();
+    }
+    choice.unwrap()
+}
+
+fn pick_input_device() -> Result<PathBuf, io::Error> {
+    eprintln!("Available devices:");
+    let mut entries =
+        fs::read_dir("/dev/input")?.filter_map(|entry| entry.ok()).collect::<Vec<_>>();
+    entries.sort_by_key(|entry| entry.path());
+    let mut highest_number = 0;
+    for entry in entries {
+        let path = entry.path();
+        let file_name = path.file_name().unwrap().to_str().unwrap();
+        if path.is_dir() || !file_name.starts_with("event") {
+            continue;
+        }
+        let number = file_name.strip_prefix("event").unwrap().parse::<u32>();
+        if number.is_err() {
+            continue;
+        }
+        let number = number.unwrap();
+        match evdev::Device::open(path.as_path()) {
+            Ok(dev) => {
+                highest_number = cmp::max(highest_number, number);
+                eprintln!(
+                    "{}:\t{}",
+                    path.display(),
+                    dev.name().unwrap_or("[could not read name]".to_string()),
+                );
+            }
+            Err(_) => {
+                eprintln!("Couldn't open {}", path.display());
+            }
+        }
+    }
+    eprint!("Select the device event number [0-{highest_number}]: ");
+    let choice = get_choice(highest_number);
+    Ok(PathBuf::from(format!("/dev/input/event{choice}")))
+}
+
+fn print_device_description(
+    device: &evdev::Device,
+    output: &mut impl Write,
+) -> Result<(), Box<dyn Error>> {
+    // TODO(b/302297266): report LED and SW states, then bump the version to EVEMU 1.3.
+    writeln!(output, "# EVEMU 1.2")?;
+    writeln!(output, "N: {}", device.name()?)?;
+
+    let ids = device.ids()?;
+    writeln!(
+        output,
+        "I: {:04x} {:04x} {:04x} {:04x}",
+        ids.bus_type, ids.vendor, ids.product, ids.version,
+    )?;
+
+    fn print_in_8_byte_chunks(
+        output: &mut impl Write,
+        prefix: &str,
+        data: &Vec<u8>,
+    ) -> Result<(), io::Error> {
+        for (i, byte) in data.iter().enumerate() {
+            if i % 8 == 0 {
+                write!(output, "{prefix}")?;
+            }
+            write!(output, " {:02x}", byte)?;
+            if (i + 1) % 8 == 0 {
+                writeln!(output)?;
+            }
+        }
+        if data.len() % 8 != 0 {
+            for _ in (data.len() % 8)..8 {
+                write!(output, " 00")?;
+            }
+            writeln!(output)?;
+        }
+        Ok(())
+    }
+
+    let props = device.properties_bitmap()?;
+    print_in_8_byte_chunks(output, "P:", &props)?;
+
+    // The SYN event type can't be queried through the EVIOCGBIT ioctl, so just hard-code it to
+    // SYN_REPORT, SYN_CONFIG, and SYN_DROPPED.
+    writeln!(output, "B: 00 0b 00 00 00 00 00 00 00")?;
+    for event_type in evdev::EVENT_TYPES_WITH_BITMAPS {
+        let bits = device.bitmap_for_event_type(event_type)?;
+        print_in_8_byte_chunks(output, format!("B: {:02x}", event_type as u16).as_str(), &bits)?;
+    }
+
+    for axis in device.supported_axes_of_type(evdev::EventType::ABS)? {
+        let info = device.absolute_axis_info(axis)?;
+        writeln!(
+            output,
+            "A: {axis:02x} {} {} {} {} {}",
+            info.minimum, info.maximum, info.fuzz, info.flat, info.resolution
+        )?;
+    }
+    Ok(())
+}
+
+fn print_events(device: &evdev::Device, output: &mut impl Write) -> Result<(), Box<dyn Error>> {
+    fn print_event(output: &mut impl Write, event: &evdev::InputEvent) -> Result<(), io::Error> {
+        // TODO(b/302297266): Translate events into human-readable names and add those as comments.
+        writeln!(
+            output,
+            "E: {}.{:06} {:04x} {:04x} {:04}",
+            event.time.tv_sec(),
+            event.time.tv_usec(),
+            event.type_,
+            event.code,
+            event.value,
+        )?;
+        Ok(())
+    }
+    let event = device.read_event()?;
+    // Due to a bug in the C implementation of evemu-play [0] that has since become part of the API,
+    // the timestamp of the first event in a recording shouldn't be exactly 0.0 seconds, so offset
+    // it by 1µs.
+    //
+    // [0]: https://gitlab.freedesktop.org/libevdev/evemu/-/commit/eba96a4d2be7260b5843e65c4b99c8b06a1f4c9d
+    let start_time = event.time - TimeVal::new(0, 1);
+    print_event(output, &event.offset_time_by(start_time))?;
+    loop {
+        let event = device.read_event()?;
+        print_event(output, &event.offset_time_by(start_time))?;
+    }
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+    let args = Args::parse();
+
+    let device_path = args.device.unwrap_or_else(|| pick_input_device().unwrap());
+
+    let device = evdev::Device::open(device_path.as_path())?;
+    let mut output = match args.output_file {
+        Some(path) => Box::new(fs::File::create(path)?) as Box<dyn Write>,
+        None => Box::new(io::stdout().lock()),
+    };
+    print_device_description(&device, &mut output)?;
+    print_events(&device, &mut output)?;
+    Ok(())
+}
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index d7c9b40..e14af77 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -471,6 +471,49 @@
     return NO_ERROR;
 }
 
+constexpr const char kXattrRestoreconInProgress[] = "user.restorecon_in_progress";
+
+static std::string lgetfilecon(const std::string& path) {
+    char* context;
+    if (::lgetfilecon(path.c_str(), &context) < 0) {
+        PLOG(ERROR) << "Failed to lgetfilecon for " << path;
+        return {};
+    }
+    std::string result{context};
+    free(context);
+    return result;
+}
+
+static bool getRestoreconInProgress(const std::string& path) {
+    bool inProgress = false;
+    if (getxattr(path.c_str(), kXattrRestoreconInProgress, &inProgress, sizeof(inProgress)) !=
+        sizeof(inProgress)) {
+        if (errno != ENODATA) {
+            PLOG(ERROR) << "Failed to check in-progress restorecon for " << path;
+        }
+        return false;
+    }
+    return inProgress;
+}
+
+struct RestoreconInProgress {
+    explicit RestoreconInProgress(const std::string& path) : mPath(path) {
+        bool inProgress = true;
+        if (setxattr(mPath.c_str(), kXattrRestoreconInProgress, &inProgress, sizeof(inProgress),
+                     0) != 0) {
+            PLOG(ERROR) << "Failed to set in-progress restorecon for " << path;
+        }
+    }
+    ~RestoreconInProgress() {
+        if (removexattr(mPath.c_str(), kXattrRestoreconInProgress) < 0) {
+            PLOG(ERROR) << "Failed to clear in-progress restorecon for " << mPath;
+        }
+    }
+
+private:
+    const std::string& mPath;
+};
+
 /**
  * Perform restorecon of the given path, but only perform recursive restorecon
  * if the label of that top-level file actually changed.  This can save us
@@ -479,56 +522,56 @@
 static int restorecon_app_data_lazy(const std::string& path, const std::string& seInfo, uid_t uid,
         bool existing) {
     ScopedTrace tracer("restorecon-lazy");
-    int res = 0;
-    char* before = nullptr;
-    char* after = nullptr;
     if (!existing) {
         ScopedTrace tracer("new-path");
         if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
                 SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
             PLOG(ERROR) << "Failed recursive restorecon for " << path;
-            goto fail;
+            return -1;
         }
-        return res;
+        return 0;
     }
 
-    // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by
-    // libselinux. Not needed here.
-    if (lgetfilecon(path.c_str(), &before) < 0) {
-        PLOG(ERROR) << "Failed before getfilecon for " << path;
-        goto fail;
-    }
-    if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid, 0) < 0) {
-        PLOG(ERROR) << "Failed top-level restorecon for " << path;
-        goto fail;
-    }
-    if (lgetfilecon(path.c_str(), &after) < 0) {
-        PLOG(ERROR) << "Failed after getfilecon for " << path;
-        goto fail;
+    // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by libselinux. Not needed here.
+
+    // Check to see if there was an interrupted operation.
+    bool inProgress = getRestoreconInProgress(path);
+    std::string before, after;
+    if (!inProgress) {
+        if (before = lgetfilecon(path); before.empty()) {
+            PLOG(ERROR) << "Failed before getfilecon for " << path;
+            return -1;
+        }
+        if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid, 0) < 0) {
+            PLOG(ERROR) << "Failed top-level restorecon for " << path;
+            return -1;
+        }
+        if (after = lgetfilecon(path); after.empty()) {
+            PLOG(ERROR) << "Failed after getfilecon for " << path;
+            return -1;
+        }
     }
 
     // If the initial top-level restorecon above changed the label, then go
     // back and restorecon everything recursively
-    if (strcmp(before, after)) {
+    if (inProgress || before != after) {
         ScopedTrace tracer("label-change");
         if (existing) {
             LOG(DEBUG) << "Detected label change from " << before << " to " << after << " at "
                     << path << "; running recursive restorecon";
         }
+
+        // Temporary mark the folder as "in-progress" to resume in case of reboot/other failure.
+        RestoreconInProgress fence(path);
+
         if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
                 SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
             PLOG(ERROR) << "Failed recursive restorecon for " << path;
-            goto fail;
+            return -1;
         }
     }
 
-    goto done;
-fail:
-    res = -1;
-done:
-    free(before);
-    free(after);
-    return res;
+    return 0;
 }
 static bool internal_storage_has_project_id() {
     // The following path is populated in setFirstBoot, so if this file is present
@@ -3283,7 +3326,7 @@
     }
 
     char *con = nullptr;
-    if (lgetfilecon(pkgdir, &con) < 0) {
+    if (::lgetfilecon(pkgdir, &con) < 0) {
         return error("Failed to lgetfilecon " + _pkgdir);
     }
 
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
index 55326ea..de0e171 100644
--- a/cmds/sfdo/sfdo.cpp
+++ b/cmds/sfdo/sfdo.cpp
@@ -16,7 +16,7 @@
 #include <inttypes.h>
 #include <stdint.h>
 #include <any>
-#include <unordered_map>
+#include <map>
 
 #include <cutils/properties.h>
 #include <sys/resource.h>
@@ -29,18 +29,28 @@
 
 using namespace android;
 
-std::unordered_map<std::string, std::any> g_functions;
+std::map<std::string, std::any> g_functions;
 
-const std::unordered_map<std::string, std::string> g_function_details = {
-    {"DebugFlash", "[optional(delay)] Perform a debug flash."},
-    {"FrameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
-    {"scheduleComposite", "Force composite ahead of next VSYNC."},
-    {"scheduleCommit", "Force commit ahead of next VSYNC."},
-    {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
+enum class ParseToggleResult {
+    kError,
+    kFalse,
+    kTrue,
+};
+
+const std::map<std::string, std::string> g_function_details = {
+        {"debugFlash", "[optional(delay)] Perform a debug flash."},
+        {"frameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
+        {"scheduleComposite", "Force composite ahead of next VSYNC."},
+        {"scheduleCommit", "Force commit ahead of next VSYNC."},
+        {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
+        {"forceClientComposition",
+         "[enabled | disabled] When enabled, it disables "
+         "Hardware Overlays, and routes all window composition to the GPU. This can "
+         "help check if there is a bug in HW Composer."},
 };
 
 static void ShowUsage() {
-    std::cout << "usage: sfdo [help, FrameRateIndicator show, DebugFlash enabled, ...]\n\n";
+    std::cout << "usage: sfdo [help, frameRateIndicator show, debugFlash enabled, ...]\n\n";
     for (const auto& sf : g_functions) {
         const std::string fn = sf.first;
         std::string fdetails = "TODO";
@@ -50,7 +60,26 @@
     }
 }
 
-int FrameRateIndicator([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+// Returns 1 for positive keywords and 0 for negative keywords.
+// If the string does not match any it will return -1.
+ParseToggleResult parseToggle(const char* str) {
+    const std::unordered_set<std::string> positive{"1",  "true",    "y",   "yes",
+                                                   "on", "enabled", "show"};
+    const std::unordered_set<std::string> negative{"0",   "false",    "n",   "no",
+                                                   "off", "disabled", "hide"};
+
+    const std::string word(str);
+    if (positive.count(word)) {
+        return ParseToggleResult::kTrue;
+    }
+    if (negative.count(word)) {
+        return ParseToggleResult::kFalse;
+    }
+
+    return ParseToggleResult::kError;
+}
+
+int frameRateIndicator(int argc, char** argv) {
     bool hide = false, show = false;
     if (argc == 3) {
         show = strcmp(argv[2], "show") == 0;
@@ -60,13 +89,13 @@
     if (show || hide) {
         ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show);
     } else {
-        std::cerr << "Incorrect usage of FrameRateIndicator. Missing [hide | show].\n";
+        std::cerr << "Incorrect usage of frameRateIndicator. Missing [hide | show].\n";
         return -1;
     }
     return 0;
 }
 
-int DebugFlash([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+int debugFlash(int argc, char** argv) {
     int delay = 0;
     if (argc == 3) {
         delay = atoi(argv[2]) == 0;
@@ -86,14 +115,40 @@
     return 0;
 }
 
+int forceClientComposition(int argc, char** argv) {
+    bool enabled = true;
+    // A valid command looks like this:
+    // adb shell sfdo forceClientComposition enabled
+    if (argc >= 3) {
+        const ParseToggleResult toggle = parseToggle(argv[2]);
+        if (toggle == ParseToggleResult::kError) {
+            std::cerr << "Incorrect usage of forceClientComposition. "
+                         "Missing [enabled | disabled].\n";
+            return -1;
+        }
+        if (argc > 3) {
+            std::cerr << "Too many arguments after [enabled | disabled]. "
+                         "Ignoring extra arguments.\n";
+        }
+        enabled = (toggle == ParseToggleResult::kTrue);
+    } else {
+        std::cerr << "Incorrect usage of forceClientComposition. Missing [enabled | disabled].\n";
+        return -1;
+    }
+
+    ComposerServiceAIDL::getComposerService()->forceClientComposition(enabled);
+    return 0;
+}
+
 int main(int argc, char** argv) {
     std::cout << "Execute SurfaceFlinger internal commands.\n";
     std::cout << "sfdo requires to be run with root permissions..\n";
 
-    g_functions["FrameRateIndicator"] = FrameRateIndicator;
-    g_functions["DebugFlash"] = DebugFlash;
+    g_functions["frameRateIndicator"] = frameRateIndicator;
+    g_functions["debugFlash"] = debugFlash;
     g_functions["scheduleComposite"] = scheduleComposite;
     g_functions["scheduleCommit"] = scheduleCommit;
+    g_functions["forceClientComposition"] = forceClientComposition;
 
     if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) {
         std::cout << "Running: " << argv[1] << "\n";
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 550b374..89736ec 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -371,6 +371,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.opengles.deqp.level-2024-03-01.prebuilt.xml",
+    src: "android.software.opengles.deqp.level-2024-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.software.opengles.deqp.level-latest.prebuilt.xml",
     src: "android.software.opengles.deqp.level-latest.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
@@ -407,6 +413,12 @@
 }
 
 prebuilt_etc {
+    name: "android.software.vulkan.deqp.level-2024-03-01.prebuilt.xml",
+    src: "android.software.vulkan.deqp.level-2024-03-01.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.software.vulkan.deqp.level-latest.prebuilt.xml",
     src: "android.software.vulkan.deqp.level-latest.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.software.opengles.deqp.level-2024-03-01.xml b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
new file mode 100644
index 0000000..4eeed2a
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+     dEQP tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+    <feature name="android.software.opengles.deqp.level" version="132645633" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
new file mode 100644
index 0000000..8b2b4c8
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
+     tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+    <feature name="android.software.vulkan.deqp.level" version="132645633" />
+</permissions>
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index ba8b02d..9d2c791 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -60,6 +60,27 @@
 
 struct APerformanceHintManager;
 struct APerformanceHintSession;
+struct AWorkDuration;
+
+/**
+ * {@link AWorkDuration} is an opaque type that represents the breakdown of the
+ * actual workload duration in each component internally.
+ *
+ * A new {@link AWorkDuration} can be obtained using
+ * {@link AWorkDuration_create()}, when the client finishes using
+ * {@link AWorkDuration}, {@link AWorkDuration_release()} must be
+ * called to destroy and free up the resources associated with
+ * {@link AWorkDuration}.
+ *
+ * This file provides a set of functions to allow clients to set the measured
+ * work duration of each component on {@link AWorkDuration}.
+ *
+ * - AWorkDuration_setWorkPeriodStartTimestampNanos()
+ * - AWorkDuration_setActualTotalDurationNanos()
+ * - AWorkDuration_setActualCpuDurationNanos()
+ * - AWorkDuration_setActualGpuDurationNanos()
+ */
+typedef struct AWorkDuration AWorkDuration;
 
 /**
  * An opaque type representing a handle to a performance hint manager.
@@ -102,7 +123,7 @@
   *
   * @return APerformanceHintManager instance on success, nullptr on failure.
   */
-APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+APerformanceHintManager* _Nullable APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Creates a session for the given set of threads and sets their initial target work
@@ -116,9 +137,9 @@
  *     This must be positive if using the work duration API, or 0 otherwise.
  * @return APerformanceHintManager instance on success, nullptr on failure.
  */
-APerformanceHintSession* APerformanceHint_createSession(
-        APerformanceHintManager* manager,
-        const int32_t* threadIds, size_t size,
+APerformanceHintSession* _Nullable APerformanceHint_createSession(
+        APerformanceHintManager* _Nonnull manager,
+        const int32_t* _Nonnull threadIds, size_t size,
         int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -128,7 +149,7 @@
  * @return the preferred update rate supported by device software.
  */
 int64_t APerformanceHint_getPreferredUpdateRateNanos(
-        APerformanceHintManager* manager) __INTRODUCED_IN(__ANDROID_API_T__);
+        APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Updates this session's target duration for each cycle of work.
@@ -140,7 +161,7 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_updateTargetWorkDuration(
-        APerformanceHintSession* session,
+        APerformanceHintSession* _Nonnull session,
         int64_t targetDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -157,7 +178,7 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_reportActualWorkDuration(
-        APerformanceHintSession* session,
+        APerformanceHintSession* _Nonnull session,
         int64_t actualDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
@@ -167,7 +188,7 @@
  * @param session The performance hint session instance to release.
  */
 void APerformanceHint_closeSession(
-        APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
+        APerformanceHintSession* _Nonnull session) __INTRODUCED_IN(__ANDROID_API_T__);
 
 /**
  * Set a list of threads to the performance hint session. This operation will replace
@@ -184,8 +205,8 @@
  *         EPERM if any thread id doesn't belong to the application.
  */
 int APerformanceHint_setThreads(
-        APerformanceHintSession* session,
-        const pid_t* threadIds,
+        APerformanceHintSession* _Nonnull session,
+        const pid_t* _Nonnull threadIds,
         size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
@@ -198,11 +219,92 @@
  *         EPIPE if communication with the system service has failed.
  */
 int APerformanceHint_setPreferPowerEfficiency(
-        APerformanceHintSession* session,
+        APerformanceHintSession* _Nonnull session,
         bool enabled) __INTRODUCED_IN(__ANDROID_API_V__);
 
+/**
+ * Reports the durations for the last cycle of work.
+ *
+ * The system will attempt to adjust the scheduling and performance of the
+ * threads within the thread group to bring the actual duration close to the target duration.
+ *
+ * @param session The {@link APerformanceHintSession} instance to update.
+ * @param workDuration The {@link AWorkDuration} structure of times the thread group took to
+ *     complete its last task in nanoseconds breaking down into different components.
+ *
+ *     The work period start timestamp, actual total duration and actual CPU duration must be
+ *     positive.
+ *
+ *     The actual GPU duration must be non-negative. If the actual GPU duration is 0, it means
+ *     the actual GPU duration is not measured.
+ *
+ * @return 0 on success.
+ *         EINVAL if session is nullptr or any duration is an invalid number.
+ *         EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_reportActualWorkDuration2(
+        APerformanceHintSession* _Nonnull session,
+        AWorkDuration* _Nonnull workDuration) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Creates a new AWorkDuration. When the client finishes using {@link AWorkDuration}, it should
+ * call {@link AWorkDuration_release()} to destroy {@link AWorkDuration} and release all resources
+ * associated with it.
+ *
+ * @return AWorkDuration on success and nullptr otherwise.
+ */
+AWorkDuration* _Nonnull AWorkDuration_create() __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Destroys {@link AWorkDuration} and free all resources associated to it.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ */
+void AWorkDuration_release(AWorkDuration* _Nonnull WorkDuration) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the work period start timestamp in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ * @param workPeriodStartTimestampNanos The work period start timestamp in nanoseconds based on
+ *        CLOCK_MONOTONIC about when the work starts, the timestamp must be positive.
+ */
+void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* _Nonnull aWorkDuration,
+        int64_t workPeriodStartTimestampNanos) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the actual total work duration in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ * @param actualTotalDurationNanos The actual total work duration in nanoseconds, the number must be
+ *        positive.
+ */
+void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
+        int64_t actualTotalDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the actual CPU work duration in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds, the number must be
+ *        positive.
+ */
+void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
+        int64_t actualCpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the actual GPU work duration in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}.
+ * @param actualGpuDurationNanos The actual GPU work duration in nanoseconds, the number must be
+ *        non-negative. If the actual GPU duration is 0, it means the actual GPU duration is
+ *        measured.
+ */
+void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
+        int64_t actualGpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
+
 __END_DECLS
 
 #endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
 
-/** @} */
\ No newline at end of file
+/** @} */
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 1f477f8..0b57e93 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -111,7 +111,7 @@
  * It's passed the updated thermal status as parameter, as well as the
  * pointer provided by the client that registered a callback.
  */
-typedef void (*AThermal_StatusCallback)(void *data, AThermalStatus status);
+typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status);
 
 /**
   * Acquire an instance of the thermal manager. This must be freed using
@@ -222,6 +222,70 @@
 float AThermal_getThermalHeadroom(AThermalManager *manager,
         int forecastSeconds) __INTRODUCED_IN(31);
 
+/**
+ * This struct defines an instance of headroom threshold value and its status.
+ * <p>
+ * The value should be monotonically non-decreasing as the thermal status increases.
+ * For {@link ATHERMAL_STATUS_SEVERE}, its headroom threshold is guaranteed to
+ * be 1.0f. For status below severe status, the value should be lower or equal
+ * to 1.0f, and for status above severe, the value should be larger or equal to 1.0f.
+ * <p>
+ * Also see {@link AThermal_getThermalHeadroom} for explanation on headroom, and
+ * {@link AThermal_getThermalHeadroomThresholds} for how to use this.
+ */
+struct AThermalHeadroomThreshold {
+    float headroom;
+    AThermalStatus thermalStatus;
+};
+
+/**
+ * Gets the thermal headroom thresholds for all available thermal status.
+ *
+ * A thermal status will only exist in output if the device manufacturer has the
+ * corresponding threshold defined for at least one of its slow-moving skin temperature
+ * sensors. If it's set, one should also expect to get it from
+ * {@link #AThermal_getCurrentThermalStatus} or {@link AThermal_StatusCallback}.
+ * <p>
+ * The headroom threshold is used to interpret the possible thermal throttling status based on
+ * the headroom prediction. For example, if the headroom threshold for
+ * {@link ATHERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75
+ * (or {@code AThermal_getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system
+ * could be in lightly throttled state if the workload remains the same. The app can consider
+ * taking actions according to the nearest throttling status the difference between the headroom and
+ * the threshold.
+ * <p>
+ * For new devices it's guaranteed to have a single sensor, but for older devices with multiple
+ * sensors reporting different threshold values, the minimum threshold is taken to be conservative
+ * on predictions. Thus, when reading real-time headroom, it's not guaranteed that a real-time value
+ * of 0.75 (or {@code AThermal_getThermalHeadroom(0)}=0.75) exceeding the threshold of 0.7 above
+ * will always come with lightly throttled state
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT}) but it can be lower
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE}).
+ * While it's always guaranteed that the device won't be throttled heavier than the unmet
+ * threshold's state, so a real-time headroom of 0.75 will never come with
+ * {@link #ATHERMAL_STATUS_MODERATE} but always lower, and 0.65 will never come with
+ * {@link ATHERMAL_STATUS_LIGHT} but {@link #ATHERMAL_STATUS_NONE}.
+ * <p>
+ * The returned list of thresholds is cached on first successful query and owned by the thermal
+ * manager, which will not change between calls to this function. The caller should only need to
+ * free the manager with {@link AThermal_releaseManager}.
+ *
+ * @param manager The manager instance to use.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param outThresholds non-null output pointer to null AThermalHeadroomThreshold pointer, which
+ *                will be set to the cached array of thresholds if thermal thresholds are supported
+ *                by the system or device, otherwise nullptr or unmodified.
+ * @param size non-null output pointer whose value will be set to the size of the threshold array
+ *             or 0 if it's not supported.
+ * @return 0 on success
+ *         EINVAL if outThresholds or size_t is nullptr, or *outThresholds is not nullptr.
+ *         EPIPE if communication with the system service has failed.
+ *         ENOSYS if the feature is disabled by the current system.
+ */
+int AThermal_getThermalHeadroomThresholds(AThermalManager* manager,
+                                          const AThermalHeadroomThreshold ** outThresholds,
+                                          size_t* size) __INTRODUCED_IN(35);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/input/Input.h b/include/input/Input.h
index bd544b5..1c4ea6b 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -1138,6 +1138,24 @@
     std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEventPool;
 };
 
+/**
+ * An input event factory implementation that simply creates the input events on the heap, when
+ * needed. The caller is responsible for destroying the returned references.
+ * It is recommended that the caller wrap these return values into std::unique_ptr.
+ */
+class DynamicInputEventFactory : public InputEventFactoryInterface {
+public:
+    explicit DynamicInputEventFactory(){};
+    ~DynamicInputEventFactory(){};
+
+    KeyEvent* createKeyEvent() override { return new KeyEvent(); };
+    MotionEvent* createMotionEvent() override { return new MotionEvent(); };
+    FocusEvent* createFocusEvent() override { return new FocusEvent(); };
+    CaptureEvent* createCaptureEvent() override { return new CaptureEvent(); };
+    DragEvent* createDragEvent() override { return new DragEvent(); };
+    TouchModeEvent* createTouchModeEvent() override { return new TouchModeEvent(); };
+};
+
 /*
  * Describes a unique request to enable or disable Pointer Capture.
  */
diff --git a/include/private/thermal_private.h b/include/private/thermal_private.h
new file mode 100644
index 0000000..951d953
--- /dev/null
+++ b/include/private/thermal_private.h
@@ -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.
+ */
+
+#ifndef ANDROID_PRIVATE_NATIVE_THERMAL_H
+#define ANDROID_PRIVATE_NATIVE_THERMAL_H
+
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/**
+ * For testing only.
+ */
+void AThermal_setIThermalServiceForTesting(void* iThermalService);
+
+__END_DECLS
+
+#endif // ANDROID_PRIVATE_NATIVE_THERMAL_H
\ No newline at end of file
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 620c23c..f90c618 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -251,13 +251,15 @@
     srcs: [
         // Trusty-specific files
         "OS_android.cpp",
-        "trusty/logging.cpp",
         "trusty/OS.cpp",
         "trusty/RpcServerTrusty.cpp",
         "trusty/RpcTransportTipcTrusty.cpp",
         "trusty/TrustyStatus.cpp",
         "trusty/socket.cpp",
     ],
+    shared_libs: [
+        "liblog",
+    ],
 }
 
 cc_defaults {
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 8243238..301dbf6 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -19,7 +19,6 @@
 #include <atomic>
 #include <set>
 
-#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <binder/BpBinder.h>
 #include <binder/IInterface.h>
@@ -308,7 +307,7 @@
     Extras* e = getOrCreateExtras();
     RpcMutexUniqueLock lock(e->mLock);
     if (mRecordingOn) {
-        LOG(INFO) << "Could not start Binder recording. Another is already in progress.";
+        ALOGI("Could not start Binder recording. Another is already in progress.");
         return INVALID_OPERATION;
     } else {
         status_t readStatus = data.readUniqueFileDescriptor(&(e->mRecordingFd));
@@ -316,7 +315,7 @@
             return readStatus;
         }
         mRecordingOn = true;
-        LOG(INFO) << "Started Binder recording.";
+        ALOGI("Started Binder recording.");
         return NO_ERROR;
     }
 }
@@ -340,10 +339,10 @@
     if (mRecordingOn) {
         e->mRecordingFd.reset();
         mRecordingOn = false;
-        LOG(INFO) << "Stopped Binder recording.";
+        ALOGI("Stopped Binder recording.");
         return NO_ERROR;
     } else {
-        LOG(INFO) << "Could not stop Binder recording. One is not in progress.";
+        ALOGI("Could not stop Binder recording. One is not in progress.");
         return INVALID_OPERATION;
     }
 }
@@ -377,11 +376,11 @@
             err = stopRecordingTransactions();
             break;
         case EXTENSION_TRANSACTION:
-            CHECK(reply != nullptr);
+            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
             err = reply->writeStrongBinder(getExtension());
             break;
         case DEBUG_PID_TRANSACTION:
-            CHECK(reply != nullptr);
+            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
             err = reply->writeInt32(getDebugPid());
             break;
         case SET_RPC_CLIENT_TRANSACTION: {
@@ -414,10 +413,10 @@
                                 reply ? *reply : emptyReply, err);
             if (transaction) {
                 if (status_t err = transaction->dumpToFile(e->mRecordingFd); err != NO_ERROR) {
-                    LOG(INFO) << "Failed to dump RecordedTransaction to file with error " << err;
+                    ALOGI("Failed to dump RecordedTransaction to file with error %d", err);
                 }
             } else {
-                LOG(INFO) << "Failed to create RecordedTransaction object.";
+                ALOGI("Failed to create RecordedTransaction object.");
             }
         }
     }
@@ -705,7 +704,7 @@
         return status;
     }
     rpcServer->setMaxThreads(binderThreadPoolMaxCount);
-    LOG(INFO) << "RpcBinder: Started Binder debug on " << getInterfaceDescriptor();
+    ALOGI("RpcBinder: Started Binder debug on %s", String8(getInterfaceDescriptor()).c_str());
     rpcServer->start();
     e->mRpcServerLinks.emplace(link);
     LOG_RPC_DETAIL("%s(fd=%d) successful", __PRETTY_FUNCTION__, socketFdForPrint);
@@ -723,20 +722,20 @@
 {
     if (!wasParceled()) {
         if (getExtension()) {
-             ALOGW("Binder %p destroyed with extension attached before being parceled.", this);
+            ALOGW("Binder %p destroyed with extension attached before being parceled.", this);
         }
         if (isRequestingSid()) {
-             ALOGW("Binder %p destroyed when requesting SID before being parceled.", this);
+            ALOGW("Binder %p destroyed when requesting SID before being parceled.", this);
         }
         if (isInheritRt()) {
-             ALOGW("Binder %p destroyed after setInheritRt before being parceled.", this);
+            ALOGW("Binder %p destroyed after setInheritRt before being parceled.", this);
         }
 #ifdef __linux__
         if (getMinSchedulerPolicy() != SCHED_NORMAL) {
-             ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
+            ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
         }
         if (getMinSchedulerPriority() != 0) {
-             ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
+            ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
         }
 #endif // __linux__
     }
@@ -752,7 +751,7 @@
 {
     switch (code) {
         case INTERFACE_TRANSACTION:
-            CHECK(reply != nullptr);
+            LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
             reply->writeString16(getInterfaceDescriptor());
             return NO_ERROR;
 
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index a1fbbf3..895690f 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -53,7 +53,7 @@
 #ifdef BINDER_RPC_SINGLE_THREADED
     return mTriggered;
 #else
-    return mWrite == -1;
+    return !mWrite.ok();
 #endif
 }
 
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp
index cedd3af..f7b5a55 100644
--- a/libs/binder/RecordedTransaction.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -15,10 +15,10 @@
  */
 
 #include <android-base/file.h>
-#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <binder/Functional.h>
 #include <binder/RecordedTransaction.h>
+#include <inttypes.h>
 #include <sys/mman.h>
 #include <algorithm>
 
@@ -127,18 +127,17 @@
 
     t.mData.mInterfaceName = std::string(String8(interfaceName).c_str());
     if (interfaceName.size() != t.mData.mInterfaceName.size()) {
-        LOG(ERROR) << "Interface Name is not valid. Contains characters that aren't single byte "
-                      "utf-8.";
+        ALOGE("Interface Name is not valid. Contains characters that aren't single byte utf-8.");
         return std::nullopt;
     }
 
     if (t.mSent.setData(dataParcel.data(), dataParcel.dataBufferSize()) != android::NO_ERROR) {
-        LOG(ERROR) << "Failed to set sent parcel data.";
+        ALOGE("Failed to set sent parcel data.");
         return std::nullopt;
     }
 
     if (t.mReply.setData(replyParcel.data(), replyParcel.dataBufferSize()) != android::NO_ERROR) {
-        LOG(ERROR) << "Failed to set reply parcel data.";
+        ALOGE("Failed to set reply parcel data.");
         return std::nullopt;
     }
 
@@ -168,38 +167,37 @@
     const long pageSize = sysconf(_SC_PAGE_SIZE);
     struct stat fileStat;
     if (fstat(fd.get(), &fileStat) != 0) {
-        LOG(ERROR) << "Unable to get file information";
+        ALOGE("Unable to get file information");
         return std::nullopt;
     }
 
     off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
     if (fdCurrentPosition == -1) {
-        LOG(ERROR) << "Invalid offset in file descriptor.";
+        ALOGE("Invalid offset in file descriptor.");
         return std::nullopt;
     }
     do {
         if (fileStat.st_size < (fdCurrentPosition + (off_t)sizeof(ChunkDescriptor))) {
-            LOG(ERROR) << "Not enough file remains to contain expected chunk descriptor";
+            ALOGE("Not enough file remains to contain expected chunk descriptor");
             return std::nullopt;
         }
 
         if (!android::base::ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) {
-            LOG(ERROR) << "Failed to read ChunkDescriptor from fd " << fd.get() << ". "
-                       << strerror(errno);
+            ALOGE("Failed to read ChunkDescriptor from fd %d. %s", fd.get(), strerror(errno));
             return std::nullopt;
         }
         transaction_checksum_t checksum = *reinterpret_cast<transaction_checksum_t*>(&chunk);
 
         fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
         if (fdCurrentPosition == -1) {
-            LOG(ERROR) << "Invalid offset in file descriptor.";
+            ALOGE("Invalid offset in file descriptor.");
             return std::nullopt;
         }
         off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
         off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
 
         if (chunk.dataSize > kMaxChunkDataSize) {
-            LOG(ERROR) << "Chunk data exceeds maximum size.";
+            ALOGE("Chunk data exceeds maximum size.");
             return std::nullopt;
         }
 
@@ -207,12 +205,12 @@
                 chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
 
         if (chunkPayloadSize > (size_t)(fileStat.st_size - fdCurrentPosition)) {
-            LOG(ERROR) << "Chunk payload exceeds remaining file size.";
+            ALOGE("Chunk payload exceeds remaining file size.");
             return std::nullopt;
         }
 
         if (PADDING8(chunkPayloadSize) != 0) {
-            LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
+            ALOGE("Invalid chunk size, not aligned %zu", chunkPayloadSize);
             return std::nullopt;
         }
 
@@ -228,8 +226,7 @@
                 sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
                                                 // page-alignment
         if (payloadMap == MAP_FAILED) {
-            LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " "
-                       << strerror(errno);
+            ALOGE("Memory mapping failed for fd %d: %d %s", fd.get(), errno, strerror(errno));
             return std::nullopt;
         }
         for (size_t checksumIndex = 0;
@@ -237,21 +234,21 @@
             checksum ^= payloadMap[checksumIndex];
         }
         if (checksum != 0) {
-            LOG(ERROR) << "Checksum failed.";
+            ALOGE("Checksum failed.");
             return std::nullopt;
         }
 
         fdCurrentPosition = lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
         if (fdCurrentPosition == -1) {
-            LOG(ERROR) << "Invalid offset in file descriptor.";
+            ALOGE("Invalid offset in file descriptor.");
             return std::nullopt;
         }
 
         switch (chunk.chunkType) {
             case HEADER_CHUNK: {
                 if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
-                    LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
-                               << sizeof(TransactionHeader) << ".";
+                    ALOGE("Header Chunk indicated size %" PRIu32 "; Expected %zu.", chunk.dataSize,
+                          sizeof(TransactionHeader));
                     return std::nullopt;
                 }
                 t.mData.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
@@ -265,7 +262,7 @@
             case DATA_PARCEL_CHUNK: {
                 if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap),
                                     chunk.dataSize) != android::NO_ERROR) {
-                    LOG(ERROR) << "Failed to set sent parcel data.";
+                    ALOGE("Failed to set sent parcel data.");
                     return std::nullopt;
                 }
                 break;
@@ -273,7 +270,7 @@
             case REPLY_PARCEL_CHUNK: {
                 if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap),
                                      chunk.dataSize) != android::NO_ERROR) {
-                    LOG(ERROR) << "Failed to set reply parcel data.";
+                    ALOGE("Failed to set reply parcel data.");
                     return std::nullopt;
                 }
                 break;
@@ -281,7 +278,7 @@
             case END_CHUNK:
                 break;
             default:
-                LOG(INFO) << "Unrecognized chunk.";
+                ALOGI("Unrecognized chunk.");
                 break;
         }
     } while (chunk.chunkType != END_CHUNK);
@@ -292,7 +289,7 @@
 android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
                                                   size_t byteCount, const uint8_t* data) const {
     if (byteCount > kMaxChunkDataSize) {
-        LOG(ERROR) << "Chunk data exceeds maximum size";
+        ALOGE("Chunk data exceeds maximum size");
         return BAD_VALUE;
     }
     ChunkDescriptor descriptor = {.chunkType = chunkType,
@@ -321,7 +318,7 @@
 
     // Write buffer to file
     if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
-        LOG(ERROR) << "Failed to write chunk fd " << fd.get();
+        ALOGE("Failed to write chunk fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
@@ -331,26 +328,26 @@
     if (NO_ERROR !=
         writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
                    reinterpret_cast<const uint8_t*>(&(mData.mHeader)))) {
-        LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
+        ALOGE("Failed to write transactionHeader to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR !=
         writeChunk(fd, INTERFACE_NAME_CHUNK, mData.mInterfaceName.size() * sizeof(uint8_t),
                    reinterpret_cast<const uint8_t*>(mData.mInterfaceName.c_str()))) {
-        LOG(INFO) << "Failed to write Interface Name Chunk to fd " << fd.get();
+        ALOGI("Failed to write Interface Name Chunk to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
 
     if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataBufferSize(), mSent.data())) {
-        LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
+        ALOGE("Failed to write sent Parcel to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataBufferSize(), mReply.data())) {
-        LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
+        ALOGE("Failed to write reply Parcel to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
-        LOG(ERROR) << "Failed to write end chunk to fd " << fd.get();
+        ALOGE("Failed to write end chunk to fd %d", fd.get());
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 1ba20b3..fefaa81 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -168,7 +168,7 @@
 
 void RpcServer::setServerSocketModifier(std::function<void(base::borrowed_fd)>&& modifier) {
     RpcMutexLockGuard _l(mLock);
-    LOG_ALWAYS_FATAL_IF(mServer.fd != -1, "Already started");
+    LOG_ALWAYS_FATAL_IF(mServer.fd.ok(), "Already started");
     mServerSocketModifier = std::move(modifier);
 }
 
@@ -200,7 +200,7 @@
 status_t RpcServer::acceptSocketConnection(const RpcServer& server, RpcTransportFd* out) {
     RpcTransportFd clientSocket(unique_fd(TEMP_FAILURE_RETRY(
             accept4(server.mServer.fd.get(), nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK))));
-    if (clientSocket.fd < 0) {
+    if (!clientSocket.fd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not accept4 socket: %s", strerror(savedErrno));
         return -savedErrno;
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index c895b21..cd8f417 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -208,7 +208,7 @@
 
     unique_fd serverFd(TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
 
-    if (serverFd == -1) {
+    if (!serverFd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not connect to /dev/null: %s", strerror(savedErrno));
         return -savedErrno;
@@ -594,7 +594,7 @@
 
         unique_fd serverFd(TEMP_FAILURE_RETRY(
                 socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
-        if (serverFd == -1) {
+        if (!serverFd.ok()) {
             int savedErrno = errno;
             ALOGE("Could not create socket at %s: %s", addr.toString().c_str(),
                   strerror(savedErrno));
diff --git a/libs/binder/RpcTrusty.cpp b/libs/binder/RpcTrusty.cpp
index 3b53b05..2f2fe7d 100644
--- a/libs/binder/RpcTrusty.cpp
+++ b/libs/binder/RpcTrusty.cpp
@@ -16,7 +16,6 @@
 
 #define LOG_TAG "RpcTrusty"
 
-#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <binder/RpcSession.h>
 #include <binder/RpcTransportTipcAndroid.h>
@@ -35,13 +34,13 @@
     auto request = [=] {
         int tipcFd = tipc_connect(device, port);
         if (tipcFd < 0) {
-            LOG(ERROR) << "Failed to connect to Trusty service. Error code: " << tipcFd;
+            ALOGE("Failed to connect to Trusty service. Error code: %d", tipcFd);
             return unique_fd();
         }
         return unique_fd(tipcFd);
     };
     if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
-        LOG(ERROR) << "Failed to set up Trusty client. Error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Trusty client. Error: %s", statusToString(status).c_str());
         return nullptr;
     }
     return session;
diff --git a/libs/binder/Utils.cpp b/libs/binder/Utils.cpp
index 47fd17d..d9a96af 100644
--- a/libs/binder/Utils.cpp
+++ b/libs/binder/Utils.cpp
@@ -16,7 +16,6 @@
 
 #include "Utils.h"
 
-#include <android-base/logging.h>
 #include <string.h>
 
 namespace android {
@@ -26,7 +25,7 @@
 }
 
 std::string HexString(const void* bytes, size_t len) {
-    CHECK(bytes != nullptr || len == 0) << bytes << " " << len;
+    LOG_ALWAYS_FATAL_IF(len > 0 && bytes == nullptr, "%p %zu", bytes, len);
 
     // b/132916539: Doing this the 'C way', std::setfill triggers ubsan implicit conversion
     const uint8_t* bytes8 = static_cast<const uint8_t*>(bytes);
diff --git a/libs/binder/Utils.h b/libs/binder/Utils.h
index c8431aa..eec09eb 100644
--- a/libs/binder/Utils.h
+++ b/libs/binder/Utils.h
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#pragma once
+
 #include <stddef.h>
 #include <sys/uio.h>
 #include <cstdint>
@@ -22,8 +24,16 @@
 #include <log/log.h>
 #include <utils/Errors.h>
 
-#define PLOGE_VA_ARGS(...) , ##__VA_ARGS__
-#define PLOGE(fmt, ...) ALOGE(fmt ": %s" PLOGE_VA_ARGS(__VA_ARGS__), strerror(errno))
+#define PLOGE(fmt, ...)                                                     \
+    do {                                                                    \
+        auto savedErrno = errno;                                            \
+        ALOGE(fmt ": %s" __VA_OPT__(, ) __VA_ARGS__, strerror(savedErrno)); \
+    } while (0)
+#define PLOGF(fmt, ...)                                                                \
+    do {                                                                               \
+        auto savedErrno = errno;                                                       \
+        LOG_ALWAYS_FATAL(fmt ": %s" __VA_OPT__(, ) __VA_ARGS__, strerror(savedErrno)); \
+    } while (0)
 
 /* TEMP_FAILURE_RETRY is not available on macOS and Trusty. */
 #ifndef TEMP_FAILURE_RETRY
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index f51cd9b..118409e 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -16,7 +16,6 @@
 
 #include <binder_rpc_unstable.hpp>
 
-#include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <android/binder_libbinder.h>
 #include <binder/RpcServer.h>
@@ -85,8 +84,8 @@
     }
 
     if (status_t status = server->setupVsockServer(bindCid, port); status != OK) {
-        LOG(ERROR) << "Failed to set up vsock server with port " << port
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up vsock server with port %u error: %s", port,
+              statusToString(status).c_str());
         return nullptr;
     }
     if (cid != VMADDR_CID_ANY) {
@@ -95,7 +94,7 @@
             const sockaddr_vm* vaddr = reinterpret_cast<const sockaddr_vm*>(addr);
             LOG_ALWAYS_FATAL_IF(vaddr->svm_family != AF_VSOCK, "address is not a vsock");
             if (cid != vaddr->svm_cid) {
-                LOG(ERROR) << "Rejected vsock connection from CID " << vaddr->svm_cid;
+                ALOGE("Rejected vsock connection from CID %u", vaddr->svm_cid);
                 return false;
             }
             return true;
@@ -109,12 +108,12 @@
     auto server = RpcServer::make();
     auto fd = unique_fd(socketFd);
     if (!fd.ok()) {
-        LOG(ERROR) << "Invalid socket fd " << socketFd;
+        ALOGE("Invalid socket fd %d", socketFd);
         return nullptr;
     }
     if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
-        LOG(ERROR) << "Failed to set up RPC server with fd " << socketFd
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up RPC server with fd %d error: %s", socketFd,
+              statusToString(status).c_str());
         return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
@@ -125,13 +124,13 @@
     auto server = RpcServer::make();
     auto fd = unique_fd(bootstrapFd);
     if (!fd.ok()) {
-        LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+        ALOGE("Invalid bootstrap fd %d", bootstrapFd);
         return nullptr;
     }
     if (status_t status = server->setupUnixDomainSocketBootstrapServer(std::move(fd));
         status != OK) {
-        LOG(ERROR) << "Failed to set up Unix Domain RPC server with bootstrap fd " << bootstrapFd
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Unix Domain RPC server with bootstrap fd %d error: %s", bootstrapFd,
+              statusToString(status).c_str());
         return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
@@ -141,8 +140,8 @@
 ARpcServer* ARpcServer_newInet(AIBinder* service, const char* address, unsigned int port) {
     auto server = RpcServer::make();
     if (status_t status = server->setupInetServer(address, port, nullptr); status != OK) {
-        LOG(ERROR) << "Failed to set up inet RPC server with address " << address << " and port "
-                   << port << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up inet RPC server with address %s and port %u error: %s", address,
+              port, statusToString(status).c_str());
         return nullptr;
     }
     server->setRootObject(AIBinder_toPlatformBinder(service));
@@ -191,8 +190,8 @@
 AIBinder* ARpcSession_setupVsockClient(ARpcSession* handle, unsigned int cid, unsigned int port) {
     auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupVsockClient(cid, port); status != OK) {
-        LOG(ERROR) << "Failed to set up vsock client with CID " << cid << " and port " << port
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up vsock client with CID %u and port %u error: %s", cid, port,
+              statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -203,8 +202,8 @@
     pathname = ANDROID_SOCKET_DIR "/" + pathname;
     auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupUnixDomainClient(pathname.c_str()); status != OK) {
-        LOG(ERROR) << "Failed to set up Unix Domain RPC client with path: " << pathname
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Unix Domain RPC client with path: %s error: %s", pathname.c_str(),
+              statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -214,13 +213,13 @@
     auto session = handleToStrongPointer<RpcSession>(handle);
     auto fd = unique_fd(dup(bootstrapFd));
     if (!fd.ok()) {
-        LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+        ALOGE("Invalid bootstrap fd %d", bootstrapFd);
         return nullptr;
     }
     if (status_t status = session->setupUnixDomainSocketBootstrapClient(std::move(fd));
         status != OK) {
-        LOG(ERROR) << "Failed to set up Unix Domain RPC client with bootstrap fd: " << bootstrapFd
-                   << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up Unix Domain RPC client with bootstrap fd: %d error: %s",
+              bootstrapFd, statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -229,8 +228,8 @@
 AIBinder* ARpcSession_setupInet(ARpcSession* handle, const char* address, unsigned int port) {
     auto session = handleToStrongPointer<RpcSession>(handle);
     if (status_t status = session->setupInetClient(address, port); status != OK) {
-        LOG(ERROR) << "Failed to set up inet RPC client with address " << address << " and port "
-                   << port << " error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up inet RPC client with address %s and port %u error: %s", address,
+              port, statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -241,7 +240,7 @@
     auto session = handleToStrongPointer<RpcSession>(handle);
     auto request = [=] { return unique_fd{requestFd(param)}; };
     if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
-        LOG(ERROR) << "Failed to set up vsock client. error: " << statusToString(status).c_str();
+        ALOGE("Failed to set up vsock client. error: %s", statusToString(status).c_str());
         return nullptr;
     }
     return AIBinder_fromPlatformBinder(session->getRootObject());
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 58ed418..47b9f58 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -60,6 +60,7 @@
         "libbinder.cpp",
         "parcel.cpp",
         "parcel_jni.cpp",
+        "persistable_bundle.cpp",
         "process.cpp",
         "stability.cpp",
         "status.cpp",
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 47da296..bf7a0ba 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-#include <android-base/logging.h>
 #include <android/binder_ibinder.h>
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_stability.h>
@@ -48,8 +47,8 @@
 void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/){/* do nothing */};
 
 static void attach(const sp<IBinder>& binder) {
-    // can only attach once
-    CHECK_EQ(nullptr, binder->attachObject(kId, kValue, nullptr /*cookie*/, clean));
+    auto alreadyAttached = binder->attachObject(kId, kValue, nullptr /*cookie*/, clean);
+    LOG_ALWAYS_FATAL_IF(alreadyAttached != nullptr, "can only attach once");
 }
 static bool has(const sp<IBinder>& binder) {
     return binder != nullptr && binder->findObject(kId) == kValue;
@@ -65,9 +64,9 @@
 };
 void clean(const void* id, void* obj, void* cookie) {
     // be weary of leaks!
-    // LOG(INFO) << "Deleting an ABpBinder";
+    // ALOGI("Deleting an ABpBinder");
 
-    CHECK(id == kId) << id << " " << obj << " " << cookie;
+    LOG_ALWAYS_FATAL_IF(id != kId, "%p %p %p", id, obj, cookie);
 
     delete static_cast<Value*>(obj);
 };
@@ -121,14 +120,13 @@
     if (mClazz != nullptr && !asABpBinder()) {
         const String16& currentDescriptor = mClazz->getInterfaceDescriptor();
         if (newDescriptor == currentDescriptor) {
-            LOG(ERROR) << __func__ << ": Class descriptors '" << currentDescriptor
-                       << "' match during associateClass, but they are different class objects ("
-                       << clazz << " vs " << mClazz << "). Class descriptor collision?";
+            ALOGE("Class descriptors '%s' match during associateClass, but they are different class"
+                  " objects (%p vs %p). Class descriptor collision?",
+                  String8(currentDescriptor).c_str(), clazz, mClazz);
         } else {
-            LOG(ERROR) << __func__
-                       << ": Class cannot be associated on object which already has a class. "
-                          "Trying to associate to '"
-                       << newDescriptor << "' but already set to '" << currentDescriptor << "'.";
+            ALOGE("%s: Class cannot be associated on object which already has a class. "
+                  "Trying to associate to '%s' but already set to '%s'.",
+                  __func__, String8(newDescriptor).c_str(), String8(currentDescriptor).c_str());
         }
 
         // always a failure because we know mClazz != clazz
@@ -141,13 +139,12 @@
     // more flake-proof. However, the check is not dependent on the lock.
     if (descriptor != newDescriptor && !(asABpBinder() && asABpBinder()->isServiceFuzzing())) {
         if (getBinder()->isBinderAlive()) {
-            LOG(ERROR) << __func__ << ": Expecting binder to have class '" << newDescriptor
-                       << "' but descriptor is actually '" << SanitizeString(descriptor) << "'.";
+            ALOGE("%s: Expecting binder to have class '%s' but descriptor is actually '%s'.",
+                  __func__, String8(newDescriptor).c_str(), SanitizeString(descriptor).c_str());
         } else {
             // b/155793159
-            LOG(ERROR) << __func__ << ": Cannot associate class '" << newDescriptor
-                       << "' to dead binder with cached descriptor '" << SanitizeString(descriptor)
-                       << "'.";
+            ALOGE("%s: Cannot associate class '%s' to dead binder with cached descriptor '%s'.",
+                  __func__, String8(newDescriptor).c_str(), SanitizeString(descriptor).c_str());
         }
         return false;
     }
@@ -164,7 +161,7 @@
 
 ABBinder::ABBinder(const AIBinder_Class* clazz, void* userData)
     : AIBinder(clazz), BBinder(), mUserData(userData) {
-    CHECK(clazz != nullptr);
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "clazz == nullptr");
 }
 ABBinder::~ABBinder() {
     getClass()->onDestroy(mUserData);
@@ -184,7 +181,7 @@
     // technically UINT32_MAX would be okay here, but INT32_MAX is expected since this may be
     // null in Java
     if (args.size() > INT32_MAX) {
-        LOG(ERROR) << "ABBinder::dump received too many arguments: " << args.size();
+        ALOGE("ABBinder::dump received too many arguments: %zu", args.size());
         return STATUS_BAD_VALUE;
     }
 
@@ -263,7 +260,7 @@
 
 ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder)
     : AIBinder(nullptr /*clazz*/), mRemote(binder) {
-    CHECK(binder != nullptr);
+    LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 }
 ABpBinder::~ABpBinder() {}
 
@@ -373,27 +370,27 @@
 }
 
 void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) {
-    CHECK(clazz != nullptr) << "setOnDump requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "setOnDump requires non-null clazz");
 
     // this is required to be called before instances are instantiated
     clazz->onDump = onDump;
 }
 
 void AIBinder_Class_disableInterfaceTokenHeader(AIBinder_Class* clazz) {
-    CHECK(clazz != nullptr) << "disableInterfaceTokenHeader requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "disableInterfaceTokenHeader requires non-null clazz");
 
     clazz->writeHeader = false;
 }
 
 void AIBinder_Class_setHandleShellCommand(AIBinder_Class* clazz,
                                           AIBinder_handleShellCommand handleShellCommand) {
-    CHECK(clazz != nullptr) << "setHandleShellCommand requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "setHandleShellCommand requires non-null clazz");
 
     clazz->handleShellCommand = handleShellCommand;
 }
 
 const char* AIBinder_Class_getDescriptor(const AIBinder_Class* clazz) {
-    CHECK(clazz != nullptr) << "getDescriptor requires non-null clazz";
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr, "getDescriptor requires non-null clazz");
 
     return clazz->getInterfaceDescriptorUtf8();
 }
@@ -405,8 +402,8 @@
 }
 
 void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinder>& who) {
-    CHECK(who == mWho) << who.unsafe_get() << "(" << who.get_refs() << ") vs " << mWho.unsafe_get()
-                       << " (" << mWho.get_refs() << ")";
+    LOG_ALWAYS_FATAL_IF(who != mWho, "%p (%p) vs %p (%p)", who.unsafe_get(), who.get_refs(),
+                        mWho.unsafe_get(), mWho.get_refs());
 
     mOnDied(mCookie);
 
@@ -417,7 +414,7 @@
     if (recipient != nullptr && strongWho != nullptr) {
         status_t result = recipient->unlinkToDeath(strongWho, mCookie);
         if (result != ::android::DEAD_OBJECT) {
-            LOG(WARNING) << "Unlinking to dead binder resulted in: " << result;
+            ALOGW("Unlinking to dead binder resulted in: %d", result);
         }
     }
 
@@ -426,7 +423,7 @@
 
 AIBinder_DeathRecipient::AIBinder_DeathRecipient(AIBinder_DeathRecipient_onBinderDied onDied)
     : mOnDied(onDied), mOnUnlinked(nullptr) {
-    CHECK(onDied != nullptr);
+    LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr");
 }
 
 void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
@@ -438,7 +435,7 @@
 }
 
 binder_status_t AIBinder_DeathRecipient::linkToDeath(const sp<IBinder>& binder, void* cookie) {
-    CHECK(binder != nullptr);
+    LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
@@ -459,7 +456,7 @@
 }
 
 binder_status_t AIBinder_DeathRecipient::unlinkToDeath(const sp<IBinder>& binder, void* cookie) {
-    CHECK(binder != nullptr);
+    LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
@@ -471,9 +468,8 @@
 
             status_t status = binder->unlinkToDeath(recipient, cookie, 0 /*flags*/);
             if (status != ::android::OK) {
-                LOG(ERROR) << __func__
-                           << ": removed reference to death recipient but unlink failed: "
-                           << statusToString(status);
+                ALOGE("%s: removed reference to death recipient but unlink failed: %s", __func__,
+                      statusToString(status).c_str());
             }
             return PruneStatusT(status);
         }
@@ -490,7 +486,7 @@
 
 AIBinder* AIBinder_new(const AIBinder_Class* clazz, void* args) {
     if (clazz == nullptr) {
-        LOG(ERROR) << __func__ << ": Must provide class to construct local binder.";
+        ALOGE("%s: Must provide class to construct local binder.", __func__);
         return nullptr;
     }
 
@@ -554,8 +550,7 @@
 binder_status_t AIBinder_linkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
                                      void* cookie) {
     if (binder == nullptr || recipient == nullptr) {
-        LOG(ERROR) << __func__ << ": Must provide binder (" << binder << ") and recipient ("
-                   << recipient << ")";
+        ALOGE("%s: Must provide binder (%p) and recipient (%p)", __func__, binder, recipient);
         return STATUS_UNEXPECTED_NULL;
     }
 
@@ -566,8 +561,7 @@
 binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
                                        void* cookie) {
     if (binder == nullptr || recipient == nullptr) {
-        LOG(ERROR) << __func__ << ": Must provide binder (" << binder << ") and recipient ("
-                   << recipient << ")";
+        ALOGE("%s: Must provide binder (%p) and recipient (%p)", __func__, binder, recipient);
         return STATUS_UNEXPECTED_NULL;
     }
 
@@ -596,7 +590,7 @@
 }
 void AIBinder_decStrong(AIBinder* binder) {
     if (binder == nullptr) {
-        LOG(ERROR) << __func__ << ": on null binder";
+        ALOGE("%s: on null binder", __func__);
         return;
     }
 
@@ -604,7 +598,7 @@
 }
 int32_t AIBinder_debugGetRefCount(AIBinder* binder) {
     if (binder == nullptr) {
-        LOG(ERROR) << __func__ << ": on null binder";
+        ALOGE("%s: on null binder", __func__);
         return -1;
     }
 
@@ -642,15 +636,14 @@
 
 binder_status_t AIBinder_prepareTransaction(AIBinder* binder, AParcel** in) {
     if (binder == nullptr || in == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null parameters binder (" << binder
-                   << ") and in (" << in << ").";
+        ALOGE("%s: requires non-null parameters binder (%p) and in (%p).", __func__, binder, in);
         return STATUS_UNEXPECTED_NULL;
     }
     const AIBinder_Class* clazz = binder->getClass();
     if (clazz == nullptr) {
-        LOG(ERROR) << __func__
-                   << ": Class must be defined for a remote binder transaction. See "
-                      "AIBinder_associateClass.";
+        ALOGE("%s: Class must be defined for a remote binder transaction. See "
+              "AIBinder_associateClass.",
+              __func__);
         return STATUS_INVALID_OPERATION;
     }
 
@@ -683,7 +676,7 @@
 binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code, AParcel** in,
                                   AParcel** out, binder_flags_t flags) {
     if (in == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null in parameter";
+        ALOGE("%s: requires non-null in parameter", __func__);
         return STATUS_UNEXPECTED_NULL;
     }
 
@@ -693,27 +686,26 @@
     AutoParcelDestroyer forIn(in, DestroyParcel);
 
     if (!isUserCommand(code)) {
-        LOG(ERROR) << __func__
-                   << ": Only user-defined transactions can be made from the NDK, but requested: "
-                   << code;
+        ALOGE("%s: Only user-defined transactions can be made from the NDK, but requested: %d",
+              __func__, code);
         return STATUS_UNKNOWN_TRANSACTION;
     }
 
     constexpr binder_flags_t kAllFlags = FLAG_PRIVATE_VENDOR | FLAG_ONEWAY | FLAG_CLEAR_BUF;
     if ((flags & ~kAllFlags) != 0) {
-        LOG(ERROR) << __func__ << ": Unrecognized flags sent: " << flags;
+        ALOGE("%s: Unrecognized flags sent: %d", __func__, flags);
         return STATUS_BAD_VALUE;
     }
 
     if (binder == nullptr || *in == nullptr || out == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null parameters binder (" << binder << "), in ("
-                   << in << "), and out (" << out << ").";
+        ALOGE("%s: requires non-null parameters binder (%p), in (%p), and out (%p).", __func__,
+              binder, in, out);
         return STATUS_UNEXPECTED_NULL;
     }
 
     if ((*in)->getBinder() != binder) {
-        LOG(ERROR) << __func__ << ": parcel is associated with binder object " << binder
-                   << " but called with " << (*in)->getBinder();
+        ALOGE("%s: parcel is associated with binder object %p but called with %p", __func__, binder,
+              (*in)->getBinder());
         return STATUS_BAD_VALUE;
     }
 
@@ -733,7 +725,7 @@
 AIBinder_DeathRecipient* AIBinder_DeathRecipient_new(
         AIBinder_DeathRecipient_onBinderDied onBinderDied) {
     if (onBinderDied == nullptr) {
-        LOG(ERROR) << __func__ << ": requires non-null onBinderDied parameter.";
+        ALOGE("%s: requires non-null onBinderDied parameter.", __func__);
         return nullptr;
     }
     auto ret = new AIBinder_DeathRecipient(onBinderDied);
@@ -799,9 +791,8 @@
 
 void AIBinder_setRequestingSid(AIBinder* binder, bool requestingSid) {
     ABBinder* localBinder = binder->asABBinder();
-    if (localBinder == nullptr) {
-        LOG(FATAL) << "AIBinder_setRequestingSid must be called on a local binder";
-    }
+    LOG_ALWAYS_FATAL_IF(localBinder == nullptr,
+                        "AIBinder_setRequestingSid must be called on a local binder");
 
     localBinder->setRequestingSid(requestingSid);
 }
@@ -816,9 +807,8 @@
 
 void AIBinder_setInheritRt(AIBinder* binder, bool inheritRt) {
     ABBinder* localBinder = binder->asABBinder();
-    if (localBinder == nullptr) {
-        LOG(FATAL) << "AIBinder_setInheritRt must be called on a local binder";
-    }
+    LOG_ALWAYS_FATAL_IF(localBinder == nullptr,
+                        "AIBinder_setInheritRt must be called on a local binder");
 
     localBinder->setInheritRt(inheritRt);
 }
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
new file mode 100644
index 0000000..f178027
--- /dev/null
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -0,0 +1,497 @@
+/*
+ * 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 <android/binder_parcel.h>
+#include <android/persistable_bundle.h>
+#include <sys/cdefs.h>
+
+#include <set>
+#include <sstream>
+
+namespace aidl::android::os {
+
+/**
+ * Wrapper class that enables interop with AIDL NDK generation
+ * Takes ownership of the APersistableBundle* given to it in reset() and will automatically
+ * destroy it in the destructor, similar to a smart pointer container
+ */
+class PersistableBundle {
+   public:
+    PersistableBundle() noexcept : mPBundle(APersistableBundle_new()) {}
+    // takes ownership of the APersistableBundle*
+    PersistableBundle(APersistableBundle* _Nonnull bundle) noexcept : mPBundle(bundle) {}
+    // takes ownership of the APersistableBundle*
+    PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {}
+    // duplicates, does not take ownership of the APersistableBundle*
+    PersistableBundle(const PersistableBundle& other) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            mPBundle = APersistableBundle_dup(other.mPBundle);
+        }
+    }
+    // duplicates, does not take ownership of the APersistableBundle*
+    PersistableBundle& operator=(const PersistableBundle& other) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            mPBundle = APersistableBundle_dup(other.mPBundle);
+        }
+        return *this;
+    }
+
+    ~PersistableBundle() { reset(); }
+
+    binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
+        reset();
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_readFromParcel(parcel, &mPBundle);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
+    }
+
+    binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
+        if (!mPBundle) {
+            return STATUS_BAD_VALUE;
+        }
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_writeToParcel(mPBundle, parcel);
+        } else {
+            return STATUS_FAILED_TRANSACTION;
+        }
+    }
+
+    /**
+     * Destroys any currently owned APersistableBundle* and takes ownership of the given
+     * APersistableBundle*
+     *
+     * @param pBundle The APersistableBundle to take ownership of
+     */
+    void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
+        if (mPBundle) {
+            if (__builtin_available(android __ANDROID_API_V__, *)) {
+                APersistableBundle_delete(mPBundle);
+            }
+            mPBundle = nullptr;
+        }
+        mPBundle = pBundle;
+    }
+
+    /**
+     * Check the actual contents of the bundle for equality. This is typically
+     * what should be used to check for equality.
+     */
+    bool deepEquals(const PersistableBundle& rhs) const {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_isEqual(get(), rhs.get());
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * NOTE: This does NOT check the contents of the PersistableBundle. This is
+     * implemented for ordering. Use deepEquals() to check for equality between
+     * two different PersistableBundle objects.
+     */
+    inline bool operator==(const PersistableBundle& rhs) const { return get() == rhs.get(); }
+    inline bool operator!=(const PersistableBundle& rhs) const { return get() != rhs.get(); }
+
+    inline bool operator<(const PersistableBundle& rhs) const { return get() < rhs.get(); }
+    inline bool operator>(const PersistableBundle& rhs) const { return get() > rhs.get(); }
+    inline bool operator>=(const PersistableBundle& rhs) const { return !(*this < rhs); }
+    inline bool operator<=(const PersistableBundle& rhs) const { return !(*this > rhs); }
+
+    PersistableBundle& operator=(PersistableBundle&& other) noexcept {
+        reset(other.release());
+        return *this;
+    }
+
+    /**
+     * Stops managing any contained APersistableBundle*, returning it to the caller. Ownership
+     * is released.
+     * @return APersistableBundle* or null if this was empty
+     */
+    [[nodiscard]] APersistableBundle* _Nullable release() noexcept {
+        APersistableBundle* _Nullable ret = mPBundle;
+        mPBundle = nullptr;
+        return ret;
+    }
+
+    inline std::string toString() const {
+        if (!mPBundle) {
+            return "<PersistableBundle: null>";
+        } else if (__builtin_available(android __ANDROID_API_V__, *)) {
+            std::ostringstream os;
+            os << "<PersistableBundle: ";
+            os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
+            os << " >";
+            return os.str();
+        }
+        return "<PersistableBundle (unknown)>";
+    }
+
+    int32_t size() const {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_size(mPBundle);
+        } else {
+            return 0;
+        }
+    }
+
+    int32_t erase(const std::string& key) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_erase(mPBundle, key.c_str());
+        } else {
+            return 0;
+        }
+    }
+
+    void putBoolean(const std::string& key, bool val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putBoolean(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putInt(const std::string& key, int32_t val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putInt(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putLong(const std::string& key, int64_t val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putLong(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putDouble(const std::string& key, double val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putDouble(mPBundle, key.c_str(), val);
+        }
+    }
+
+    void putString(const std::string& key, const std::string& val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putString(mPBundle, key.c_str(), val.c_str());
+        }
+    }
+
+    void putBooleanVector(const std::string& key, const std::vector<bool>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            // std::vector<bool> has no ::data().
+            int32_t num = vec.size();
+            if (num > 0) {
+                bool* newVec = (bool*)malloc(num * sizeof(bool));
+                if (newVec) {
+                    for (int32_t i = 0; i < num; i++) {
+                        newVec[i] = vec[i];
+                    }
+                    APersistableBundle_putBooleanVector(mPBundle, key.c_str(), newVec, num);
+                    free(newVec);
+                }
+            }
+        }
+    }
+
+    void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
+            }
+        }
+    }
+    void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
+            }
+        }
+    }
+    void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
+            }
+        }
+    }
+    void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t num = vec.size();
+            if (num > 0) {
+                char** inVec = (char**)malloc(num * sizeof(char*));
+                if (inVec) {
+                    for (int32_t i = 0; i < num; i++) {
+                        inVec[i] = strdup(vec[i].c_str());
+                    }
+                    APersistableBundle_putStringVector(mPBundle, key.c_str(), inVec, num);
+                    free(inVec);
+                }
+            }
+        }
+    }
+    void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle);
+        }
+    }
+
+    bool getBoolean(const std::string& key, bool* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    bool getInt(const std::string& key, int32_t* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getInt(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    bool getLong(const std::string& key, int64_t* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getLong(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    bool getDouble(const std::string& key, double* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
+        } else {
+            return false;
+        }
+    }
+
+    static char* _Nullable stringAllocator(int32_t bufferSizeBytes, void* _Nullable) {
+        return (char*)malloc(bufferSizeBytes);
+    }
+
+    bool getString(const std::string& key, std::string* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            char* outString = nullptr;
+            bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
+                                                    &stringAllocator, nullptr);
+            if (ret && outString) {
+                *val = std::string(outString);
+            }
+            return ret;
+        } else {
+            return false;
+        }
+    }
+
+    template <typename T>
+    bool getVecInternal(int32_t (*_Nonnull getVec)(const APersistableBundle* _Nonnull,
+                                                   const char* _Nonnull, T* _Nullable, int32_t),
+                        const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                        std::vector<T>* _Nonnull vec) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            int32_t bytes = 0;
+            // call first with nullptr to get required size in bytes
+            bytes = getVec(pBundle, key, nullptr, 0);
+            if (bytes > 0) {
+                T* newVec = (T*)malloc(bytes);
+                if (newVec) {
+                    bytes = getVec(pBundle, key, newVec, bytes);
+                    int32_t elements = bytes / sizeof(T);
+                    vec->clear();
+                    for (int32_t i = 0; i < elements; i++) {
+                        vec->push_back(newVec[i]);
+                    }
+                    free(newVec);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) {
+        return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
+                                    vec);
+    }
+    bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) {
+        return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
+                                       vec);
+    }
+    bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) {
+        return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
+                                       vec);
+    }
+    bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) {
+        return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle, key.c_str(),
+                                      vec);
+    }
+
+    // Takes ownership of and frees the char** and its elements.
+    // Creates a new set or vector based on the array of char*.
+    template <typename T>
+    T moveStringsInternal(char* _Nullable* _Nonnull strings, int32_t bufferSizeBytes) {
+        if (strings && bufferSizeBytes > 0) {
+            int32_t num = bufferSizeBytes / sizeof(char*);
+            T ret;
+            for (int32_t i = 0; i < num; i++) {
+                ret.insert(ret.end(), std::string(strings[i]));
+                free(strings[i]);
+            }
+            free(strings);
+            return ret;
+        }
+        return T();
+    }
+
+    bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) {
+        int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
+                                                           &stringAllocator, nullptr);
+        if (bytes > 0) {
+            char** strings = (char**)malloc(bytes);
+            if (strings) {
+                bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), strings, bytes,
+                                                           &stringAllocator, nullptr);
+                *vec = moveStringsInternal<std::vector<std::string>>(strings, bytes);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            APersistableBundle* bundle = nullptr;
+            bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
+            if (ret) {
+                *val = PersistableBundle(bundle);
+            }
+            return ret;
+        } else {
+            return false;
+        }
+    }
+
+    std::set<std::string> getKeys(
+            int32_t (*_Nonnull getTypedKeys)(const APersistableBundle* _Nonnull pBundle,
+                                             char* _Nullable* _Nullable outKeys,
+                                             int32_t bufferSizeBytes,
+                                             APersistableBundle_stringAllocator stringAllocator,
+                                             void* _Nullable),
+            const APersistableBundle* _Nonnull pBundle) {
+        // call first with nullptr to get required size in bytes
+        int32_t bytes = getTypedKeys(pBundle, nullptr, 0, &stringAllocator, nullptr);
+        if (bytes > 0) {
+            char** keys = (char**)malloc(bytes);
+            if (keys) {
+                bytes = getTypedKeys(pBundle, keys, bytes, &stringAllocator, nullptr);
+                return moveStringsInternal<std::set<std::string>>(keys, bytes);
+            }
+        }
+        return {};
+    }
+
+    std::set<std::string> getBooleanKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getIntKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getIntKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getLongKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getLongKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getDoubleKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getStringKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getStringKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getBooleanVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getIntVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getLongVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getDoubleVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getStringVectorKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getPersistableBundleKeys() {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
+            return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle);
+        } else {
+            return {};
+        }
+    }
+    std::set<std::string> getMonKeys() {
+        // :P
+        return {"c(o,o)b", "c(o,o)b"};
+    }
+
+   private:
+    inline APersistableBundle* _Nullable get() const { return mPBundle; }
+    APersistableBundle* _Nullable mPBundle = nullptr;
+};
+
+}  // namespace aidl::android::os
diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
new file mode 100644
index 0000000..eff8104
--- /dev/null
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -0,0 +1,905 @@
+/*
+ * 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.
+ */
+#include <android/binder_parcel.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * A mapping from string keys to values of various types.
+ * See frameworks/base/core/java/android/os/PersistableBundle.java
+ * for the Java type than can be used in SDK APIs.
+ * APersistableBundle exists to be used in AIDL interfaces and seamlessly
+ * interact with framework services.
+ * frameworks/native/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+ * contains the AIDL type used in the ndk backend of AIDL interfaces.
+ */
+struct APersistableBundle;
+typedef struct APersistableBundle APersistableBundle;
+
+/**
+ * This is a user supplied allocator that allocates a buffer for the
+ * APersistableBundle APIs to fill in with a string.
+ *
+ * \param the required size in bytes for the allocated buffer
+ * \param  void* _Nullable context if needed by the callback
+ *
+ * \return allocated buffer of sizeBytes. Null if allocation failed.
+ */
+typedef char* _Nullable (*_Nonnull APersistableBundle_stringAllocator)(int32_t sizeBytes,
+                                                                       void* _Nullable context);
+
+/**
+ * Create a new APersistableBundle.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \return Pointer to a new APersistableBundle
+ */
+APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Create a new APersistableBundle based off an existing APersistableBundle.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to duplicate
+ *
+ * \return Pointer to a new APersistableBundle
+ */
+APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _Nonnull pBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Delete an APersistableBundle. This must always be called when finished using
+ * the object.
+ *
+ * \param bundle to delete
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_delete(APersistableBundle* _Nonnull pBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Check for equality of APersistableBundles.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param lhs bundle to compare agains the other param
+ * \param rhs bundle to compare agains the other param
+ *
+ * \return true when equal, false when not
+ */
+bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs,
+                                const APersistableBundle* _Nonnull rhs)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Read an APersistableBundle from an AParcel.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param parcel to read from
+ * \param outPBundle bundle to write to
+ *
+ * \return STATUS_OK on success
+ *         STATUS_BAD_VALUE if the parcel or outBuffer is null, or if there's an
+ *                          issue deserializing (eg, corrupted parcel)
+ *         STATUS_BAD_TYPE if the parcel's current data position is not that of
+ *                         an APersistableBundle type
+ *         STATUS_NO_MEMORY if an allocation fails
+ */
+binder_status_t APersistableBundle_readFromParcel(
+        const AParcel* _Nonnull parcel, APersistableBundle* _Nullable* _Nonnull outPBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Write an APersistableBundle to an AParcel.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param pBundle bundle to write to the parcel
+ * \param parcel to write to
+ *
+ * \return STATUS_OK on success.
+ *         STATUS_BAD_VALUE if either pBundle or parcel is null, or if the
+ *         APersistableBundle*
+ *                          fails to serialize (eg, internally corrupted)
+ *         STATUS_NO_MEMORY if the parcel runs out of space to store the pBundle & is
+ *                          unable to allocate more
+ *         STATUS_FDS_NOT_ALLOWED if the parcel does not allow storing FDs
+ */
+binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* _Nonnull pBundle,
+                                                 AParcel* _Nonnull parcel)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get the size of an APersistableBundle. This is the number of mappings in the
+ * object.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to get the size of (number of mappings)
+ *
+ * \return number of mappings in the object
+ */
+int32_t APersistableBundle_size(APersistableBundle* _Nonnull pBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Erase any entries added with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping to erase
+ *
+ * \return number of entries erased. Either 0 or 1.
+ */
+int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a boolean associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                   bool val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int32_t associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                               int32_t val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int64_t associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                int64_t val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a double associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                  double val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a string associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                  const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a boolean vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle,
+                                         const char* _Nonnull key, const bool* _Nonnull vec,
+                                         int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int32_t vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                                     const int32_t* _Nonnull vec, int32_t num)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an int64_t vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle,
+                                      const char* _Nonnull key, const int64_t* _Nonnull vec,
+                                      int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a double vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle,
+                                        const char* _Nonnull key, const double* _Nonnull vec,
+                                        int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put a string vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ * \param size in number of elements in the vector
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle,
+                                        const char* _Nonnull key,
+                                        const char* _Nullable const* _Nullable vec, int32_t num)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Put an APersistableBundle associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param value to put for the mapping
+ *
+ * Available since API level __ANDROID_API_V__.
+ */
+void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundle,
+                                             const char* _Nonnull key,
+                                             const APersistableBundle* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a boolean associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle,
+                                   const char* _Nonnull key, bool* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int32_t associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+                               int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int64_t associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle,
+                                const char* _Nonnull key, int64_t* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a double associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle,
+                                  const char* _Nonnull key, double* _Nonnull val)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a string associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to write the value to
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of string associated with the provided key on success
+ *         0 if no string exists for the provided key
+ *         -1 if the provided allocator fails and returns false
+ */
+int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle,
+                                     const char* _Nonnull key, char* _Nullable* _Nonnull val,
+                                     APersistableBundle_stringAllocator stringAllocator,
+                                     void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a boolean vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle,
+                                            const char* _Nonnull key, bool* _Nullable buffer,
+                                            int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int32_t vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle,
+                                        const char* _Nonnull key, int32_t* _Nullable buffer,
+                                        int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an int64_t vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle,
+                                         const char* _Nonnull key, int64_t* _Nullable buffer,
+                                         int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a double vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle,
+                                           const char* _Nonnull key, double* _Nullable buffer,
+                                           int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get a string vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getStringVector(const APersistableBundle* _Nonnull pBundle,
+                                           const char* _Nonnull key,
+                                           char* _Nullable* _Nullable buffer,
+                                           int32_t bufferSizeBytes,
+                                           APersistableBundle_stringAllocator stringAllocator,
+                                           void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get an APersistableBundle* associated with the provided key.
+ *
+ * Available since API level __ANDROID_API_V__.
+ *
+ * \param bundle to operate on
+ * \param key for the mapping
+ * \param nonnull pointer to an APersistableBundle pointer to write to point to
+ * a new copy of the stored APersistableBundle. The caller takes ownership of
+ * the new APersistableBundle and must be deleted with
+ * APersistableBundle_delete.
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getPersistableBundle(const APersistableBundle* _Nonnull pBundle,
+                                             const char* _Nonnull key,
+                                             APersistableBundle* _Nullable* _Nonnull outBundle)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* _Nonnull pBundle,
+                                          char* _Nullable* _Nullable outKeys,
+                                          int32_t bufferSizeBytes,
+                                          APersistableBundle_stringAllocator stringAllocator,
+                                          void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle,
+                                      char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
+                                      APersistableBundle_stringAllocator stringAllocator,
+                                      void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle,
+                                       char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
+                                       APersistableBundle_stringAllocator stringAllocator,
+                                       void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* _Nonnull pBundle,
+                                         char* _Nullable* _Nullable outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getStringKeys(const APersistableBundle* _Nonnull pBundle,
+                                         char* _Nullable* _Nullable outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                                char* _Nullable* _Nullable outKeys,
+                                                int32_t bufferSizeBytes,
+                                                APersistableBundle_stringAllocator stringAllocator,
+                                                void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                            char* _Nullable* _Nullable outKeys,
+                                            int32_t bufferSizeBytes,
+                                            APersistableBundle_stringAllocator stringAllocator,
+                                            void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                             char* _Nullable* _Nullable outKeys,
+                                             int32_t bufferSizeBytes,
+                                             APersistableBundle_stringAllocator stringAllocator,
+                                             void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                               char* _Nullable* _Nullable outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* _Nonnull pBundle,
+                                               char* _Nullable* _Nullable outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the suplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param bundle to operate on
+ * \param nonnull pointer to a pre-allocated buffer to write the values to
+ * \param size of the pre-allocated buffer
+ * \param function pointer to the string dup allocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ *         0 if no string vector exists for the provided key
+ *         -1 if the user supplied APersistableBundle_stringAllocator returns
+ *         false
+ */
+int32_t APersistableBundle_getPersistableBundleKeys(
+        const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys,
+        int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator,
+        void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 1c5f79f..0843a8e 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -161,6 +161,51 @@
     AServiceManager_addServiceWithFlags; # systemapi llndk
 };
 
+LIBBINDER_NDK35 { # introduced=VanillaIceCream
+  global:
+    APersistableBundle_readFromParcel;
+    APersistableBundle_writeToParcel;
+    APersistableBundle_new;
+    APersistableBundle_dup;
+    APersistableBundle_delete;
+    APersistableBundle_isEqual;
+    APersistableBundle_size;
+    APersistableBundle_erase;
+    APersistableBundle_putBoolean;
+    APersistableBundle_putInt;
+    APersistableBundle_putLong;
+    APersistableBundle_putDouble;
+    APersistableBundle_putString;
+    APersistableBundle_putBooleanVector;
+    APersistableBundle_putIntVector;
+    APersistableBundle_putLongVector;
+    APersistableBundle_putDoubleVector;
+    APersistableBundle_putStringVector;
+    APersistableBundle_putPersistableBundle;
+    APersistableBundle_getBoolean;
+    APersistableBundle_getInt;
+    APersistableBundle_getLong;
+    APersistableBundle_getDouble;
+    APersistableBundle_getString;
+    APersistableBundle_getBooleanVector;
+    APersistableBundle_getIntVector;
+    APersistableBundle_getLongVector;
+    APersistableBundle_getDoubleVector;
+    APersistableBundle_getStringVector;
+    APersistableBundle_getPersistableBundle;
+    APersistableBundle_getBooleanKeys;
+    APersistableBundle_getIntKeys;
+    APersistableBundle_getLongKeys;
+    APersistableBundle_getDoubleKeys;
+    APersistableBundle_getStringKeys;
+    APersistableBundle_getBooleanVectorKeys;
+    APersistableBundle_getIntVectorKeys;
+    APersistableBundle_getLongVectorKeys;
+    APersistableBundle_getDoubleVectorKeys;
+    APersistableBundle_getStringVectorKeys;
+    APersistableBundle_getPersistableBundleKeys;
+};
+
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
diff --git a/libs/binder/ndk/parcel.cpp b/libs/binder/ndk/parcel.cpp
index 037aa2e..c15bcf9 100644
--- a/libs/binder/ndk/parcel.cpp
+++ b/libs/binder/ndk/parcel.cpp
@@ -14,20 +14,19 @@
  * limitations under the License.
  */
 
+#include <android-base/unique_fd.h>
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_platform.h>
-#include "parcel_internal.h"
-
-#include "ibinder_internal.h"
-#include "status_internal.h"
+#include <binder/Parcel.h>
+#include <binder/ParcelFileDescriptor.h>
+#include <inttypes.h>
+#include <utils/Unicode.h>
 
 #include <limits>
 
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
-#include <binder/Parcel.h>
-#include <binder/ParcelFileDescriptor.h>
-#include <utils/Unicode.h>
+#include "ibinder_internal.h"
+#include "parcel_internal.h"
+#include "status_internal.h"
 
 using ::android::IBinder;
 using ::android::Parcel;
@@ -52,11 +51,11 @@
     if (length < -1) return STATUS_BAD_VALUE;
 
     if (!isNullArray && length < 0) {
-        LOG(ERROR) << __func__ << ": non-null array but length is " << length;
+        ALOGE("non-null array but length is %" PRIi32, length);
         return STATUS_BAD_VALUE;
     }
     if (isNullArray && length > 0) {
-        LOG(ERROR) << __func__ << ": null buffer cannot be for size " << length << " array.";
+        ALOGE("null buffer cannot be for size %" PRIi32 " array.", length);
         return STATUS_BAD_VALUE;
     }
 
@@ -325,7 +324,7 @@
 binder_status_t AParcel_writeString(AParcel* parcel, const char* string, int32_t length) {
     if (string == nullptr) {
         if (length != -1) {
-            LOG(WARNING) << __func__ << ": null string must be used with length == -1.";
+            ALOGW("null string must be used with length == -1.");
             return STATUS_BAD_VALUE;
         }
 
@@ -334,7 +333,7 @@
     }
 
     if (length < 0) {
-        LOG(WARNING) << __func__ << ": Negative string length: " << length;
+        ALOGW("Negative string length: %" PRIi32, length);
         return STATUS_BAD_VALUE;
     }
 
@@ -342,7 +341,7 @@
     const ssize_t len16 = utf8_to_utf16_length(str8, length);
 
     if (len16 < 0 || len16 >= std::numeric_limits<int32_t>::max()) {
-        LOG(WARNING) << __func__ << ": Invalid string length: " << len16;
+        ALOGW("Invalid string length: %zd", len16);
         return STATUS_BAD_VALUE;
     }
 
@@ -383,7 +382,7 @@
     }
 
     if (len8 <= 0 || len8 > std::numeric_limits<int32_t>::max()) {
-        LOG(WARNING) << __func__ << ": Invalid string length: " << len8;
+        ALOGW("Invalid string length: %zd", len8);
         return STATUS_BAD_VALUE;
     }
 
@@ -391,7 +390,7 @@
     bool success = allocator(stringData, len8, &str8);
 
     if (!success || str8 == nullptr) {
-        LOG(WARNING) << __func__ << ": AParcel_stringAllocator failed to allocate.";
+        ALOGW("AParcel_stringAllocator failed to allocate.");
         return STATUS_NO_MEMORY;
     }
 
diff --git a/libs/binder/ndk/persistable_bundle.cpp b/libs/binder/ndk/persistable_bundle.cpp
new file mode 100644
index 0000000..404611c
--- /dev/null
+++ b/libs/binder/ndk/persistable_bundle.cpp
@@ -0,0 +1,313 @@
+/*
+ * 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.
+ */
+#include <android/binder_libbinder.h>
+#include <android/persistable_bundle.h>
+#include <binder/PersistableBundle.h>
+#include <log/log.h>
+#include <persistable_bundle_internal.h>
+#include <string.h>
+
+#include <set>
+
+__BEGIN_DECLS
+
+struct APersistableBundle {
+    APersistableBundle(const APersistableBundle& pBundle) : mPBundle(pBundle.mPBundle) {}
+    APersistableBundle(const android::os::PersistableBundle& pBundle) : mPBundle(pBundle) {}
+    APersistableBundle() = default;
+    android::os::PersistableBundle mPBundle;
+};
+
+APersistableBundle* _Nullable APersistableBundle_new() {
+    return new (std::nothrow) APersistableBundle();
+}
+
+APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* pBundle) {
+    if (pBundle) {
+        return new APersistableBundle(*pBundle);
+    } else {
+        return new APersistableBundle();
+    }
+}
+
+void APersistableBundle_delete(APersistableBundle* pBundle) {
+    free(pBundle);
+}
+
+bool APersistableBundle_isEqual(const APersistableBundle* lhs, const APersistableBundle* rhs) {
+    if (lhs && rhs) {
+        return lhs->mPBundle == rhs->mPBundle;
+    } else if (lhs == rhs) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+binder_status_t APersistableBundle_readFromParcel(const AParcel* parcel,
+                                                  APersistableBundle* _Nullable* outPBundle) {
+    if (!parcel || !outPBundle) return STATUS_BAD_VALUE;
+    APersistableBundle* newPBundle = APersistableBundle_new();
+    if (newPBundle == nullptr) return STATUS_NO_MEMORY;
+    binder_status_t status =
+            newPBundle->mPBundle.readFromParcel(AParcel_viewPlatformParcel(parcel));
+    if (status == STATUS_OK) {
+        *outPBundle = newPBundle;
+    }
+    return status;
+}
+
+binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* pBundle,
+                                                 AParcel* parcel) {
+    if (!parcel || !pBundle) return STATUS_BAD_VALUE;
+    return pBundle->mPBundle.writeToParcel(AParcel_viewPlatformParcel(parcel));
+}
+
+int32_t APersistableBundle_size(APersistableBundle* pBundle) {
+    size_t size = pBundle->mPBundle.size();
+    LOG_ALWAYS_FATAL_IF(size > INT32_MAX,
+                        "The APersistableBundle has gotten too large! There will be an overflow in "
+                        "the reported size.");
+    return pBundle->mPBundle.size();
+}
+int32_t APersistableBundle_erase(APersistableBundle* pBundle, const char* key) {
+    return pBundle->mPBundle.erase(android::String16(key));
+}
+void APersistableBundle_putBoolean(APersistableBundle* pBundle, const char* key, bool val) {
+    pBundle->mPBundle.putBoolean(android::String16(key), val);
+}
+void APersistableBundle_putInt(APersistableBundle* pBundle, const char* key, int32_t val) {
+    pBundle->mPBundle.putInt(android::String16(key), val);
+}
+void APersistableBundle_putLong(APersistableBundle* pBundle, const char* key, int64_t val) {
+    pBundle->mPBundle.putLong(android::String16(key), val);
+}
+void APersistableBundle_putDouble(APersistableBundle* pBundle, const char* key, double val) {
+    pBundle->mPBundle.putDouble(android::String16(key), val);
+}
+void APersistableBundle_putString(APersistableBundle* pBundle, const char* key, const char* val) {
+    pBundle->mPBundle.putString(android::String16(key), android::String16(val));
+}
+void APersistableBundle_putBooleanVector(APersistableBundle* pBundle, const char* key,
+                                         const bool* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<bool> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putBooleanVector(android::String16(key), newVec);
+}
+void APersistableBundle_putIntVector(APersistableBundle* pBundle, const char* key,
+                                     const int32_t* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<int32_t> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putIntVector(android::String16(key), newVec);
+}
+void APersistableBundle_putLongVector(APersistableBundle* pBundle, const char* key,
+                                      const int64_t* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<int64_t> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putLongVector(android::String16(key), newVec);
+}
+void APersistableBundle_putDoubleVector(APersistableBundle* pBundle, const char* key,
+                                        const double* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<double> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = vec[i];
+    }
+    pBundle->mPBundle.putDoubleVector(android::String16(key), newVec);
+}
+void APersistableBundle_putStringVector(APersistableBundle* pBundle, const char* key,
+                                        const char* const* vec, int32_t num) {
+    LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+    std::vector<android::String16> newVec(num);
+    for (int32_t i = 0; i < num; i++) {
+        newVec[i] = android::String16(vec[i]);
+    }
+    pBundle->mPBundle.putStringVector(android::String16(key), newVec);
+}
+void APersistableBundle_putPersistableBundle(APersistableBundle* pBundle, const char* key,
+                                             const APersistableBundle* val) {
+    pBundle->mPBundle.putPersistableBundle(android::String16(key), val->mPBundle);
+}
+bool APersistableBundle_getBoolean(const APersistableBundle* pBundle, const char* key, bool* val) {
+    return pBundle->mPBundle.getBoolean(android::String16(key), val);
+}
+bool APersistableBundle_getInt(const APersistableBundle* pBundle, const char* key, int32_t* val) {
+    return pBundle->mPBundle.getInt(android::String16(key), val);
+}
+bool APersistableBundle_getLong(const APersistableBundle* pBundle, const char* key, int64_t* val) {
+    return pBundle->mPBundle.getLong(android::String16(key), val);
+}
+bool APersistableBundle_getDouble(const APersistableBundle* pBundle, const char* key, double* val) {
+    return pBundle->mPBundle.getDouble(android::String16(key), val);
+}
+int32_t APersistableBundle_getString(const APersistableBundle* pBundle, const char* key, char** val,
+                                     APersistableBundle_stringAllocator stringAllocator,
+                                     void* context) {
+    android::String16 outVal;
+    bool ret = pBundle->mPBundle.getString(android::String16(key), &outVal);
+    if (ret) {
+        android::String8 tmp8(outVal);
+        *val = stringAllocator(tmp8.bytes() + 1, context);
+        if (*val) {
+            strncpy(*val, tmp8.c_str(), tmp8.bytes() + 1);
+            return tmp8.bytes();
+        } else {
+            return -1;
+        }
+    }
+    return 0;
+}
+int32_t APersistableBundle_getBooleanVector(const APersistableBundle* pBundle, const char* key,
+                                            bool* buffer, int32_t bufferSizeBytes) {
+    std::vector<bool> newVec;
+    pBundle->mPBundle.getBooleanVector(android::String16(key), &newVec);
+    return getVecInternal<bool>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getIntVector(const APersistableBundle* pBundle, const char* key,
+                                        int32_t* buffer, int32_t bufferSizeBytes) {
+    std::vector<int32_t> newVec;
+    pBundle->mPBundle.getIntVector(android::String16(key), &newVec);
+    return getVecInternal<int32_t>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getLongVector(const APersistableBundle* pBundle, const char* key,
+                                         int64_t* buffer, int32_t bufferSizeBytes) {
+    std::vector<int64_t> newVec;
+    pBundle->mPBundle.getLongVector(android::String16(key), &newVec);
+    return getVecInternal<int64_t>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getDoubleVector(const APersistableBundle* pBundle, const char* key,
+                                           double* buffer, int32_t bufferSizeBytes) {
+    std::vector<double> newVec;
+    pBundle->mPBundle.getDoubleVector(android::String16(key), &newVec);
+    return getVecInternal<double>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getStringVector(const APersistableBundle* pBundle, const char* key,
+                                           char** vec, int32_t bufferSizeBytes,
+                                           APersistableBundle_stringAllocator stringAllocator,
+                                           void* context) {
+    std::vector<android::String16> newVec;
+    pBundle->mPBundle.getStringVector(android::String16(key), &newVec);
+    return getStringsInternal<std::vector<android::String16>>(newVec, vec, bufferSizeBytes,
+                                                              stringAllocator, context);
+}
+bool APersistableBundle_getPersistableBundle(const APersistableBundle* pBundle, const char* key,
+                                             APersistableBundle** outBundle) {
+    APersistableBundle* bundle = APersistableBundle_new();
+    bool ret = pBundle->mPBundle.getPersistableBundle(android::String16(key), &bundle->mPBundle);
+    if (ret) {
+        *outBundle = bundle;
+        return true;
+    }
+    return false;
+}
+int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* pBundle, char** outKeys,
+                                          int32_t bufferSizeBytes,
+                                          APersistableBundle_stringAllocator stringAllocator,
+                                          void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getBooleanKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getIntKeys(const APersistableBundle* pBundle, char** outKeys,
+                                      int32_t bufferSizeBytes,
+                                      APersistableBundle_stringAllocator stringAllocator,
+                                      void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getIntKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getLongKeys(const APersistableBundle* pBundle, char** outKeys,
+                                       int32_t bufferSizeBytes,
+                                       APersistableBundle_stringAllocator stringAllocator,
+                                       void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getLongKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* pBundle, char** outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getDoubleKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getStringKeys(const APersistableBundle* pBundle, char** outKeys,
+                                         int32_t bufferSizeBytes,
+                                         APersistableBundle_stringAllocator stringAllocator,
+                                         void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getStringKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                                int32_t bufferSizeBytes,
+                                                APersistableBundle_stringAllocator stringAllocator,
+                                                void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getBooleanVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                            int32_t bufferSizeBytes,
+                                            APersistableBundle_stringAllocator stringAllocator,
+                                            void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getIntVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                             int32_t bufferSizeBytes,
+                                             APersistableBundle_stringAllocator stringAllocator,
+                                             void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getLongVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getDoubleVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+                                               int32_t bufferSizeBytes,
+                                               APersistableBundle_stringAllocator stringAllocator,
+                                               void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getStringVectorKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+int32_t APersistableBundle_getPersistableBundleKeys(
+        const APersistableBundle* pBundle, char** outKeys, int32_t bufferSizeBytes,
+        APersistableBundle_stringAllocator stringAllocator, void* context) {
+    std::set<android::String16> ret = pBundle->mPBundle.getPersistableBundleKeys();
+    return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+                                                           stringAllocator, context);
+}
+
+__END_DECLS
diff --git a/libs/binder/ndk/persistable_bundle_internal.h b/libs/binder/ndk/persistable_bundle_internal.h
new file mode 100644
index 0000000..279c66f
--- /dev/null
+++ b/libs/binder/ndk/persistable_bundle_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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 <android/persistable_bundle.h>
+#include <log/log.h>
+#include <utils/String8.h>
+
+//  take a vector and put the contents into a buffer.
+//  return the size of the contents.
+//  This may not put all of the contents into the buffer if the buffer is not
+//  large enough.
+template <typename T>
+int32_t getVecInternal(const std::vector<T>& inVec, T* _Nullable buffer, int32_t bufferSizeBytes) {
+    LOG_ALWAYS_FATAL_IF(inVec.size() > INT32_MAX,
+                        "The size of the APersistableBundle has gotten too large!");
+    LOG_ALWAYS_FATAL_IF(
+            bufferSizeBytes < 0,
+            "The buffer size in bytes can not be larger than INT32_MAX and can not be negative.");
+    int32_t num = inVec.size();
+    int32_t numAvailable = bufferSizeBytes / sizeof(T);
+    int32_t numFill = numAvailable < num ? numAvailable : num;
+
+    if (numFill > 0 && buffer) {
+        for (int32_t i = 0; i < numFill; i++) {
+            buffer[i] = inVec[i];
+        }
+    }
+    return num * sizeof(T);
+}
+
+//  take a vector or a set of String16 and put the contents into a char** buffer.
+//  return the size of the contents.
+//  This may not put all of the contents into the buffer if the buffer is not
+//  large enough.
+//  The strings are duped with a user supplied callback
+template <typename T>
+int32_t getStringsInternal(const T& strings, char* _Nullable* _Nullable buffer,
+                           int32_t bufferSizeBytes,
+                           APersistableBundle_stringAllocator stringAllocator,
+                           void* _Nullable context) {
+    LOG_ALWAYS_FATAL_IF(strings.size() > INT32_MAX,
+                        "The size of the APersistableBundle has gotten too large!");
+    LOG_ALWAYS_FATAL_IF(
+            bufferSizeBytes < 0,
+            "The buffer size in bytes can not be larger than INT32_MAX and can not be negative.");
+    int32_t num = strings.size();
+    int32_t numAvailable = bufferSizeBytes / sizeof(char*);
+    int32_t numFill = numAvailable < num ? numAvailable : num;
+    if (!stringAllocator) {
+        return -1;
+    }
+
+    if (numFill > 0 && buffer) {
+        int32_t i = 0;
+        for (const auto& val : strings) {
+            android::String8 tmp8 = android::String8(val);
+            buffer[i] = stringAllocator(tmp8.bytes() + 1, context);
+            if (buffer[i] == nullptr) {
+                return -1;
+            }
+            strncpy(buffer[i], tmp8.c_str(), tmp8.bytes() + 1);
+            i++;
+            if (i > numFill - 1) {
+                // buffer is too small to keep going or this is the end of the
+                // set
+                break;
+            }
+        }
+    }
+    return num * sizeof(char*);
+}
diff --git a/libs/binder/ndk/process.cpp b/libs/binder/ndk/process.cpp
index 0fea57b..0072ac3 100644
--- a/libs/binder/ndk/process.cpp
+++ b/libs/binder/ndk/process.cpp
@@ -15,12 +15,10 @@
  */
 
 #include <android/binder_process.h>
+#include <binder/IPCThreadState.h>
 
 #include <mutex>
 
-#include <android-base/logging.h>
-#include <binder/IPCThreadState.h>
-
 using ::android::IPCThreadState;
 using ::android::ProcessState;
 
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 2977786..3bfdc59 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -15,14 +15,12 @@
  */
 
 #include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <binder/LazyServiceRegistrar.h>
 
 #include "ibinder_internal.h"
 #include "status_internal.h"
 
-#include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <binder/LazyServiceRegistrar.h>
-
 using ::android::defaultServiceManager;
 using ::android::IBinder;
 using ::android::IServiceManager;
@@ -115,7 +113,8 @@
         std::lock_guard<std::mutex> l(m);
         if (onRegister == nullptr) return;
 
-        CHECK_EQ(String8(smInstance), instance);
+        LOG_ALWAYS_FATAL_IF(String8(smInstance) != instance, "onServiceRegistration: %s != %s",
+                            String8(smInstance).c_str(), instance);
 
         sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(binder);
         AIBinder_incStrong(ret.get());
@@ -135,8 +134,8 @@
 AServiceManager_registerForServiceNotifications(const char* instance,
                                                 AServiceManager_onRegister onRegister,
                                                 void* cookie) {
-    CHECK_NE(instance, nullptr);
-    CHECK_NE(onRegister, nullptr) << instance;
+    LOG_ALWAYS_FATAL_IF(instance == nullptr, "instance == nullptr");
+    LOG_ALWAYS_FATAL_IF(onRegister == nullptr, "onRegister == nullptr for %s", instance);
     // cookie can be nullptr
 
     auto cb = sp<AServiceManager_NotificationRegistration>::make();
@@ -146,8 +145,8 @@
 
     sp<IServiceManager> sm = defaultServiceManager();
     if (status_t res = sm->registerForNotifications(String16(instance), cb); res != STATUS_OK) {
-        LOG(ERROR) << "Failed to register for service notifications for " << instance << ": "
-                   << statusToString(res);
+        ALOGE("Failed to register for service notifications for %s: %s", instance,
+              statusToString(res).c_str());
         return nullptr;
     }
 
@@ -157,7 +156,7 @@
 
 void AServiceManager_NotificationRegistration_delete(
         AServiceManager_NotificationRegistration* notification) {
-    CHECK_NE(notification, nullptr);
+    LOG_ALWAYS_FATAL_IF(notification == nullptr, "notification == nullptr");
     notification->clear();
     notification->decStrong(nullptr);
 }
@@ -172,9 +171,9 @@
 }
 void AServiceManager_forEachDeclaredInstance(const char* interface, void* context,
                                              void (*callback)(const char*, void*)) {
-    CHECK(interface != nullptr);
+    LOG_ALWAYS_FATAL_IF(interface == nullptr, "interface == nullptr");
     // context may be nullptr
-    CHECK(callback != nullptr);
+    LOG_ALWAYS_FATAL_IF(callback == nullptr, "callback == nullptr");
 
     sp<IServiceManager> sm = defaultServiceManager();
     for (const String16& instance : sm->getDeclaredInstances(String16(interface))) {
@@ -191,9 +190,9 @@
 }
 void AServiceManager_getUpdatableApexName(const char* instance, void* context,
                                           void (*callback)(const char*, void*)) {
-    CHECK_NE(instance, nullptr);
+    LOG_ALWAYS_FATAL_IF(instance == nullptr, "instance == nullptr");
     // context may be nullptr
-    CHECK_NE(callback, nullptr);
+    LOG_ALWAYS_FATAL_IF(callback == nullptr, "callback == nullptr");
 
     sp<IServiceManager> sm = defaultServiceManager();
     std::optional<String16> updatableViaApex = sm->updatableViaApex(String16(instance));
diff --git a/libs/binder/ndk/status.cpp b/libs/binder/ndk/status.cpp
index 8ed91a5..3aac3c0 100644
--- a/libs/binder/ndk/status.cpp
+++ b/libs/binder/ndk/status.cpp
@@ -17,8 +17,6 @@
 #include <android/binder_status.h>
 #include "status_internal.h"
 
-#include <android-base/logging.h>
-
 using ::android::status_t;
 using ::android::statusToString;
 using ::android::binder::Status;
@@ -127,8 +125,8 @@
             return STATUS_UNKNOWN_ERROR;
 
         default:
-            LOG(WARNING) << __func__ << ": Unknown status_t (" << statusToString(status)
-                         << ") pruned into STATUS_UNKNOWN_ERROR";
+            ALOGW("%s: Unknown status_t (%s) pruned into STATUS_UNKNOWN_ERROR", __func__,
+                  statusToString(status).c_str());
             return STATUS_UNKNOWN_ERROR;
     }
 }
@@ -159,8 +157,8 @@
             return EX_TRANSACTION_FAILED;
 
         default:
-            LOG(WARNING) << __func__ << ": Unknown binder exception (" << exception
-                         << ") pruned into EX_TRANSACTION_FAILED";
+            ALOGW("%s: Unknown binder exception (%d) pruned into EX_TRANSACTION_FAILED", __func__,
+                  exception);
             return EX_TRANSACTION_FAILED;
     }
 }
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 78f8877..6d122c5 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -27,7 +27,7 @@
 use std::convert::TryFrom;
 use std::ffi::{c_void, CStr, CString};
 use std::fmt;
-use std::fs::File;
+use std::io::Write;
 use std::marker::PhantomData;
 use std::ops::Deref;
 use std::os::raw::c_char;
@@ -62,7 +62,7 @@
     ///
     /// This handler is a no-op by default and should be implemented for each
     /// Binder service struct that wishes to respond to dump transactions.
-    fn dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+    fn dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
         Ok(())
     }
 }
@@ -165,7 +165,7 @@
 
     /// Handle a request to invoke the dump transaction on this
     /// object.
-    fn on_dump(&self, file: &File, args: &[&CStr]) -> Result<()>;
+    fn on_dump(&self, file: &mut dyn Write, args: &[&CStr]) -> Result<()>;
 
     /// Retrieve the class of this remote object.
     ///
@@ -934,8 +934,8 @@
                 }
             }
 
-            fn on_dump(&self, file: &std::fs::File, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
-                self.0.dump(file, args)
+            fn on_dump(&self, writer: &mut dyn std::io::Write, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
+                self.0.dump(writer, args)
             }
 
             fn get_class() -> $crate::binder_impl::InterfaceClass {
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index b248f5e..b250012 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -25,6 +25,7 @@
 use std::convert::TryFrom;
 use std::ffi::{c_void, CStr, CString};
 use std::fs::File;
+use std::io::Write;
 use std::mem::ManuallyDrop;
 use std::ops::Deref;
 use std::os::raw::c_char;
@@ -341,7 +342,7 @@
         }
         // Safety: Our caller promised that fd is a file descriptor. We don't
         // own this file descriptor, so we need to be careful not to drop it.
-        let file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
+        let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
 
         if args.is_null() && num_args != 0 {
             return StatusCode::UNEXPECTED_NULL as status_t;
@@ -366,7 +367,7 @@
         // Safety: Our caller promised that the binder has a `T` pointer in its
         // user data.
         let binder: &T = unsafe { &*(object as *const T) };
-        let res = binder.on_dump(&file, &args);
+        let res = binder.on_dump(&mut *file, &args);
 
         match res {
             Ok(()) => 0,
@@ -569,7 +570,7 @@
         Ok(())
     }
 
-    fn on_dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+    fn on_dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
         Ok(())
     }
 
diff --git a/libs/binder/rust/src/parcel/file_descriptor.rs b/libs/binder/rust/src/parcel/file_descriptor.rs
index 5c688fa..6afe5ab 100644
--- a/libs/binder/rust/src/parcel/file_descriptor.rs
+++ b/libs/binder/rust/src/parcel/file_descriptor.rs
@@ -22,29 +22,28 @@
 use crate::error::{status_result, Result, StatusCode};
 use crate::sys;
 
-use std::fs::File;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 
 /// Rust version of the Java class android.os.ParcelFileDescriptor
 #[derive(Debug)]
-pub struct ParcelFileDescriptor(File);
+pub struct ParcelFileDescriptor(OwnedFd);
 
 impl ParcelFileDescriptor {
     /// Create a new `ParcelFileDescriptor`
-    pub fn new(file: File) -> Self {
-        Self(file)
+    pub fn new<F: Into<OwnedFd>>(fd: F) -> Self {
+        Self(fd.into())
     }
 }
 
-impl AsRef<File> for ParcelFileDescriptor {
-    fn as_ref(&self) -> &File {
+impl AsRef<OwnedFd> for ParcelFileDescriptor {
+    fn as_ref(&self) -> &OwnedFd {
         &self.0
     }
 }
 
-impl From<ParcelFileDescriptor> for File {
-    fn from(file: ParcelFileDescriptor) -> File {
-        file.0
+impl From<ParcelFileDescriptor> for OwnedFd {
+    fn from(fd: ParcelFileDescriptor) -> OwnedFd {
+        fd.0
     }
 }
 
@@ -120,7 +119,7 @@
             // Safety: At this point, we know that the file descriptor was
             // not -1, so must be a valid, owned file descriptor which we
             // can safely turn into a `File`.
-            let file = unsafe { File::from_raw_fd(fd) };
+            let file = unsafe { OwnedFd::from_raw_fd(fd) };
             Ok(Some(ParcelFileDescriptor::new(file)))
         }
     }
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index c049b80..c87fa89 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -26,7 +26,7 @@
 
 use std::convert::{TryFrom, TryInto};
 use std::ffi::CStr;
-use std::fs::File;
+use std::io::Write;
 use std::sync::Mutex;
 
 /// Name of service runner.
@@ -118,7 +118,7 @@
 }
 
 impl Interface for TestService {
-    fn dump(&self, _file: &File, args: &[&CStr]) -> Result<(), StatusCode> {
+    fn dump(&self, _writer: &mut dyn Write, args: &[&CStr]) -> Result<(), StatusCode> {
         let mut dump_args = self.dump_args.lock().unwrap();
         dump_args.extend(args.iter().map(|s| s.to_str().unwrap().to_owned()));
         Ok(())
diff --git a/libs/binder/rust/tests/ndk_rust_interop.rs b/libs/binder/rust/tests/ndk_rust_interop.rs
index 37f182e..fbedfee 100644
--- a/libs/binder/rust/tests/ndk_rust_interop.rs
+++ b/libs/binder/rust/tests/ndk_rust_interop.rs
@@ -58,7 +58,7 @@
     let wrong_service: Result<binder::Strong<dyn IBinderRustNdkInteropTestOther>, StatusCode> =
         binder::get_interface(service_name);
     match wrong_service {
-        Err(e) if e == StatusCode::BAD_TYPE => {}
+        Err(StatusCode::BAD_TYPE) => {}
         Err(e) => {
             eprintln!("Trying to use a service via the wrong interface errored with unexpected error {:?}", e);
             return e as c_int;
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index bc34d4c..624edba 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -35,6 +35,7 @@
 #include <trusty/tipc.h>
 #endif // BINDER_RPC_TO_TRUSTY_TEST
 
+#include "../Utils.h"
 #include "binderRpcTestCommon.h"
 #include "binderRpcTestFixture.h"
 
@@ -86,8 +87,8 @@
                                      android::base::borrowed_fd /* readEnd */)>& f) {
         android::base::unique_fd childWriteEnd;
         android::base::unique_fd childReadEnd;
-        CHECK(android::base::Pipe(&mReadEnd, &childWriteEnd, 0)) << strerror(errno);
-        CHECK(android::base::Pipe(&childReadEnd, &mWriteEnd, 0)) << strerror(errno);
+        if (!android::base::Pipe(&mReadEnd, &childWriteEnd, 0)) PLOGF("child write pipe failed");
+        if (!android::base::Pipe(&childReadEnd, &mWriteEnd, 0)) PLOGF("child read pipe failed");
         if (0 == (mPid = fork())) {
             // racey: assume parent doesn't crash before this is set
             prctl(PR_SET_PDEATHSIG, SIGHUP);
@@ -145,8 +146,10 @@
     auto socket_addr = UnixSocketAddress(addr.c_str());
     base::unique_fd fd(
             TEMP_FAILURE_RETRY(socket(socket_addr.addr()->sa_family, SOCK_STREAM, AF_UNIX)));
-    CHECK(fd.ok());
-    CHECK_EQ(0, TEMP_FAILURE_RETRY(bind(fd.get(), socket_addr.addr(), socket_addr.addrSize())));
+    if (!fd.ok()) PLOGF("initUnixSocket failed to create socket");
+    if (0 != TEMP_FAILURE_RETRY(bind(fd.get(), socket_addr.addr(), socket_addr.addrSize()))) {
+        PLOGF("initUnixSocket failed to bind");
+    }
     return fd;
 }
 
@@ -204,14 +207,12 @@
 static base::unique_fd connectTo(const RpcSocketAddress& addr) {
     base::unique_fd serverFd(
             TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
-    int savedErrno = errno;
-    CHECK(serverFd.ok()) << "Could not create socket " << addr.toString() << ": "
-                         << strerror(savedErrno);
+    if (!serverFd.ok()) {
+        PLOGF("Could not create socket %s", addr.toString().c_str());
+    }
 
     if (0 != TEMP_FAILURE_RETRY(connect(serverFd.get(), addr.addr(), addr.addrSize()))) {
-        int savedErrno = errno;
-        LOG(FATAL) << "Could not connect to socket " << addr.toString() << ": "
-                   << strerror(savedErrno);
+        PLOGF("Could not connect to socket %s", addr.toString().c_str());
     }
     return serverFd;
 }
@@ -220,8 +221,7 @@
 static base::unique_fd connectToUnixBootstrap(const RpcTransportFd& transportFd) {
     base::unique_fd sockClient, sockServer;
     if (!base::Socketpair(SOCK_STREAM, &sockClient, &sockServer)) {
-        int savedErrno = errno;
-        LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
+        PLOGF("Failed socketpair()");
     }
 
     int zero = 0;
@@ -230,8 +230,7 @@
     fds.emplace_back(std::move(sockServer));
 
     if (binder::os::sendMessageOnSocket(transportFd, &iov, 1, &fds) < 0) {
-        int savedErrno = errno;
-        LOG(FATAL) << "Failed sendMessageOnSocket: " << strerror(savedErrno);
+        PLOGF("Failed sendMessageOnSocket");
     }
     return std::move(sockClient);
 }
@@ -245,10 +244,12 @@
 // threads.
 std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
         const BinderRpcOptions& options) {
-    CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
+    LOG_ALWAYS_FATAL_IF(options.numSessions < 1, "Must have at least one session to a server");
 
     if (options.numIncomingConnectionsBySession.size() != 0) {
-        CHECK_EQ(options.numIncomingConnectionsBySession.size(), options.numSessions);
+        LOG_ALWAYS_FATAL_IF(options.numIncomingConnectionsBySession.size() != options.numSessions,
+                            "%s: %zu != %zu", __func__,
+                            options.numIncomingConnectionsBySession.size(), options.numSessions);
     }
 
     SocketType socketType = GetParam().type;
@@ -273,8 +274,7 @@
         // Do not set O_CLOEXEC, bootstrapServerFd needs to survive fork/exec.
         // This is because we cannot pass ParcelFileDescriptor over a pipe.
         if (!base::Socketpair(SOCK_STREAM, &bootstrapClientFd, &socketFd)) {
-            int savedErrno = errno;
-            LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
+            PLOGF("Failed socketpair()");
         }
     }
 
@@ -287,8 +287,10 @@
 
                 auto writeFd = std::to_string(writeEnd.get());
                 auto readFd = std::to_string(readEnd.get());
-                execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(), readFd.c_str(),
-                      NULL);
+                auto status = execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(),
+                                    readFd.c_str(), NULL);
+                PLOGF("execl('%s', _, %s, %s) should not return at all, but it returned %d",
+                      servicePath.c_str(), writeFd.c_str(), readFd.c_str(), status);
             }));
 
     BinderRpcTestServerConfig serverConfig;
@@ -333,16 +335,16 @@
         }
         writeToFd(ret->host.writeEnd(), clientInfo);
 
-        CHECK_LE(serverInfo.port, std::numeric_limits<unsigned int>::max());
+        LOG_ALWAYS_FATAL_IF(serverInfo.port > std::numeric_limits<unsigned int>::max());
         if (socketType == SocketType::INET) {
-            CHECK_NE(0, serverInfo.port);
+            LOG_ALWAYS_FATAL_IF(0 == serverInfo.port);
         }
 
         if (rpcSecurity == RpcSecurity::TLS) {
             const auto& serverCert = serverInfo.cert.data;
-            CHECK_EQ(OK,
-                     certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
-                                                             serverCert));
+            LOG_ALWAYS_FATAL_IF(
+                    OK !=
+                    certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM, serverCert));
         }
     }
 
@@ -355,7 +357,7 @@
                 ? options.numIncomingConnectionsBySession.at(i)
                 : 0;
 
-        CHECK(session->setProtocolVersion(clientVersion));
+        LOG_ALWAYS_FATAL_IF(!session->setProtocolVersion(clientVersion));
         session->setMaxIncomingThreads(numIncoming);
         session->setMaxOutgoingConnections(options.numOutgoingConnections);
         session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
@@ -407,7 +409,7 @@
             ret->sessions.clear();
             break;
         }
-        CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
+        LOG_ALWAYS_FATAL_IF(status != OK, "Could not connect: %s", statusToString(status).c_str());
         ret->sessions.push_back({session, session->getRootObject()});
     }
     return ret;
@@ -588,12 +590,12 @@
     android::os::ParcelFileDescriptor fdA;
     EXPECT_OK(proc.rootIface->blockingRecvFd(&fdA));
     std::string result;
-    CHECK(android::base::ReadFdToString(fdA.get(), &result));
+    ASSERT_TRUE(android::base::ReadFdToString(fdA.get(), &result));
     EXPECT_EQ(result, "a");
 
     android::os::ParcelFileDescriptor fdB;
     EXPECT_OK(proc.rootIface->blockingRecvFd(&fdB));
-    CHECK(android::base::ReadFdToString(fdB.get(), &result));
+    ASSERT_TRUE(android::base::ReadFdToString(fdB.get(), &result));
     EXPECT_EQ(result, "b");
 
     saturateThreadPool(kNumServerThreads, proc.rootIface);
@@ -950,8 +952,8 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    CHECK(android::base::ReadFdToString(out.get(), &result));
-    EXPECT_EQ(result, "hello");
+    ASSERT_TRUE(android::base::ReadFdToString(out.get(), &result));
+    ASSERT_EQ(result, "hello");
 }
 
 TEST_P(BinderRpc, SendFiles) {
@@ -980,7 +982,7 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    CHECK(android::base::ReadFdToString(out.get(), &result));
+    EXPECT_TRUE(android::base::ReadFdToString(out.get(), &result));
     EXPECT_EQ(result, "123abcd");
 }
 
@@ -1005,7 +1007,7 @@
     ASSERT_TRUE(status.isOk()) << status;
 
     std::string result;
-    CHECK(android::base::ReadFdToString(out.get(), &result));
+    EXPECT_TRUE(android::base::ReadFdToString(out.get(), &result));
     EXPECT_EQ(result, std::string(253, 'a'));
 }
 
@@ -1157,7 +1159,7 @@
         return false;
     }
 
-    LOG_ALWAYS_FATAL_IF(serverFd == -1, "Could not create socket: %s", strerror(errno));
+    LOG_ALWAYS_FATAL_IF(!serverFd.ok(), "Could not create socket: %s", strerror(errno));
 
     sockaddr_vm serverAddr{
             .svm_family = AF_VSOCK,
@@ -1179,7 +1181,7 @@
     // and they return ETIMEDOUT after that.
     android::base::unique_fd connectFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
-    LOG_ALWAYS_FATAL_IF(connectFd == -1, "Could not create socket for port %u: %s", vsockPort,
+    LOG_ALWAYS_FATAL_IF(!connectFd.ok(), "Could not create socket for port %u: %s", vsockPort,
                         strerror(errno));
 
     bool success = false;
@@ -1197,7 +1199,7 @@
                     {.fd = serverFd.get(), .events = POLLIN, .revents = 0},
                     {.fd = connectFd.get(), .events = POLLOUT, .revents = 0},
             };
-            ret = TEMP_FAILURE_RETRY(poll(pfd, arraysize(pfd), -1));
+            ret = TEMP_FAILURE_RETRY(poll(pfd, countof(pfd), -1));
             LOG_ALWAYS_FATAL_IF(ret < 0, "Error polling: %s", strerror(errno));
 
             if (pfd[0].revents & POLLIN) {
@@ -1358,7 +1360,11 @@
 };
 
 TEST(BinderRpc, Java) {
-#if !defined(__ANDROID__)
+    bool expectDebuggable = false;
+#if defined(__ANDROID__)
+    expectDebuggable = android::base::GetBoolProperty("ro.debuggable", false) &&
+            android::base::GetProperty("ro.build.type", "") != "user";
+#else
     GTEST_SKIP() << "This test is only run on Android. Though it can technically run on host on"
                     "createRpcDelegateServiceManager() with a device attached, such test belongs "
                     "to binderHostDeviceTest. Hence, just disable this test on host.";
@@ -1386,8 +1392,7 @@
     auto keepAlive = sp<BBinder>::make();
     auto setRpcClientDebugStatus = binder->setRpcClientDebug(std::move(socket), keepAlive);
 
-    if (!android::base::GetBoolProperty("ro.debuggable", false) ||
-        android::base::GetProperty("ro.build.type", "") == "user") {
+    if (!expectDebuggable) {
         ASSERT_EQ(INVALID_OPERATION, setRpcClientDebugStatus)
                 << "setRpcClientDebug should return INVALID_OPERATION on non-debuggable or user "
                    "builds, but get "
@@ -1592,12 +1597,10 @@
             iovec iov{&buf, sizeof(buf)};
 
             if (binder::os::receiveMessageFromSocket(mFd, &iov, 1, &fds) < 0) {
-                int savedErrno = errno;
-                LOG(FATAL) << "Failed receiveMessage: " << strerror(savedErrno);
+                PLOGF("Failed receiveMessage");
             }
-            if (fds.size() != 1) {
-                LOG(FATAL) << "Expected one FD from receiveMessage(), got " << fds.size();
-            }
+            LOG_ALWAYS_FATAL_IF(fds.size() != 1, "Expected one FD from receiveMessage(), got %zu",
+                                fds.size());
             return std::move(std::get<base::unique_fd>(fds[0]));
         }
 
@@ -2082,7 +2085,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
-    android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+    __android_log_set_logger(__android_log_stderr_logger);
 
     return RUN_ALL_TESTS();
 }
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index c3070dd..b2b63e4 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -36,10 +36,12 @@
 #include <string>
 #include <vector>
 
+#ifdef __ANDROID__
+#include <android-base/properties.h>
+#endif
+
 #ifndef __TRUSTY__
 #include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_libbinder.h>
 #include <binder/ProcessState.h>
@@ -156,21 +158,21 @@
 #ifndef __TRUSTY__
 static inline void writeString(android::base::borrowed_fd fd, std::string_view str) {
     uint64_t length = str.length();
-    CHECK(android::base::WriteFully(fd, &length, sizeof(length)));
-    CHECK(android::base::WriteFully(fd, str.data(), str.length()));
+    LOG_ALWAYS_FATAL_IF(!android::base::WriteFully(fd, &length, sizeof(length)));
+    LOG_ALWAYS_FATAL_IF(!android::base::WriteFully(fd, str.data(), str.length()));
 }
 
 static inline std::string readString(android::base::borrowed_fd fd) {
     uint64_t length;
-    CHECK(android::base::ReadFully(fd, &length, sizeof(length)));
+    LOG_ALWAYS_FATAL_IF(!android::base::ReadFully(fd, &length, sizeof(length)));
     std::string ret(length, '\0');
-    CHECK(android::base::ReadFully(fd, ret.data(), length));
+    LOG_ALWAYS_FATAL_IF(!android::base::ReadFully(fd, ret.data(), length));
     return ret;
 }
 
 static inline void writeToFd(android::base::borrowed_fd fd, const Parcelable& parcelable) {
     Parcel parcel;
-    CHECK_EQ(OK, parcelable.writeToParcel(&parcel));
+    LOG_ALWAYS_FATAL_IF(OK != parcelable.writeToParcel(&parcel));
     writeString(fd, std::string(reinterpret_cast<const char*>(parcel.data()), parcel.dataSize()));
 }
 
@@ -178,9 +180,10 @@
 static inline T readFromFd(android::base::borrowed_fd fd) {
     std::string data = readString(fd);
     Parcel parcel;
-    CHECK_EQ(OK, parcel.setData(reinterpret_cast<const uint8_t*>(data.data()), data.size()));
+    LOG_ALWAYS_FATAL_IF(OK !=
+                        parcel.setData(reinterpret_cast<const uint8_t*>(data.data()), data.size()));
     T object;
-    CHECK_EQ(OK, object.readFromParcel(&parcel));
+    LOG_ALWAYS_FATAL_IF(OK != object.readFromParcel(&parcel));
     return object;
 }
 
@@ -207,7 +210,7 @@
 // Create an FD that returns `contents` when read.
 static inline base::unique_fd mockFileDescriptor(std::string contents) {
     android::base::unique_fd readFd, writeFd;
-    CHECK(android::base::Pipe(&readFd, &writeFd)) << strerror(errno);
+    LOG_ALWAYS_FATAL_IF(!android::base::Pipe(&readFd, &writeFd), "%s", strerror(errno));
     RpcMaybeThread([writeFd = std::move(writeFd), contents = std::move(contents)]() {
         signal(SIGPIPE, SIG_IGN); // ignore possible SIGPIPE from the write
         if (!WriteStringToFd(contents, writeFd)) {
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index 7435f30..5025bd6 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -65,7 +65,7 @@
         std::string acc;
         for (const auto& file : files) {
             std::string result;
-            CHECK(android::base::ReadFdToString(file.get(), &result));
+            LOG_ALWAYS_FATAL_IF(!android::base::ReadFdToString(file.get(), &result));
             acc.append(result);
         }
         out->reset(mockFileDescriptor(acc));
@@ -98,7 +98,7 @@
 };
 
 int main(int argc, char* argv[]) {
-    android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+    __android_log_set_logger(__android_log_stderr_logger);
 
     LOG_ALWAYS_FATAL_IF(argc != 3, "Invalid number of arguments: %d", argc);
     base::unique_fd writeEnd(atoi(argv[1]));
@@ -118,7 +118,7 @@
     auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
     sp<RpcServer> server = RpcServer::make(newTlsFactory(rpcSecurity, certVerifier));
 
-    CHECK(server->setProtocolVersion(serverConfig.serverVersion));
+    LOG_ALWAYS_FATAL_IF(!server->setProtocolVersion(serverConfig.serverVersion));
     server->setMaxThreads(serverConfig.numThreads);
     server->setSupportedFileDescriptorTransportModes(serverSupportedFileDescriptorTransportModes);
 
@@ -129,22 +129,25 @@
         case SocketType::PRECONNECTED:
             [[fallthrough]];
         case SocketType::UNIX:
-            CHECK_EQ(OK, server->setupUnixDomainServer(serverConfig.addr.c_str()))
-                    << serverConfig.addr;
+            LOG_ALWAYS_FATAL_IF(OK != server->setupUnixDomainServer(serverConfig.addr.c_str()),
+                                "%s", serverConfig.addr.c_str());
             break;
         case SocketType::UNIX_BOOTSTRAP:
-            CHECK_EQ(OK, server->setupUnixDomainSocketBootstrapServer(std::move(socketFd)));
+            LOG_ALWAYS_FATAL_IF(OK !=
+                                server->setupUnixDomainSocketBootstrapServer(std::move(socketFd)));
             break;
         case SocketType::UNIX_RAW:
-            CHECK_EQ(OK, server->setupRawSocketServer(std::move(socketFd)));
+            LOG_ALWAYS_FATAL_IF(OK != server->setupRawSocketServer(std::move(socketFd)));
             break;
         case SocketType::VSOCK:
-            CHECK_EQ(OK, server->setupVsockServer(VMADDR_CID_LOCAL, serverConfig.vsockPort))
-                    << "Need `sudo modprobe vsock_loopback`?";
+            LOG_ALWAYS_FATAL_IF(OK !=
+                                        server->setupVsockServer(VMADDR_CID_LOCAL,
+                                                                 serverConfig.vsockPort),
+                                "Need `sudo modprobe vsock_loopback`?");
             break;
         case SocketType::INET: {
-            CHECK_EQ(OK, server->setupInetServer(kLocalInetAddress, 0, &outPort));
-            CHECK_NE(0, outPort);
+            LOG_ALWAYS_FATAL_IF(OK != server->setupInetServer(kLocalInetAddress, 0, &outPort));
+            LOG_ALWAYS_FATAL_IF(0 == outPort);
             break;
         }
         default:
@@ -159,21 +162,21 @@
 
     if (rpcSecurity == RpcSecurity::TLS) {
         for (const auto& clientCert : clientInfo.certs) {
-            CHECK_EQ(OK,
-                     certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
-                                                             clientCert.data));
+            LOG_ALWAYS_FATAL_IF(OK !=
+                                certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
+                                                                        clientCert.data));
         }
     }
 
     server->setPerSessionRootObject([&](wp<RpcSession> session, const void* addrPtr, size_t len) {
         {
             sp<RpcSession> spSession = session.promote();
-            CHECK_NE(nullptr, spSession.get());
+            LOG_ALWAYS_FATAL_IF(nullptr == spSession.get());
         }
 
         // UNIX sockets with abstract addresses return
         // sizeof(sa_family_t)==2 in addrlen
-        CHECK_GE(len, sizeof(sa_family_t));
+        LOG_ALWAYS_FATAL_IF(len < sizeof(sa_family_t));
         const sockaddr* addr = reinterpret_cast<const sockaddr*>(addrPtr);
         sp<MyBinderRpcTestAndroid> service = sp<MyBinderRpcTestAndroid>::make();
         switch (addr->sa_family) {
@@ -181,15 +184,15 @@
                 // nothing to save
                 break;
             case AF_VSOCK:
-                CHECK_EQ(len, sizeof(sockaddr_vm));
+                LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_vm));
                 service->port = reinterpret_cast<const sockaddr_vm*>(addr)->svm_port;
                 break;
             case AF_INET:
-                CHECK_EQ(len, sizeof(sockaddr_in));
+                LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_in));
                 service->port = ntohs(reinterpret_cast<const sockaddr_in*>(addr)->sin_port);
                 break;
             case AF_INET6:
-                CHECK_EQ(len, sizeof(sockaddr_in));
+                LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_in));
                 service->port = ntohs(reinterpret_cast<const sockaddr_in6*>(addr)->sin6_port);
                 break;
             default:
diff --git a/libs/binder/trusty/binderRpcTest/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json
index 1cefac5..6e20b8a 100644
--- a/libs/binder/trusty/binderRpcTest/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/manifest.json
@@ -2,5 +2,5 @@
     "uuid": "9dbe9fb8-60fd-4bdd-af86-03e95d7ad78b",
     "app_name": "binderRpcTest",
     "min_heap": 262144,
-    "min_stack": 16384
+    "min_stack": 20480
 }
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
index 1f05ef7..d2b37aa 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -24,7 +24,6 @@
 FMTLIB_DIR := external/fmtlib
 
 MODULE_SRCS := \
-	$(LOCAL_DIR)/../logging.cpp \
 	$(LOCAL_DIR)/../TrustyStatus.cpp \
 	$(LIBBINDER_DIR)/Binder.cpp \
 	$(LIBBINDER_DIR)/BpBinder.cpp \
diff --git a/libs/binder/trusty/logging.cpp b/libs/binder/trusty/logging.cpp
deleted file mode 100644
index 88a1075..0000000
--- a/libs/binder/trusty/logging.cpp
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define TLOG_TAG "libbinder"
-
-#include "android-base/logging.h"
-
-#include <trusty_log.h>
-#include <iostream>
-#include <string>
-
-#include <android-base/strings.h>
-
-namespace android {
-namespace base {
-
-static const char* GetFileBasename(const char* file) {
-    const char* last_slash = strrchr(file, '/');
-    if (last_slash != nullptr) {
-        return last_slash + 1;
-    }
-    return file;
-}
-
-// This splits the message up line by line, by calling log_function with a pointer to the start of
-// each line and the size up to the newline character.  It sends size = -1 for the final line.
-template <typename F, typename... Args>
-static void SplitByLines(const char* msg, const F& log_function, Args&&... args) {
-    const char* newline;
-    while ((newline = strchr(msg, '\n')) != nullptr) {
-        log_function(msg, newline - msg, args...);
-        msg = newline + 1;
-    }
-
-    log_function(msg, -1, args...);
-}
-
-void DefaultAborter(const char* abort_message) {
-    TLOGC("aborting: %s\n", abort_message);
-    abort();
-}
-
-static void TrustyLogLine(const char* msg, int /*length*/, android::base::LogSeverity severity,
-                          const char* tag) {
-    switch (severity) {
-        case VERBOSE:
-        case DEBUG:
-            TLOGD("%s: %s\n", tag, msg);
-            break;
-        case INFO:
-            TLOGI("%s: %s\n", tag, msg);
-            break;
-        case WARNING:
-            TLOGW("%s: %s\n", tag, msg);
-            break;
-        case ERROR:
-            TLOGE("%s: %s\n", tag, msg);
-            break;
-        case FATAL_WITHOUT_ABORT:
-        case FATAL:
-            TLOGC("%s: %s\n", tag, msg);
-            break;
-    }
-}
-
-void TrustyLogger(android::base::LogId, android::base::LogSeverity severity, const char* tag,
-                  const char*, unsigned int, const char* full_message) {
-    SplitByLines(full_message, TrustyLogLine, severity, tag);
-}
-
-// This indirection greatly reduces the stack impact of having lots of
-// checks/logging in a function.
-class LogMessageData {
-public:
-    LogMessageData(const char* file, unsigned int line, LogSeverity severity, const char* tag,
-                   int error)
-          : file_(GetFileBasename(file)),
-            line_number_(line),
-            severity_(severity),
-            tag_(tag),
-            error_(error) {}
-
-    const char* GetFile() const { return file_; }
-
-    unsigned int GetLineNumber() const { return line_number_; }
-
-    LogSeverity GetSeverity() const { return severity_; }
-
-    const char* GetTag() const { return tag_; }
-
-    int GetError() const { return error_; }
-
-    std::ostream& GetBuffer() { return buffer_; }
-
-    std::string ToString() const { return buffer_.str(); }
-
-private:
-    std::ostringstream buffer_;
-    const char* const file_;
-    const unsigned int line_number_;
-    const LogSeverity severity_;
-    const char* const tag_;
-    const int error_;
-
-    DISALLOW_COPY_AND_ASSIGN(LogMessageData);
-};
-
-LogMessage::LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity,
-                       const char* tag, int error)
-      : LogMessage(file, line, severity, tag, error) {}
-
-LogMessage::LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag,
-                       int error)
-      : data_(new LogMessageData(file, line, severity, tag, error)) {}
-
-LogMessage::~LogMessage() {
-    // Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.
-    if (!WOULD_LOG(data_->GetSeverity())) {
-        return;
-    }
-
-    // Finish constructing the message.
-    if (data_->GetError() != -1) {
-        data_->GetBuffer() << ": " << strerror(data_->GetError());
-    }
-    std::string msg(data_->ToString());
-
-    LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), data_->GetTag(),
-            msg.c_str());
-
-    // Abort if necessary.
-    if (data_->GetSeverity() == FATAL) {
-        DefaultAborter(msg.c_str());
-    }
-}
-
-std::ostream& LogMessage::stream() {
-    return data_->GetBuffer();
-}
-
-void LogMessage::LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag,
-                         const char* message) {
-    TrustyLogger(DEFAULT, severity, tag ?: "<unknown>", file, line, message);
-}
-
-bool ShouldLog(LogSeverity /*severity*/, const char* /*tag*/) {
-    // This is controlled by Trusty's log level.
-    return true;
-}
-
-} // namespace base
-} // namespace android
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 2e56cbd..9cad556 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -24,7 +24,6 @@
 FMTLIB_DIR := external/fmtlib
 
 MODULE_SRCS := \
-	$(LOCAL_DIR)/logging.cpp \
 	$(LOCAL_DIR)/OS.cpp \
 	$(LOCAL_DIR)/RpcServerTrusty.cpp \
 	$(LOCAL_DIR)/RpcTransportTipcTrusty.cpp \
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 8d0331e..f317a2e 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -41,6 +41,8 @@
 #include <android-base/thread_annotations.h>
 #include <chrono>
 
+#include <com_android_graphics_libgui_flags.h>
+
 using namespace com::android::graphics::libgui;
 using namespace std::chrono_literals;
 
@@ -102,12 +104,11 @@
     }
 }
 
-void BLASTBufferItemConsumer::updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
-                                                    const sp<Fence>& glDoneFence,
-                                                    const sp<Fence>& presentFence,
-                                                    const sp<Fence>& prevReleaseFence,
-                                                    CompositorTiming compositorTiming,
-                                                    nsecs_t latchTime, nsecs_t dequeueReadyTime) {
+void BLASTBufferItemConsumer::updateFrameTimestamps(
+        uint64_t frameNumber, uint64_t previousFrameNumber, nsecs_t refreshStartTime,
+        const sp<Fence>& glDoneFence, const sp<Fence>& presentFence,
+        const sp<Fence>& prevReleaseFence, CompositorTiming compositorTiming, nsecs_t latchTime,
+        nsecs_t dequeueReadyTime) {
     Mutex::Autolock lock(mMutex);
 
     // if the producer is not connected, don't bother updating,
@@ -118,7 +119,15 @@
     std::shared_ptr<FenceTime> releaseFenceTime = std::make_shared<FenceTime>(prevReleaseFence);
 
     mFrameEventHistory.addLatch(frameNumber, latchTime);
-    mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+    if (flags::frametimestamps_previousrelease()) {
+        if (previousFrameNumber > 0) {
+            mFrameEventHistory.addRelease(previousFrameNumber, dequeueReadyTime,
+                                          std::move(releaseFenceTime));
+        }
+    } else {
+        mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+    }
+
     mFrameEventHistory.addPreComposition(frameNumber, refreshStartTime);
     mFrameEventHistory.addPostComposition(frameNumber, glDoneFenceTime, presentFenceTime,
                                           compositorTiming);
@@ -364,6 +373,7 @@
                 if (stat.latchTime > 0) {
                     mBufferItemConsumer
                             ->updateFrameTimestamps(stat.frameEventStats.frameNumber,
+                                                    stat.frameEventStats.previousFrameNumber,
                                                     stat.frameEventStats.refreshStartTime,
                                                     stat.frameEventStats.gpuCompositionDoneFence,
                                                     stat.presentFence, stat.previousReleaseFence,
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 5b34ba1..b6a47fb 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -318,35 +318,44 @@
     ATRACE_CALL();
     ATRACE_BUFFER_INDEX(slot);
     BQ_LOGV("detachBuffer: slot %d", slot);
-    std::lock_guard<std::mutex> lock(mCore->mMutex);
+    sp<IProducerListener> listener;
+    {
+        std::lock_guard<std::mutex> lock(mCore->mMutex);
 
-    if (mCore->mIsAbandoned) {
-        BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
-        return NO_INIT;
+        if (mCore->mIsAbandoned) {
+            BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
+            return NO_INIT;
+        }
+
+        if (mCore->mSharedBufferMode || slot == mCore->mSharedBufferSlot) {
+            BQ_LOGE("detachBuffer: detachBuffer not allowed in shared buffer mode");
+            return BAD_VALUE;
+        }
+
+        if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+            BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
+                    slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+            return BAD_VALUE;
+        } else if (!mSlots[slot].mBufferState.isAcquired()) {
+            BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
+                    "(state = %s)", slot, mSlots[slot].mBufferState.string());
+            return BAD_VALUE;
+        }
+        if (mCore->mBufferReleasedCbEnabled) {
+            listener = mCore->mConnectedProducerListener;
+        }
+
+        mSlots[slot].mBufferState.detachConsumer();
+        mCore->mActiveBuffers.erase(slot);
+        mCore->mFreeSlots.insert(slot);
+        mCore->clearBufferSlotLocked(slot);
+        mCore->mDequeueCondition.notify_all();
+        VALIDATE_CONSISTENCY();
     }
 
-    if (mCore->mSharedBufferMode || slot == mCore->mSharedBufferSlot) {
-        BQ_LOGE("detachBuffer: detachBuffer not allowed in shared buffer mode");
-        return BAD_VALUE;
+    if (listener) {
+        listener->onBufferDetached(slot);
     }
-
-    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
-        BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
-                slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
-        return BAD_VALUE;
-    } else if (!mSlots[slot].mBufferState.isAcquired()) {
-        BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
-                "(state = %s)", slot, mSlots[slot].mBufferState.string());
-        return BAD_VALUE;
-    }
-
-    mSlots[slot].mBufferState.detachConsumer();
-    mCore->mActiveBuffers.erase(slot);
-    mCore->mFreeSlots.insert(slot);
-    mCore->clearBufferSlotLocked(slot);
-    mCore->mDequeueCondition.notify_all();
-    VALIDATE_CONSISTENCY();
-
     return NO_ERROR;
 }
 
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 29d64af..f5d19aa 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -25,6 +25,10 @@
 #include <gui/LayerState.h>
 #include <private/gui/ParcelUtils.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
+
 namespace android {
 
 namespace { // Anonymous
@@ -49,6 +53,11 @@
     status_t err = output->writeUint64(frameNumber);
     if (err != NO_ERROR) return err;
 
+    if (flags::frametimestamps_previousrelease()) {
+        err = output->writeUint64(previousFrameNumber);
+        if (err != NO_ERROR) return err;
+    }
+
     if (gpuCompositionDoneFence) {
         err = output->writeBool(true);
         if (err != NO_ERROR) return err;
@@ -79,6 +88,11 @@
     status_t err = input->readUint64(&frameNumber);
     if (err != NO_ERROR) return err;
 
+    if (flags::frametimestamps_previousrelease()) {
+        err = input->readUint64(&previousFrameNumber);
+        if (err != NO_ERROR) return err;
+    }
+
     bool hasFence = false;
     err = input->readBool(&hasFence);
     if (err != NO_ERROR) return err;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index a351811..922b0dd 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -3122,12 +3122,12 @@
     return statusTFromBinderStatus(status);
 }
 
-status_t ScreenshotClient::captureDisplay(DisplayId displayId,
+status_t ScreenshotClient::captureDisplay(DisplayId displayId, const gui::CaptureArgs& captureArgs,
                                           const sp<IScreenCaptureListener>& captureListener) {
     sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
     if (s == nullptr) return NO_INIT;
 
-    binder::Status status = s->captureDisplayById(displayId.value, captureListener);
+    binder::Status status = s->captureDisplayById(displayId.value, captureArgs, captureListener);
     return statusTFromBinderStatus(status);
 }
 
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 2eb6bd6..6a4460b 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -66,8 +66,9 @@
 bool WindowInfo::operator==(const WindowInfo& info) const {
     return info.token == token && info.id == id && info.name == name &&
             info.dispatchingTimeout == dispatchingTimeout && info.frame == frame &&
-            info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
-            info.transform == transform && info.touchableRegion.hasSameRects(touchableRegion) &&
+            info.contentSize == contentSize && info.surfaceInset == surfaceInset &&
+            info.globalScaleFactor == globalScaleFactor && info.transform == transform &&
+            info.touchableRegion.hasSameRects(touchableRegion) &&
             info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
             info.ownerUid == ownerUid && info.packageName == packageName &&
             info.inputConfig == inputConfig && info.displayId == displayId &&
@@ -101,6 +102,8 @@
         parcel->writeInt32(
                 static_cast<std::underlying_type_t<WindowInfo::Type>>(layoutParamsType)) ?:
         parcel->write(frame) ?:
+        parcel->writeInt32(contentSize.width) ?:
+        parcel->writeInt32(contentSize.height) ?:
         parcel->writeInt32(surfaceInset) ?:
         parcel->writeFloat(globalScaleFactor) ?:
         parcel->writeFloat(alpha) ?:
@@ -150,6 +153,8 @@
     status = parcel->readInt32(&lpFlags) ?:
         parcel->readInt32(&lpType) ?:
         parcel->read(frame) ?:
+        parcel->readInt32(&contentSize.width) ?:
+        parcel->readInt32(&contentSize.height) ?:
         parcel->readInt32(&surfaceInset) ?:
         parcel->readFloat(&globalScaleFactor) ?:
         parcel->readFloat(&alpha) ?:
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
new file mode 100644
index 0000000..920d949
--- /dev/null
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.gui;
+
+parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index a7cf5dd..d24f8ee 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -16,6 +16,7 @@
 
 package android.gui;
 
+import android.gui.CaptureArgs;
 import android.gui.Color;
 import android.gui.CompositionPreference;
 import android.gui.ContentSamplingAttributes;
@@ -238,7 +239,8 @@
      * Capture the specified screen. This requires the READ_FRAME_BUFFER
      * permission.
      */
-    oneway void captureDisplayById(long displayId, IScreenCaptureListener listener);
+    oneway void captureDisplayById(long displayId, in CaptureArgs args,
+            IScreenCaptureListener listener);
 
     /**
      * Capture a subtree of the layer hierarchy, potentially ignoring the root node.
@@ -514,6 +516,13 @@
     void scheduleCommit();
 
     /**
+     * Force all window composition to the GPU (i.e. disable Hardware Overlays).
+     * This can help check if there is a bug in HW Composer.
+     * Requires root or android.permission.HARDWARE_TEST
+     */
+    void forceClientComposition(boolean enabled);
+
+    /**
      * Gets priority of the RenderEngine in SurfaceFlinger.
      */
     int getGpuContextPriority();
diff --git a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
index 17f4c63..2e270b7 100644
--- a/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
+++ b/libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp
@@ -141,8 +141,8 @@
     CompositorTiming compTiming;
     sp<Fence> previousFence = new Fence(memfd_create("pfd", MFD_ALLOW_SEALING));
     sp<Fence> gpuFence = new Fence(memfd_create("gfd", MFD_ALLOW_SEALING));
-    FrameEventHistoryStats frameStats(frameNumber, gpuFence, compTiming,
-                                      mFdp.ConsumeIntegral<int64_t>(),
+    FrameEventHistoryStats frameStats(frameNumber, mFdp.ConsumeIntegral<uint64_t>(), gpuFence,
+                                      compTiming, mFdp.ConsumeIntegral<int64_t>(),
                                       mFdp.ConsumeIntegral<int64_t>());
     std::vector<SurfaceControlStats> stats;
     sp<Fence> presentFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 3142103..ffe7e41 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -100,8 +100,8 @@
     MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override));
     MOCK_METHOD(binder::Status, captureDisplay,
                 (const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
-    MOCK_METHOD(binder::Status, captureDisplayById, (int64_t, const sp<IScreenCaptureListener>&),
-                (override));
+    MOCK_METHOD(binder::Status, captureDisplayById,
+                (int64_t, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&), (override));
     MOCK_METHOD(binder::Status, captureLayers,
                 (const LayerCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
     MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override));
@@ -154,6 +154,7 @@
     MOCK_METHOD(binder::Status, setDebugFlash, (int), (override));
     MOCK_METHOD(binder::Status, scheduleComposite, (), (override));
     MOCK_METHOD(binder::Status, scheduleCommit, (), (override));
+    MOCK_METHOD(binder::Status, forceClientComposition, (bool), (override));
     MOCK_METHOD(binder::Status, updateSmallAreaDetection,
                 (const std::vector<int32_t>&, const std::vector<float>&), (override));
     MOCK_METHOD(binder::Status, setSmallAreaDetectionThreshold, (int32_t, float), (override));
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 892215e..0e1a505 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -50,8 +50,8 @@
     void onDisconnect() override EXCLUDES(mMutex);
     void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
                                   FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex);
-    void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
-                               const sp<Fence>& gpuCompositionDoneFence,
+    void updateFrameTimestamps(uint64_t frameNumber, uint64_t previousFrameNumber,
+                               nsecs_t refreshStartTime, const sp<Fence>& gpuCompositionDoneFence,
                                const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
                                CompositorTiming compositorTiming, nsecs_t latchTime,
                                nsecs_t dequeueReadyTime) EXCLUDES(mMutex);
diff --git a/libs/gui/include/gui/IProducerListener.h b/libs/gui/include/gui/IProducerListener.h
index f7ffbb9..b15f501 100644
--- a/libs/gui/include/gui/IProducerListener.h
+++ b/libs/gui/include/gui/IProducerListener.h
@@ -49,6 +49,12 @@
     // onBuffersFreed is called from IGraphicBufferConsumer::discardFreeBuffers
     // to notify the producer that certain free buffers are discarded by the consumer.
     virtual void onBuffersDiscarded(const std::vector<int32_t>& slots) = 0; // Asynchronous
+    // onBufferDetached is called from IGraphicBufferConsumer::detachBuffer to
+    // notify the producer that a buffer slot is free and ready to be dequeued.
+    //
+    // This is called without any lock held and can be called concurrently by
+    // multiple threads.
+    virtual void onBufferDetached(int /*slot*/) {} // Asynchronous
 };
 
 #ifndef NO_BINDER
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index 364a57b..bc97cd0 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -95,15 +95,18 @@
     status_t readFromParcel(const Parcel* input) override;
 
     FrameEventHistoryStats() = default;
-    FrameEventHistoryStats(uint64_t fn, const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
+    FrameEventHistoryStats(uint64_t frameNumber, uint64_t previousFrameNumber,
+                           const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
                            nsecs_t refreshTime, nsecs_t dequeueReadyTime)
-          : frameNumber(fn),
+          : frameNumber(frameNumber),
+            previousFrameNumber(previousFrameNumber),
             gpuCompositionDoneFence(gpuCompFence),
             compositorTiming(compTiming),
             refreshStartTime(refreshTime),
             dequeueReadyTime(dequeueReadyTime) {}
 
     uint64_t frameNumber;
+    uint64_t previousFrameNumber;
     sp<Fence> gpuCompositionDoneFence;
     CompositorTiming compositorTiming;
     nsecs_t refreshStartTime;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 54c3aa7..5bf6c47 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -841,8 +841,14 @@
 class ScreenshotClient {
 public:
     static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
-    static status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+    static status_t captureDisplay(DisplayId, const gui::CaptureArgs&,
+                                   const sp<IScreenCaptureListener>&);
     static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+
+    [[deprecated]] static status_t captureDisplay(DisplayId id,
+                                                  const sp<IScreenCaptureListener>& listener) {
+        return captureDisplay(id, gui::CaptureArgs(), listener);
+    }
 };
 
 // ---------------------------------------------------------------------------
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index bd2eb74..dcc38d7 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -26,6 +26,7 @@
 #include <gui/constants.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
+#include <ui/Size.h>
 #include <ui/Transform.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
@@ -196,6 +197,9 @@
     /* These values are filled in by SurfaceFlinger. */
     Rect frame = Rect::INVALID_RECT;
 
+    // The real size of the content, excluding any crop. If no buffer is rendered, this is 0,0
+    ui::Size contentSize = ui::Size(0, 0);
+
     /*
      * SurfaceFlinger consumes this value to shrink the computed frame. This is
      * different from shrinking the touchable region in that it DOES shift the coordinate
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index a16be78..b081030 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -8,3 +8,10 @@
   is_fixed_read_only: true
 }
 
+flag {
+  name: "frametimestamps_previousrelease"
+  namespace: "core_graphics"
+  description: "Controls a fence fixup for timestamp apis"
+  bug: "310927247"
+  is_fixed_read_only: true
+}
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index 9893c71..ea7078d 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -42,9 +42,12 @@
 
 #include <gtest/gtest.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 using namespace std::chrono_literals;
 
 namespace android {
+using namespace com::android::graphics::libgui;
 
 using Transaction = SurfaceComposerClient::Transaction;
 using android::hardware::graphics::common::V1_2::BufferUsage;
@@ -1581,6 +1584,9 @@
     nsecs_t postedTimeB = 0;
     setUpAndQueueBuffer(igbProducer, &requestedPresentTimeB, &postedTimeB, &qbOutput, true);
     history.applyDelta(qbOutput.frameTimestamps);
+
+    adapter.waitForCallback(2);
+
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
 
@@ -1590,7 +1596,9 @@
     ASSERT_GE(events->postedTime, postedTimeA);
 
     ASSERT_GE(events->latchTime, postedTimeA);
-    ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+    if (flags::frametimestamps_previousrelease()) {
+        ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+    }
     ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
     ASSERT_NE(nullptr, events->displayPresentFence);
     ASSERT_NE(nullptr, events->releaseFence);
@@ -1602,6 +1610,50 @@
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
 
+    // Now do the same as above with a third buffer, so that timings related to
+    // buffer releases make it back to the first frame.
+    nsecs_t requestedPresentTimeC = 0;
+    nsecs_t postedTimeC = 0;
+    setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
+    history.applyDelta(qbOutput.frameTimestamps);
+
+    adapter.waitForCallback(3);
+
+    // Check the first frame...
+    events = history.getFrame(1);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeA);
+    ASSERT_GE(events->latchTime, postedTimeA);
+    // Now dequeueReadyTime is valid, because the release timings finally
+    // propaged to queueBuffer()
+    ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+    ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+    ASSERT_NE(nullptr, events->displayPresentFence);
+    ASSERT_NE(nullptr, events->releaseFence);
+
+    // ...and the second
+    events = history.getFrame(2);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeB);
+    ASSERT_GE(events->latchTime, postedTimeB);
+    if (flags::frametimestamps_previousrelease()) {
+        ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+    }
+    ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+    ASSERT_NE(nullptr, events->displayPresentFence);
+    ASSERT_NE(nullptr, events->releaseFence);
+
+    // ...and finally the third!
+    events = history.getFrame(3);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(3, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeC);
+
     // wait for any callbacks that have not been received
     adapter.waitForCallbacks();
 }
@@ -1660,6 +1712,8 @@
     setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
     history.applyDelta(qbOutput.frameTimestamps);
 
+    adapter.waitForCallback(3);
+
     // frame number, requestedPresentTime, and postTime should not have changed
     ASSERT_EQ(1, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
@@ -1679,6 +1733,42 @@
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
+
+    if (flags::frametimestamps_previousrelease()) {
+        ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+    }
+    ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+    ASSERT_NE(nullptr, events->displayPresentFence);
+    ASSERT_NE(nullptr, events->releaseFence);
+
+    // Queue another buffer to check for timestamps that came late
+    nsecs_t requestedPresentTimeD = 0;
+    nsecs_t postedTimeD = 0;
+    setUpAndQueueBuffer(igbProducer, &requestedPresentTimeD, &postedTimeD, &qbOutput, true);
+    history.applyDelta(qbOutput.frameTimestamps);
+
+    adapter.waitForCallback(4);
+
+    // frame number, requestedPresentTime, and postTime should not have changed
+    events = history.getFrame(1);
+    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeA);
+
+    // a valid latchtime and pre and post composition info should not be set for the dropped frame
+    ASSERT_FALSE(events->hasLatchInfo());
+    ASSERT_FALSE(events->hasDequeueReadyInfo());
+    ASSERT_FALSE(events->hasGpuCompositionDoneInfo());
+    ASSERT_FALSE(events->hasDisplayPresentInfo());
+    ASSERT_FALSE(events->hasReleaseInfo());
+
+    // we should also have gotten values for the presented frame
+    events = history.getFrame(2);
+    ASSERT_NE(nullptr, events);
+    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+    ASSERT_GE(events->postedTime, postedTimeB);
+    ASSERT_GE(events->latchTime, postedTimeB);
     ASSERT_GE(events->dequeueReadyTime, events->latchTime);
     ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
     ASSERT_NE(nullptr, events->displayPresentFence);
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 1410c7d..df7739c 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -1187,6 +1187,76 @@
     ASSERT_EQ(true, output.bufferReplaced);
 }
 
+struct BufferDetachedListener : public BnProducerListener {
+public:
+    BufferDetachedListener() = default;
+    virtual ~BufferDetachedListener() = default;
+
+    virtual void onBufferReleased() {}
+    virtual bool needsReleaseNotify() { return true; }
+    virtual void onBufferDetached(int slot) {
+        mDetachedSlots.push_back(slot);
+    }
+    const std::vector<int>& getDetachedSlots() const { return mDetachedSlots; }
+private:
+    std::vector<int> mDetachedSlots;
+};
+
+TEST_F(BufferQueueTest, TestConsumerDetachProducerListener) {
+    createBufferQueue();
+    sp<MockConsumer> mc(new MockConsumer);
+    ASSERT_EQ(OK, mConsumer->consumerConnect(mc, true));
+    IGraphicBufferProducer::QueueBufferOutput output;
+    sp<BufferDetachedListener> pl(new BufferDetachedListener);
+    ASSERT_EQ(OK, mProducer->connect(pl, NATIVE_WINDOW_API_CPU, true, &output));
+    ASSERT_EQ(OK, mProducer->setDequeueTimeout(0));
+    ASSERT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(1));
+
+    sp<Fence> fence = Fence::NO_FENCE;
+    sp<GraphicBuffer> buffer = nullptr;
+    IGraphicBufferProducer::QueueBufferInput input(0ull, true,
+        HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
+        NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
+
+    int slots[2] = {};
+    status_t result = OK;
+    ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(2));
+
+    result = mProducer->dequeueBuffer(&slots[0], &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+    ASSERT_EQ(OK, mProducer->requestBuffer(slots[0], &buffer));
+
+    result = mProducer->dequeueBuffer(&slots[1], &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+    ASSERT_EQ(OK, mProducer->requestBuffer(slots[1], &buffer));
+
+    // Queue & detach one from two dequeued buffes.
+    ASSERT_EQ(OK, mProducer->queueBuffer(slots[1], input, &output));
+    BufferItem item{};
+    ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+    ASSERT_EQ(OK, mConsumer->detachBuffer(item.mSlot));
+
+    // Check whether the slot from IProducerListener is same to the detached slot.
+    ASSERT_EQ(pl->getDetachedSlots().size(), 1);
+    ASSERT_EQ(pl->getDetachedSlots()[0], slots[1]);
+
+    // Dequeue another buffer.
+    int slot;
+    result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+    ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+
+    // Dequeue should fail here, since we dequeued 3 buffers and one buffer was
+    // detached from consumer(Two buffers are dequeued, and the current max
+    // dequeued buffer count is two).
+    result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0,
+                                      GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+    ASSERT_TRUE(result == WOULD_BLOCK || result == TIMED_OUT || result == INVALID_OPERATION);
+}
+
 TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
     createBufferQueue();
     sp<MockConsumer> mc(new MockConsumer);
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 86ced2c..60221aa 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -208,21 +208,6 @@
         ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
     }
 
-    static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
-                                   ScreenCaptureResults& captureResults) {
-        const auto sf = ComposerServiceAIDL::getComposerService();
-        SurfaceComposerClient::Transaction().apply(true);
-
-        const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
-        binder::Status status = sf->captureDisplay(captureArgs, captureListener);
-        status_t err = gui::aidl_utils::statusTFromBinderStatus(status);
-        if (err != NO_ERROR) {
-            return err;
-        }
-        captureResults = captureListener->waitForResults();
-        return fenceStatus(captureResults.fenceResult);
-    }
-
     sp<Surface> mSurface;
     sp<SurfaceComposerClient> mComposerClient;
     sp<SurfaceControl> mSurfaceControl;
@@ -260,56 +245,6 @@
     EXPECT_EQ(1, result);
 }
 
-// This test probably doesn't belong here.
-TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersDontSucceed) {
-    sp<ANativeWindow> anw(mSurface);
-
-    // Verify the screenshot works with no protected buffers.
-    const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-    ASSERT_FALSE(ids.empty());
-    // display 0 is picked for now, can extend to support all displays if needed
-    const sp<IBinder> display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-    ASSERT_FALSE(display == nullptr);
-
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = display;
-    captureArgs.width = 64;
-    captureArgs.height = 64;
-
-    ScreenCaptureResults captureResults;
-    ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-
-    ASSERT_EQ(NO_ERROR, native_window_api_connect(anw.get(),
-            NATIVE_WINDOW_API_CPU));
-    // Set the PROTECTED usage bit and verify that the screenshot fails.  Note
-    // that we need to dequeue a buffer in order for it to actually get
-    // allocated in SurfaceFlinger.
-    ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(),
-            GRALLOC_USAGE_PROTECTED));
-    ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(anw.get(), 3));
-    ANativeWindowBuffer* buf = nullptr;
-
-    status_t err = native_window_dequeue_buffer_and_wait(anw.get(), &buf);
-    if (err) {
-        // we could fail if GRALLOC_USAGE_PROTECTED is not supported.
-        // that's okay as long as this is the reason for the failure.
-        // try again without the GRALLOC_USAGE_PROTECTED bit.
-        ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(), 0));
-        ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
-                &buf));
-        return;
-    }
-    ASSERT_EQ(NO_ERROR, anw->cancelBuffer(anw.get(), buf, -1));
-
-    for (int i = 0; i < 4; i++) {
-        // Loop to make sure SurfaceFlinger has retired a protected buffer.
-        ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
-                &buf));
-        ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf, -1));
-    }
-    ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-}
-
 TEST_F(SurfaceTest, ConcreteTypeIsSurface) {
     sp<ANativeWindow> anw(mSurface);
     int result = -123;
@@ -851,7 +786,8 @@
         return binder::Status::ok();
     }
 
-    binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override {
+    binder::Status captureDisplayById(int64_t, const gui::CaptureArgs&,
+                                      const sp<IScreenCaptureListener>&) override {
         return binder::Status::ok();
     }
 
@@ -999,6 +935,10 @@
 
     binder::Status scheduleCommit() override { return binder::Status::ok(); }
 
+    binder::Status forceClientComposition(bool /*enabled*/) override {
+        return binder::Status::ok();
+    }
+
     binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*appIds*/,
                                             const std::vector<float>& /*thresholds*/) {
         return binder::Status::ok();
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index f2feaef..5eb5d3b 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -28,6 +28,7 @@
 using gui::InputApplicationInfo;
 using gui::TouchOcclusionMode;
 using gui::WindowInfo;
+using ui::Size;
 
 namespace test {
 
@@ -53,6 +54,7 @@
     i.layoutParamsType = WindowInfo::Type::INPUT_METHOD;
     i.dispatchingTimeout = 12s;
     i.frame = Rect(93, 34, 16, 19);
+    i.contentSize = Size(10, 40);
     i.surfaceInset = 17;
     i.globalScaleFactor = 0.3;
     i.alpha = 0.7;
@@ -83,6 +85,7 @@
     ASSERT_EQ(i.layoutParamsType, i2.layoutParamsType);
     ASSERT_EQ(i.dispatchingTimeout, i2.dispatchingTimeout);
     ASSERT_EQ(i.frame, i2.frame);
+    ASSERT_EQ(i.contentSize, i2.contentSize);
     ASSERT_EQ(i.surfaceInset, i2.surfaceInset);
     ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
     ASSERT_EQ(i.alpha, i2.alpha);
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index c37db16..dd8dc8d 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -50,7 +50,7 @@
     // overrides for flags, without having to link against a separate version of libinput or of this
     // library. Bundling this library directly into libinput prevents us from having to add this
     // library as a shared lib dependency everywhere where libinput is used.
-    test: true,
+    mode: "test",
     shared: {
         enabled: false,
     },
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index 9a459b1..db7031a 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -193,6 +193,7 @@
         {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
         {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
         {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
+        {AKEYCODE_LANGUAGE_SWITCH, KEY_LANGUAGE},
 };
 VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
 VirtualKeyboard::~VirtualKeyboard() {}
diff --git a/libs/permission/Android.bp b/libs/permission/Android.bp
index 86dcaef..0eeca54 100644
--- a/libs/permission/Android.bp
+++ b/libs/permission/Android.bp
@@ -20,12 +20,6 @@
     ],
 }
 
-filegroup {
-    name: "framework-permission-aidl-filegroup",
-    srcs: ["aidl/android/**/*.aidl"],
-    path: "aidl",
-}
-
 cc_library {
     name: "libpermission",
     host_supported: true,
@@ -41,7 +35,6 @@
         "-Werror",
     ],
     srcs: [
-        ":framework-permission-aidl-filegroup",
         "AppOpsManager.cpp",
         "IAppOpsCallback.cpp",
         "IAppOpsService.cpp",
diff --git a/libs/permission/AppOpsManager.cpp b/libs/permission/AppOpsManager.cpp
index b407d02..6959274 100644
--- a/libs/permission/AppOpsManager.cpp
+++ b/libs/permission/AppOpsManager.cpp
@@ -31,9 +31,6 @@
 
 namespace android {
 
-using ::android::String16;
-using ::android::String8;
-
 static const sp<IBinder>& getClientId() {
     static pthread_mutex_t gClientIdMutex = PTHREAD_MUTEX_INITIALIZER;
     static sp<IBinder> gClientId;
@@ -46,11 +43,6 @@
     return gClientId;
 }
 
-
-static std::string getString(const String16& stringToConvert) {
-  return std::string(String8(stringToConvert).c_str());
-}
-
 AppOpsManager::AppOpsManager()
 {
 }
@@ -86,14 +78,9 @@
 int32_t AppOpsManager::checkOp(int32_t op, int32_t uid, const String16& callingPackage)
 {
     sp<IAppOpsService> service = getService();
-    if (service == nullptr) {
-        return AppOpsManager::MODE_IGNORED;
-    }
-    AttributionSourceState attributionSourceState;
-    attributionSourceState.uid = uid;
-    attributionSourceState.packageName = getString(callingPackage);
-
-    return service->checkOperationWithState(op, attributionSourceState);
+    return service != nullptr
+            ? service->checkOperation(op, uid, callingPackage)
+            : AppOpsManager::MODE_IGNORED;
 }
 
 int32_t AppOpsManager::checkAudioOpNoThrow(int32_t op, int32_t usage, int32_t uid,
@@ -112,18 +99,12 @@
 int32_t AppOpsManager::noteOp(int32_t op, int32_t uid, const String16& callingPackage,
         const std::optional<String16>& attributionTag, const String16& message) {
     sp<IAppOpsService> service = getService();
-    if (service == nullptr) {
-        return AppOpsManager::MODE_IGNORED;
-    }
-    AttributionSourceState attributionSourceState;
-    attributionSourceState.uid = uid;
-    attributionSourceState.packageName = getString(callingPackage);
-    if (attributionTag.has_value()) {
-        attributionSourceState.attributionTag = getString(attributionTag.value());
-    }
+    int32_t mode = service != nullptr
+            ? service->noteOperation(op, uid, callingPackage, attributionTag,
+                    shouldCollectNotes(op), message, uid == AID_SYSTEM)
+            : AppOpsManager::MODE_IGNORED;
 
-    return service->noteOperationWithState(op, attributionSourceState,
-            shouldCollectNotes(op), message, uid == AID_SYSTEM);
+    return mode;
 }
 
 int32_t AppOpsManager::startOpNoThrow(int32_t op, int32_t uid, const String16& callingPackage,
@@ -136,18 +117,13 @@
         bool startIfModeDefault, const std::optional<String16>& attributionTag,
         const String16& message) {
     sp<IAppOpsService> service = getService();
-    if (service == nullptr) {
-        return AppOpsManager::MODE_IGNORED;
-    }
-    AttributionSourceState attributionSourceState;
-    attributionSourceState.uid = uid;
-    attributionSourceState.packageName = getString(callingPackage);
-    if (attributionTag.has_value()) {
-        attributionSourceState.attributionTag = getString(attributionTag.value());
-    }
+    int32_t mode = service != nullptr
+            ? service->startOperation(getClientId(), op, uid, callingPackage,
+                    attributionTag, startIfModeDefault, shouldCollectNotes(op), message,
+                    uid == AID_SYSTEM)
+            : AppOpsManager::MODE_IGNORED;
 
-    return service->startOperationWithState(getClientId(), op, attributionSourceState,
-            startIfModeDefault,shouldCollectNotes(op), message, uid == AID_SYSTEM);
+    return mode;
 }
 
 void AppOpsManager::finishOp(int32_t op, int32_t uid, const String16& callingPackage) {
@@ -157,16 +133,9 @@
 void AppOpsManager::finishOp(int32_t op, int32_t uid, const String16& callingPackage,
         const std::optional<String16>& attributionTag) {
     sp<IAppOpsService> service = getService();
-    if (service == nullptr) {
-        return;
+    if (service != nullptr) {
+        service->finishOperation(getClientId(), op, uid, callingPackage, attributionTag);
     }
-    AttributionSourceState attributionSourceState;
-    attributionSourceState.uid = uid;
-    attributionSourceState.packageName = getString(callingPackage);
-    if (attributionTag.has_value()) {
-        attributionSourceState.attributionTag = getString(attributionTag.value());
-    }
-    service->finishOperationWithState(getClientId(), op, attributionSourceState);
 }
 
 void AppOpsManager::startWatchingMode(int32_t op, const String16& packageName,
diff --git a/libs/permission/IAppOpsService.cpp b/libs/permission/IAppOpsService.cpp
index 33dd24d..7f235a4 100644
--- a/libs/permission/IAppOpsService.cpp
+++ b/libs/permission/IAppOpsService.cpp
@@ -26,8 +26,6 @@
 
 namespace android {
 
-using android::content::AttributionSourceState;
-
 // ----------------------------------------------------------------------
 
 class BpAppOpsService : public BpInterface<IAppOpsService>
@@ -38,30 +36,31 @@
     {
     }
 
-    virtual int32_t checkOperationWithState(int32_t code,
-                const AttributionSourceState &attributionSourceState) {
+    virtual int32_t checkOperation(int32_t code, int32_t uid, const String16& packageName) {
         Parcel data, reply;
         data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor());
         data.writeInt32(code);
-        data.writeParcelable(attributionSourceState);
-        remote()->transact(CHECK_OPERATION_WITH_STATE_TRANSACTION, data, &reply);
+        data.writeInt32(uid);
+        data.writeString16(packageName);
+        remote()->transact(CHECK_OPERATION_TRANSACTION, data, &reply);
         // fail on exception
         if (reply.readExceptionCode() != 0) return MODE_ERRORED;
         return reply.readInt32();
     }
 
-    virtual int32_t noteOperationWithState(int32_t code,
-                const AttributionSourceState& attributionSourceState,
-                bool shouldCollectAsyncNotedOp, const String16& message,
-                bool shouldCollectMessage) {
+    virtual int32_t noteOperation(int32_t code, int32_t uid, const String16& packageName,
+                const std::optional<String16>& attributionTag, bool shouldCollectAsyncNotedOp,
+                const String16& message, bool shouldCollectMessage) {
         Parcel data, reply;
         data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor());
         data.writeInt32(code);
-        data.writeParcelable(attributionSourceState);
+        data.writeInt32(uid);
+        data.writeString16(packageName);
+        data.writeString16(attributionTag);
         data.writeBool(shouldCollectAsyncNotedOp);
         data.writeString16(message);
         data.writeBool(shouldCollectMessage);
-        remote()->transact(NOTE_OPERATION_WITH_STATE_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_OPERATION_TRANSACTION, data, &reply);
         // fail on exception
         if (reply.readExceptionCode() != 0) return MODE_ERRORED;
         // TODO b/184855056: extract to class
@@ -70,20 +69,22 @@
         return reply.readInt32();
     }
 
-    virtual int32_t startOperationWithState(const sp<IBinder>& token, int32_t code,
-                const AttributionSourceState& attributionSourceState, bool startIfModeDefault,
-                bool shouldCollectAsyncNotedOp, const String16& message,
+    virtual int32_t startOperation(const sp<IBinder>& token, int32_t code, int32_t uid,
+                const String16& packageName, const std::optional<String16>& attributionTag,
+                bool startIfModeDefault, bool shouldCollectAsyncNotedOp, const String16& message,
                 bool shouldCollectMessage) {
         Parcel data, reply;
         data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor());
         data.writeStrongBinder(token);
         data.writeInt32(code);
-        data.writeParcelable(attributionSourceState);
+        data.writeInt32(uid);
+        data.writeString16(packageName);
+        data.writeString16(attributionTag);
         data.writeBool(startIfModeDefault);
         data.writeBool(shouldCollectAsyncNotedOp);
         data.writeString16(message);
         data.writeBool(shouldCollectMessage);
-        remote()->transact(START_OPERATION_WITH_STATE_TRANSACTION, data, &reply);
+        remote()->transact(START_OPERATION_TRANSACTION, data, &reply);
         // fail on exception
         if (reply.readExceptionCode() != 0) return MODE_ERRORED;
         // TODO b/184855056: extract to class
@@ -92,14 +93,16 @@
         return reply.readInt32();
     }
 
-    virtual void finishOperationWithState(const sp<IBinder>& token, int32_t code,
-                const AttributionSourceState& attributionSourceState) {
+    virtual void finishOperation(const sp<IBinder>& token, int32_t code, int32_t uid,
+            const String16& packageName, const std::optional<String16>& attributionTag) {
         Parcel data, reply;
         data.writeInterfaceToken(IAppOpsService::getInterfaceDescriptor());
         data.writeStrongBinder(token);
         data.writeInt32(code);
-        data.writeParcelable(attributionSourceState);
-        remote()->transact(FINISH_OPERATION_WITH_STATE_TRANSACTION, data, &reply);
+        data.writeInt32(uid);
+        data.writeString16(packageName);
+        data.writeString16(attributionTag);
+        remote()->transact(FINISH_OPERATION_TRANSACTION, data, &reply);
     }
 
     virtual void startWatchingMode(int32_t op, const String16& packageName,
@@ -186,65 +189,59 @@
 {
     //printf("AppOpsService received: "); data.print();
     switch(code) {
-        case CHECK_OPERATION_WITH_STATE_TRANSACTION: {
+        case CHECK_OPERATION_TRANSACTION: {
             CHECK_INTERFACE(IAppOpsService, data, reply);
             int32_t code = data.readInt32();
-            AttributionSourceState attributionSourceState;
-            status_t status = data.readParcelable(&attributionSourceState);
-            if (status != NO_ERROR) {
-                return status;
-            }
-            int32_t res = checkOperationWithState(code, attributionSourceState);
+            int32_t uid = data.readInt32();
+            String16 packageName = data.readString16();
+            int32_t res = checkOperation(code, uid, packageName);
             reply->writeNoException();
             reply->writeInt32(res);
             return NO_ERROR;
         } break;
-        case NOTE_OPERATION_WITH_STATE_TRANSACTION: {
+        case NOTE_OPERATION_TRANSACTION: {
             CHECK_INTERFACE(IAppOpsService, data, reply);
             int32_t code = data.readInt32();
-            AttributionSourceState attributionSourceState;
-            status_t status = data.readParcelable(&attributionSourceState);
-            if (status != NO_ERROR) {
-                return status;
-            }
+            int32_t uid = data.readInt32();
+            String16 packageName = data.readString16();
+            std::optional<String16> attributionTag;
+            data.readString16(&attributionTag);
             bool shouldCollectAsyncNotedOp = data.readBool();
             String16 message = data.readString16();
             bool shouldCollectMessage = data.readBool();
-            int32_t res = noteOperationWithState(code, attributionSourceState,
+            int32_t res = noteOperation(code, uid, packageName, attributionTag,
                     shouldCollectAsyncNotedOp, message, shouldCollectMessage);
             reply->writeNoException();
             reply->writeInt32(res);
             return NO_ERROR;
         } break;
-        case START_OPERATION_WITH_STATE_TRANSACTION: {
+        case START_OPERATION_TRANSACTION: {
             CHECK_INTERFACE(IAppOpsService, data, reply);
             sp<IBinder> token = data.readStrongBinder();
             int32_t code = data.readInt32();
-            AttributionSourceState attributionSourceState;
-            status_t status = data.readParcelable(&attributionSourceState);
-            if (status != NO_ERROR) {
-                return status;
-            }
+            int32_t uid = data.readInt32();
+            String16 packageName = data.readString16();
+            std::optional<String16> attributionTag;
+            data.readString16(&attributionTag);
             bool startIfModeDefault = data.readBool();
             bool shouldCollectAsyncNotedOp = data.readBool();
             String16 message = data.readString16();
             bool shouldCollectMessage = data.readBool();
-            int32_t res = startOperationWithState(token, code, attributionSourceState,
+            int32_t res = startOperation(token, code, uid, packageName, attributionTag,
                     startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
             reply->writeNoException();
             reply->writeInt32(res);
             return NO_ERROR;
         } break;
-        case FINISH_OPERATION_WITH_STATE_TRANSACTION: {
+        case FINISH_OPERATION_TRANSACTION: {
             CHECK_INTERFACE(IAppOpsService, data, reply);
             sp<IBinder> token = data.readStrongBinder();
             int32_t code = data.readInt32();
-            AttributionSourceState attributionSourceState;
-            status_t status = data.readParcelable(&attributionSourceState);
-            if (status != NO_ERROR) {
-                return status;
-            }
-            finishOperationWithState(token, code, attributionSourceState);
+            int32_t uid = data.readInt32();
+            String16 packageName = data.readString16();
+            std::optional<String16> attributionTag;
+            data.readString16(&attributionTag);
+            finishOperation(token, code, uid, packageName, attributionTag);
             reply->writeNoException();
             return NO_ERROR;
         } break;
diff --git a/libs/permission/include/binder/IAppOpsService.h b/libs/permission/include/binder/IAppOpsService.h
index a5fdc54..918fcdb 100644
--- a/libs/permission/include/binder/IAppOpsService.h
+++ b/libs/permission/include/binder/IAppOpsService.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <android/content/AttributionSourceState.h>
 #include <binder/IAppOpsCallback.h>
 #include <binder/IInterface.h>
 
@@ -28,24 +27,23 @@
 
 namespace android {
 
-using android::content::AttributionSourceState;
-
 // ----------------------------------------------------------------------
 
 class IAppOpsService : public IInterface
 {
 public:
     DECLARE_META_INTERFACE(AppOpsService)
-    virtual int32_t checkOperationWithState(int32_t code,
-            const AttributionSourceState& attributionSourceState) = 0;
-    virtual int32_t noteOperationWithState(int32_t code,
-            const AttributionSourceState& attributionSourceState, bool shouldCollectAsyncNotedOp,
+
+    virtual int32_t checkOperation(int32_t code, int32_t uid, const String16& packageName) = 0;
+    virtual int32_t noteOperation(int32_t code, int32_t uid, const String16& packageName,
+            const std::optional<String16>& attributionTag, bool shouldCollectAsyncNotedOp,
             const String16& message, bool shouldCollectMessage) = 0;
-    virtual int32_t startOperationWithState(const sp<IBinder>& token, int32_t code,
-            const AttributionSourceState& attributionSourceState, bool startIfModeDefault,
-            bool shouldCollectAsyncNotedOp, const String16& message, bool shouldCollectMessage) = 0;
-    virtual void finishOperationWithState(const sp<IBinder>& token, int32_t code,
-            const AttributionSourceState& attributionSourceState) = 0;
+    virtual int32_t startOperation(const sp<IBinder>& token, int32_t code, int32_t uid,
+            const String16& packageName, const std::optional<String16>& attributionTag,
+            bool startIfModeDefault, bool shouldCollectAsyncNotedOp, const String16& message,
+            bool shouldCollectMessage) = 0;
+    virtual void finishOperation(const sp<IBinder>& token, int32_t code, int32_t uid,
+            const String16& packageName, const std::optional<String16>& attributionTag) = 0;
     virtual void startWatchingMode(int32_t op, const String16& packageName,
             const sp<IAppOpsCallback>& callback) = 0;
     virtual void stopWatchingMode(const sp<IAppOpsCallback>& callback) = 0;
@@ -58,10 +56,10 @@
             int32_t flags, const sp<IAppOpsCallback>& callback) = 0;
 
     enum {
-        CHECK_OPERATION_WITH_STATE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+50,
-        NOTE_OPERATION_WITH_STATE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+52,
-        START_OPERATION_WITH_STATE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+53,
-        FINISH_OPERATION_WITH_STATE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+54,
+        CHECK_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
+        NOTE_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+1,
+        START_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+2,
+        FINISH_OPERATION_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+3,
         START_WATCHING_MODE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+4,
         STOP_WATCHING_MODE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+5,
         PERMISSION_TO_OP_CODE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION+6,
diff --git a/libs/ui/FenceTime.cpp b/libs/ui/FenceTime.cpp
index 538c1d2..4246c40 100644
--- a/libs/ui/FenceTime.cpp
+++ b/libs/ui/FenceTime.cpp
@@ -363,9 +363,9 @@
 }
 
 void FenceToFenceTimeMap::garbageCollectLocked() {
-    for (auto& it : mMap) {
+    for (auto it = mMap.begin(); it != mMap.end();) {
         // Erase all expired weak pointers from the vector.
-        auto& vect = it.second;
+        auto& vect = it->second;
         vect.erase(
                 std::remove_if(vect.begin(), vect.end(),
                         [](const std::weak_ptr<FenceTime>& ft) {
@@ -375,7 +375,9 @@
 
         // Also erase the map entry if the vector is now empty.
         if (vect.empty()) {
-            mMap.erase(it.first);
+            it = mMap.erase(it);
+        } else {
+            it++;
         }
     }
 }
diff --git a/opengl/Android.bp b/opengl/Android.bp
index b15694b..4454f36 100644
--- a/opengl/Android.bp
+++ b/opengl/Android.bp
@@ -72,6 +72,10 @@
     llndk: {
         llndk_headers: true,
     },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 subdirs = [
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index ed3c616..9905210 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -18,6 +18,7 @@
 
 #include "MultifileBlobCache.h"
 
+#include <android-base/properties.h>
 #include <dirent.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -62,12 +63,15 @@
 namespace android {
 
 MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
-                                       const std::string& baseDir)
+                                       size_t maxTotalEntries, const std::string& baseDir)
       : mInitialized(false),
+        mCacheVersion(0),
         mMaxKeySize(maxKeySize),
         mMaxValueSize(maxValueSize),
         mMaxTotalSize(maxTotalSize),
+        mMaxTotalEntries(maxTotalEntries),
         mTotalCacheSize(0),
+        mTotalCacheEntries(0),
         mHotCacheLimit(0),
         mHotCacheSize(0),
         mWorkerThreadIdle(true) {
@@ -76,6 +80,26 @@
         return;
     }
 
+    // Set the cache version, override if debug value set
+    mCacheVersion = kMultifileBlobCacheVersion;
+    int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
+    if (debugCacheVersion >= 0) {
+        ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
+        mCacheVersion = debugCacheVersion;
+    }
+
+    // Set the platform build ID, override if debug value set
+    mBuildId = base::GetProperty("ro.build.id", "");
+    std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
+    if (!debugBuildId.empty()) {
+        ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
+        if (debugBuildId.length() > PROP_VALUE_MAX) {
+            ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
+                  PROP_VALUE_MAX);
+        }
+        mBuildId = debugBuildId;
+    }
+
     // Establish the name of our multifile directory
     mMultifileDirName = baseDir + ".multifile";
 
@@ -93,14 +117,30 @@
     mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
 
     // See if the dir exists, and initialize using its contents
+    bool statusGood = false;
+
+    // Check that our cacheVersion and buildId match
     struct stat st;
     if (stat(mMultifileDirName.c_str(), &st) == 0) {
+        if (checkStatus(mMultifileDirName.c_str())) {
+            statusGood = true;
+        } else {
+            ALOGV("INIT: Cache status has changed, clearing the cache");
+            if (!clearCache()) {
+                ALOGE("INIT: Unable to clear cache");
+                return;
+            }
+        }
+    }
+
+    if (statusGood) {
         // Read all the files and gather details, then preload their contents
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
             while ((entry = readdir(dir)) != nullptr) {
-                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                if (entry->d_name == "."s || entry->d_name == ".."s ||
+                    strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
 
@@ -123,7 +163,8 @@
                 if (st.st_size <= 0 || st.st_atime <= 0) {
                     ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     continue;
                 }
@@ -140,7 +181,7 @@
                 MultifileHeader header;
                 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
                 if (result != sizeof(MultifileHeader)) {
-                    ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
+                    ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
                           fullPath.c_str(), std::strerror(errno));
                     close(fd);
                     return;
@@ -150,7 +191,8 @@
                 if (header.magic != kMultifileMagic) {
                     ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     close(fd);
                     continue;
@@ -175,7 +217,7 @@
                 if (header.crc !=
                     crc32c(mappedEntry + sizeof(MultifileHeader),
                            fileSize - sizeof(MultifileHeader))) {
-                    ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash);
+                    ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
                     if (remove(fullPath.c_str()) != 0) {
                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
                     }
@@ -184,11 +226,12 @@
 
                 // If the cache entry is damaged or no good, remove it
                 if (header.keySize <= 0 || header.valueSize <= 0) {
-                    ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
+                    ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
                           "removing.",
                           entryHash, header.keySize, header.valueSize);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     continue;
                 }
@@ -226,9 +269,17 @@
         // If the multifile directory does not exist, create it and start from scratch
         if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
             ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
+            return;
+        }
+
+        // Create new status file
+        if (!createStatus(mMultifileDirName.c_str())) {
+            ALOGE("INIT: Failed to create status file!");
+            return;
         }
     }
 
+    ALOGV("INIT: Multifile BlobCache initialization succeeded");
     mInitialized = true;
 }
 
@@ -270,7 +321,7 @@
     size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
 
     // If we're going to be over the cache limit, kick off a trim to clear space
-    if (getTotalSize() + fileSize > mMaxTotalSize) {
+    if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
         ALOGV("SET: Cache is full, calling trimCache to clear space");
         trimCache();
     }
@@ -469,6 +520,112 @@
     }
 }
 
+bool MultifileBlobCache::createStatus(const std::string& baseDir) {
+    // Populate the status struct
+    struct MultifileStatus status;
+    memset(&status, 0, sizeof(status));
+    status.magic = kMultifileMagic;
+    status.cacheVersion = mCacheVersion;
+
+    // Copy the buildId string in, up to our allocated space
+    strncpy(status.buildId, mBuildId.c_str(),
+            mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
+
+    // Finally update the crc, using cacheVersion and everything the follows
+    status.crc =
+            crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+                   sizeof(status) - offsetof(MultifileStatus, cacheVersion));
+
+    // Create the status file
+    std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+    int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fd == -1) {
+        ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    // Write the buffer contents to disk
+    ssize_t result = write(fd, &status, sizeof(status));
+    close(fd);
+    if (result != sizeof(status)) {
+        ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
+    return true;
+}
+
+bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
+    std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+
+    // Does status exist
+    struct stat st;
+    if (stat(cacheStatus.c_str(), &st) != 0) {
+        ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
+        return false;
+    }
+
+    // If the status entry is damaged or no good, remove it
+    if (st.st_size <= 0 || st.st_atime <= 0) {
+        ALOGE("STATUS(CHECK): Cache status has invalid stats!");
+        return false;
+    }
+
+    // Open the file so we can read its header
+    int fd = open(cacheStatus.c_str(), O_RDONLY);
+    if (fd == -1) {
+        ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
+              cacheStatus.c_str(), std::strerror(errno));
+        return false;
+    }
+
+    // Read in the status header
+    MultifileStatus status;
+    size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
+    close(fd);
+    if (result != sizeof(MultifileStatus)) {
+        ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    // Verify header magic
+    if (status.magic != kMultifileMagic) {
+        ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
+        return false;
+    }
+
+    // Ensure we have a good CRC
+    if (status.crc !=
+        crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+               sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
+        ALOGE("STATUS(CHECK): Cache status failed CRC check!");
+        return false;
+    }
+
+    // Check cacheVersion
+    if (status.cacheVersion != mCacheVersion) {
+        ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
+              mCacheVersion);
+        return false;
+    }
+
+    // Check buildId
+    if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
+        ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
+              mBuildId.c_str());
+        return false;
+    }
+
+    // All checks passed!
+    ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
+          status.cacheVersion, status.buildId, cacheStatus.c_str());
+    return true;
+}
+
 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
                                     time_t accessTime) {
     mEntries.insert(entryHash);
@@ -485,10 +642,12 @@
 
 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
     mTotalCacheSize += fileSize;
+    mTotalCacheEntries++;
 }
 
 void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
     mTotalCacheSize -= fileSize;
+    mTotalCacheEntries--;
 }
 
 bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
@@ -557,7 +716,7 @@
     return false;
 }
 
-bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
+bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
     // Walk through our map of sorted last access times and remove files until under the limit
     for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
         uint32_t entryHash = cacheEntryIter->first;
@@ -590,9 +749,10 @@
 
         // See if it has been reduced enough
         size_t totalCacheSize = getTotalSize();
-        if (totalCacheSize <= cacheLimit) {
+        size_t totalCacheEntries = getTotalEntries();
+        if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
             // Success
-            ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
+            ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
             return true;
         }
     }
@@ -601,8 +761,43 @@
     return false;
 }
 
+// Clear the cache by removing all entries and deleting the directory
+bool MultifileBlobCache::clearCache() {
+    DIR* dir;
+    struct dirent* entry;
+    dir = opendir(mMultifileDirName.c_str());
+    if (dir == nullptr) {
+        ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
+        return false;
+    }
+
+    // Delete all entries and the status file
+    while ((entry = readdir(dir)) != nullptr) {
+        if (entry->d_name == "."s || entry->d_name == ".."s) {
+            continue;
+        }
+
+        std::string entryName = entry->d_name;
+        std::string fullPath = mMultifileDirName + "/" + entryName;
+        if (remove(fullPath.c_str()) != 0) {
+            ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+            return false;
+        }
+    }
+
+    // Delete the directory
+    if (remove(mMultifileDirName.c_str()) != 0) {
+        ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
+        return false;
+    }
+
+    ALOGV("CLEAR: Cleared the multifile blobcache");
+    return true;
+}
+
 // When removing files, what fraction of the overall limit should be reached when removing files
 // A divisor of two will decrease the cache to 50%, four to 25% and so on
+// We use the same limit to manage size and entry count
 constexpr uint32_t kCacheLimitDivisor = 2;
 
 // Calculate the cache size and remove old entries until under the limit
@@ -611,8 +806,9 @@
     ALOGV("TRIM: Waiting for work to complete.");
     waitForWorkComplete();
 
-    ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor);
-    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) {
+    ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
+          mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
+    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
         ALOGE("Error when clearing multifile shader cache");
         return;
     }
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index 5e527dc..18566c2 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -21,6 +21,7 @@
 #include <EGL/eglext.h>
 
 #include <android-base/thread_annotations.h>
+#include <cutils/properties.h>
 #include <future>
 #include <map>
 #include <queue>
@@ -33,6 +34,9 @@
 
 namespace android {
 
+constexpr uint32_t kMultifileBlobCacheVersion = 1;
+constexpr char kMultifileBlobCacheStatusFile[] = "cache.status";
+
 struct MultifileHeader {
     uint32_t magic;
     uint32_t crc;
@@ -46,6 +50,13 @@
     time_t accessTime;
 };
 
+struct MultifileStatus {
+    uint32_t magic;
+    uint32_t crc;
+    uint32_t cacheVersion;
+    char buildId[PROP_VALUE_MAX];
+};
+
 struct MultifileHotCache {
     int entryFd;
     uint8_t* entryBuffer;
@@ -92,7 +103,7 @@
 class MultifileBlobCache {
 public:
     MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
-                       const std::string& baseDir);
+                       size_t maxTotalEntries, const std::string& baseDir);
     ~MultifileBlobCache();
 
     void set(const void* key, EGLsizeiANDROID keySize, const void* value,
@@ -103,6 +114,13 @@
     void finish();
 
     size_t getTotalSize() const { return mTotalCacheSize; }
+    size_t getTotalEntries() const { return mTotalCacheEntries; }
+
+    const std::string& getCurrentBuildId() const { return mBuildId; }
+    void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; }
+
+    uint32_t getCurrentCacheVersion() const { return mCacheVersion; }
+    void setCurrentCacheVersion(uint32_t cacheVersion) { mCacheVersion = cacheVersion; }
 
 private:
     void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
@@ -111,6 +129,9 @@
     bool removeEntry(uint32_t entryHash);
     MultifileEntryStats getEntryStats(uint32_t entryHash);
 
+    bool createStatus(const std::string& baseDir);
+    bool checkStatus(const std::string& baseDir);
+
     size_t getFileSize(uint32_t entryHash);
     size_t getValueSize(uint32_t entryHash);
 
@@ -120,12 +141,16 @@
     bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
     bool removeFromHotCache(uint32_t entryHash);
 
+    bool clearCache();
     void trimCache();
-    bool applyLRU(size_t cacheLimit);
+    bool applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit);
 
     bool mInitialized;
     std::string mMultifileDirName;
 
+    std::string mBuildId;
+    uint32_t mCacheVersion;
+
     std::unordered_set<uint32_t> mEntries;
     std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
     std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
@@ -133,7 +158,9 @@
     size_t mMaxKeySize;
     size_t mMaxValueSize;
     size_t mMaxTotalSize;
+    size_t mMaxTotalEntries;
     size_t mTotalCacheSize;
+    size_t mTotalCacheEntries;
     size_t mHotCacheLimit;
     size_t mHotCacheEntryLimit;
     size_t mHotCacheSize;
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index 1639be6..90a0f1e 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -16,13 +16,17 @@
 
 #include "MultifileBlobCache.h"
 
+#include <android-base/properties.h>
 #include <android-base/test_utils.h>
 #include <fcntl.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
 
+#include <fstream>
 #include <memory>
 
+using namespace std::literals;
+
 namespace android {
 
 template <typename T>
@@ -31,23 +35,40 @@
 constexpr size_t kMaxKeySize = 2 * 1024;
 constexpr size_t kMaxValueSize = 6 * 1024;
 constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
 
 class MultifileBlobCacheTest : public ::testing::Test {
 protected:
     virtual void SetUp() {
+        clearProperties();
         mTempFile.reset(new TemporaryFile());
         mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
-                                          &mTempFile->path[0]));
+                                          kMaxTotalEntries, &mTempFile->path[0]));
     }
 
-    virtual void TearDown() { mMBC.reset(); }
+    virtual void TearDown() {
+        clearProperties();
+        mMBC.reset();
+    }
 
     int getFileDescriptorCount();
+    std::vector<std::string> getCacheEntries();
+
+    void clearProperties();
 
     std::unique_ptr<TemporaryFile> mTempFile;
     std::unique_ptr<MultifileBlobCache> mMBC;
 };
 
+void MultifileBlobCacheTest::clearProperties() {
+    // Clear any debug properties used in the tests
+    base::SetProperty("debug.egl.blobcache.cache_version", "");
+    base::WaitForProperty("debug.egl.blobcache.cache_version", "");
+
+    base::SetProperty("debug.egl.blobcache.build_id", "");
+    base::WaitForProperty("debug.egl.blobcache.build_id", "");
+}
+
 TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
     unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
     mMBC->set("abcd", 4, "efgh", 4);
@@ -211,6 +232,23 @@
     }
 }
 
+TEST_F(MultifileBlobCacheTest, CacheMaxEntrySucceeds) {
+    // Fill the cache with max entries
+    int i = 0;
+    for (i = 0; i < kMaxTotalEntries; i++) {
+        mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+    }
+
+    // Ensure it is full
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+    // Add another entry
+    mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+
+    // Ensure total entries is cut in half + 1
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries / 2 + 1);
+}
+
 TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
     unsigned char buf[1] = {0xee};
     mMBC->set("x", 1, "y", 1);
@@ -234,8 +272,7 @@
 
 TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
     // Populate the cache with a bunch of entries
-    size_t kLargeNumberOfEntries = 1024;
-    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+    for (int i = 0; i < kMaxTotalEntries; i++) {
         // printf("Caching: %i", i);
 
         // Use the index as the key and value
@@ -247,27 +284,223 @@
     }
 
     // Ensure we don't have a bunch of open fds
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
 
     // Close the cache so everything writes out
     mMBC->finish();
     mMBC.reset();
 
     // Now open it again and ensure we still don't have a bunch of open fds
-    mMBC.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &mTempFile->path[0]));
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
 
     // Check after initialization
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
 
-    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+    for (int i = 0; i < kMaxTotalEntries; i++) {
         int result = 0;
         ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
         ASSERT_EQ(i, result);
     }
 
     // And again after we've actually used it
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
+}
+
+std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() {
+    std::string cachePath = &mTempFile->path[0];
+    std::string multifileDirName = cachePath + ".multifile";
+    std::vector<std::string> cacheEntries;
+
+    struct stat info;
+    if (stat(multifileDirName.c_str(), &info) == 0) {
+        // We have a multifile dir. Skip the status file and return the only entry.
+        DIR* dir;
+        struct dirent* entry;
+        if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+            while ((entry = readdir(dir)) != nullptr) {
+                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                    continue;
+                }
+                if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
+                    continue;
+                }
+                cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
+            }
+        } else {
+            printf("Unable to open %s, error: %s\n", multifileDirName.c_str(),
+                   std::strerror(errno));
+        }
+    } else {
+        printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno));
+    }
+
+    return cacheEntries;
+}
+
+TEST_F(MultifileBlobCacheTest, CacheContainsStatus) {
+    struct stat info;
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // After INIT, cache should have a status
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure status lives after closing the cache
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+    // Open the cache again
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we still have a status
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+}
+
+// Verify missing cache status file causes cache the be cleared
+TEST_F(MultifileBlobCacheTest, MissingCacheStatusClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Delete the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+    remove(statusFile.str().c_str());
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file BEGIN causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusBeginClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Modify the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // Stomp on the beginning of the cache file
+    const char* stomp = "BADF00D";
+    std::fstream fs(statusFile.str());
+    fs.seekp(0, std::ios_base::beg);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file END causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusEndClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Modify the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // Stomp on the END of the cache status file, modifying its contents
+    const char* stomp = "BADF00D";
+    std::fstream fs(statusFile.str());
+    fs.seekp(-strlen(stomp), std::ios_base::end);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched cacheVersion causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Set a debug cacheVersion
+    std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1);
+    ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+    ASSERT_TRUE(
+            base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched buildId causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Set a debug buildId
+    base::SetProperty("debug.egl.blobcache.build_id", "foo");
+    base::WaitForProperty("debug.egl.blobcache.build_id", "foo");
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
 }
 
 } // namespace android
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 1b68344..98ff1d1 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -41,6 +41,7 @@
 constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024;
 constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024;
 constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileTotalEntries = 4 * 1024;
 
 namespace android {
 
@@ -277,7 +278,7 @@
     if (mMultifileBlobCache == nullptr) {
         mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize,
                                                          kMaxMultifileValueSize, mCacheByteLimit,
-                                                         mFilename));
+                                                         kMaxMultifileTotalEntries, mFilename));
     }
     return mMultifileBlobCache.get();
 }
diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
index 633cc9c..2d9ee3a 100644
--- a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
+++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
@@ -28,6 +28,7 @@
 constexpr size_t kMaxKeySize = 2 * 1024;
 constexpr size_t kMaxValueSize = 6 * 1024;
 constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     // To fuzz this, we're going to create a key/value pair from data
@@ -79,8 +80,8 @@
     std::unique_ptr<MultifileBlobCache> mbc;
 
     tempFile.reset(new TemporaryFile());
-    mbc.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                     &tempFile->path[0]));
     // With remaining data, select different paths below
     int loopCount = 1;
     uint8_t bumpCount = 0;
@@ -131,8 +132,8 @@
     // Place the maxKey/maxValue twice
     // The first will fit, the second will trigger hot cache trimming
     tempFile.reset(new TemporaryFile());
-    mbc.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                     &tempFile->path[0]));
     uint8_t* buffer = new uint8_t[kMaxValueSize];
     mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
     mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
@@ -145,7 +146,7 @@
     // overflow
     tempFile.reset(new TemporaryFile());
     mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
-                                     &tempFile->path[0]));
+                                     kMaxTotalEntries, &tempFile->path[0]));
     mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
     mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
     mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index f81c68f..ce58182 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -114,25 +114,26 @@
     struct stat info;
     if (stat(multifileDirName.c_str(), &info) == 0) {
         // Ensure we only have one file to manage
-        int realFileCount = 0;
+        int entryFileCount = 0;
 
-        // We have a multifile dir. Return the only real file in it.
+        // We have a multifile dir. Return the only entry file in it.
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
             while ((entry = readdir(dir)) != nullptr) {
-                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                if (entry->d_name == "."s || entry->d_name == ".."s ||
+                    strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
                 cachefileName = multifileDirName + "/" + entry->d_name;
-                realFileCount++;
+                entryFileCount++;
             }
         } else {
             printf("Unable to open %s, error: %s\n",
                    multifileDirName.c_str(), std::strerror(errno));
         }
 
-        if (realFileCount != 1) {
+        if (entryFileCount != 1) {
             // If there was more than one real file in the directory, this
             // violates test assumptions
             cachefileName = "";
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 10f386e..45c9b5c 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -244,6 +244,9 @@
         "Bug-115739809",
         "StructLayout_test",
 
+        // jni
+        "libservices.core",
+
         // rust targets
         "libinput_rust_test",
 
@@ -257,6 +260,7 @@
         "inputflinger_input_reader_fuzzer",
         "inputflinger_blocking_queue_fuzzer",
         "inputflinger_input_classifier_fuzzer",
+        "inputflinger_input_dispatcher_fuzzer",
 
         // Java/Kotlin targets
         "CtsWindowManagerDeviceWindow",
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 1092bdb..e529bdd 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -31,16 +31,30 @@
             args.pointerProperties[0].toolType == ToolType::MOUSE;
 }
 
+bool isHoverAction(int32_t action) {
+    return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+            action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
+}
+
+bool isStylusHoverEvent(const NotifyMotionArgs& args) {
+    return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
+}
 } // namespace
 
 // --- PointerChoreographer ---
 
 PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
                                            PointerChoreographerPolicyInterface& policy)
-      : mNextListener(listener),
+      : mTouchControllerConstructor([this]() REQUIRES(mLock) {
+            return mPolicy.createPointerController(
+                    PointerControllerInterface::ControllerType::TOUCH);
+        }),
+        mNextListener(listener),
         mPolicy(policy),
         mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
-        mNotifiedPointerDisplayId(ADISPLAY_ID_NONE) {}
+        mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
+        mShowTouchesEnabled(false),
+        mStylusPointerIconEnabled(false) {}
 
 void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
     std::scoped_lock _l(mLock);
@@ -69,8 +83,10 @@
 
     if (isFromMouse(args)) {
         return processMouseEventLocked(args);
+    } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+        processStylusHoverEventLocked(args);
     } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
-        return processTouchscreenEventLocked(args);
+        processTouchscreenAndStylusEventLocked(args);
     }
     return args;
 }
@@ -114,12 +130,70 @@
  * mouse device keeps moving and unfades the cursor.
  * For touch events, we do not need to populate the cursor position.
  */
-NotifyMotionArgs PointerChoreographer::processTouchscreenEventLocked(const NotifyMotionArgs& args) {
+void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) {
+    if (args.displayId == ADISPLAY_ID_NONE) {
+        return;
+    }
+
     if (const auto it = mMousePointersByDisplay.find(args.displayId);
         it != mMousePointersByDisplay.end() && args.action == AMOTION_EVENT_ACTION_DOWN) {
         it->second->fade(PointerControllerInterface::Transition::GRADUAL);
     }
-    return args;
+
+    if (!mShowTouchesEnabled) {
+        return;
+    }
+
+    // Get the touch pointer controller for the device, or create one if it doesn't exist.
+    auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+
+    PointerControllerInterface& pc = *it->second;
+
+    const PointerCoords* coords = args.pointerCoords.data();
+    const int32_t maskedAction = MotionEvent::getActionMasked(args.action);
+    const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
+    std::array<uint32_t, MAX_POINTER_ID + 1> idToIndex;
+    BitSet32 idBits;
+    if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL) {
+        for (size_t i = 0; i < args.getPointerCount(); i++) {
+            if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP && actionIndex == i) {
+                continue;
+            }
+            uint32_t id = args.pointerProperties[i].id;
+            idToIndex[id] = i;
+            idBits.markBit(id);
+        }
+    }
+    // The PointerController already handles setting spots per-display, so
+    // we do not need to manually manage display changes for touch spots for now.
+    pc.setSpots(coords, idToIndex.cbegin(), idBits, args.displayId);
+}
+
+void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) {
+    if (args.displayId == ADISPLAY_ID_NONE) {
+        return;
+    }
+
+    if (args.getPointerCount() != 1) {
+        LOG(WARNING) << "Only stylus hover events with a single pointer are currently supported: "
+                     << args.dump();
+    }
+
+    // Get the stylus pointer controller for the device, or create one if it doesn't exist.
+    auto [it, _] =
+            mStylusPointersByDevice.try_emplace(args.deviceId,
+                                                getStylusControllerConstructor(args.displayId));
+
+    PointerControllerInterface& pc = *it->second;
+
+    const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
+    const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+    pc.setPosition(x, y);
+    if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+        pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+    } else {
+        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+    }
 }
 
 void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) {
@@ -135,9 +209,17 @@
 }
 
 void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    processDeviceReset(args);
+
     mNextListener.notify(args);
 }
 
+void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) {
+    std::scoped_lock _l(mLock);
+    mTouchPointersByDevice.erase(args.deviceId);
+    mStylusPointersByDevice.erase(args.deviceId);
+}
+
 void PointerChoreographer::notifyPointerCaptureChanged(
         const NotifyPointerCaptureChangedArgs& args) {
     if (args.request.enable) {
@@ -153,12 +235,25 @@
     std::scoped_lock _l(mLock);
 
     dump += "PointerChoreographer:\n";
+    dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "true" : "false");
+    dump += StringPrintf("stylus pointer icon enabled: %s\n",
+                         mStylusPointerIconEnabled ? "true" : "false");
 
     dump += INDENT "MousePointerControllers:\n";
     for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
         std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
         dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump;
     }
+    dump += INDENT "TouchPointerControllers:\n";
+    for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) {
+        std::string pointerControllerDump = addLinePrefix(touchPointerController->dump(), INDENT);
+        dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+    }
+    dump += INDENT "StylusPointerControllers:\n";
+    for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+        std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
+        dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+    }
     dump += "\n";
 }
 
@@ -175,8 +270,16 @@
     return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
 }
 
+InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) {
+    auto it = std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
+                           [deviceId](const auto& info) { return info.getId() == deviceId; });
+    return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
+}
+
 void PointerChoreographer::updatePointerControllersLocked() {
     std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
+    std::set<DeviceId> touchDevicesToKeep;
+    std::set<DeviceId> stylusDevicesToKeep;
 
     // Mark the displayIds or deviceIds of PointerControllers currently needed.
     for (const auto& info : mInputDeviceInfos) {
@@ -187,17 +290,25 @@
                     getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
             mouseDisplaysToKeep.insert(resolvedDisplayId);
         }
+        if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
+            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            touchDevicesToKeep.insert(info.getId());
+        }
+        if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
+            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            stylusDevicesToKeep.insert(info.getId());
+        }
     }
 
     // Remove PointerControllers no longer needed.
-    // This has the side-effect of fading pointers or clearing spots before removal.
     std::erase_if(mMousePointersByDisplay, [&mouseDisplaysToKeep](const auto& pair) {
-        auto& [displayId, controller] = pair;
-        if (mouseDisplaysToKeep.find(displayId) == mouseDisplaysToKeep.end()) {
-            controller->fade(PointerControllerInterface::Transition::IMMEDIATE);
-            return true;
-        }
-        return false;
+        return mouseDisplaysToKeep.find(pair.first) == mouseDisplaysToKeep.end();
+    });
+    std::erase_if(mTouchPointersByDevice, [&touchDevicesToKeep](const auto& pair) {
+        return touchDevicesToKeep.find(pair.first) == touchDevicesToKeep.end();
+    });
+    std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
+        return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
     });
 
     // Notify the policy if there's a change on the pointer display ID.
@@ -234,10 +345,17 @@
 void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
     std::scoped_lock _l(mLock);
     for (const auto& viewport : viewports) {
-        if (const auto it = mMousePointersByDisplay.find(viewport.displayId);
+        const int32_t displayId = viewport.displayId;
+        if (const auto it = mMousePointersByDisplay.find(displayId);
             it != mMousePointersByDisplay.end()) {
             it->second->setDisplayViewport(viewport);
         }
+        for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+            const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+            if (info && info->getAssociatedDisplayId() == displayId) {
+                stylusPointerController->setDisplayViewport(viewport);
+            }
+        }
     }
     mViewports = viewports;
     notifyPointerDisplayIdChangedLocked();
@@ -263,6 +381,24 @@
     return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
 }
 
+void PointerChoreographer::setShowTouchesEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+    if (mShowTouchesEnabled == enabled) {
+        return;
+    }
+    mShowTouchesEnabled = enabled;
+    updatePointerControllersLocked();
+}
+
+void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) {
+    std::scoped_lock _l(mLock);
+    if (mStylusPointerIconEnabled == enabled) {
+        return;
+    }
+    mStylusPointerIconEnabled = enabled;
+    updatePointerControllersLocked();
+}
+
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
         int32_t displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
@@ -277,4 +413,18 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
+PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
+        int32_t displayId) {
+    std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
+            [this, displayId]() REQUIRES(mLock) {
+                auto pc = mPolicy.createPointerController(
+                        PointerControllerInterface::ControllerType::STYLUS);
+                if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
+                    pc->setDisplayViewport(*viewport);
+                }
+                return pc;
+            };
+    return ConstructorDelegate(std::move(ctor));
+}
+
 } // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index c1b900f..26d2fef 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -56,6 +56,8 @@
     virtual std::optional<DisplayViewport> getViewportForPointerDevice(
             int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
     virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0;
+    virtual void setShowTouchesEnabled(bool enabled) = 0;
+    virtual void setStylusPointerIconEnabled(bool enabled) = 0;
     /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
      */
@@ -73,6 +75,8 @@
     std::optional<DisplayViewport> getViewportForPointerDevice(
             int32_t associatedDisplayId) override;
     FloatPoint getMouseCursorPosition(int32_t displayId) override;
+    void setShowTouchesEnabled(bool enabled) override;
+    void setStylusPointerIconEnabled(bool enabled) override;
 
     void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
     void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -91,14 +95,19 @@
     void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
     const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
     int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
+    InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
 
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
     NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
-    NotifyMotionArgs processTouchscreenEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+    void processDeviceReset(const NotifyDeviceResetArgs& args);
 
     using ControllerConstructor =
             ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
+    ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock);
     ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
+    ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock);
 
     std::mutex mLock;
 
@@ -107,11 +116,17 @@
 
     std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
             GUARDED_BY(mLock);
+    std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
+            GUARDED_BY(mLock);
+    std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
+            GUARDED_BY(mLock);
 
     int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
     int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
     std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
     std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
+    bool mShowTouchesEnabled GUARDED_BY(mLock);
+    bool mStylusPointerIconEnabled GUARDED_BY(mLock);
 };
 
 } // namespace android
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index 0f62324..1e2b9b3a 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -67,6 +67,16 @@
 const bool DEBUG_MODEL =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
 
+/**
+ * When multi-device input is enabled, we shouldn't use PreferStylusOverTouchBlocker at all.
+ * However, multi-device input has the following default behaviour: hovering stylus rejects touch.
+ * Therefore, if we want to disable that behaviour (and go back to a place where stylus down
+ * blocks touch, but hovering stylus doesn't interact with touch), we should just disable the entire
+ * multi-device input feature.
+ */
+const bool ENABLE_MULTI_DEVICE_INPUT = input_flags::enable_multi_device_input() &&
+        !input_flags::disable_reject_touch_on_stylus_hover();
+
 // Category (=namespace) name for the input settings that are applied at boot time
 static const char* INPUT_NATIVE_BOOT = "input_native_boot";
 /**
@@ -347,7 +357,7 @@
     ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
     { // acquire lock
         std::scoped_lock lock(mLock);
-        if (input_flags::enable_multi_device_input()) {
+        if (ENABLE_MULTI_DEVICE_INPUT) {
             notifyMotionLocked(args);
         } else {
             const std::vector<NotifyMotionArgs> processedArgs =
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 188d5f0..5ae3715 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -20,10 +20,12 @@
 #include <binder/Binder.h>
 #include <gui/constants.h>
 #include "../dispatcher/InputDispatcher.h"
+#include "../tests/FakeApplicationHandle.h"
+#include "../tests/FakeInputDispatcherPolicy.h"
+#include "../tests/FakeWindowHandle.h"
 
 using android::base::Result;
 using android::gui::WindowInfo;
-using android::gui::WindowInfoHandle;
 using android::os::IInputConstants;
 using android::os::InputEventInjectionResult;
 using android::os::InputEventInjectionSync;
@@ -33,171 +35,17 @@
 namespace {
 
 // An arbitrary device id.
-constexpr int32_t DEVICE_ID = 1;
+constexpr DeviceId DEVICE_ID = 1;
 
-// The default pid and uid for windows created by the test.
-constexpr gui::Pid WINDOW_PID{999};
-constexpr gui::Uid WINDOW_UID{1001};
+// An arbitrary display id
+constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
 
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s;
-static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
 
 static nsecs_t now() {
     return systemTime(SYSTEM_TIME_MONOTONIC);
 }
 
-// --- FakeInputDispatcherPolicy ---
-
-class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
-public:
-    FakeInputDispatcherPolicy() = default;
-    virtual ~FakeInputDispatcherPolicy() = default;
-
-private:
-    void notifyConfigurationChanged(nsecs_t) override {}
-
-    void notifyNoFocusedWindowAnr(
-            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
-        ALOGE("There is no focused window for %s", applicationHandle->getName().c_str());
-    }
-
-    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
-                                  const std::string& reason) override {
-        ALOGE("Window is not responding: %s", reason.c_str());
-    }
-
-    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
-                                std::optional<gui::Pid> pid) override {}
-
-    void notifyInputChannelBroken(const sp<IBinder>&) override {}
-
-    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
-
-    void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
-                           InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
-                           const std::vector<float>& values) override {}
-
-    void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
-                              InputDeviceSensorAccuracy accuracy) override {}
-
-    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
-
-    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
-        return true; // dispatch event normally
-    }
-
-    void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
-
-    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
-
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
-        return 0;
-    }
-
-    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
-                                                 uint32_t) override {
-        return {};
-    }
-
-    void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
-
-    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
-
-    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
-
-    void setPointerCapture(const PointerCaptureRequest&) override {}
-
-    void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
-
-    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
-                                 const std::set<gui::Uid>& uids) override {}
-
-    InputDispatcherConfiguration mConfig;
-};
-
-class FakeApplicationHandle : public InputApplicationHandle {
-public:
-    FakeApplicationHandle() {}
-    virtual ~FakeApplicationHandle() {}
-
-    virtual bool updateInfo() {
-        mInfo.dispatchingTimeoutMillis =
-                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
-        return true;
-    }
-};
-
-class FakeInputReceiver {
-public:
-    void consumeEvent() {
-        uint32_t consumeSeq = 0;
-        InputEvent* event;
-
-        std::chrono::time_point start = std::chrono::steady_clock::now();
-        status_t result = WOULD_BLOCK;
-        while (result == WOULD_BLOCK) {
-            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
-            if (elapsed > 10ms) {
-                ALOGE("Waited too long for consumer to produce an event, giving up");
-                break;
-            }
-            result = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                        &event);
-        }
-        if (result != OK) {
-            ALOGE("Received result = %d from consume()", result);
-        }
-        result = mConsumer->sendFinishedSignal(consumeSeq, true);
-        if (result != OK) {
-            ALOGE("Received result = %d from sendFinishedSignal", result);
-        }
-    }
-
-protected:
-    explicit FakeInputReceiver(InputDispatcher& dispatcher, const std::string name) {
-        Result<std::unique_ptr<InputChannel>> channelResult = dispatcher.createInputChannel(name);
-        LOG_ALWAYS_FATAL_IF(!channelResult.ok());
-        mClientChannel = std::move(*channelResult);
-        mConsumer = std::make_unique<InputConsumer>(mClientChannel);
-    }
-
-    virtual ~FakeInputReceiver() {}
-
-    std::shared_ptr<InputChannel> mClientChannel;
-    std::unique_ptr<InputConsumer> mConsumer;
-    PreallocatedInputEventFactory mEventFactory;
-};
-
-class FakeWindowHandle : public WindowInfoHandle, public FakeInputReceiver {
-public:
-    static const int32_t WIDTH = 200;
-    static const int32_t HEIGHT = 200;
-
-    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     InputDispatcher& dispatcher, const std::string name)
-          : FakeInputReceiver(dispatcher, name), mFrame(Rect(0, 0, WIDTH, HEIGHT)) {
-        inputApplicationHandle->updateInfo();
-        updateInfo();
-        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
-    }
-
-    void updateInfo() {
-        mInfo.token = mClientChannel->getConnectionToken();
-        mInfo.name = "FakeWindowHandle";
-        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
-        mInfo.frame = mFrame;
-        mInfo.globalScaleFactor = 1.0;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(mFrame);
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
-        mInfo.displayId = ADISPLAY_ID_DEFAULT;
-    }
-
-protected:
-    Rect mFrame;
-};
-
 static MotionEvent generateMotionEvent() {
     PointerProperties pointerProperties[1];
     PointerCoords pointerCoords[1];
@@ -263,7 +111,7 @@
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
+            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
     dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -281,8 +129,8 @@
         motionArgs.eventTime = now();
         dispatcher.notifyMotion(motionArgs);
 
-        window->consumeEvent();
-        window->consumeEvent();
+        window->consumeMotion();
+        window->consumeMotion();
     }
 
     dispatcher.stop();
@@ -298,7 +146,7 @@
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
+            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
     dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -315,8 +163,8 @@
                                     INJECT_EVENT_TIMEOUT,
                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
-        window->consumeEvent();
-        window->consumeEvent();
+        window->consumeMotion();
+        window->consumeMotion();
     }
 
     dispatcher.stop();
@@ -332,7 +180,7 @@
     // Create a window
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window");
+            sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
     std::vector<gui::WindowInfo> windowInfos{*window->getInfo()};
     gui::DisplayInfo info;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index c8528e1..23ac088 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -4547,7 +4547,6 @@
 }
 
 void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
-    // TODO(b/308677868) Remove device reset from the InputListener interface
     if (debugInboundEventDetails()) {
         ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime,
               args.deviceId);
@@ -4693,30 +4692,6 @@
             }
 
             mLock.lock();
-
-            if (policyFlags & POLICY_FLAG_FILTERED) {
-                // The events from InputFilter impersonate real hardware devices. Check these
-                // events for consistency and print an error. An inconsistent event sent from
-                // InputFilter could cause a crash in the later stages of dispatching pipeline.
-                auto [it, _] =
-                        mInputFilterVerifiersByDisplay
-                                .try_emplace(displayId,
-                                             StringPrintf("Injection on %" PRId32, displayId));
-                InputVerifier& verifier = it->second;
-
-                Result<void> result =
-                        verifier.processMovement(resolvedDeviceId, motionEvent.getSource(),
-                                                 motionEvent.getAction(),
-                                                 motionEvent.getPointerCount(),
-                                                 motionEvent.getPointerProperties(),
-                                                 motionEvent.getSamplePointerCoords(), flags);
-                if (!result.ok()) {
-                    logDispatchStateLocked();
-                    LOG(ERROR) << "Inconsistent event: " << motionEvent
-                               << ", reason: " << result.error();
-                }
-            }
-
             const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
             const size_t pointerCount = motionEvent.getPointerCount();
             const std::vector<PointerProperties>
@@ -6760,7 +6735,6 @@
         // Remove the associated touch mode state.
         mTouchModePerDisplay.erase(displayId);
         mVerifiersByDisplay.erase(displayId);
-        mInputFilterVerifiersByDisplay.erase(displayId);
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
@@ -6953,11 +6927,12 @@
     return nullptr;
 }
 
-void InputDispatcher::setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
+void InputDispatcher::setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
+                                                std::chrono::nanoseconds delay) {
     std::scoped_lock _l(mLock);
 
-    mConfig.keyRepeatTimeout = timeout;
-    mConfig.keyRepeatDelay = delay;
+    mConfig.keyRepeatTimeout = timeout.count();
+    mConfig.keyRepeatDelay = delay.count();
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index f0f6772..574cf8b 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -145,7 +145,8 @@
     // Public to allow tests to verify that a Monitor can get ANR.
     void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
 
-    void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) override;
+    void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
+                                   std::chrono::nanoseconds delay) override;
 
 private:
     enum class DropReason {
@@ -286,8 +287,7 @@
     void transformMotionEntryForInjectionLocked(MotionEntry&,
                                                 const ui::Transform& injectedTransform) const
             REQUIRES(mLock);
-    // Per-display correction of injected events
-    std::map</*displayId*/ int32_t, InputVerifier> mInputFilterVerifiersByDisplay GUARDED_BY(mLock);
+
     std::condition_variable mInjectionSyncFinished;
     void incrementPendingForegroundDispatches(EventEntry& entry);
     void decrementPendingForegroundDispatches(EventEntry& entry);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 16cc266..17f0b87 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -24,19 +24,6 @@
 
 namespace android::inputdispatcher {
 
-namespace {
-bool isHoverAction(int32_t action) {
-    switch (MotionEvent::getActionMasked(action)) {
-        case AMOTION_EVENT_ACTION_HOVER_ENTER:
-        case AMOTION_EVENT_ACTION_HOVER_MOVE:
-        case AMOTION_EVENT_ACTION_HOVER_EXIT: {
-            return true;
-        }
-    }
-    return false;
-}
-} // namespace
-
 InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
 
 InputState::~InputState() {}
@@ -113,13 +100,6 @@
         if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
             !isStylusEvent(entry.source, entry.pointerProperties)) {
             // We already have a stylus stream, and the new event is not from stylus.
-            if (!lastMemento.hovering) {
-                // If stylus is currently down, reject the new event unconditionally.
-                return false;
-            }
-        }
-        if (!lastMemento.hovering && isHoverAction(action)) {
-            // Reject hovers if already down
             return false;
         }
     }
@@ -366,19 +346,13 @@
         return false;
     }
 
-    // We want stylus down to block touch and other source types, but stylus hover should not
-    // have such an effect.
-    if (isHoverAction(motionEntry.action) && !lastMemento.hovering) {
-        // New event is a hover. Keep the current non-hovering gesture instead
-        return false;
-    }
-
-    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !lastMemento.hovering) {
-        // We have non-hovering stylus already active.
+    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
+        // A stylus is already active.
         if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
             actionMasked == AMOTION_EVENT_ACTION_DOWN) {
-            // If this new event is a stylus from a different device going down, then cancel the old
-            // stylus and allow the new stylus to take over
+            // If this new event is from a different device, then cancel the old
+            // stylus and allow the new stylus to take over, but only if it's going down.
+            // Otherwise, they will start to race each other.
             return true;
         }
 
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index d099b44..bc7b644 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -221,7 +221,8 @@
     /*
      * Updates key repeat configuration timeout and delay.
      */
-    virtual void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) = 0;
+    virtual void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
+                                           std::chrono::nanoseconds delay) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index bf48804..1c23720 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -21,7 +21,9 @@
 #include <android-base/properties.h>
 #include <binder/IBinder.h>
 #include <gui/InputApplication.h>
+#include <gui/PidUid.h>
 #include <input/Input.h>
+#include <input/InputDevice.h>
 #include <utils/RefBase.h>
 #include <set>
 
@@ -146,7 +148,7 @@
     virtual void notifyDropWindow(const sp<IBinder>& token, float x, float y) = 0;
 
     /* Notifies the policy that there was an input device interaction with apps. */
-    virtual void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+    virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
                                          const std::set<gui::Uid>& uids) = 0;
 };
 
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 8837b25..ef74a55 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -48,8 +48,8 @@
  */
 class PointerControllerInterface {
 protected:
-    PointerControllerInterface() { }
-    virtual ~PointerControllerInterface() { }
+    PointerControllerInterface() {}
+    virtual ~PointerControllerInterface() {}
 
 public:
     /**
@@ -63,6 +63,10 @@
         LEGACY,
         // Represents a single mouse pointer.
         MOUSE,
+        // Represents multiple touch spots.
+        TOUCH,
+        // Represents a single stylus pointer.
+        STYLUS,
     };
 
     /* Dumps the state of the pointer controller. */
@@ -121,7 +125,7 @@
      * pressed (not hovering).
      */
     virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-            BitSet32 spotIdBits, int32_t displayId) = 0;
+                          BitSet32 spotIdBits, int32_t displayId) = 0;
 
     /* Removes all spots. */
     virtual void clearSpots() = 0;
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index 9c87c62..2dd05f5 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -131,7 +131,7 @@
                 bumpGeneration();
             }
         }
-        if (shouldSimulateStylusWithTouch() && outPointer.toolType == ToolType::FINGER) {
+        if (mShouldSimulateStylusWithTouch && outPointer.toolType == ToolType::FINGER) {
             outPointer.toolType = ToolType::STYLUS;
         }
 
@@ -177,6 +177,18 @@
     mMultiTouchMotionAccumulator.finishSync();
 }
 
+std::list<NotifyArgs> MultiTouchInputMapper::reconfigure(nsecs_t when,
+                                                         const InputReaderConfiguration& config,
+                                                         ConfigurationChanges changes) {
+    const bool simulateStylusWithTouch =
+            sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
+    if (simulateStylusWithTouch != mShouldSimulateStylusWithTouch) {
+        mShouldSimulateStylusWithTouch = simulateStylusWithTouch;
+        bumpGeneration();
+    }
+    return TouchInputMapper::reconfigure(when, config, changes);
+}
+
 void MultiTouchInputMapper::configureRawPointerAxes() {
     TouchInputMapper::configureRawPointerAxes();
 
@@ -211,14 +223,7 @@
 
 bool MultiTouchInputMapper::hasStylus() const {
     return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() ||
-            shouldSimulateStylusWithTouch();
-}
-
-bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
-    static const bool SIMULATE_STYLUS_WITH_TOUCH =
-            sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
-    return SIMULATE_STYLUS_WITH_TOUCH &&
-            mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
+            mShouldSimulateStylusWithTouch;
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 1d788df..5c173f3 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -32,6 +32,9 @@
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+                                                    const InputReaderConfiguration& config,
+                                                    ConfigurationChanges changes) override;
 
 protected:
     void syncTouch(nsecs_t when, RawState* outState) override;
@@ -41,13 +44,6 @@
 private:
     explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
                                    const InputReaderConfiguration& readerConfig);
-    // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
-    // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
-    // It is used to simulate stylus events for debugging and testing on a device that does not
-    // support styluses. It can be enabled using
-    // "adb shell setprop persist.debug.input.simulate_stylus_with_touch true",
-    // and requires a reboot to take effect.
-    inline bool shouldSimulateStylusWithTouch() const;
 
     // If the slot is in use, return the bit id. Return std::nullopt otherwise.
     std::optional<int32_t> getActiveBitId(const MultiTouchMotionAccumulator::Slot& inSlot);
@@ -58,6 +54,15 @@
     int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
 
     bool mStylusMtToolSeen{false};
+
+    // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
+    // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
+    // It is used to simulate stylus events for debugging and testing on a device that does not
+    // support styluses. It can be enabled using
+    // "adb shell setprop debug.input.simulate_stylus_with_touch true".
+    // After enabling, the touchscreen will need to be reconfigured. A reconfiguration usually
+    // happens when turning the screen on/off or by rotating the device orientation.
+    bool mShouldSimulateStylusWithTouch{false};
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeApplicationHandle.h b/services/inputflinger/tests/FakeApplicationHandle.h
new file mode 100644
index 0000000..2f634d5
--- /dev/null
+++ b/services/inputflinger/tests/FakeApplicationHandle.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/properties.h>
+#include <android/os/IInputConstants.h>
+#include <gui/InputApplication.h>
+
+namespace android {
+
+namespace inputdispatcher {
+
+class FakeApplicationHandle : public InputApplicationHandle {
+public:
+    FakeApplicationHandle() {
+        static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
+                android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+                android::base::HwTimeoutMultiplier());
+        mInfo.name = "Fake Application";
+        mInfo.token = sp<BBinder>::make();
+        mInfo.dispatchingTimeoutMillis =
+                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
+    }
+    virtual ~FakeApplicationHandle() {}
+
+    bool updateInfo() override { return true; }
+
+    void setDispatchingTimeout(std::chrono::milliseconds timeout) {
+        mInfo.dispatchingTimeoutMillis = timeout.count();
+    }
+};
+
+} // namespace inputdispatcher
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
new file mode 100644
index 0000000..e9d93af
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+#include "InputDispatcherPolicyInterface.h"
+
+namespace android {
+
+// --- FakeInputDispatcherPolicy ---
+
+class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
+public:
+    FakeInputDispatcherPolicy() = default;
+    virtual ~FakeInputDispatcherPolicy() = default;
+
+private:
+    void notifyConfigurationChanged(nsecs_t) override {}
+
+    void notifyNoFocusedWindowAnr(
+            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
+        LOG(ERROR) << "There is no focused window for " << applicationHandle->getName();
+    }
+
+    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
+                                  const std::string& reason) override {
+        LOG(ERROR) << "Window is not responding: " << reason;
+    }
+
+    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
+                                std::optional<gui::Pid> pid) override {}
+
+    void notifyInputChannelBroken(const sp<IBinder>&) override {}
+
+    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
+
+    void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
+                           InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
+                           const std::vector<float>& values) override {}
+
+    void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
+                              InputDeviceSensorAccuracy accuracy) override {}
+
+    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
+
+    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
+        return true; // dispatch event normally
+    }
+
+    void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
+
+    void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
+
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
+        return 0;
+    }
+
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
+                                                 uint32_t) override {
+        return {};
+    }
+
+    void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
+
+    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
+
+    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
+
+    void setPointerCapture(const PointerCaptureRequest&) override {}
+
+    void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
+
+    void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
+                                 const std::set<gui::Uid>& uids) override {}
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index ca517f3..5475594 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -47,6 +47,8 @@
 
 void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) {
     mDisplayId = viewport.displayId;
+    setBounds(viewport.logicalLeft, viewport.logicalTop, viewport.logicalRight - 1,
+              viewport.logicalBottom - 1);
 }
 
 void FakePointerController::assertPosition(float x, float y) {
@@ -55,6 +57,12 @@
     ASSERT_NEAR(y, actualY, 1);
 }
 
+void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) {
+    auto it = mSpotsByDisplay.find(displayId);
+    ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId;
+    ASSERT_EQ(static_cast<size_t>(count), it->second.size());
+}
+
 bool FakePointerController::isPointerShown() {
     return mIsPointerShown;
 }
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index c75f6ed..d7e40b3 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -37,6 +37,7 @@
     void setDisplayViewport(const DisplayViewport& viewport) override;
 
     void assertPosition(float x, float y);
+    void assertSpotCount(int32_t displayId, int32_t count);
     bool isPointerShown();
 
 private:
diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h
new file mode 100644
index 0000000..fe25130
--- /dev/null
+++ b/services/inputflinger/tests/FakeWindowHandle.h
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/logging.h>
+#include "../dispatcher/InputDispatcher.h"
+
+using android::base::Result;
+using android::gui::Pid;
+using android::gui::TouchOcclusionMode;
+using android::gui::Uid;
+using android::gui::WindowInfo;
+using android::gui::WindowInfoHandle;
+
+namespace android {
+namespace inputdispatcher {
+
+namespace {
+
+// The default pid and uid for windows created by the test.
+constexpr gui::Pid WINDOW_PID{999};
+constexpr gui::Uid WINDOW_UID{1001};
+
+static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
+
+} // namespace
+
+class FakeInputReceiver {
+public:
+    std::unique_ptr<InputEvent> consumeEvent(std::chrono::milliseconds timeout) {
+        uint32_t consumeSeq = 0;
+        std::unique_ptr<InputEvent> event;
+
+        std::chrono::time_point start = std::chrono::steady_clock::now();
+        status_t result = WOULD_BLOCK;
+        while (result == WOULD_BLOCK) {
+            InputEvent* rawEventPtr = nullptr;
+            result = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+                                       &rawEventPtr);
+            event = std::unique_ptr<InputEvent>(rawEventPtr);
+            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+            if (elapsed > timeout) {
+                if (timeout != 0ms) {
+                    LOG(ERROR) << "Waited too long for consumer to produce an event, giving up";
+                }
+                break;
+            }
+        }
+        // Events produced by this factory are owned pointers.
+        if (result != OK) {
+            if (timeout == 0ms) {
+                // This is likely expected. No need to log.
+            } else {
+                LOG(ERROR) << "Received result =  " << result << " from consume";
+            }
+            return nullptr;
+        }
+        result = mConsumer.sendFinishedSignal(consumeSeq, true);
+        if (result != OK) {
+            LOG(ERROR) << "Received result = " << result << " from sendFinishedSignal";
+        }
+        return event;
+    }
+
+    explicit FakeInputReceiver(std::unique_ptr<InputChannel> channel, const std::string name)
+          : mConsumer(std::move(channel)) {}
+
+    virtual ~FakeInputReceiver() {}
+
+private:
+    std::unique_ptr<InputChannel> mClientChannel;
+    InputConsumer mConsumer;
+    DynamicInputEventFactory mEventFactory;
+};
+
+class FakeWindowHandle : public WindowInfoHandle {
+public:
+    static const int32_t WIDTH = 600;
+    static const int32_t HEIGHT = 800;
+
+    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
+                     InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+          : mName(name) {
+        Result<std::unique_ptr<InputChannel>> channel = dispatcher.createInputChannel(name);
+        mInfo.token = (*channel)->getConnectionToken();
+        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
+
+        inputApplicationHandle->updateInfo();
+        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
+
+        mInfo.id = sId++;
+        mInfo.name = name;
+        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
+        mInfo.alpha = 1.0;
+        mInfo.frame.left = 0;
+        mInfo.frame.top = 0;
+        mInfo.frame.right = WIDTH;
+        mInfo.frame.bottom = HEIGHT;
+        mInfo.transform.set(0, 0);
+        mInfo.globalScaleFactor = 1.0;
+        mInfo.touchableRegion.clear();
+        mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
+        mInfo.ownerPid = WINDOW_PID;
+        mInfo.ownerUid = WINDOW_UID;
+        mInfo.displayId = displayId;
+        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
+    }
+
+    sp<FakeWindowHandle> clone(int32_t displayId) {
+        sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
+        handle->mInfo = mInfo;
+        handle->mInfo.displayId = displayId;
+        handle->mInfo.id = sId++;
+        handle->mInputReceiver = mInputReceiver;
+        return handle;
+    }
+
+    void setTouchable(bool touchable) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
+    }
+
+    void setFocusable(bool focusable) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
+    }
+
+    void setVisible(bool visible) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+    }
+
+    void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
+        mInfo.dispatchingTimeout = timeout;
+    }
+
+    void setPaused(bool paused) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
+    }
+
+    void setPreventSplitting(bool preventSplitting) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
+    }
+
+    void setSlippery(bool slippery) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
+    }
+
+    void setWatchOutsideTouch(bool watchOutside) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
+    }
+
+    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
+
+    void setInterceptsStylus(bool interceptsStylus) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
+    }
+
+    void setDropInput(bool dropInput) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
+    }
+
+    void setDropInputIfObscured(bool dropInputIfObscured) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
+    }
+
+    void setNoInputChannel(bool noInputChannel) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
+    }
+
+    void setDisableUserActivity(bool disableUserActivity) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
+    }
+
+    void setAlpha(float alpha) { mInfo.alpha = alpha; }
+
+    void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
+
+    void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
+
+    void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
+        mInfo.frame.left = frame.left;
+        mInfo.frame.top = frame.top;
+        mInfo.frame.right = frame.right;
+        mInfo.frame.bottom = frame.bottom;
+        mInfo.touchableRegion.clear();
+        mInfo.addTouchableRegion(frame);
+
+        const Rect logicalDisplayFrame = displayTransform.transform(frame);
+        ui::Transform translate;
+        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
+        mInfo.transform = translate * displayTransform;
+    }
+
+    void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
+
+    void setIsWallpaper(bool isWallpaper) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
+    }
+
+    void setDupTouchToWallpaper(bool hasWallpaper) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
+    }
+
+    void setTrustedOverlay(bool trustedOverlay) {
+        mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
+    }
+
+    void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
+        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
+    }
+
+    void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
+
+    void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
+
+    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout) {
+        if (mInputReceiver == nullptr) {
+            return nullptr;
+        }
+        return mInputReceiver->consumeEvent(timeout);
+    }
+
+    void consumeMotion() {
+        std::unique_ptr<InputEvent> event = consume(100ms);
+
+        if (event == nullptr) {
+            LOG(FATAL) << mName << ": expected a MotionEvent, but didn't get one.";
+            return;
+        }
+
+        if (event->getType() != InputEventType::MOTION) {
+            LOG(FATAL) << mName << " expected a MotionEvent, got " << *event;
+            return;
+        }
+    }
+
+    sp<IBinder> getToken() { return mInfo.token; }
+
+    const std::string& getName() { return mName; }
+
+    void setOwnerInfo(Pid ownerPid, Uid ownerUid) {
+        mInfo.ownerPid = ownerPid;
+        mInfo.ownerUid = ownerUid;
+    }
+
+    Pid getPid() const { return mInfo.ownerPid; }
+
+    void destroyReceiver() { mInputReceiver = nullptr; }
+
+private:
+    FakeWindowHandle(std::string name) : mName(name){};
+    const std::string mName;
+    std::shared_ptr<FakeInputReceiver> mInputReceiver;
+    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
+    friend class sp<FakeWindowHandle>;
+};
+
+std::atomic<int32_t> FakeWindowHandle::sId{1};
+
+} // namespace inputdispatcher
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 3c2b31d..71362e3 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -16,6 +16,7 @@
 
 #include "../dispatcher/InputDispatcher.h"
 #include "../BlockingQueue.h"
+#include "FakeApplicationHandle.h"
 #include "TestEventMatchers.h"
 
 #include <NotifyArgsBuilders.h>
@@ -851,29 +852,10 @@
         android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
         android::base::HwTimeoutMultiplier());
 
-class FakeApplicationHandle : public InputApplicationHandle {
-public:
-    FakeApplicationHandle() {
-        mInfo.name = "Fake Application";
-        mInfo.token = sp<BBinder>::make();
-        mInfo.dispatchingTimeoutMillis =
-                std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
-    }
-    virtual ~FakeApplicationHandle() {}
-
-    virtual bool updateInfo() override { return true; }
-
-    void setDispatchingTimeout(std::chrono::milliseconds timeout) {
-        mInfo.dispatchingTimeoutMillis = timeout.count();
-    }
-};
-
 class FakeInputReceiver {
 public:
     explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name)
-          : mName(name) {
-        mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel));
-    }
+          : mConsumer(std::move(clientChannel)), mName(name) {}
 
     InputEvent* consume(std::chrono::milliseconds timeout, bool handled = false) {
         InputEvent* event;
@@ -897,8 +879,8 @@
         std::chrono::time_point start = std::chrono::steady_clock::now();
         status_t status = WOULD_BLOCK;
         while (status == WOULD_BLOCK) {
-            status = mConsumer->consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                        &event);
+            status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+                                       &event);
             std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
             if (elapsed > timeout) {
                 break;
@@ -928,12 +910,12 @@
      * To be used together with "receiveEvent" to complete the consumption of an event.
      */
     void finishEvent(uint32_t consumeSeq, bool handled = true) {
-        const status_t status = mConsumer->sendFinishedSignal(consumeSeq, handled);
+        const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
         ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
     }
 
     void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
-        const status_t status = mConsumer->sendTimeline(inputEventId, timeline);
+        const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
         ASSERT_EQ(OK, status);
     }
 
@@ -1091,12 +1073,12 @@
                << ": should not have received any events, so consume() should return NULL";
     }
 
-    sp<IBinder> getToken() { return mConsumer->getChannel()->getConnectionToken(); }
+    sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); }
 
-    int getChannelFd() { return mConsumer->getChannel()->getFd().get(); }
+    int getChannelFd() { return mConsumer.getChannel()->getFd().get(); }
 
-protected:
-    std::unique_ptr<InputConsumer> mConsumer;
+private:
+    InputConsumer mConsumer;
     PreallocatedInputEventFactory mEventFactory;
 
     std::string mName;
@@ -1436,42 +1418,39 @@
 
 class FakeMonitorReceiver {
 public:
-    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId) {
-        base::Result<std::unique_ptr<InputChannel>> channel =
-                dispatcher.createInputMonitor(displayId, name, MONITOR_PID);
-        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
-    }
+    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+          : mInputReceiver(*dispatcher.createInputMonitor(displayId, name, MONITOR_PID), name) {}
 
-    sp<IBinder> getToken() { return mInputReceiver->getToken(); }
+    sp<IBinder> getToken() { return mInputReceiver.getToken(); }
 
     void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
-                                     expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
+                                    expectedFlags);
     }
 
     std::optional<int32_t> receiveEvent() {
-        return mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+        return mInputReceiver.receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
     }
 
-    void finishEvent(uint32_t consumeSeq) { return mInputReceiver->finishEvent(consumeSeq); }
+    void finishEvent(uint32_t consumeSeq) { return mInputReceiver.finishEvent(consumeSeq); }
 
     void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
+                                    expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
+                                    expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
-                                     expectedDisplayId, expectedFlags);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
+                                    expectedDisplayId, expectedFlags);
     }
 
     void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        mInputReceiver->consumeMotionEvent(
+        mInputReceiver.consumeMotionEvent(
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
                       WithDisplayId(expectedDisplayId),
                       WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
@@ -1480,20 +1459,20 @@
     void consumeMotionPointerDown(int32_t pointerIdx) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        mInputReceiver->consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
-                                     /*expectedFlags=*/0);
+        mInputReceiver.consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
+                                    /*expectedFlags=*/0);
     }
 
     void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
-        mInputReceiver->consumeMotionEvent(matcher);
+        mInputReceiver.consumeMotionEvent(matcher);
     }
 
-    MotionEvent* consumeMotion() { return mInputReceiver->consumeMotion(); }
+    MotionEvent* consumeMotion() { return mInputReceiver.consumeMotion(); }
 
-    void assertNoEvents() { mInputReceiver->assertNoEvents(); }
+    void assertNoEvents() { mInputReceiver.assertNoEvents(); }
 
 private:
-    std::unique_ptr<FakeInputReceiver> mInputReceiver;
+    FakeInputReceiver mInputReceiver;
 };
 
 static InputEventInjectionResult injectKey(
@@ -2614,9 +2593,9 @@
 
 /**
  * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
- * touch is not dropped, because stylus hover should be ignored.
+ * touch is dropped, because stylus hover takes precedence.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2645,34 +2624,29 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
                                       .build());
 
-    // Stylus hover is canceled because touch is down
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT),
-                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId),
-                                     WithCoords(140, 145)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(141, 146)));
+    // Touch is ignored because stylus is hovering
 
-    // Subsequent stylus movements are ignored
+    // Subsequent stylus movements are delivered correctly
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
 
-    // but subsequent touches continue to be delivered
+    // and subsequent touches continue to be ignored
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
                                       .build());
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(142, 147)));
+    window->assertNoEvents();
 }
 
 /**
  * One window. Touch down on the window. Then, stylus hover on the window from another device.
- * Ensure that touch is not canceled, because stylus hover should be dropped.
+ * Ensure that touch is canceled, because stylus hover should take precedence.
  */
-TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -2704,15 +2678,21 @@
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
                                       .build());
-    // Stylus hover movement is dropped
+    // Stylus hover movement causes touch to be canceled
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
+                                     WithCoords(141, 146)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
 
+    // Subsequent touch movements are ignored
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
                                       .build());
-    // Subsequent touch movements are delivered correctly
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
-                                     WithCoords(142, 147)));
+
+    window->assertNoEvents();
 }
 
 /**
@@ -3029,11 +3009,11 @@
  * Three windows: a window on the left, a window on the right, and a spy window positioned above
  * both.
  * Check hover in left window and touch down in the right window.
- * At first, spy should receive hover, but the touch down should cancel hovering inside spy.
+ * At first, spy should receive hover. Spy shouldn't receive touch while stylus is hovering.
  * At the same time, left and right should be getting independent streams of hovering and touch,
  * respectively.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlockedByTouchWithSpy) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> spyWindow =
@@ -3073,28 +3053,25 @@
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
                                       .build());
     leftWindow->assertNoEvents();
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spyWindow->assertNoEvents();
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
 
-    // Stylus movements continue. They should be delivered to the left window only.
+    // Stylus movements continue. They should be delivered to the left window and the spy.
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
                                       .deviceId(stylusDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
                                       .build());
     leftWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
-    // Touch movements continue. They should be delivered to the right window and to the spy
+    // Touch movements continue. They should be delivered to the right window only
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
                                       .deviceId(touchDeviceId)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
                                       .build());
-    spyWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
     rightWindow->consumeMotionEvent(
             AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
 
@@ -3309,7 +3286,7 @@
  * While the touch is down, new hover events from the stylus device should be ignored. After the
  * touch is gone, stylus hovering should start working again.
  */
-TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDroppedWhenTouchTap) {
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
@@ -3335,10 +3312,7 @@
                                         .deviceId(touchDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    // The touch device should cause hover to stop!
-    window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(stylusDeviceId)));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    // The touch device should be ignored!
 
     // Continue hovering with stylus.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3348,7 +3322,9 @@
                                         .deviceId(stylusDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
                                         .build()));
-    // Hovers are now ignored
+    // Hovers continue to work
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
 
     // Lift up the finger
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -3358,7 +3334,6 @@
                                         .deviceId(touchDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
                                         .build()));
-    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
@@ -3367,8 +3342,8 @@
                                         .deviceId(stylusDeviceId)
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
                                         .build()));
-    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                     WithDeviceId(stylusDeviceId)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
     window->assertNoEvents();
 }
 
@@ -6773,8 +6748,8 @@
 
 class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
 protected:
-    static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms
-    static constexpr nsecs_t KEY_REPEAT_DELAY = 40 * 1000000;   // 40 ms
+    static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms;
+    static constexpr std::chrono::nanoseconds KEY_REPEAT_DELAY = 40ms;
 
     std::shared_ptr<FakeApplicationHandle> mApp;
     sp<FakeWindowHandle> mWindow;
@@ -8156,7 +8131,8 @@
     // Injection is async, so it will succeed
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE, INJECT_EVENT_TIMEOUT,
+                        /*allowKeyRepeat=*/false));
     // At this point, key is still pending, and should not be sent to the application yet.
     // Make sure the `assertNoEvents` check doesn't take too long. It uses
     // CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index da2e205..68f5857 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -27,6 +27,7 @@
 namespace android {
 
 using ControllerType = PointerControllerInterface::ControllerType;
+using testing::AllOf;
 
 namespace {
 
@@ -37,12 +38,18 @@
 Visitor(V...) -> Visitor<V...>;
 
 constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
 constexpr int32_t DISPLAY_ID = 5;
 constexpr int32_t ANOTHER_DISPLAY_ID = 10;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
 
 const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE)
                                    .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
                                    .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
+const auto FIRST_TOUCH_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200);
+const auto SECOND_TOUCH_POINTER = PointerBuilder(/*id=*/1, ToolType::FINGER).x(200).y(300);
+const auto STYLUS_POINTER = PointerBuilder(/*id=*/0, ToolType::STYLUS).x(100).y(200);
 
 static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
                                               int32_t associatedDisplayId) {
@@ -60,6 +67,8 @@
     for (auto displayId : displayIds) {
         DisplayViewport viewport;
         viewport.displayId = displayId;
+        viewport.logicalRight = DISPLAY_WIDTH;
+        viewport.logicalBottom = DISPLAY_HEIGHT;
         viewports.push_back(viewport);
     }
     return viewports;
@@ -115,6 +124,7 @@
         EXPECT_FALSE(mLastCreatedController.has_value())
                 << "More than one PointerController created at a time";
         std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
+        EXPECT_FALSE(pc->isPointerShown());
         mLastCreatedController = {type, pc};
         return pc;
     }
@@ -385,4 +395,659 @@
     assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
 }
 
+TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set bounds and initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    pc->assertPosition(110, 220);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Check that x-y cooridnates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
+}
+
+TEST_F(PointerChoreographerTest,
+       AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
+    // Add two displays and set one to default.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Add two devices, one unassociated and the other associated with non-default mouse display.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+
+    // Set bounds and initial position for PointerControllers.
+    unassociatedMousePc->setPosition(100, 200);
+    associatedMousePc->setPosition(300, 400);
+
+    // Make NotifyMotionArgs from the associated mouse and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // Check the status of the PointerControllers.
+    unassociatedMousePc->assertPosition(100, 200);
+    ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+    associatedMousePc->assertPosition(310, 420);
+    ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+    ASSERT_TRUE(associatedMousePc->isPointerShown());
+
+    // Check that x-y cooridnates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID),
+                  WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420)));
+}
+
+TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set bounds and initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Assume that pointer capture is enabled.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/1,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+
+    // Notify motion as if pointer capture is enabled.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE_RELATIVE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+                                     .x(10)
+                                     .y(20)
+                                     .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+                                     .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+
+    // Check that there's no update on the PointerController.
+    pc->assertPosition(100, 200);
+    ASSERT_FALSE(pc->isPointerShown());
+
+    // Check x-y cooridnates, displayId and cursor position are not changed.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE),
+                  WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                     AMOTION_EVENT_INVALID_CURSOR_POSITION)));
+}
+
+TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+    mTestListener.assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE));
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set bounds and initial position of the PointerController.
+    pc->setPosition(100, 200);
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ADISPLAY_ID_NONE)
+                    .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    pc->assertPosition(110, 220);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Enable pointer capture and check if the PointerController hid the pointer.
+    mChoreographer.notifyPointerCaptureChanged(
+            NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) {
+    // Disable show touches and add a touch device.
+    mChoreographer.setShowTouchesEnabled(false);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    assertPointerControllerNotCreated();
+
+    // Enable show touches. PointerController still should not be created.
+    mChoreographer.setShowTouchesEnabled(true);
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchEventOccursCreatesPointerController) {
+    // Add a touch device and enable show touches.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+
+    // Emit touch event. Now PointerController should be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerCreated(ControllerType::TOUCH);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenShowTouchesDisabledAndTouchEventOccursDoesNotCreatePointerController) {
+    // Add a touch device and disable show touches.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(false);
+    assertPointerControllerNotCreated();
+
+    // Emit touch event. Still, PointerController should not be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsRemovedRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+    // Remove the device.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesDisabledRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    assertPointerControllerNotCreated();
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+    // Disable show touches.
+    mChoreographer.setShowTouchesEnabled(false);
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpots) {
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+
+    // Emit first pointer down.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Emit second pointer down.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                      (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .pointer(SECOND_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertSpotCount(DISPLAY_ID, 2);
+
+    // Emit second pointer up.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_UP |
+                                      (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                              AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .pointer(SECOND_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Emit first pointer up.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertSpotCount(DISPLAY_ID, 0);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForStylusEvent) {
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+                                     DISPLAY_ID)}});
+
+    // Emit down event with stylus properties.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForTwoDisplays) {
+    mChoreographer.setShowTouchesEnabled(true);
+    // Add two touch devices associated to different displays.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+                                     ANOTHER_DISPLAY_ID)}});
+
+    // Emit touch event with first device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+    firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Emit touch events with second device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .pointer(SECOND_TOUCH_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // There should be another PointerController created.
+    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+    // Check if the spots are set for the second device.
+    secondDisplayPc->assertSpotCount(ANOTHER_DISPLAY_ID, 2);
+
+    // Check if there's no change on the spot of the first device.
+    firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsResetClearsSpots) {
+    // Make sure the PointerController is created and there is a spot.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    // Reset the device and ensure the touch pointer controller was removed.
+    mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+    // Disable stylus pointer icon and add a stylus device.
+    mChoreographer.setStylusPointerIconEnabled(false);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    assertPointerControllerNotCreated();
+
+    // Enable stylus pointer icon. PointerController still should not be created.
+    mChoreographer.setStylusPointerIconEnabled(true);
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) {
+    // Add a stylus device and enable stylus pointer icon.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    assertPointerControllerNotCreated();
+
+    // Emit hover event. Now PointerController should be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerCreated(ControllerType::STYLUS);
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) {
+    // Add a stylus device and disable stylus pointer icon.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerNotCreated();
+
+    // Emit hover event. Still, PointerController should not be created.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Remove the device.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Disable stylus pointer icon.
+    mChoreographer.setStylusPointerIconEnabled(false);
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) {
+    // Set viewport.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Check that displayId is set.
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Check that displayId is unset.
+    ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+
+    // Set viewport.
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Check that displayId is set.
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest,
+       WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+    // Make sure the PointerController is created.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Check that displayId is unset.
+    ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+
+    // Set viewport which does not match the associated display of the stylus.
+    mChoreographer.setDisplayViewports(createViewports({ANOTHER_DISPLAY_ID}));
+
+    // Check that displayId is still unset.
+    ASSERT_EQ(ADISPLAY_ID_NONE, pc->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Emit hover enter event. This is for creating PointerController.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Emit hover move event. After bounds are set, PointerController will update the position.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    pc->assertPosition(150, 250);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Emit hover exit event.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    // Check that the pointer is gone.
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) {
+    mChoreographer.setStylusPointerIconEnabled(true);
+    // Add two stylus devices associated to different displays.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}});
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+    // Emit hover event with first device. This is for creating PointerController.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Emit hover event with second device. This is for creating PointerController.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // There should be another PointerController created.
+    auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    // Emit hover event with first device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // Check the pointer of the first device.
+    firstDisplayPc->assertPosition(150, 250);
+    ASSERT_TRUE(firstDisplayPc->isPointerShown());
+
+    // Emit hover event with second device.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350))
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(ANOTHER_DISPLAY_ID)
+                    .build());
+
+    // Check the pointer of the second device.
+    secondDisplayPc->assertPosition(250, 350);
+    ASSERT_TRUE(secondDisplayPc->isPointerShown());
+
+    // Check that there's no change on the pointer of the first device.
+    firstDisplayPc->assertPosition(150, 250);
+    ASSERT_TRUE(firstDisplayPc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) {
+    // Make sure the PointerController is created and there is a pointer.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                    .pointer(STYLUS_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Reset the device and see the pointer controller was removed.
+    mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+    assertPointerControllerRemoved(pc);
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 9313a89..8a4f6f0 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -167,3 +167,17 @@
         "LatencyTrackerFuzzer.cpp",
     ],
 }
+
+cc_fuzz {
+    name: "inputflinger_input_dispatcher_fuzzer",
+    defaults: [
+        "inputflinger_fuzz_defaults",
+        "libinputdispatcher_defaults",
+    ],
+    shared_libs: [
+        "libinputreporter",
+    ],
+    srcs: [
+        "InputDispatcherFuzzer.cpp",
+    ],
+}
diff --git a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
new file mode 100644
index 0000000..885820f
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+#include <fuzzer/FuzzedDataProvider.h>
+
+namespace android {
+
+namespace {
+static constexpr int32_t MAX_RANDOM_POINTERS = 4;
+static constexpr int32_t MAX_RANDOM_DEVICES = 4;
+} // namespace
+
+int getFuzzedMotionAction(FuzzedDataProvider& fdp) {
+    int actionMasked = fdp.PickValueInArray<int>({
+            AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_ACTION_UP, AMOTION_EVENT_ACTION_MOVE,
+            AMOTION_EVENT_ACTION_HOVER_ENTER, AMOTION_EVENT_ACTION_HOVER_MOVE,
+            AMOTION_EVENT_ACTION_HOVER_EXIT, AMOTION_EVENT_ACTION_CANCEL,
+            // do not inject AMOTION_EVENT_ACTION_OUTSIDE,
+            AMOTION_EVENT_ACTION_SCROLL, AMOTION_EVENT_ACTION_POINTER_DOWN,
+            AMOTION_EVENT_ACTION_POINTER_UP,
+            // do not send buttons until verifier supports them
+            // AMOTION_EVENT_ACTION_BUTTON_PRESS,
+            // AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+    });
+    switch (actionMasked) {
+        case AMOTION_EVENT_ACTION_POINTER_DOWN:
+        case AMOTION_EVENT_ACTION_POINTER_UP: {
+            const int32_t index = fdp.ConsumeIntegralInRange(0, MAX_RANDOM_POINTERS - 1);
+            const int32_t action =
+                    actionMasked | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+            return action;
+        }
+        default:
+            return actionMasked;
+    }
+}
+
+/**
+ * For now, focus on the 3 main sources.
+ */
+int getFuzzedSource(FuzzedDataProvider& fdp) {
+    return fdp.PickValueInArray<int>({
+            // AINPUT_SOURCE_UNKNOWN,
+            // AINPUT_SOURCE_KEYBOARD,
+            // AINPUT_SOURCE_DPAD,
+            // AINPUT_SOURCE_GAMEPAD,
+            AINPUT_SOURCE_TOUCHSCREEN, AINPUT_SOURCE_MOUSE, AINPUT_SOURCE_STYLUS,
+            // AINPUT_SOURCE_BLUETOOTH_STYLUS,
+            // AINPUT_SOURCE_TRACKBALL,
+            // AINPUT_SOURCE_MOUSE_RELATIVE,
+            // AINPUT_SOURCE_TOUCHPAD,
+            // AINPUT_SOURCE_TOUCH_NAVIGATION,
+            // AINPUT_SOURCE_JOYSTICK,
+            // AINPUT_SOURCE_HDMI,
+            // AINPUT_SOURCE_SENSOR,
+            // AINPUT_SOURCE_ROTARY_ENCODER,
+            // AINPUT_SOURCE_ANY,
+    });
+}
+
+int getFuzzedButtonState(FuzzedDataProvider& fdp) {
+    return fdp.PickValueInArray<int>({
+            0,
+            // AMOTION_EVENT_BUTTON_PRIMARY,
+            // AMOTION_EVENT_BUTTON_SECONDARY,
+            // AMOTION_EVENT_BUTTON_TERTIARY,
+            // AMOTION_EVENT_BUTTON_BACK,
+            // AMOTION_EVENT_BUTTON_FORWARD,
+            // AMOTION_EVENT_BUTTON_STYLUS_PRIMARY,
+            // AMOTION_EVENT_BUTTON_STYLUS_SECONDARY,
+    });
+}
+
+int32_t getFuzzedFlags(FuzzedDataProvider& fdp, int32_t action) {
+    constexpr std::array<int32_t, 4> FLAGS{
+            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
+            AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+            AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+            AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
+    };
+
+    int32_t flags = 0;
+    for (size_t i = 0; i < fdp.ConsumeIntegralInRange(size_t(0), FLAGS.size()); i++) {
+        flags |= fdp.PickValueInArray<int32_t>(FLAGS);
+    }
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    if (MotionEvent::getActionMasked(action) == AMOTION_EVENT_ACTION_POINTER_UP) {
+        if (fdp.ConsumeBool()) {
+            flags |= AMOTION_EVENT_FLAG_CANCELED;
+        }
+    }
+    return flags;
+}
+
+int32_t getFuzzedPointerCount(FuzzedDataProvider& fdp, int32_t action) {
+    switch (MotionEvent::getActionMasked(action)) {
+        case AMOTION_EVENT_ACTION_DOWN:
+        case AMOTION_EVENT_ACTION_UP: {
+            return 1;
+        }
+        case AMOTION_EVENT_ACTION_OUTSIDE:
+        case AMOTION_EVENT_ACTION_CANCEL:
+        case AMOTION_EVENT_ACTION_MOVE:
+            return fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS);
+        case AMOTION_EVENT_ACTION_HOVER_ENTER:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_EXIT:
+            return 1;
+        case AMOTION_EVENT_ACTION_SCROLL:
+            return 1;
+        case AMOTION_EVENT_ACTION_POINTER_DOWN:
+        case AMOTION_EVENT_ACTION_POINTER_UP: {
+            const uint8_t actionIndex = MotionEvent::getActionIndex(action);
+            const int32_t count =
+                    std::max(actionIndex + 1,
+                             fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS));
+            // Need to have at least 2 pointers
+            return std::max(2, count);
+        }
+        case AMOTION_EVENT_ACTION_BUTTON_PRESS:
+        case AMOTION_EVENT_ACTION_BUTTON_RELEASE: {
+            return 1;
+        }
+    }
+    return 1;
+}
+
+ToolType getToolType(int32_t source) {
+    switch (source) {
+        case AINPUT_SOURCE_TOUCHSCREEN:
+            return ToolType::FINGER;
+        case AINPUT_SOURCE_MOUSE:
+            return ToolType::MOUSE;
+        case AINPUT_SOURCE_STYLUS:
+            return ToolType::STYLUS;
+    }
+    return ToolType::UNKNOWN;
+}
+
+inline nsecs_t now() {
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+NotifyMotionArgs generateFuzzedMotionArgs(IdGenerator& idGenerator, FuzzedDataProvider& fdp,
+                                          int32_t maxDisplays) {
+    // Create a basic motion event for testing
+    const int32_t source = getFuzzedSource(fdp);
+    const ToolType toolType = getToolType(source);
+    const int32_t action = getFuzzedMotionAction(fdp);
+    const int32_t pointerCount = getFuzzedPointerCount(fdp, action);
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+    for (int i = 0; i < pointerCount; i++) {
+        PointerProperties properties{};
+        properties.id = i;
+        properties.toolType = toolType;
+        pointerProperties.push_back(properties);
+
+        PointerCoords coords{};
+        coords.setAxisValue(AMOTION_EVENT_AXIS_X, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
+        coords.setAxisValue(AMOTION_EVENT_AXIS_Y, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
+        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1);
+        pointerCoords.push_back(coords);
+    }
+
+    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1);
+    const int32_t deviceId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DEVICES - 1);
+
+    // Current time +- 5 seconds
+    const nsecs_t currentTime = now();
+    const nsecs_t downTime =
+            fdp.ConsumeIntegralInRange<nsecs_t>(currentTime - 5E9, currentTime + 5E9);
+    const nsecs_t readTime = downTime;
+    const nsecs_t eventTime = fdp.ConsumeIntegralInRange<nsecs_t>(downTime, downTime + 1E9);
+
+    const float cursorX = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
+    const float cursorY = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
+    return NotifyMotionArgs(idGenerator.nextId(), eventTime, readTime, deviceId, source, displayId,
+                            POLICY_FLAG_PASS_TO_USER, action,
+                            /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
+                            getFuzzedFlags(fdp, action), AMETA_NONE, getFuzzedButtonState(fdp),
+                            MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+                            pointerProperties.data(), pointerCoords.data(),
+                            /*xPrecision=*/0,
+                            /*yPrecision=*/0, cursorX, cursorY, downTime,
+                            /*videoFrames=*/{});
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index 3b3ed9b..deb811d 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -16,44 +16,16 @@
 
 #include <MapperHelpers.h>
 #include <fuzzer/FuzzedDataProvider.h>
+#include "FuzzedInputStream.h"
 #include "InputCommonConverter.h"
 #include "InputProcessor.h"
 
 namespace android {
 
-static constexpr int32_t MAX_AXES = 64;
+namespace {
 
-// Used by two fuzz operations and a bit lengthy, so pulled out into a function.
-NotifyMotionArgs generateFuzzedMotionArgs(FuzzedDataProvider &fdp) {
-    // Create a basic motion event for testing
-    PointerProperties properties;
-    properties.id = 0;
-    properties.toolType = getFuzzedToolType(fdp);
-    PointerCoords coords;
-    coords.clear();
-    for (int32_t i = 0; i < fdp.ConsumeIntegralInRange<int32_t>(0, MAX_AXES); i++) {
-        coords.setAxisValue(fdp.ConsumeIntegral<int32_t>(), fdp.ConsumeFloatingPoint<float>());
-    }
+constexpr int32_t MAX_RANDOM_DISPLAYS = 4;
 
-    const nsecs_t downTime = 2;
-    const nsecs_t readTime = downTime + fdp.ConsumeIntegralInRange<nsecs_t>(0, 1E8);
-    NotifyMotionArgs motionArgs(/*sequenceNum=*/fdp.ConsumeIntegral<uint32_t>(),
-                                /*eventTime=*/downTime, readTime,
-                                /*deviceId=*/fdp.ConsumeIntegral<int32_t>(), AINPUT_SOURCE_ANY,
-                                ADISPLAY_ID_DEFAULT,
-                                /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
-                                AMOTION_EVENT_ACTION_DOWN,
-                                /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
-                                /*flags=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
-                                /*buttonState=*/fdp.ConsumeIntegral<int32_t>(),
-                                MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
-                                /*pointerCount=*/1, &properties, &coords,
-                                /*xPrecision=*/fdp.ConsumeFloatingPoint<float>(),
-                                /*yPrecision=*/fdp.ConsumeFloatingPoint<float>(),
-                                AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                AMOTION_EVENT_INVALID_CURSOR_POSITION, downTime,
-                                /*videoFrames=*/{});
-    return motionArgs;
 }
 
 extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
@@ -62,6 +34,7 @@
     std::unique_ptr<FuzzInputListener> mFuzzListener = std::make_unique<FuzzInputListener>();
     std::unique_ptr<InputProcessorInterface> mClassifier =
             std::make_unique<InputProcessor>(*mFuzzListener);
+    IdGenerator idGenerator(IdGenerator::Source::OTHER);
 
     while (fdp.remaining_bytes() > 0) {
         fdp.PickValueInArray<std::function<void()>>({
@@ -90,7 +63,8 @@
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifyMotionArgs
-                    mClassifier->notifyMotion(generateFuzzedMotionArgs(fdp));
+                    mClassifier->notifyMotion(
+                            generateFuzzedMotionArgs(idGenerator, fdp, MAX_RANDOM_DISPLAYS));
                 },
                 [&]() -> void {
                     // SendToNextStage_NotifySwitchArgs
@@ -108,7 +82,8 @@
                 },
                 [&]() -> void {
                     // InputClassifierConverterTest
-                    const NotifyMotionArgs motionArgs = generateFuzzedMotionArgs(fdp);
+                    const NotifyMotionArgs motionArgs =
+                            generateFuzzedMotionArgs(idGenerator, fdp, MAX_RANDOM_DISPLAYS);
                     aidl::android::hardware::input::common::MotionEvent motionEvent =
                             notifyMotionArgsToHalMotionEvent(motionArgs);
                 },
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
new file mode 100644
index 0000000..214649c
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#include <android-base/stringprintf.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "../FakeApplicationHandle.h"
+#include "../FakeInputDispatcherPolicy.h"
+#include "../FakeWindowHandle.h"
+#include "FuzzedInputStream.h"
+#include "dispatcher/InputDispatcher.h"
+#include "input/InputVerifier.h"
+
+namespace android {
+
+using android::base::Result;
+using android::gui::WindowInfo;
+
+namespace inputdispatcher {
+
+namespace {
+
+static constexpr int32_t MAX_RANDOM_DISPLAYS = 4;
+static constexpr int32_t MAX_RANDOM_WINDOWS = 4;
+
+/**
+ * Provide a valid motion stream, to make the fuzzer more effective.
+ */
+class NotifyStreamProvider {
+public:
+    NotifyStreamProvider(FuzzedDataProvider& fdp)
+          : mFdp(fdp), mIdGenerator(IdGenerator::Source::OTHER), mVerifier("Fuzz verifier") {}
+
+    std::optional<NotifyMotionArgs> nextMotion() {
+        NotifyMotionArgs args = generateFuzzedMotionArgs(mIdGenerator, mFdp, MAX_RANDOM_DISPLAYS);
+        const Result<void> result =
+                mVerifier.processMovement(args.deviceId, args.source, args.action,
+                                          args.getPointerCount(), args.pointerProperties.data(),
+                                          args.pointerCoords.data(), args.flags);
+        if (result.ok()) {
+            return args;
+        }
+        return {};
+    }
+
+private:
+    FuzzedDataProvider& mFdp;
+
+    IdGenerator mIdGenerator;
+
+    InputVerifier mVerifier;
+};
+
+} // namespace
+
+sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatcher& dispatcher,
+                                          int32_t displayId) {
+    static size_t windowNumber = 0;
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    std::string windowName = android::base::StringPrintf("Win") + std::to_string(windowNumber++);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, dispatcher, "Fake", displayId);
+
+    const int32_t left = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t top = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t width = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+    const int32_t height = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+
+    window->setFrame(Rect(left, top, left + width, top + height));
+    window->setSlippery(fdp.ConsumeBool());
+    window->setDupTouchToWallpaper(fdp.ConsumeBool());
+    window->setTrustedOverlay(fdp.ConsumeBool());
+    return window;
+}
+
+void randomizeWindows(
+        std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>>& windowsPerDisplay,
+        FuzzedDataProvider& fdp, InputDispatcher& dispatcher) {
+    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1);
+    std::vector<sp<FakeWindowHandle>>& windows = windowsPerDisplay[displayId];
+
+    fdp.PickValueInArray<std::function<void()>>({
+            // Add a new window
+            [&]() -> void {
+                if (windows.size() < MAX_RANDOM_WINDOWS) {
+                    windows.push_back(generateFuzzedWindow(fdp, dispatcher, displayId));
+                }
+            },
+            // Remove a window
+            [&]() -> void {
+                if (windows.empty()) {
+                    return;
+                }
+                const int32_t erasedPosition =
+                        fdp.ConsumeIntegralInRange<int32_t>(0, windows.size() - 1);
+
+                windows.erase(windows.begin() + erasedPosition);
+                if (windows.empty()) {
+                    windowsPerDisplay.erase(displayId);
+                }
+            },
+            // Could also clone a window, change flags, reposition, etc...
+    })();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+    FuzzedDataProvider fdp(data, size);
+    NotifyStreamProvider streamProvider(fdp);
+
+    FakeInputDispatcherPolicy fakePolicy;
+    InputDispatcher dispatcher(fakePolicy);
+    dispatcher.setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+    // Start InputDispatcher thread
+    dispatcher.start();
+
+    std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay;
+
+    // Randomly invoke InputDispatcher api's until randomness is exhausted.
+    while (fdp.remaining_bytes() > 0) {
+        fdp.PickValueInArray<std::function<void()>>({
+                [&]() -> void {
+                    std::optional<NotifyMotionArgs> motion = streamProvider.nextMotion();
+                    if (motion) {
+                        dispatcher.notifyMotion(*motion);
+                    }
+                },
+                [&]() -> void {
+                    // Scramble the windows we currently have
+                    randomizeWindows(/*byref*/ windowsPerDisplay, fdp, dispatcher);
+
+                    std::vector<WindowInfo> windowInfos;
+                    for (const auto& [displayId, windows] : windowsPerDisplay) {
+                        for (const sp<FakeWindowHandle>& window : windows) {
+                            windowInfos.emplace_back(*window->getInfo());
+                        }
+                    }
+
+                    dispatcher.onWindowInfosChanged(
+                            {windowInfos, {}, /*vsyncId=*/0, /*timestamp=*/0});
+                },
+                // Consume on all the windows
+                [&]() -> void {
+                    for (const auto& [_, windows] : windowsPerDisplay) {
+                        for (const sp<FakeWindowHandle>& window : windows) {
+                            // To speed up the fuzzing, don't wait for consumption. If there's an
+                            // event pending, this can be consumed on the next call instead.
+                            // We also don't care about whether consumption succeeds here, or what
+                            // kind of event is returned.
+                            window->consume(0ms);
+                        }
+                    }
+                },
+        })();
+    }
+
+    dispatcher.stop();
+
+    return 0;
+}
+
+} // namespace inputdispatcher
+
+} // namespace android
\ No newline at end of file
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 8b16890..1f72e8b 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -19,6 +19,7 @@
         "PowerHalWrapper.cpp",
         "PowerSaveState.cpp",
         "Temperature.cpp",
+        "WorkDuration.cpp",
         "WorkSource.cpp",
         ":libpowermanager_aidl",
     ],
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
new file mode 100644
index 0000000..ef723c2
--- /dev/null
+++ b/services/powermanager/WorkDuration.cpp
@@ -0,0 +1,51 @@
+/**
+ * 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 "WorkDuration"
+
+#include <android/WorkDuration.h>
+#include <android/performance_hint.h>
+#include <binder/Parcel.h>
+#include <utils/Log.h>
+
+namespace android::os {
+
+WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
+                           int64_t cpuDurationNanos, int64_t gpuDurationNanos)
+      : workPeriodStartTimestampNanos(startTimestampNanos),
+        actualTotalDurationNanos(totalDurationNanos),
+        actualCpuDurationNanos(cpuDurationNanos),
+        actualGpuDurationNanos(gpuDurationNanos) {}
+
+status_t WorkDuration::writeToParcel(Parcel* parcel) const {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    parcel->writeInt64(workPeriodStartTimestampNanos);
+    parcel->writeInt64(actualTotalDurationNanos);
+    parcel->writeInt64(actualCpuDurationNanos);
+    parcel->writeInt64(actualGpuDurationNanos);
+    parcel->writeInt64(timestampNanos);
+    return OK;
+}
+
+status_t WorkDuration::readFromParcel(const Parcel*) {
+    return INVALID_OPERATION;
+}
+
+} // namespace android::os
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
new file mode 100644
index 0000000..99b5b8b
--- /dev/null
+++ b/services/powermanager/include/android/WorkDuration.h
@@ -0,0 +1,71 @@
+/**
+ * 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 <binder/Parcelable.h>
+#include <math.h>
+
+struct AWorkDuration {};
+
+namespace android::os {
+
+/**
+ * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in
+ * binder calls.
+ * This file needs to be kept in sync with the WorkDuration in
+ * frameworks/base/core/java/android/os/WorkDuration.java
+ */
+struct WorkDuration : AWorkDuration, android::Parcelable {
+    WorkDuration() = default;
+    ~WorkDuration() = default;
+
+    WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos,
+                 int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos);
+    status_t writeToParcel(Parcel* parcel) const override;
+    status_t readFromParcel(const Parcel* parcel) override;
+
+    inline bool equalsWithoutTimestamp(const WorkDuration& other) const {
+        return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos &&
+                actualTotalDurationNanos == other.actualTotalDurationNanos &&
+                actualCpuDurationNanos == other.actualCpuDurationNanos &&
+                actualGpuDurationNanos == other.actualGpuDurationNanos;
+    }
+
+    bool operator==(const WorkDuration& other) const {
+        return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other);
+    }
+
+    bool operator!=(const WorkDuration& other) const { return !(*this == other); }
+
+    friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) {
+        os << "{"
+           << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos
+           << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos
+           << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos
+           << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos
+           << ", timestampNanos: " << workDuration.timestampNanos << "}";
+        return os;
+    }
+
+    int64_t workPeriodStartTimestampNanos;
+    int64_t actualTotalDurationNanos;
+    int64_t actualCpuDurationNanos;
+    int64_t actualGpuDurationNanos;
+    int64_t timestampNanos;
+};
+
+} // namespace android::os
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 17fa7be..0989863 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -93,6 +93,7 @@
         "libscheduler",
         "libserviceutils",
         "libshaders",
+        "libsurfaceflinger_common",
         "libtimestats",
         "libtonemap",
         "libsurfaceflingerflags",
@@ -175,7 +176,6 @@
         "FrontEnd/LayerLifecycleManager.cpp",
         "FrontEnd/RequestedLayerState.cpp",
         "FrontEnd/TransactionHandler.cpp",
-        "FlagManager.cpp",
         "FpsReporter.cpp",
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 370e4b6..2740a97 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -63,7 +63,10 @@
 cc_library {
     name: "libcompositionengine",
     defaults: ["libcompositionengine_defaults"],
-    static_libs: ["libsurfaceflingerflags"],
+    static_libs: [
+        "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
+    ],
     srcs: [
         "src/planner/CachedSet.cpp",
         "src/planner/Flattener.cpp",
@@ -108,6 +111,7 @@
         "libgtest",
         "libgmock",
         "libcompositionengine",
+        "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
     local_include_dirs: ["include"],
@@ -143,6 +147,7 @@
         "librenderengine_mocks",
         "libgmock",
         "libgtest",
+        "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
     // For some reason, libvulkan isn't picked up from librenderengine
diff --git a/services/surfaceflinger/CompositionEngine/AndroidTest.xml b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
new file mode 100644
index 0000000..94e92f0
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for libcompositionengine_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push"
+                value="libcompositionengine_test->/data/local/tmp/libcompositionengine_test" />
+    </target_preparer>
+
+    <!--
+        Disable SELinux so that crashes in the test suite produces symbolized stack traces.
+    -->
+    <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="libcompositionengine_test" />
+    </test>
+</configuration>
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 1a8644e..1a235e9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -96,6 +96,9 @@
     // The expected time for the next present
     nsecs_t expectedPresentTime{0};
 
+    // The frameInterval for the next present
+    Fps frameInterval{};
+
     // If set, a frame has been scheduled for that time.
     std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 6cb1e7e..692ed24 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -127,6 +127,9 @@
     // The expected time for the next present
     nsecs_t expectedPresentTime{0};
 
+    // The frameInterval for the next present
+    Fps frameInterval{};
+
     // Current display brightness
     float displayBrightnessNits{-1.f};
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 4fa5f19..469fb38 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -255,10 +255,10 @@
 
     const TimePoint hwcValidateStartTime = TimePoint::now();
 
-    if (status_t result =
-                hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
-                                                getState().earliestPresentTime,
-                                                getState().expectedPresentTime, outChanges);
+    if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
+                                                          getState().earliestPresentTime,
+                                                          getState().expectedPresentTime,
+                                                          getState().frameInterval, outChanges);
         result != NO_ERROR) {
         ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result,
               strerror(-result));
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 81bfe9e..2ae80de 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -836,6 +836,7 @@
 
     editState().earliestPresentTime = refreshArgs.earliestPresentTime;
     editState().expectedPresentTime = refreshArgs.expectedPresentTime;
+    editState().frameInterval = refreshArgs.frameInterval;
     editState().powerCallback = refreshArgs.powerCallback;
 
     compositionengine::OutputLayer* peekThroughLayer = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 2f891b3..4a778d4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -582,7 +582,7 @@
 TEST_F(DisplayChooseCompositionStrategyTest, takesEarlyOutOnHwcError) {
     EXPECT_CALL(*mDisplay, anyLayersRequireClientComposition()).WillOnce(Return(false));
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _))
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), false, _, _, _, _))
             .WillOnce(Return(INVALID_OPERATION));
 
     chooseCompositionStrategy(mDisplay.get());
@@ -606,8 +606,8 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
-            .WillOnce(testing::DoAll(testing::SetArgPointee<4>(mDeviceRequestedChanges),
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
+            .WillOnce(testing::DoAll(testing::SetArgPointee<5>(mDeviceRequestedChanges),
                                      Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
             .Times(1);
@@ -659,8 +659,8 @@
             .WillOnce(Return(false));
 
     EXPECT_CALL(mHwComposer,
-                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _))
-            .WillOnce(DoAll(SetArgPointee<4>(mDeviceRequestedChanges), Return(NO_ERROR)));
+                getDeviceCompositionChanges(HalDisplayId(DEFAULT_DISPLAY_ID), true, _, _, _, _))
+            .WillOnce(DoAll(SetArgPointee<5>(mDeviceRequestedChanges), Return(NO_ERROR)));
     EXPECT_CALL(*mDisplay, applyChangedTypesToLayers(mDeviceRequestedChanges.changedTypes))
             .Times(1);
     EXPECT_CALL(*mDisplay, applyDisplayRequests(mDeviceRequestedChanges.displayRequests)).Times(1);
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index a44e4be..b2491d8 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -54,9 +54,9 @@
     MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
 
     MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
-    MOCK_METHOD5(getDeviceCompositionChanges,
-                 status_t(HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>,
-                          nsecs_t, std::optional<android::HWComposer::DeviceRequestedChanges>*));
+    MOCK_METHOD(status_t, getDeviceCompositionChanges,
+                (HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>, nsecs_t,
+                 Fps, std::optional<android::HWComposer::DeviceRequestedChanges>*));
     MOCK_METHOD5(setClientTarget,
                  status_t(HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&,
                           ui::Dataspace));
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 00590e6..d9318af 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -223,9 +223,6 @@
 }
 
 TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) {
-    mTestLayers[0]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold / 2;
-    mTestLayers[1]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold;
-
     auto& layerState1 = mTestLayers[0]->layerState;
     auto& layerState2 = mTestLayers[1]->layerState;
 
@@ -235,6 +232,10 @@
     };
 
     initializeFlattener(layers);
+
+    mTestLayers[0]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold / 2;
+    mTestLayers[1]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold;
+
     expectAllLayersFlattened(layers);
 }
 
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 5b6591a..2ffe92b 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -63,13 +63,14 @@
         mDisplayToken(args.displayToken),
         mSequenceId(args.sequenceId),
         mCompositionDisplay{args.compositionDisplay},
-        mActiveModeFPSTrace("ActiveModeFPS -" + to_string(getId())),
-        mActiveModeFPSHwcTrace("ActiveModeFPS_HWC -" + to_string(getId())),
-        mRenderFrameRateFPSTrace("RenderRateFPS -" + to_string(getId())),
+        mActiveModeFPSTrace(concatId("ActiveModeFPS")),
+        mActiveModeFPSHwcTrace(concatId("ActiveModeFPS_HWC")),
+        mRenderFrameRateFPSTrace(concatId("RenderRateFPS")),
         mPhysicalOrientation(args.physicalOrientation),
         mIsPrimary(args.isPrimary),
         mRequestedRefreshRate(args.requestedRefreshRate),
-        mRefreshRateSelector(std::move(args.refreshRateSelector)) {
+        mRefreshRateSelector(std::move(args.refreshRateSelector)),
+        mDesiredActiveModeChanged(concatId("DesiredActiveModeChanged"), false) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
     mCompositionDisplay->createRenderSurface(
             compositionengine::RenderSurfaceCreationArgsBuilder()
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index a044534..a40f310 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -269,6 +269,11 @@
     void dump(utils::Dumper&) const;
 
 private:
+    template <size_t N>
+    inline std::string concatId(const char (&str)[N]) const {
+        return std::string(ftl::Concat(str, ' ', getId().value).str());
+    }
+
     const sp<SurfaceFlinger> mFlinger;
     HWComposer& mHwComposer;
     const wp<IBinder> mDisplayToken;
@@ -316,8 +321,7 @@
 
     mutable std::mutex mActiveModeLock;
     ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
-    TracedOrdinal<bool> mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock) =
-            {ftl::Concat("DesiredActiveModeChanged-", getId().value).c_str(), false};
+    TracedOrdinal<bool> mDesiredActiveModeChanged GUARDED_BY(mActiveModeLock);
 
     ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
     bool mIsModeSetPending GUARDED_BY(kMainThreadContext) = false;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 1643ad0..2d957e6 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -24,6 +24,7 @@
 #include <android-base/file.h>
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
+#include <common/FlagManager.h>
 #include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <utils/Trace.h>
@@ -281,7 +282,7 @@
 }
 
 bool AidlComposer::getDisplayConfigurationsSupported() const {
-    return mComposerInterfaceVersion >= 3;
+    return mComposerInterfaceVersion >= 3 && FlagManager::getInstance().vrr_config();
 }
 
 std::vector<Capability> AidlComposer::getCapabilities() {
@@ -749,7 +750,8 @@
 }
 
 Error AidlComposer::validateDisplay(Display display, nsecs_t expectedPresentTime,
-                                    uint32_t* outNumTypes, uint32_t* outNumRequests) {
+                                    int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                    uint32_t* outNumRequests) {
     const auto displayId = translate<int64_t>(display);
     ATRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId);
 
@@ -758,7 +760,8 @@
     auto writer = getWriter(display);
     auto reader = getReader(display);
     if (writer && reader) {
-        writer->get().validateDisplay(displayId, ClockMonotonicTimestamp{expectedPresentTime});
+        writer->get().validateDisplay(displayId, ClockMonotonicTimestamp{expectedPresentTime},
+                                      frameIntervalNs);
         error = execute(display);
     } else {
         error = Error::BAD_DISPLAY;
@@ -776,8 +779,9 @@
 }
 
 Error AidlComposer::presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                             uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                             int* outPresentFence, uint32_t* state) {
+                                             int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                             uint32_t* outNumRequests, int* outPresentFence,
+                                             uint32_t* state) {
     const auto displayId = translate<int64_t>(display);
     ATRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId);
 
@@ -787,7 +791,8 @@
     auto reader = getReader(display);
     if (writer && reader) {
         writer->get().presentOrvalidateDisplay(displayId,
-                                               ClockMonotonicTimestamp{expectedPresentTime});
+                                               ClockMonotonicTimestamp{expectedPresentTime},
+                                               frameIntervalNs);
         error = execute(display);
     } else {
         error = Error::BAD_DISPLAY;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 7693a80..1635a16 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -134,12 +134,13 @@
 
     Error setClientTargetSlotCount(Display display) override;
 
-    Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                          uint32_t* outNumRequests) override;
+    Error validateDisplay(Display display, nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                          uint32_t* outNumTypes, uint32_t* outNumRequests) override;
 
     Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                   uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                   int* outPresentFence, uint32_t* state) override;
+                                   int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                   uint32_t* outNumRequests, int* outPresentFence,
+                                   uint32_t* state) override;
 
     Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
     /* see setClientTarget for the purpose of slot */
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 6704d88..082717a 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -174,11 +174,13 @@
     virtual Error setClientTargetSlotCount(Display display) = 0;
 
     virtual Error validateDisplay(Display display, nsecs_t expectedPresentTime,
-                                  uint32_t* outNumTypes, uint32_t* outNumRequests) = 0;
+                                  int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                  uint32_t* outNumRequests) = 0;
 
     virtual Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                           uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                           int* outPresentFence, uint32_t* state) = 0;
+                                           int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                           uint32_t* outNumRequests, int* outPresentFence,
+                                           uint32_t* state) = 0;
 
     virtual Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) = 0;
     /* see setClientTarget for the purpose of slot */
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index f32fb3a..ba0825c 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -29,8 +29,8 @@
 
 #include <scheduler/Fps.h>
 
+#include <common/FlagManager.h>
 #include "DisplayHardware/Hal.h"
-#include "FlagManager.h"
 #include "Scheduler/StrongTyping.h"
 
 namespace android {
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 0c2b77d..bc763b2 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -517,11 +517,12 @@
     return static_cast<Error>(intError);
 }
 
-Error Display::validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+Error Display::validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs, uint32_t* outNumTypes,
                         uint32_t* outNumRequests) {
     uint32_t numTypes = 0;
     uint32_t numRequests = 0;
-    auto intError = mComposer.validateDisplay(mId, expectedPresentTime, &numTypes, &numRequests);
+    auto intError = mComposer.validateDisplay(mId, expectedPresentTime, frameIntervalNs, &numTypes,
+                                              &numRequests);
     auto error = static_cast<Error>(intError);
     if (error != Error::NONE && !hasChangesError(error)) {
         return error;
@@ -532,14 +533,15 @@
     return error;
 }
 
-Error Display::presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                                 uint32_t* outNumRequests, sp<android::Fence>* outPresentFence,
-                                 uint32_t* state) {
+Error Display::presentOrValidate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                                 uint32_t* outNumTypes, uint32_t* outNumRequests,
+                                 sp<android::Fence>* outPresentFence, uint32_t* state) {
     uint32_t numTypes = 0;
     uint32_t numRequests = 0;
     int32_t presentFenceFd = -1;
-    auto intError = mComposer.presentOrValidateDisplay(mId, expectedPresentTime, &numTypes,
-                                                       &numRequests, &presentFenceFd, state);
+    auto intError =
+            mComposer.presentOrValidateDisplay(mId, expectedPresentTime, frameIntervalNs, &numTypes,
+                                               &numRequests, &presentFenceFd, state);
     auto error = static_cast<Error>(intError);
     if (error != Error::NONE && !hasChangesError(error)) {
         return error;
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 23dd3e5..e7f807f 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -148,9 +148,10 @@
             const android::sp<android::Fence>& releaseFence) = 0;
     [[nodiscard]] virtual hal::Error setPowerMode(hal::PowerMode mode) = 0;
     [[nodiscard]] virtual hal::Error setVsyncEnabled(hal::Vsync enabled) = 0;
-    [[nodiscard]] virtual hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                                              uint32_t* outNumRequests) = 0;
+    [[nodiscard]] virtual hal::Error validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                                              uint32_t* outNumTypes, uint32_t* outNumRequests) = 0;
     [[nodiscard]] virtual hal::Error presentOrValidate(nsecs_t expectedPresentTime,
+                                                       int32_t frameIntervalNs,
                                                        uint32_t* outNumTypes,
                                                        uint32_t* outNumRequests,
                                                        android::sp<android::Fence>* outPresentFence,
@@ -233,10 +234,10 @@
                                const android::sp<android::Fence>& releaseFence) override;
     hal::Error setPowerMode(hal::PowerMode) override;
     hal::Error setVsyncEnabled(hal::Vsync enabled) override;
-    hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+    hal::Error validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs, uint32_t* outNumTypes,
                         uint32_t* outNumRequests) override;
-    hal::Error presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                                 uint32_t* outNumRequests,
+    hal::Error presentOrValidate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                                 uint32_t* outNumTypes, uint32_t* outNumRequests,
                                  android::sp<android::Fence>* outPresentFence,
                                  uint32_t* state) override;
     ftl::Future<hal::Error> setDisplayBrightness(
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 1d9f9ce..6be57d4 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -498,7 +498,7 @@
 status_t HWComposer::getDeviceCompositionChanges(
         HalDisplayId displayId, bool frameUsesClientComposition,
         std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
-        nsecs_t expectedPresentTime,
+        nsecs_t expectedPresentTime, Fps frameInterval,
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
     ATRACE_CALL();
 
@@ -545,10 +545,10 @@
     }
 
     if (canSkipValidate) {
-        sp<Fence> outPresentFence;
+        sp<Fence> outPresentFence = Fence::NO_FENCE;
         uint32_t state = UINT32_MAX;
-        error = hwcDisplay->presentOrValidate(expectedPresentTime, &numTypes, &numRequests,
-                                              &outPresentFence, &state);
+        error = hwcDisplay->presentOrValidate(expectedPresentTime, frameInterval.getPeriodNsecs(),
+                                              &numTypes, &numRequests, &outPresentFence, &state);
         if (!hasChangesError(error)) {
             RETURN_IF_HWC_ERROR_FOR("presentOrValidate", error, displayId, UNKNOWN_ERROR);
         }
@@ -563,7 +563,8 @@
         }
         // Present failed but Validate ran.
     } else {
-        error = hwcDisplay->validate(expectedPresentTime, &numTypes, &numRequests);
+        error = hwcDisplay->validate(expectedPresentTime, frameInterval.getPeriodNsecs(), &numTypes,
+                                     &numRequests);
     }
     ALOGV("SkipValidate failed, Falling back to SLOW validate/present");
     if (!hasChangesError(error)) {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 51e9319..5846c07 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -147,7 +147,8 @@
     virtual status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
-            nsecs_t expectedPresentTime, std::optional<DeviceRequestedChanges>* outChanges) = 0;
+            nsecs_t expectedPresentTime, Fps frameInterval,
+            std::optional<DeviceRequestedChanges>* outChanges) = 0;
 
     virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
                                      const sp<GraphicBuffer>& target, ui::Dataspace) = 0;
@@ -347,7 +348,7 @@
     status_t getDeviceCompositionChanges(
             HalDisplayId, bool frameUsesClientComposition,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
-            nsecs_t expectedPresentTime,
+            nsecs_t expectedPresentTime, Fps frameInterval,
             std::optional<DeviceRequestedChanges>* outChanges) override;
 
     status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index c13e568..1e7c6da 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -665,7 +665,8 @@
 }
 
 Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
-                                    uint32_t* outNumTypes, uint32_t* outNumRequests) {
+                                    int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
+                                    uint32_t* outNumRequests) {
     ATRACE_NAME("HwcValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.validateDisplay();
@@ -681,8 +682,9 @@
 }
 
 Error HidlComposer::presentOrValidateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
-                                             uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                             int* outPresentFence, uint32_t* state) {
+                                             int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
+                                             uint32_t* outNumRequests, int* outPresentFence,
+                                             uint32_t* state) {
     ATRACE_NAME("HwcPresentOrValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.presentOrvalidateDisplay();
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 1004ddd..5c19b47 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -236,12 +236,13 @@
 
     Error setClientTargetSlotCount(Display display) override;
 
-    Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
-                          uint32_t* outNumRequests) override;
+    Error validateDisplay(Display display, nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+                          uint32_t* outNumTypes, uint32_t* outNumRequests) override;
 
     Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
-                                   uint32_t* outNumTypes, uint32_t* outNumRequests,
-                                   int* outPresentFence, uint32_t* state) override;
+                                   int32_t frameIntervalNs, uint32_t* outNumTypes,
+                                   uint32_t* outNumRequests, int* outPresentFence,
+                                   uint32_t* state) override;
 
     Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
     /* see setClientTarget for the purpose of slot */
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index f00ef67..e005ad3 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -251,7 +251,12 @@
     actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
     mActualDuration = actualDuration;
     WorkDuration duration;
+    duration.workPeriodStartTimestampNanos = mCommitStartTimes[0].ns();
+    // TODO(b/284324521): Correctly calculate total duration.
     duration.durationNanos = actualDuration->ns();
+    duration.cpuDurationNanos = actualDuration->ns();
+    // TODO(b/284324521): Calculate RenderEngine GPU time.
+    duration.gpuDurationNanos = 0;
     duration.timeStampNanos = TimePoint::now().ns();
     mHintSessionQueue.push_back(duration);
 
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 2a0857d..9476ff4 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -739,6 +739,7 @@
         !snapshot.changes.test(RequestedLayerState::Changes::Created)) {
         if (forceUpdate ||
             snapshot.changes.any(RequestedLayerState::Changes::Geometry |
+                                 RequestedLayerState::Changes::BufferSize |
                                  RequestedLayerState::Changes::Input)) {
             updateInput(snapshot, requested, parentSnapshot, path, args);
         }
@@ -1095,6 +1096,8 @@
         snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY;
     }
 
+    snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
+
     // If the layer is a clone, we need to crop the input region to cloned root to prevent
     // touches from going outside the cloned area.
     if (path.isClone()) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 66ea15c..0c0639e 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -136,6 +136,7 @@
 using gui::GameMode;
 using gui::LayerMetadata;
 using gui::WindowInfo;
+using ui::Size;
 
 using PresentState = frametimeline::SurfaceFrame::PresentState;
 
@@ -165,6 +166,7 @@
     mDrawingState.sequence = 0;
     mDrawingState.transform.set(0, 0);
     mDrawingState.frameNumber = 0;
+    mDrawingState.previousFrameNumber = 0;
     mDrawingState.barrierFrameNumber = 0;
     mDrawingState.producerId = 0;
     mDrawingState.barrierProducerId = 0;
@@ -2591,6 +2593,9 @@
         }
     }
 
+    Rect bufferSize = getBufferSize(getDrawingState());
+    info.contentSize = Size(bufferSize.width(), bufferSize.height());
+
     return info;
 }
 
@@ -2931,7 +2936,6 @@
             break;
         }
     }
-
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
@@ -2940,6 +2944,10 @@
     if (mBufferInfo.mBuffer) {
         mPreviouslyPresentedLayerStacks.push_back(layerStack);
     }
+
+    if (mDrawingState.frameNumber > 0) {
+        mDrawingState.previousFrameNumber = mDrawingState.frameNumber;
+    }
 }
 
 void Layer::onSurfaceFrameCreated(
@@ -3144,6 +3152,7 @@
 void Layer::resetDrawingStateBufferInfo() {
     mDrawingState.producerId = 0;
     mDrawingState.frameNumber = 0;
+    mDrawingState.previousFrameNumber = 0;
     mDrawingState.releaseBufferListener = nullptr;
     mDrawingState.buffer = nullptr;
     mDrawingState.acquireFence = sp<Fence>::make(-1);
@@ -3420,6 +3429,7 @@
             // If this transaction set an acquire fence on this layer, set its acquire time
             handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
             handle->frameNumber = mDrawingState.frameNumber;
+            handle->previousFrameNumber = mDrawingState.previousFrameNumber;
 
             // Store so latched time and release fence can be set
             mDrawingState.callbackHandles.push_back(handle);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index f715910..28168c3 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -138,6 +138,7 @@
         ui::Dataspace dataspace;
 
         uint64_t frameNumber;
+        uint64_t previousFrameNumber;
         // high watermark framenumber to use to check for barriers to protect ourselves
         // from out of order transactions
         uint64_t barrierFrameNumber;
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 6752a0b..b960e33 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -16,8 +16,8 @@
 
 #include <algorithm>
 
+#include <common/FlagManager.h>
 #include "Client.h"
-#include "FlagManager.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 7f627f8..693a357 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -43,9 +43,9 @@
 #include <utils/Errors.h>
 #include <utils/Trace.h>
 
+#include <common/FlagManager.h>
 #include <scheduler/VsyncConfig.h>
 #include "DisplayHardware/DisplayMode.h"
-#include "FlagManager.h"
 #include "FrameTimeline.h"
 #include "VSyncDispatch.h"
 #include "VSyncTracker.h"
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 8fc9cba..d309adc 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -31,9 +31,9 @@
 #include <string>
 #include <utility>
 
+#include <common/FlagManager.h>
 #include "../Layer.h"
 #include "EventThread.h"
-#include "FlagManager.h"
 #include "LayerInfo.h"
 
 namespace android::scheduler {
@@ -167,6 +167,27 @@
     info->setDefaultLayerVote(getVoteType(frameRateCompatibility, contentDetectionEnabled));
 }
 
+void LayerHistory::setLayerProperties(int32_t id, const LayerProps& properties) {
+    std::lock_guard lock(mLock);
+
+    auto [found, layerPair] = findLayer(id);
+    if (found == LayerStatus::NotFound) {
+        // Offscreen layer
+        ALOGV("%s: %d not registered", __func__, id);
+        return;
+    }
+
+    const auto& info = layerPair->second;
+    info->setProperties(properties);
+
+    // Activate layer if inactive and visible.
+    if (found == LayerStatus::LayerInInactiveMap && info->isVisible()) {
+        mActiveLayerInfos.insert(
+                {id, std::make_pair(layerPair->first, std::move(layerPair->second))});
+        mInactiveLayerInfos.erase(id);
+    }
+}
+
 auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
     ATRACE_CALL();
     Summary summary;
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index bac1ec6..5a9445b 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -73,7 +73,7 @@
     // does not set a preference for refresh rate.
     void setDefaultFrameRateCompatibility(int32_t id, FrameRateCompatibility frameRateCompatibility,
                                           bool contentDetectionEnabled);
-
+    void setLayerProperties(int32_t id, const LayerProps&);
     using Summary = std::vector<RefreshRateSelector::LayerRequirement>;
 
     // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 8d18769..bf3a7bc 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -75,6 +75,10 @@
     }
 }
 
+void LayerInfo::setProperties(const android::scheduler::LayerProps& properties) {
+    *mLayerProps = properties;
+}
+
 bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
     return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
                                           mFrameTimeValidSince.time_since_epoch())
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 03ab0df..d24fc33 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -182,6 +182,8 @@
     // layer can go back to whatever vote it had before the app voted for it.
     void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
 
+    void setProperties(const LayerProps&);
+
     // Resets the layer vote to its default.
     void resetLayerVote() {
         mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default, FrameRateCategory::Default};
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index 5892b2b..47c8ef9 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -1487,7 +1487,7 @@
         case FrameRateCategory::Normal:
             return FpsRange{60_Hz, 90_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 60_Hz};
+            return FpsRange{30_Hz, 30_Hz};
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
             LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 56a4ae2..b54f334 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -45,9 +45,9 @@
 #include <memory>
 #include <numeric>
 
+#include <common/FlagManager.h>
 #include "../Layer.h"
 #include "EventThread.h"
-#include "FlagManager.h"
 #include "FrameRateOverrideMappings.h"
 #include "FrontEnd/LayerHandle.h"
 #include "OneShotTimer.h"
@@ -654,6 +654,10 @@
                                                    mFeatures.test(Feature::kContentDetection));
 }
 
+void Scheduler::setLayerProperties(int32_t id, const android::scheduler::LayerProps& properties) {
+    mLayerHistory.setLayerProperties(id, properties);
+}
+
 void Scheduler::chooseRefreshRateForContent(
         const surfaceflinger::frontend::LayerHierarchy* hierarchy,
         bool updateAttachedChoreographer) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index a02180a..c78051a 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -234,6 +234,7 @@
                             nsecs_t now, LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock);
     void setModeChangePending(bool pending);
     void setDefaultFrameRateCompatibility(int32_t id, scheduler::FrameRateCompatibility);
+    void setLayerProperties(int32_t id, const LayerProps&);
     void deregisterLayer(Layer*);
     void onLayerDestroyed(Layer*) EXCLUDES(mChoreographerLock);
 
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 3e7ec49..ef30887 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -25,7 +25,7 @@
 
 #include <scheduler/TimeKeeper.h>
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 #include "VSyncDispatchTimerQueue.h"
 #include "VSyncTracker.h"
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 57aa010..f5f93ce 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -29,13 +29,13 @@
 
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/concat.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 
-#include "FlagManager.h"
 #include "RefreshRateSelector.h"
 #include "VSyncPredictor.h"
 
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index 19e951a..2806450 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -83,7 +83,7 @@
 };
 
 // The frame rate category of a Layer.
-enum class FrameRateCategory {
+enum class FrameRateCategory : int32_t {
     Default,
     NoPreference,
     Low,
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 4d02b44..644b6ef 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -113,6 +113,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <common/FlagManager.h>
 #include <gui/LayerStatePermissions.h>
 #include <gui/SchedulingPolicy.h>
 #include <ui/DisplayIdentification.h>
@@ -129,7 +130,6 @@
 #include "DisplayHardware/VirtualDisplaySurface.h"
 #include "DisplayRenderArea.h"
 #include "Effects/Daltonizer.h"
-#include "FlagManager.h"
 #include "FpsReporter.h"
 #include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
@@ -875,7 +875,7 @@
         mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
 
         if (setSchedFifo(true) != NO_ERROR) {
-            ALOGW("Can't set SCHED_OTHER for primeCache");
+            ALOGW("Can't set SCHED_FIFO after primeCache");
         }
     }
 
@@ -2219,7 +2219,8 @@
                  snapshot->changes.any(Changes::Geometry));
 
         const bool hasChanges =
-                snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation) ||
+                snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation |
+                                      Changes::Geometry | Changes::Visibility) ||
                 (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) !=
                         0;
 
@@ -2250,6 +2251,10 @@
                 .isFrontBuffered = snapshot->isFrontBuffered(),
         };
 
+        if (snapshot->changes.any(Changes::Geometry | Changes::Visibility)) {
+            mScheduler->setLayerProperties(snapshot->sequence, layerProps);
+        }
+
         if (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
             mScheduler->setDefaultFrameRateCompatibility(snapshot->sequence,
                                                          snapshot->defaultFrameRateCompatibility);
@@ -2621,6 +2626,10 @@
                 refreshArgs.outputs.push_back(display->getCompositionDisplay());
             }
         }
+        if (display->getId() == pacesetterId) {
+            // TODO(b/255601557) Update frameInterval per display
+            refreshArgs.frameInterval = display->refreshRateSelector().getActiveMode().fps;
+        }
     }
     mPowerAdvisor->setDisplays(displayIds);
 
@@ -2913,7 +2922,6 @@
                                             const scheduler::FrameTargeters& frameTargeters,
                                             nsecs_t presentStartTime) {
     ATRACE_CALL();
-    ALOGV(__func__);
 
     ui::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<FenceTime>> presentFences;
     ui::PhysicalDisplayMap<PhysicalDisplayId, const sp<Fence>> gpuCompositionDoneFences;
@@ -3801,7 +3809,6 @@
                 // first frame before the display is available, we rely
                 // on WMS and DMS to provide the right information
                 // so the client can calculate the hint.
-                ALOGV("Skipping reporting transform hint update for %s", layer->getDebugName());
                 layer->skipReportingTransformHint();
             } else {
                 layer->updateTransformHint(hintDisplay->getTransformHint());
@@ -6762,8 +6769,7 @@
             case 1007: // Unused.
                 return NAME_NOT_FOUND;
             case 1008: // Toggle forced GPU composition.
-                mDebugDisableHWC = data.readInt32() != 0;
-                scheduleRepaint();
+                sfdo_forceClientComposition(data.readInt32() != 0);
                 return NO_ERROR;
             case 1009: // Toggle use of transform hint.
                 mDebugDisableTransformHint = data.readInt32() != 0;
@@ -7573,7 +7579,7 @@
                         args.allowProtected, args.grayscale, captureListener);
 }
 
-void SurfaceFlinger::captureDisplay(DisplayId displayId,
+void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
                                     const sp<IScreenCaptureListener>& captureListener) {
     ui::LayerStack layerStack;
     wp<const DisplayDevice> displayWeak;
@@ -7592,10 +7598,23 @@
         size = display->getLayerStackSpaceRect().getSize();
     }
 
+    size.width *= args.frameScaleX;
+    size.height *= args.frameScaleY;
+
+    // We could query a real value for this but it'll be a long, long time until we support
+    // displays that need upwards of 1GB per buffer so...
+    constexpr auto kMaxTextureSize = 16384;
+    if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize ||
+        size.height >= kMaxTextureSize) {
+        ALOGE("capture display resolved to invalid size %d x %d", size.width, size.height);
+        invokeScreenCaptureError(BAD_VALUE, captureListener);
+        return;
+    }
+
     RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, Rect(), size, ui::Dataspace::UNKNOWN,
+        return DisplayRenderArea::create(displayWeak, Rect(), size, args.dataspace,
                                          false /* useIdentityTransform */,
-                                         false /* hintForSeamlessTransition */,
+                                         args.hintForSeamlessTransition,
                                          false /* captureSecureLayers */);
     });
 
@@ -7619,8 +7638,8 @@
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size,
-                        ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale, captureListener);
+    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, args.pixelFormat,
+                        kAllowProtected, kGrayscale, captureListener);
 }
 
 void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
@@ -8413,7 +8432,8 @@
 void SurfaceFlinger::enableRefreshRateOverlay(bool enable) {
     bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG);
     for (const auto& [id, display] : mPhysicalDisplays) {
-        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
+        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal ||
+            FlagManager::getInstance().refresh_rate_overlay_on_external_display()) {
             if (const auto device = getDisplayDeviceLocked(id)) {
                 const auto enableOverlay = [&](const bool setByHwc) FTL_FAKE_GUARD(
                                                    kMainThreadContext) {
@@ -9051,6 +9071,11 @@
     setTransactionFlags(eTransactionNeeded | eDisplayTransactionNeeded | eTraversalNeeded);
 }
 
+void SurfaceFlinger::sfdo_forceClientComposition(bool enabled) {
+    mDebugDisableHWC = enabled;
+    scheduleRepaint();
+}
+
 // gui::ISurfaceComposer
 
 binder::Status SurfaceComposerAIDL::bootFinished() {
@@ -9413,13 +9438,14 @@
 }
 
 binder::Status SurfaceComposerAIDL::captureDisplayById(
-        int64_t displayId, const sp<IScreenCaptureListener>& captureListener) {
+        int64_t displayId, const CaptureArgs& args,
+        const sp<IScreenCaptureListener>& captureListener) {
     // status_t status;
     IPCThreadState* ipc = IPCThreadState::self();
     const int uid = ipc->getCallingUid();
     if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) {
         std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
-        mFlinger->captureDisplay(*id, captureListener);
+        mFlinger->captureDisplay(*id, args, captureListener);
     } else {
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
     }
@@ -9769,6 +9795,11 @@
     return binder::Status::ok();
 }
 
+binder::Status SurfaceComposerAIDL::forceClientComposition(bool enabled) {
+    mFlinger->sfdo_forceClientComposition(enabled);
+    return binder::Status::ok();
+}
+
 binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                                              const std::vector<float>& thresholds) {
     status_t status;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 1e90340..9e6da3f 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -63,13 +63,13 @@
 #include <scheduler/interface/ICompositor.h>
 #include <ui/FenceResult.h>
 
+#include <common/FlagManager.h>
 #include "Display/PhysicalDisplay.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/PowerAdvisor.h"
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
-#include "FlagManager.h"
 #include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerLifecycleManager.h"
@@ -531,7 +531,7 @@
             const sp<IBinder>& layerHandle = nullptr);
 
     void captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
-    void captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+    void captureDisplay(DisplayId, const CaptureArgs&, const sp<IScreenCaptureListener>&);
     void captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
 
     status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
@@ -1462,6 +1462,7 @@
     void sfdo_setDebugFlash(int delay);
     void sfdo_scheduleComposite();
     void sfdo_scheduleCommit();
+    void sfdo_forceClientComposition(bool enabled);
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
@@ -1507,7 +1508,8 @@
     binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
     binder::Status captureDisplay(const DisplayCaptureArgs&,
                                   const sp<IScreenCaptureListener>&) override;
-    binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override;
+    binder::Status captureDisplayById(int64_t, const CaptureArgs&,
+                                      const sp<IScreenCaptureListener>&) override;
     binder::Status captureLayers(const LayerCaptureArgs&,
                                  const sp<IScreenCaptureListener>&) override;
 
@@ -1572,6 +1574,7 @@
     binder::Status setDebugFlash(int delay) override;
     binder::Status scheduleComposite() override;
     binder::Status scheduleCommit() override;
+    binder::Status forceClientComposition(bool enabled) override;
     binder::Status updateSmallAreaDetection(const std::vector<int32_t>& appIds,
                                             const std::vector<float>& thresholds) override;
     binder::Status setSmallAreaDetectionThreshold(int32_t appId, float threshold) override;
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 3587a72..6a155c1 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -158,7 +158,7 @@
         handle->previousReleaseFence = prevFence;
         handle->previousReleaseFences.clear();
 
-        FrameEventHistoryStats eventStats(handle->frameNumber,
+        FrameEventHistoryStats eventStats(handle->frameNumber, handle->previousFrameNumber,
                                           handle->gpuCompositionDoneFence->getSnapshot().fence,
                                           handle->compositorTiming, handle->refreshStartTime,
                                           handle->dequeueReadyTime);
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 3074795..245398f 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -56,6 +56,7 @@
     nsecs_t refreshStartTime = 0;
     nsecs_t dequeueReadyTime = 0;
     uint64_t frameNumber = 0;
+    uint64_t previousFrameNumber = 0;
     ReleaseCallbackId previousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
 };
 
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
new file mode 100644
index 0000000..5ef22b5
--- /dev/null
+++ b/services/surfaceflinger/common/Android.bp
@@ -0,0 +1,48 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_defaults",
+    defaults: [
+        "android.hardware.graphics.composer3-ndk_shared",
+        "surfaceflinger_defaults",
+    ],
+    shared_libs: [
+        "libSurfaceFlingerProp",
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "librenderengine",
+    ],
+    srcs: [
+        "FlagManager.cpp",
+    ],
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+}
+
+cc_library_static {
+    name: "libsurfaceflinger_common",
+    defaults: [
+        "libsurfaceflinger_common_defaults",
+    ],
+    static_libs: [
+        "libsurfaceflingerflags",
+    ],
+}
+
+cc_library_static {
+    name: "libsurfaceflinger_common_test",
+    defaults: [
+        "libsurfaceflinger_common_defaults",
+    ],
+    static_libs: [
+        "libsurfaceflingerflags_test",
+    ],
+}
diff --git a/services/surfaceflinger/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
similarity index 97%
rename from services/surfaceflinger/FlagManager.cpp
rename to services/surfaceflinger/common/FlagManager.cpp
index 6d0dbc7..8da7d8e 100644
--- a/services/surfaceflinger/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 
 #include <SurfaceFlingerProperties.sysprop.h>
 #include <android-base/parsebool.h>
@@ -110,6 +110,7 @@
     /// Trunk stable server flags ///
     DUMP_SERVER_FLAG(late_boot_misc2);
     DUMP_SERVER_FLAG(dont_skip_on_early);
+    DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
 
     /// Trunk stable readonly flags ///
     DUMP_READ_ONLY_FLAG(connected_display);
@@ -190,6 +191,7 @@
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "")
+FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
 
 /// Exceptions ///
 bool FlagManager::dont_skip_on_early() const {
diff --git a/services/surfaceflinger/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
similarity index 97%
rename from services/surfaceflinger/FlagManager.h
rename to services/surfaceflinger/common/include/common/FlagManager.h
index cefce9b..e3e4f80 100644
--- a/services/surfaceflinger/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -49,6 +49,7 @@
     /// Trunk stable server flags ///
     bool late_boot_misc2() const;
     bool dont_skip_on_early() const;
+    bool refresh_rate_overlay_on_external_display() const;
 
     /// Trunk stable readonly flags ///
     bool connected_display() const;
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index 243b8e0..ab3b352 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -39,6 +39,7 @@
         "libgtest_ndk_c++",
         "libgmock_main_ndk",
         "librenderengine_mocks",
+        "libsurfaceflinger_common",
         "perfetto_trace_protos",
         "libcompositionengine_mocks",
         "perfetto_trace_protos",
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
index f22315a..afb5f5c 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp
@@ -163,16 +163,22 @@
 
 void DisplayHardwareFuzzer::validateDisplay(Hwc2::AidlComposer* composer, Display display) {
     uint32_t outNumTypes, outNumRequests;
-    composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
-                              &outNumRequests);
+    const auto frameIntervalRange =
+            mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
+                                                 Fps::fromValue(120).getPeriodNsecs());
+    composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
+                              &outNumTypes, &outNumRequests);
 }
 
 void DisplayHardwareFuzzer::presentOrValidateDisplay(Hwc2::AidlComposer* composer,
                                                      Display display) {
     int32_t outPresentFence;
     uint32_t outNumTypes, outNumRequests, state;
-    composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
-                                       &outNumRequests, &outPresentFence, &state);
+    const auto frameIntervalRange =
+            mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
+                                                 Fps::fromValue(120).getPeriodNsecs());
+    composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
+                                       &outNumTypes, &outNumRequests, &outPresentFence, &state);
 }
 
 void DisplayHardwareFuzzer::setOutputBuffer(Hwc2::AidlComposer* composer, Display display) {
@@ -223,7 +229,10 @@
     mHwc.getDeviceCompositionChanges(halDisplayID,
                                      mFdp.ConsumeBool() /*frameUsesClientComposition*/,
                                      std::chrono::steady_clock::now(),
-                                     mFdp.ConsumeIntegral<nsecs_t>(), &outChanges);
+                                     mFdp.ConsumeIntegral<nsecs_t>(),
+                                     Fps::fromValue(
+                                             mFdp.ConsumeFloatingPointInRange<float>(1.f, 120.f)),
+                                     &outChanges);
 }
 
 void DisplayHardwareFuzzer::getDisplayedContentSamplingAttributes(HalDisplayId halDisplayID) {
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
index 8978971..ce8d47e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
@@ -30,12 +30,6 @@
 constexpr int32_t kConfigDuration = 500;
 constexpr int32_t kBufferSize = 1024;
 constexpr int32_t kTimeOffset = 100000;
-constexpr perfetto::BackendType backendTypes[] = {
-        perfetto::kUnspecifiedBackend,
-        perfetto::kInProcessBackend,
-        perfetto::kSystemBackend,
-        perfetto::kCustomBackend,
-};
 
 class FrameTracerFuzzer {
 public:
@@ -71,8 +65,7 @@
     auto* dsCfg = cfg.add_data_sources()->mutable_config();
     dsCfg->set_name(android::FrameTracer::kFrameTracerDataSource);
 
-    auto tracingSession =
-            perfetto::Tracing::NewTrace(mFdp.PickValueInArray<perfetto::BackendType>(backendTypes));
+    auto tracingSession = perfetto::Tracing::NewTrace(perfetto::kInProcessBackend);
     tracingSession->Setup(cfg);
     return tracingSession;
 }
@@ -115,11 +108,15 @@
     std::vector<int32_t> layerIds =
             generateLayerIds(mFdp.ConsumeIntegralInRange<size_t>(kMinLayerIds, kMaxLayerIds));
 
+    std::unique_ptr<perfetto::TracingSession> tracingSession;
     while (mFdp.remaining_bytes()) {
         auto invokeFrametracerAPI = mFdp.PickValueInArray<const std::function<void()>>({
                 [&]() { mFrameTracer->registerDataSource(); },
                 [&]() {
-                    auto tracingSession = getTracingSessionForTest();
+                    if (tracingSession) {
+                        tracingSession->StopBlocking();
+                    }
+                    tracingSession = getTracingSessionForTest();
                     tracingSession->StartBlocking();
                 },
                 [&]() { traceTimestamp(layerIds, layerIds.size()); },
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index 9889cb9..6c8972f 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -29,12 +29,12 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
+#include <common/FlagManager.h>
 #include <configstore/Utils.h>
 #include <displayservice/DisplayService.h>
 #include <errno.h>
 #include <hidl/LegacySupport.h>
 #include <processgroup/sched_policy.h>
-#include "FlagManager.h"
 #include "SurfaceFlinger.h"
 #include "SurfaceFlingerFactory.h"
 #include "SurfaceFlingerProperties.h"
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index a81f9b8..bb3c94a 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -70,3 +70,9 @@
   is_fixed_read_only: true
 }
 
+flag {
+  name: "refresh_rate_overlay_on_external_display"
+  namespace: "core_graphics"
+  description: "enable refresh rate indicator on the external display"
+  bug: "301647974"
+}
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index 9269e7c..c9af432 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -47,7 +47,6 @@
         ASSERT_NO_FATAL_FAILURE(SetUpDisplay());
 
         sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
-        mCaptureArgs.displayToken = mDisplay;
     }
 
     virtual void TearDown() {
@@ -279,8 +278,6 @@
     const int32_t mLayerZBase = std::numeric_limits<int32_t>::max() - 256;
 
     sp<SurfaceControl> mBlackBgSurface;
-
-    DisplayCaptureArgs mCaptureArgs;
     ScreenCaptureResults mCaptureResults;
 
 private:
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 96cc333..79864e0 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -15,6 +15,8 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include <sys/types.h>
+#include <cstdint>
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -31,26 +33,22 @@
         LayerTransactionTest::SetUp();
         ASSERT_EQ(NO_ERROR, mClient->initCheck());
 
-        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-        ASSERT_FALSE(ids.empty());
-        mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-        ASSERT_FALSE(mDisplayToken == nullptr);
-
-        ui::DisplayMode mode;
-        ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &mode));
-        const ui::Size& resolution = mode.resolution;
-
-        mDisplaySize = resolution;
+        // Root surface
+        mRootSurfaceControl =
+                createLayer(String8("RootTestSurface"), mDisplayWidth, mDisplayHeight, 0);
+        ASSERT_TRUE(mRootSurfaceControl != nullptr);
+        ASSERT_TRUE(mRootSurfaceControl->isValid());
 
         // Background surface
-        mBGSurfaceControl = createLayer(String8("BG Test Surface"), resolution.getWidth(),
-                                        resolution.getHeight(), 0);
+        mBGSurfaceControl = createLayer(String8("BG Test Surface"), mDisplayWidth, mDisplayHeight,
+                                        0, mRootSurfaceControl.get());
         ASSERT_TRUE(mBGSurfaceControl != nullptr);
         ASSERT_TRUE(mBGSurfaceControl->isValid());
         TransactionUtils::fillSurfaceRGBA8(mBGSurfaceControl, 63, 63, 195);
 
         // Foreground surface
-        mFGSurfaceControl = createLayer(String8("FG Test Surface"), 64, 64, 0);
+        mFGSurfaceControl =
+                createLayer(String8("FG Test Surface"), 64, 64, 0, mRootSurfaceControl.get());
 
         ASSERT_TRUE(mFGSurfaceControl != nullptr);
         ASSERT_TRUE(mFGSurfaceControl->isValid());
@@ -58,7 +56,7 @@
         TransactionUtils::fillSurfaceRGBA8(mFGSurfaceControl, 195, 63, 63);
 
         asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(mDisplayToken, ui::DEFAULT_LAYER_STACK);
+            t.setDisplayLayerStack(mDisplay, ui::DEFAULT_LAYER_STACK);
 
             t.setLayer(mBGSurfaceControl, INT32_MAX - 2).show(mBGSurfaceControl);
 
@@ -66,25 +64,22 @@
                     .setPosition(mFGSurfaceControl, 64, 64)
                     .show(mFGSurfaceControl);
         });
+
+        mCaptureArgs.sourceCrop = mDisplayRect;
+        mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
     }
 
     virtual void TearDown() {
         LayerTransactionTest::TearDown();
         mBGSurfaceControl = 0;
         mFGSurfaceControl = 0;
-
-        // Restore display rotation
-        asTransaction([&](Transaction& t) {
-            Rect displayBounds{mDisplaySize};
-            t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, displayBounds, displayBounds);
-        });
     }
 
+    sp<SurfaceControl> mRootSurfaceControl;
     sp<SurfaceControl> mBGSurfaceControl;
     sp<SurfaceControl> mFGSurfaceControl;
     std::unique_ptr<ScreenCapture> mCapture;
-    sp<IBinder> mDisplayToken;
-    ui::Size mDisplaySize;
+    LayerCaptureArgs mCaptureArgs;
 };
 
 TEST_F(ScreenCaptureTest, SetFlagsSecureEUidSystem) {
@@ -92,7 +87,8 @@
     ASSERT_NO_FATAL_FAILURE(
             layer = createLayer("test", 32, 32,
                                 ISurfaceComposerClient::eSecure |
-                                        ISurfaceComposerClient::eFXSurfaceBufferQueue));
+                                        ISurfaceComposerClient::eFXSurfaceBufferQueue,
+                                mRootSurfaceControl.get()));
     ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layer, Color::RED, 32, 32));
 
     Transaction().show(layer).setLayer(layer, INT32_MAX).apply(true);
@@ -100,14 +96,14 @@
     {
         // Ensure the UID is not root because root has all permissions
         UIDFaker f(AID_APP_START);
-        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(mCaptureArgs, mCaptureResults));
+        ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     }
 
     UIDFaker f(AID_SYSTEM);
 
     // By default the system can capture screenshots with secure layers but they
     // will be blacked out
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(mCaptureArgs, mCaptureResults));
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
 
     {
         SCOPED_TRACE("as system");
@@ -117,10 +113,8 @@
 
     // Here we pass captureSecureLayers = true and since we are AID_SYSTEM we should be able
     // to receive them...we are expected to take care with the results.
-    DisplayCaptureArgs args;
-    args.displayToken = mDisplay;
-    args.captureSecureLayers = true;
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, mCaptureResults));
+    mCaptureArgs.captureSecureLayers = true;
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
     ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
     sc.expectColor(Rect(0, 0, 32, 32), Color::RED);
@@ -131,7 +125,8 @@
     ASSERT_NO_FATAL_FAILURE(
             parentLayer = createLayer("parent-test", 32, 32,
                                       ISurfaceComposerClient::eSecure |
-                                              ISurfaceComposerClient::eFXSurfaceBufferQueue));
+                                              ISurfaceComposerClient::eFXSurfaceBufferQueue,
+                                      mRootSurfaceControl.get()));
     ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(parentLayer, Color::RED, 32, 32));
 
     sp<SurfaceControl> childLayer;
@@ -152,10 +147,8 @@
 
     // Here we pass captureSecureLayers = true and since we are AID_SYSTEM we should be able
     // to receive them...we are expected to take care with the results.
-    DisplayCaptureArgs args;
-    args.displayToken = mDisplay;
-    args.captureSecureLayers = true;
-    ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, mCaptureResults));
+    mCaptureArgs.captureSecureLayers = true;
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
     ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
     sc.expectColor(Rect(0, 0, 10, 10), Color::BLUE);
@@ -232,7 +225,7 @@
 
 TEST_F(ScreenCaptureTest, CaptureLayerExcludeThroughDisplayArgs) {
     mCaptureArgs.excludeHandles = {mFGSurfaceControl->getHandle()};
-    ScreenCapture::captureDisplay(&mCapture, mCaptureArgs);
+    ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
     mCapture->expectBGColor(0, 0);
     // Doesn't capture FG layer which is at 64, 64
     mCapture->expectBGColor(64, 64);
@@ -605,60 +598,55 @@
     mCapture->expectColor(Rect(30, 30, 60, 60), Color::RED);
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayWithUid) {
-    uid_t fakeUid = 12345;
+TEST_F(ScreenCaptureTest, ScreenshotProtectedBuffer) {
+    const uint32_t bufferWidth = 60;
+    const uint32_t bufferHeight = 60;
 
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
+    sp<SurfaceControl> layer =
+            createLayer(String8("Colored surface"), bufferWidth, bufferHeight,
+                        ISurfaceComposerClient::eFXSurfaceBufferState, mRootSurfaceControl.get());
 
-    sp<SurfaceControl> layer;
-    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
-                                                ISurfaceComposerClient::eFXSurfaceBufferQueue,
-                                                mBGSurfaceControl.get()));
-    ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layer, Color::RED, 32, 32));
+    Transaction().show(layer).setLayer(layer, INT32_MAX).apply(true);
 
-    Transaction().show(layer).setLayer(layer, INT32_MAX).apply();
+    sp<Surface> surface = layer->getSurface();
+    ASSERT_TRUE(surface != nullptr);
+    sp<ANativeWindow> anw(surface);
 
-    // Make sure red layer with the background layer is screenshot.
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
-    mCapture->expectColor(Rect(0, 0, 32, 32), Color::RED);
-    mCapture->expectBorder(Rect(0, 0, 32, 32), {63, 63, 195, 255});
+    ASSERT_EQ(NO_ERROR, native_window_api_connect(anw.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(), GRALLOC_USAGE_PROTECTED));
 
-    // From non system uid, can't request screenshot without a specified uid.
-    UIDFaker f(fakeUid);
-    ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(captureArgs, mCaptureResults));
+    int fenceFd;
+    ANativeWindowBuffer* buf = nullptr;
 
-    // Make screenshot request with current uid set. No layers were created with the current
-    // uid so screenshot will be black.
-    captureArgs.uid = fakeUid;
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
-    mCapture->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
-    mCapture->expectBorder(Rect(0, 0, 32, 32), Color::BLACK);
+    // End test if device does not support USAGE_PROTECTED
+    // b/309965549 This check does not exit the test when running on AVDs
+    status_t err = anw->dequeueBuffer(anw.get(), &buf, &fenceFd);
+    if (err) {
+        return;
+    }
+    anw->queueBuffer(anw.get(), buf, fenceFd);
 
-    sp<SurfaceControl> layerWithFakeUid;
-    // Create a new layer with the current uid
-    ASSERT_NO_FATAL_FAILURE(layerWithFakeUid =
-                                    createLayer("new test layer", 32, 32,
-                                                ISurfaceComposerClient::eFXSurfaceBufferQueue,
-                                                mBGSurfaceControl.get()));
-    ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layerWithFakeUid, Color::GREEN, 32, 32));
-    Transaction()
-            .show(layerWithFakeUid)
-            .setLayer(layerWithFakeUid, INT32_MAX)
-            .setPosition(layerWithFakeUid, 128, 128)
-            .apply();
+    // USAGE_PROTECTED buffer is read as a black screen
+    ScreenCaptureResults captureResults;
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, captureResults));
 
-    // Screenshot from the fakeUid caller with the uid requested allows the layer
-    // with that uid to be screenshotted. Everything else is black
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
-    mCapture->expectColor(Rect(128, 128, 160, 160), Color::GREEN);
-    mCapture->expectBorder(Rect(128, 128, 160, 160), Color::BLACK);
+    ScreenCapture sc(captureResults.buffer, captureResults.capturedHdrLayers);
+    sc.expectColor(Rect(0, 0, bufferWidth, bufferHeight), Color::BLACK);
+
+    // Reading color data will expectedly result in crash, only check usage bit
+    // b/309965549 Checking that the usage bit is protected does not work for
+    // devices that do not support usage protected.
+    mCaptureArgs.allowProtected = true;
+    ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, captureResults));
+    // ASSERT_EQ(GRALLOC_USAGE_PROTECTED, GRALLOC_USAGE_PROTECTED &
+    // captureResults.buffer->getUsage());
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayPrimaryDisplayOnly) {
+TEST_F(ScreenCaptureTest, CaptureLayer) {
     sp<SurfaceControl> layer;
-    ASSERT_NO_FATAL_FAILURE(
-            layer = createLayer("test layer", 0, 0, ISurfaceComposerClient::eFXSurfaceEffect));
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 0, 0,
+                                                ISurfaceComposerClient::eFXSurfaceEffect,
+                                                mRootSurfaceControl.get()));
 
     const Color layerColor = Color::RED;
     const Rect bounds = Rect(10, 10, 40, 40);
@@ -666,17 +654,13 @@
     Transaction()
             .show(layer)
             .hide(mFGSurfaceControl)
-            .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
             .setLayer(layer, INT32_MAX)
             .setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
             .setCrop(layer, bounds)
             .apply();
 
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-
     {
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(bounds, layerColor);
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
     }
@@ -689,17 +673,18 @@
     {
         // Can't screenshot test layer since it now has flag
         // eLayerSkipScreenshot
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(bounds, {63, 63, 195, 255});
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
     }
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayChildPrimaryDisplayOnly) {
+TEST_F(ScreenCaptureTest, CaptureLayerChild) {
     sp<SurfaceControl> layer;
     sp<SurfaceControl> childLayer;
-    ASSERT_NO_FATAL_FAILURE(
-            layer = createLayer("test layer", 0, 0, ISurfaceComposerClient::eFXSurfaceEffect));
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 0, 0,
+                                                ISurfaceComposerClient::eFXSurfaceEffect,
+                                                mRootSurfaceControl.get()));
     ASSERT_NO_FATAL_FAILURE(childLayer = createLayer("test layer", 0, 0,
                                                      ISurfaceComposerClient::eFXSurfaceEffect,
                                                      layer.get()));
@@ -713,7 +698,6 @@
             .show(layer)
             .show(childLayer)
             .hide(mFGSurfaceControl)
-            .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
             .setLayer(layer, INT32_MAX)
             .setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
             .setColor(childLayer, {childColor.r / 255, childColor.g / 255, childColor.b / 255})
@@ -721,11 +705,8 @@
             .setCrop(childLayer, childBounds)
             .apply();
 
-    DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-
     {
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(childBounds, childColor);
         mCapture->expectBorder(childBounds, layerColor);
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
@@ -739,7 +720,7 @@
     {
         // Can't screenshot child layer since the parent has the flag
         // eLayerSkipScreenshot
-        ScreenCapture::captureDisplay(&mCapture, captureArgs);
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
         mCapture->expectColor(childBounds, {63, 63, 195, 255});
         mCapture->expectBorder(childBounds, {63, 63, 195, 255});
         mCapture->expectBorder(bounds, {63, 63, 195, 255});
@@ -860,14 +841,10 @@
 
     Transaction().show(layer).hide(mFGSurfaceControl).reparent(layer, nullptr).apply();
 
-    DisplayCaptureArgs displayCaptureArgs;
-    displayCaptureArgs.displayToken = mDisplay;
-
     {
         // Validate that the red layer is not on screen
-        ScreenCapture::captureDisplay(&mCapture, displayCaptureArgs);
-        mCapture->expectColor(Rect(0, 0, mDisplaySize.width, mDisplaySize.height),
-                              {63, 63, 195, 255});
+        ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
+        mCapture->expectColor(Rect(0, 0, mDisplayWidth, mDisplayHeight), {63, 63, 195, 255});
     }
 
     LayerCaptureArgs captureArgs;
@@ -878,42 +855,6 @@
     mCapture->expectColor(Rect(0, 0, 32, 32), Color::RED);
 }
 
-TEST_F(ScreenCaptureTest, CaptureDisplayWith90DegRotation) {
-    asTransaction([&](Transaction& t) {
-        Rect newDisplayBounds{mDisplaySize.height, mDisplaySize.width};
-        t.setDisplayProjection(mDisplayToken, ui::ROTATION_90, newDisplayBounds, newDisplayBounds);
-    });
-
-    DisplayCaptureArgs displayCaptureArgs;
-    displayCaptureArgs.displayToken = mDisplayToken;
-    displayCaptureArgs.width = mDisplaySize.width;
-    displayCaptureArgs.height = mDisplaySize.height;
-    displayCaptureArgs.useIdentityTransform = true;
-    ScreenCapture::captureDisplay(&mCapture, displayCaptureArgs);
-
-    mCapture->expectBGColor(0, 0);
-    mCapture->expectFGColor(mDisplaySize.width - 65, 65);
-}
-
-TEST_F(ScreenCaptureTest, CaptureDisplayWith270DegRotation) {
-    asTransaction([&](Transaction& t) {
-        Rect newDisplayBounds{mDisplaySize.height, mDisplaySize.width};
-        t.setDisplayProjection(mDisplayToken, ui::ROTATION_270, newDisplayBounds, newDisplayBounds);
-    });
-
-    DisplayCaptureArgs displayCaptureArgs;
-    displayCaptureArgs.displayToken = mDisplayToken;
-    displayCaptureArgs.width = mDisplaySize.width;
-    displayCaptureArgs.height = mDisplaySize.height;
-    displayCaptureArgs.useIdentityTransform = true;
-    ScreenCapture::captureDisplay(&mCapture, displayCaptureArgs);
-
-    std::this_thread::sleep_for(std::chrono::seconds{5});
-
-    mCapture->expectBGColor(mDisplayWidth - 1, mDisplaySize.height - 1);
-    mCapture->expectFGColor(65, mDisplaySize.height - 65);
-}
-
 TEST_F(ScreenCaptureTest, CaptureNonHdrLayer) {
     sp<SurfaceControl> layer;
     ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp
index d0ab105..c5d118c 100644
--- a/services/surfaceflinger/tests/TextureFiltering_test.cpp
+++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp
@@ -80,29 +80,22 @@
     sp<SurfaceControl> mParent;
     sp<SurfaceControl> mLayer;
     std::unique_ptr<ScreenCapture> mCapture;
+    gui::LayerCaptureArgs captureArgs;
 };
 
 TEST_F(TextureFilteringTest, NoFiltering) {
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
-    captureArgs.sourceCrop = Rect{100, 100};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
     mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
 }
 
 TEST_F(TextureFilteringTest, BufferCropNoFiltering) {
-    Transaction().setBufferCrop(mLayer, Rect{0, 0, 100, 100}).apply();
-
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
     captureArgs.sourceCrop = Rect{0, 0, 100, 100};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
     mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
@@ -112,24 +105,20 @@
 TEST_F(TextureFilteringTest, BufferCropIsFiltered) {
     Transaction().setBufferCrop(mLayer, Rect{25, 25, 75, 75}).apply();
 
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
     captureArgs.sourceCrop = Rect{0, 0, 100, 100};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
 }
 
 // Expect filtering because the output source crop is stretched to the output buffer's size.
 TEST_F(TextureFilteringTest, OutputSourceCropIsFiltered) {
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
+    captureArgs.frameScaleX = 2;
+    captureArgs.frameScaleY = 2;
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
 }
@@ -138,20 +127,17 @@
 // buffer's size.
 TEST_F(TextureFilteringTest, LayerCropOutputSourceCropIsFiltered) {
     Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
-
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 100;
-    captureArgs.height = 100;
+    captureArgs.frameScaleX = 2;
+    captureArgs.frameScaleY = 2;
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mParent->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
 }
 
 // Expect filtering because the layer is scaled up.
 TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) {
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.frameScaleX = 2;
     captureArgs.frameScaleY = 2;
@@ -162,7 +148,6 @@
 
 // Expect no filtering because the output buffer's size matches the source crop.
 TEST_F(TextureFilteringTest, LayerCaptureOutputSourceCropNoFiltering) {
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
@@ -176,7 +161,6 @@
 TEST_F(TextureFilteringTest, LayerCaptureWithCropNoFiltering) {
     Transaction().setCrop(mLayer, Rect{10, 10, 90, 90}).apply();
 
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
@@ -187,12 +171,9 @@
 
 // Expect no filtering because the output source crop and output buffer are the same size.
 TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) {
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    captureArgs.width = 50;
-    captureArgs.height = 50;
+    captureArgs.layerHandle = mLayer->getHandle();
     captureArgs.sourceCrop = Rect{25, 25, 75, 75};
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
     mCapture->expectColor(Rect{25, 0, 50, 50}, Color::BLUE);
@@ -202,9 +183,8 @@
 TEST_F(TextureFilteringTest, LayerCropDisplayFrameMatchNoFiltering) {
     Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
 
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mLayer->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
     mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
@@ -214,9 +194,8 @@
 TEST_F(TextureFilteringTest, ParentCropNoFiltering) {
     Transaction().setCrop(mParent, Rect{25, 25, 75, 75}).apply();
 
-    gui::DisplayCaptureArgs captureArgs;
-    captureArgs.displayToken = mDisplay;
-    ScreenCapture::captureDisplay(&mCapture, captureArgs);
+    captureArgs.layerHandle = mLayer->getHandle();
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{25, 25, 50, 75}, Color::RED);
     mCapture->expectColor(Rect{50, 25, 75, 75}, Color::BLUE);
@@ -226,7 +205,6 @@
 TEST_F(TextureFilteringTest, ParentHasTransformNoFiltering) {
     Transaction().setPosition(mParent, 100, 100).apply();
 
-    LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = mParent->getHandle();
     captureArgs.sourceCrop = Rect{0, 0, 100, 100};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 5a3bca1..dea0194 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -45,7 +45,7 @@
 cc_aconfig_library {
     name: "libsurfaceflingerflags_test",
     aconfig_declarations: "surfaceflinger_flags",
-    test: true,
+    mode: "test",
 }
 
 cc_test {
@@ -170,6 +170,7 @@
         "librenderengine_mocks",
         "libscheduler",
         "libserviceutils",
+        "libsurfaceflinger_common_test",
         "libtimestats",
         "libtimestats_atoms_proto",
         "libtimestats_proto",
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 8e13c0d..ee967979 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -347,7 +347,7 @@
     }
 
     static void setupHwcCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _))
                 .Times(1);
 
         EXPECT_CALL(*test->mDisplaySurface,
@@ -356,12 +356,12 @@
     }
 
     static void setupHwcClientCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+        EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _))
                 .Times(1);
     }
 
     static void setupHwcForcedClientCompositionCallExpectations(CompositionTest* test) {
-        EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _, _)).Times(1);
+        EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _, _, _)).Times(1);
     }
 
     static void setupRECompositionCallExpectations(CompositionTest* test) {
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index aa37754..c040f29 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -17,7 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "FlagManagerTest"
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 #include "FlagUtils.h"
 
 #include <gmock/gmock.h>
diff --git a/services/surfaceflinger/tests/unittests/FlagUtils.h b/services/surfaceflinger/tests/unittests/FlagUtils.h
index 333e4e7..550c70d 100644
--- a/services/surfaceflinger/tests/unittests/FlagUtils.h
+++ b/services/surfaceflinger/tests/unittests/FlagUtils.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 
 #define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
 
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 58d7a40..f1e841b 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -30,6 +30,7 @@
 #include <gmock/gmock.h>
 #pragma clang diagnostic pop
 
+#include <common/FlagManager.h>
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
 #include <chrono>
@@ -38,9 +39,12 @@
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayIdentificationTestHelpers.h"
+#include "FlagUtils.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockHWC2.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
@@ -57,7 +61,6 @@
 using hal::IComposerClient;
 using ::testing::_;
 using ::testing::DoAll;
-using ::testing::ElementsAreArray;
 using ::testing::Return;
 using ::testing::SetArgPointee;
 using ::testing::StrictMock;
@@ -217,7 +220,102 @@
     }
 }
 
-TEST_F(HWComposerTest, getModesWithDisplayConfigurations) {
+TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_OFF) {
+    // if vrr_config is off, getDisplayConfigurationsSupported() is off as well
+    // then getModesWithLegacyDisplayConfigs should be called instead
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, false);
+    ASSERT_FALSE(FlagManager::getInstance().vrr_config());
+
+    constexpr hal::HWDisplayId kHwcDisplayId = 2;
+    constexpr hal::HWConfigId kConfigId = 42;
+    constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+
+    expectHotplugConnect(kHwcDisplayId);
+    const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+    ASSERT_TRUE(info);
+
+    EXPECT_CALL(*mHal, getDisplayConfigurationsSupported()).WillRepeatedly(Return(false));
+
+    {
+        EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+                .WillOnce(Return(HalError::BAD_DISPLAY));
+        EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
+    }
+    {
+        constexpr int32_t kWidth = 480;
+        constexpr int32_t kHeight = 720;
+        constexpr int32_t kConfigGroup = 1;
+        constexpr int32_t kVsyncPeriod = 16666667;
+
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
+                                        _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kWidth), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::HEIGHT, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kHeight), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::CONFIG_GROUP, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kConfigGroup), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId,
+                                        IComposerClient::Attribute::VSYNC_PERIOD, _))
+                .WillRepeatedly(DoAll(SetArgPointee<3>(kVsyncPeriod), Return(HalError::NONE)));
+
+        // Optional Parameters UNSUPPORTED
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+                                        _))
+                .WillOnce(Return(HalError::UNSUPPORTED));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+                                        _))
+                .WillOnce(Return(HalError::UNSUPPORTED));
+
+        EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+                .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{kConfigId}),
+                                      Return(HalError::NONE)));
+
+        auto modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        EXPECT_EQ(modes.front().dpiX, -1);
+        EXPECT_EQ(modes.front().dpiY, -1);
+
+        // Optional parameters are supported
+        constexpr int32_t kDpi = 320;
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+                                        _))
+                .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+        EXPECT_CALL(*mHal,
+                    getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+                                        _))
+                .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+
+        modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+        EXPECT_EQ(modes.size(), size_t{1});
+        EXPECT_EQ(modes.front().hwcId, kConfigId);
+        EXPECT_EQ(modes.front().width, kWidth);
+        EXPECT_EQ(modes.front().height, kHeight);
+        EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+        EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+        // DPI values are scaled by 1000 in the legacy implementation.
+        EXPECT_EQ(modes.front().dpiX, kDpi / 1000.f);
+        EXPECT_EQ(modes.front().dpiY, kDpi / 1000.f);
+    }
+}
+
+TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_ON) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+    ASSERT_TRUE(FlagManager::getInstance().vrr_config());
+
     constexpr hal::HWDisplayId kHwcDisplayId = 2;
     constexpr hal::HWConfigId kConfigId = 42;
     constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 190c0e8..befef48 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -839,6 +839,81 @@
     ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate);
 }
 
+TEST_F(LayerHistoryIntegrationTest, hidingLayerUpdatesLayerHistory) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    setBuffer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+    auto summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+
+    hideLayer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    EXPECT_TRUE(summary.empty());
+    EXPECT_EQ(0u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, showingLayerUpdatesLayerHistory) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    hideLayer(1);
+    setBuffer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+    auto summary = summarizeLayerHistory(time);
+    EXPECT_TRUE(summary.empty());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    showLayer(1);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, updatingGeometryUpdatesWeight) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    setBuffer(1,
+              std::make_shared<
+                      renderengine::mock::FakeExternalTexture>(100U /*width*/, 100U /*height*/, 1,
+                                                               HAL_PIXEL_FORMAT_RGBA_8888,
+                                                               GRALLOC_USAGE_PROTECTED /*usage*/));
+    mFlinger.setLayerHistoryDisplayArea(100 * 100);
+    updateLayerSnapshotsAndLayerHistory(time);
+    auto summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+
+    auto startingWeight = summary[0].weight;
+
+    setMatrix(1, 0.1f, 0.f, 0.f, 0.1f);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    summary = summarizeLayerHistory(time);
+    ASSERT_EQ(1u, summary.size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_GT(startingWeight, summary[0].weight);
+}
+
 class LayerHistoryIntegrationTestParameterized
       : public LayerHistoryIntegrationTest,
         public testing::WithParamInterface<std::chrono::nanoseconds> {};
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 57babaf..2cc6dc7 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -789,6 +789,143 @@
     EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
               scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
     EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate set to default with strategy default)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRate(12, -1.f, 0, 0);
+    setFrameRateSelectionStrategy(12, 0 /* Default */);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_FALSE(getSnapshot({.id = 121})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 123.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 123.f);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) {
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate category set to high with strategy OverrideChildren)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRateCategory(12, 4 /* high */);
+    setFrameRate(122, 123.f, 0, 0);
+    setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.category, FrameRateCategory::High);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.category, FrameRateCategory::High);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::High);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::High);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // ROOT
+    // ├── 1
+    // │   ├── 11
+    // │   │   └── 111
+    // │   ├── 12 (frame rate category to default with strategy default)
+    // │   │   ├── 121
+    // │   │   └── 122 (frame rate set to 123.f)
+    // │   │       └── 1221
+    // │   └── 13
+    // └── 2
+    setFrameRateCategory(12, 0 /* default */);
+    setFrameRateSelectionStrategy(12, 0 /* Default */);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent 1 gets no vote
+    EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate.category, FrameRateCategory::Default);
+    EXPECT_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+    EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::NoVote);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate.category, FrameRateCategory::Default);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.category, FrameRateCategory::Default);
+    EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.type,
+              scheduler::FrameRateCompatibility::Default);
+    EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 123.f);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Default);
+    EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 123.f);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+              scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+    EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Default);
+    EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
 }
 
 TEST_F(LayerSnapshotTest, skipRoundCornersWhenProtected) {
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index faa12a1..a00e886 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -1568,7 +1568,7 @@
             // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Low, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Low, true, 120_Hz, kModeId120},
             {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index d0290ea..b80cb66 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -103,7 +103,7 @@
     EXPECT_CALL(*mDisplaySurface,
                 prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
             .Times(1);
-    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _)).WillOnce([] {
+    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
         return hardware::graphics::composer::V2_1::Error::NONE;
@@ -124,7 +124,7 @@
     EXPECT_CALL(*mDisplaySurface,
                 prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
             .Times(1);
-    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _)).WillOnce([] {
+    EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
         constexpr Duration kMockHwcRunTime = 20ms;
         std::this_thread::sleep_for(kMockHwcRunTime);
         return hardware::graphics::composer::V2_1::Error::NONE;
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index d0b2199..bca14f5 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -623,6 +623,9 @@
 
     void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
 
+    auto setLayerHistoryDisplayArea(uint32_t displayArea) {
+        return mFlinger->mScheduler->onActiveDisplayAreaChanged(displayArea);
+    };
     auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
     auto setDaltonizerType(ColorBlindnessType type) {
         mFlinger->mDaltonizer.setType(type);
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 0b07745..3b74f0a 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -95,9 +95,9 @@
     MOCK_METHOD2(setPowerMode, Error(Display, IComposerClient::PowerMode));
     MOCK_METHOD2(setVsyncEnabled, Error(Display, IComposerClient::Vsync));
     MOCK_METHOD1(setClientTargetSlotCount, Error(Display));
-    MOCK_METHOD4(validateDisplay, Error(Display, nsecs_t, uint32_t*, uint32_t*));
-    MOCK_METHOD6(presentOrValidateDisplay,
-                 Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*));
+    MOCK_METHOD(Error, validateDisplay, (Display, nsecs_t, int32_t, uint32_t*, uint32_t*));
+    MOCK_METHOD(Error, presentOrValidateDisplay,
+                (Display, nsecs_t, int32_t, uint32_t*, uint32_t*, int*, uint32_t*));
     MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t));
     MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp<GraphicBuffer>&, int));
     MOCK_METHOD4(setLayerBufferSlotsToClear,
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 40f59b8..a7ddb6d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -76,9 +76,9 @@
                 (override));
     MOCK_METHOD(hal::Error, setPowerMode, (hal::PowerMode), (override));
     MOCK_METHOD(hal::Error, setVsyncEnabled, (hal::Vsync), (override));
-    MOCK_METHOD(hal::Error, validate, (nsecs_t, uint32_t *, uint32_t *), (override));
+    MOCK_METHOD(hal::Error, validate, (nsecs_t, int32_t, uint32_t*, uint32_t*), (override));
     MOCK_METHOD(hal::Error, presentOrValidate,
-                (nsecs_t, uint32_t *, uint32_t *, android::sp<android::Fence> *, uint32_t *),
+                (nsecs_t, int32_t, uint32_t*, uint32_t*, android::sp<android::Fence>*, uint32_t*),
                 (override));
     MOCK_METHOD(ftl::Future<hal::Error>, setDisplayBrightness,
                 (float, float, const Hwc2::Composer::DisplayBrightnessOptions &), (override));
diff --git a/vulkan/libvulkan/Android.bp b/vulkan/libvulkan/Android.bp
index a87f82f..436e6c6 100644
--- a/vulkan/libvulkan/Android.bp
+++ b/vulkan/libvulkan/Android.bp
@@ -109,6 +109,7 @@
         "libnativeloader_lazy",
         "libnativewindow",
         "libvndksupport",
+        "libdl_android",
         "android.hardware.graphics.common@1.0",
         "libSurfaceFlingerProp",
     ],
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 5d7a4aa..0e45d2d 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -46,6 +46,8 @@
 using namespace android::hardware::configstore;
 using namespace android::hardware::configstore::V1_0;
 
+extern "C" android_namespace_t* android_get_exported_namespace(const char*);
+
 // #define ENABLE_ALLOC_CALLSTACKS 1
 #if ENABLE_ALLOC_CALLSTACKS
 #include <utils/CallStack.h>
@@ -159,6 +161,7 @@
     "ro.board.platform",
 }};
 constexpr int LIB_DL_FLAGS = RTLD_LOCAL | RTLD_NOW;
+constexpr char RO_VULKAN_APEX_PROPERTY[] = "ro.vulkan.apex";
 
 // LoadDriver returns:
 // * 0 when succeed, or
@@ -166,6 +169,7 @@
 // * -EINVAL when fail to find HAL_MODULE_INFO_SYM_AS_STR or
 //   HWVULKAN_HARDWARE_MODULE_ID in the library.
 int LoadDriver(android_namespace_t* library_namespace,
+               const char* ns_name,
                const hwvulkan_module_t** module) {
     ATRACE_CALL();
 
@@ -183,8 +187,10 @@
                 .library_namespace = library_namespace,
             };
             so = android_dlopen_ext(lib_name.c_str(), LIB_DL_FLAGS, &dlextinfo);
-            ALOGE("Could not load %s from updatable gfx driver namespace: %s.",
-                  lib_name.c_str(), dlerror());
+            if (!so) {
+                ALOGE("Could not load %s from %s namespace: %s.",
+                      lib_name.c_str(), ns_name, dlerror());
+            }
         } else {
             // load built-in driver
             so = android_load_sphal_library(lib_name.c_str(), LIB_DL_FLAGS);
@@ -211,12 +217,30 @@
     return 0;
 }
 
+int LoadDriverFromApex(const hwvulkan_module_t** module) {
+    ATRACE_CALL();
+
+    auto apex_name = android::base::GetProperty(RO_VULKAN_APEX_PROPERTY, "");
+    if (apex_name == "") {
+        return -ENOENT;
+    }
+    // Get linker namespace for Vulkan APEX
+    std::replace(apex_name.begin(), apex_name.end(), '.', '_');
+    auto ns = android_get_exported_namespace(apex_name.c_str());
+    if (!ns) {
+        return -ENOENT;
+    }
+    android::GraphicsEnv::getInstance().setDriverToLoad(
+        android::GpuStatsInfo::Driver::VULKAN);
+    return LoadDriver(ns, apex_name.c_str(), module);
+}
+
 int LoadBuiltinDriver(const hwvulkan_module_t** module) {
     ATRACE_CALL();
 
     android::GraphicsEnv::getInstance().setDriverToLoad(
         android::GpuStatsInfo::Driver::VULKAN);
-    return LoadDriver(nullptr, module);
+    return LoadDriver(nullptr, nullptr, module);
 }
 
 int LoadUpdatedDriver(const hwvulkan_module_t** module) {
@@ -227,7 +251,7 @@
         return -ENOENT;
     android::GraphicsEnv::getInstance().setDriverToLoad(
         android::GpuStatsInfo::Driver::VULKAN_UPDATED);
-    int result = LoadDriver(ns, module);
+    int result = LoadDriver(ns, "updatable gfx driver", module);
     if (result != 0) {
         LOG_ALWAYS_FATAL(
             "couldn't find an updated Vulkan implementation from %s",
@@ -256,6 +280,9 @@
 
     result = LoadUpdatedDriver(&module);
     if (result == -ENOENT) {
+        result = LoadDriverFromApex(&module);
+    }
+    if (result == -ENOENT) {
         result = LoadBuiltinDriver(&module);
     }
     if (result != 0) {