Merge "Add proposed trendy teams for VTS modules" into main
diff --git a/Android.bp b/Android.bp
index 2520a71..119c7ea 100644
--- a/Android.bp
+++ b/Android.bp
@@ -119,3 +119,9 @@
     srcs: ["aidl/android/hardware/display/IDeviceProductInfoConstants.aidl"],
     path: "aidl",
 }
+
+dirgroup {
+    name: "trusty_dirgroup_frameworks_native",
+    dirs: ["libs/binder"],
+    visibility: ["//trusty/vendor/google/aosp/scripts"],
+}
diff --git a/cmds/dumpstate/DumpstateInternal.cpp b/cmds/dumpstate/DumpstateInternal.cpp
index 6f7fea3..ce7c55c 100644
--- a/cmds/dumpstate/DumpstateInternal.cpp
+++ b/cmds/dumpstate/DumpstateInternal.cpp
@@ -108,7 +108,7 @@
 
     const uint32_t cap_syslog_mask = CAP_TO_MASK(CAP_SYSLOG);
     const uint32_t cap_syslog_index = CAP_TO_INDEX(CAP_SYSLOG);
-    bool has_cap_syslog = (capdata[cap_syslog_index].effective & cap_syslog_mask) != 0;
+    bool has_cap_syslog = (capdata[cap_syslog_index].permitted & cap_syslog_mask) != 0;
 
     memset(&capdata, 0, sizeof(capdata));
     if (has_cap_syslog) {
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index 52b485a..98a7555 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -39,10 +39,14 @@
         "account",
         "activity",
         "alarm",
+        "android.frameworks.stats.IStats/default",
         "android.system.keystore2.IKeystoreService/default",
         "appops",
         "audio",
+        "autofill",
+        "batteryproperties",
         "batterystats",
+        "biometic",
         "carrier_config",
         "connectivity",
         "content",
@@ -58,6 +62,7 @@
         "jobscheduler",
         "legacy_permission",
         "location",
+        "lock_settings",
         "media.extractor",
         "media.metrics",
         "media.player",
@@ -78,15 +83,19 @@
         "phone",
         "platform_compat",
         "power",
+        "processinfo",
         "role",
+        "sensitive_content_protection_service",
         "sensorservice",
         "statscompanion",
         "telephony.registry",
         "thermalservice",
         "time_detector",
+        "tracing.proxy",
         "trust",
         "uimode",
         "user",
+        "vibrator",
         "virtualdevice",
         "virtualdevice_native",
         "webviewupdate",
@@ -114,11 +123,30 @@
     if (!kUseCache) {
         return binder::Status::ok();
     }
+    std::string traceStr;
+    if (atrace_is_tag_enabled(ATRACE_TAG_AIDL)) {
+        traceStr = "BinderCacheWithInvalidation::updateCache : " + serviceName;
+    }
+    binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL, traceStr.c_str());
+
     if (service.getTag() == os::Service::Tag::binder) {
         sp<IBinder> binder = service.get<os::Service::Tag::binder>();
-        if (binder && mCacheForGetService->isClientSideCachingEnabled(serviceName) &&
-            binder->isBinderAlive()) {
+        if (!binder) {
+            binder::ScopedTrace
+                    aidlTrace(ATRACE_TAG_AIDL,
+                              "BinderCacheWithInvalidation::updateCache failed: binder_null");
+        } else if (!binder->isBinderAlive()) {
+            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                          "BinderCacheWithInvalidation::updateCache failed: "
+                                          "isBinderAlive_false");
+        } else if (mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
+            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                          "BinderCacheWithInvalidation::updateCache successful");
             return mCacheForGetService->setItem(serviceName, binder);
+        } else {
+            binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                          "BinderCacheWithInvalidation::updateCache failed: "
+                                          "caching_not_enabled");
         }
     }
     return binder::Status::ok();
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index 47b2ec9..feb8470 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -18,6 +18,7 @@
 #include <android/os/BnServiceManager.h>
 #include <android/os/IServiceManager.h>
 #include <binder/IPCThreadState.h>
+#include <binder/Trace.h>
 #include <map>
 #include <memory>
 
@@ -59,6 +60,12 @@
     }
 
     bool removeItem(const std::string& key, const sp<IBinder>& who) {
+        std::string traceStr;
+        uint64_t tag = ATRACE_TAG_AIDL;
+        if (atrace_is_tag_enabled(tag)) {
+            traceStr = "BinderCacheWithInvalidation::removeItem " + key;
+        }
+        binder::ScopedTrace aidlTrace(tag, traceStr.c_str());
         std::lock_guard<std::mutex> lock(mCacheMutex);
         if (auto it = mCache.find(key); it != mCache.end()) {
             if (it->second.service == who) {
@@ -81,11 +88,22 @@
         if (item->localBinder() == nullptr) {
             status_t status = item->linkToDeath(deathRecipient);
             if (status != android::OK) {
+                std::string traceStr;
+                uint64_t tag = ATRACE_TAG_AIDL;
+                if (atrace_is_tag_enabled(tag)) {
+                    traceStr =
+                            "BinderCacheWithInvalidation::setItem Failed LinkToDeath for service " +
+                            key + " : " + std::to_string(status);
+                }
+                binder::ScopedTrace aidlTrace(tag, traceStr.c_str());
+
                 ALOGE("Failed to linkToDeath binder for service %s. Error: %d", key.c_str(),
                       status);
                 return binder::Status::fromStatusT(status);
             }
         }
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::setItem Successfully Cached");
         std::lock_guard<std::mutex> lock(mCacheMutex);
         Entry entry = {.service = item, .deathRecipient = deathRecipient};
         mCache[key] = entry;
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index eae844c..3758b65 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -197,7 +197,9 @@
                     && currentValue < sBinderProxyCountHighWatermark
                     && ((trackedValue & WARNING_REACHED_MASK) == 0)) [[unlikely]] {
                 sTrackingMap[trackedUid] |= WARNING_REACHED_MASK;
-                if (sWarningCallback) sWarningCallback(trackedUid);
+                if (sWarningCallback) {
+                    *postTask = [=]() { sWarningCallback(trackedUid); };
+                }
             } else if (currentValue >= sBinderProxyCountHighWatermark) {
                 ALOGE("Too many binder proxy objects sent to uid %d from uid %d (%d proxies held)",
                       getuid(), trackedUid, trackedValue);
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 95a5da2..ab44957 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -94,6 +94,9 @@
     },
     {
       "name": "libbinder_rpc_unstable_bindgen_test"
+    },
+    {
+      "name": "binderCacheUnitTest"
     }
   ],
   "presubmit-large": [
@@ -133,9 +136,6 @@
     {
       "name": "binder_sdk_test",
       "host": true
-    },
-    {
-      "name": "binderCacheUnitTest"
     }
   ],
   "imports": [
diff --git a/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl b/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl
index 75f8753..9bac386 100644
--- a/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl
+++ b/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import android.content.pm.StagedApexInfo;
+
 /**
  * This event is designed for notification to native code listener about
  * any changes to set of apex packages staged for installation on next boot.
@@ -23,5 +25,5 @@
  * @hide
  */
 parcelable ApexStagedEvent {
-  @utf8InCpp String[] stagedApexModuleNames;
+  StagedApexInfo[] stagedApexInfos;
 }
diff --git a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
index 3ddfefa..0f0be2f 100644
--- a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
+++ b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
@@ -135,13 +135,7 @@
     void unregisterStagedApexObserver(in IStagedApexObserver observer);
 
     /**
-     * Get APEX module names of all APEX that are staged ready for installation
+     * Get information of staged APEXes.
      */
-    @utf8InCpp String[] getStagedApexModuleNames();
-
-    /**
-     * Get information of APEX which is staged ready for installation.
-     * Returns null if no such APEX is found.
-     */
-    @nullable StagedApexInfo getStagedApexInfo(in @utf8InCpp String moduleName);
+    StagedApexInfo[] getStagedApexInfos();
 }
diff --git a/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl b/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl
index 949835b..8f7ad30 100644
--- a/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl
+++ b/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl
@@ -22,6 +22,7 @@
  *
  * @hide
  */
+@JavaDerive(equals=true)
 parcelable StagedApexInfo {
   @utf8InCpp String moduleName;
   @utf8InCpp String diskImagePath;
diff --git a/libs/binder/include/binder/SafeInterface.h b/libs/binder/include/binder/SafeInterface.h
index c671eed..0b4f196 100644
--- a/libs/binder/include/binder/SafeInterface.h
+++ b/libs/binder/include/binder/SafeInterface.h
@@ -152,6 +152,14 @@
         return callParcel("writeParcelableVector",
                           [&]() { return parcel->writeParcelableVector(v); });
     }
+
+    status_t read(const Parcel& parcel, std::vector<bool>* v) const {
+        return callParcel("readBoolVector", [&]() { return parcel.readBoolVector(v); });
+    }
+    status_t write(Parcel* parcel, const std::vector<bool>& v) const {
+        return callParcel("writeBoolVector", [&]() { return parcel->writeBoolVector(v); });
+    }
+
     status_t read(const Parcel& parcel, float* f) const {
         return callParcel("readFloat", [&]() { return parcel.readFloat(f); });
     }
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 8b0dda3..28a3f65 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -731,6 +731,9 @@
         "liblog",
         "libutils",
     ],
+    static_libs: [
+        "libgmock",
+    ],
     test_suites: [
         "general-tests",
         "vts",
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index 0aa678d..849dc7c 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -40,6 +40,8 @@
 #include <sys/eventfd.h>
 #include <sys/prctl.h>
 
+#include <gmock/gmock.h>
+
 using namespace std::chrono_literals; // NOLINT - google-build-using-namespace
 using android::binder::unique_fd;
 
@@ -222,6 +224,7 @@
         SetDeathToken = IBinder::FIRST_CALL_TRANSACTION,
         ReturnsNoMemory,
         LogicalNot,
+        LogicalNotVector,
         ModifyEnum,
         IncrementFlattenable,
         IncrementLightFlattenable,
@@ -249,6 +252,7 @@
 
     // These are ordered according to their corresponding methods in SafeInterface::ParcelHandler
     virtual status_t logicalNot(bool a, bool* notA) const = 0;
+    virtual status_t logicalNot(const std::vector<bool>& a, std::vector<bool>* notA) const = 0;
     virtual status_t modifyEnum(TestEnum a, TestEnum* b) const = 0;
     virtual status_t increment(const TestFlattenable& a, TestFlattenable* aPlusOne) const = 0;
     virtual status_t increment(const TestLightFlattenable& a,
@@ -288,7 +292,14 @@
     }
     status_t logicalNot(bool a, bool* notA) const override {
         ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
-        return callRemote<decltype(&ISafeInterfaceTest::logicalNot)>(Tag::LogicalNot, a, notA);
+        using Signature = status_t (ISafeInterfaceTest::*)(bool, bool*) const;
+        return callRemote<Signature>(Tag::LogicalNot, a, notA);
+    }
+    status_t logicalNot(const std::vector<bool>& a, std::vector<bool>* notA) const override {
+        ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
+        using Signature = status_t (ISafeInterfaceTest::*)(const std::vector<bool>&,
+                                                           std::vector<bool>*) const;
+        return callRemote<Signature>(Tag::LogicalNotVector, a, notA);
     }
     status_t modifyEnum(TestEnum a, TestEnum* b) const override {
         ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
@@ -406,6 +417,14 @@
         *notA = !a;
         return NO_ERROR;
     }
+    status_t logicalNot(const std::vector<bool>& a, std::vector<bool>* notA) const override {
+        ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
+        notA->clear();
+        for (bool value : a) {
+            notA->push_back(!value);
+        }
+        return NO_ERROR;
+    }
     status_t modifyEnum(TestEnum a, TestEnum* b) const override {
         ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
         *b = (a == TestEnum::INITIAL) ? TestEnum::FINAL : TestEnum::INVALID;
@@ -513,7 +532,13 @@
                 return callLocal(data, reply, &ISafeInterfaceTest::returnsNoMemory);
             }
             case ISafeInterfaceTest::Tag::LogicalNot: {
-                return callLocal(data, reply, &ISafeInterfaceTest::logicalNot);
+                using Signature = status_t (ISafeInterfaceTest::*)(bool a, bool* notA) const;
+                return callLocal<Signature>(data, reply, &ISafeInterfaceTest::logicalNot);
+            }
+            case ISafeInterfaceTest::Tag::LogicalNotVector: {
+                using Signature = status_t (ISafeInterfaceTest::*)(const std::vector<bool>& a,
+                                                                   std::vector<bool>* notA) const;
+                return callLocal<Signature>(data, reply, &ISafeInterfaceTest::logicalNot);
             }
             case ISafeInterfaceTest::Tag::ModifyEnum: {
                 return callLocal(data, reply, &ISafeInterfaceTest::modifyEnum);
@@ -639,6 +664,15 @@
     ASSERT_EQ(!b, notB);
 }
 
+TEST_F(SafeInterfaceTest, TestLogicalNotVector) {
+    const std::vector<bool> a = {true, false, true};
+    std::vector<bool> notA;
+    status_t result = mSafeInterfaceTest->logicalNot(a, &notA);
+    ASSERT_EQ(NO_ERROR, result);
+    std::vector<bool> expected = {false, true, false};
+    ASSERT_THAT(notA, testing::ContainerEq(expected));
+}
+
 TEST_F(SafeInterfaceTest, TestModifyEnum) {
     const TestEnum a = TestEnum::INITIAL;
     TestEnum b = TestEnum::INVALID;
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index a4d105d..ead9f0f 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -1501,7 +1501,6 @@
 
     const bool useDefaultSize = !width && !height;
     while (true) {
-        size_t newBufferCount = 0;
         uint32_t allocWidth = 0;
         uint32_t allocHeight = 0;
         PixelFormat allocFormat = PIXEL_FORMAT_UNKNOWN;
@@ -1523,8 +1522,9 @@
 
             // Only allocate one buffer at a time to reduce risks of overlapping an allocation from
             // both allocateBuffers and dequeueBuffer.
-            newBufferCount = mCore->mFreeSlots.empty() ? 0 : 1;
-            if (newBufferCount == 0) {
+            if (mCore->mFreeSlots.empty()) {
+                BQ_LOGV("allocateBuffers: a slot was occupied while "
+                        "allocating. Dropping allocated buffer.");
                 return;
             }
 
@@ -1566,27 +1566,23 @@
         };
 #endif
 
-        Vector<sp<GraphicBuffer>> buffers;
-        for (size_t i = 0; i < newBufferCount; ++i) {
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
-            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
 #else
-            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
-                    allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
-                    allocUsage, allocName);
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
+                allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
+                allocUsage, allocName);
 #endif
 
-            status_t result = graphicBuffer->initCheck();
+        status_t result = graphicBuffer->initCheck();
 
-            if (result != NO_ERROR) {
-                BQ_LOGE("allocateBuffers: failed to allocate buffer (%u x %u, format"
-                        " %u, usage %#" PRIx64 ")", width, height, format, usage);
-                std::lock_guard<std::mutex> lock(mCore->mMutex);
-                mCore->mIsAllocating = false;
-                mCore->mIsAllocatingCondition.notify_all();
-                return;
-            }
-            buffers.push_back(graphicBuffer);
+        if (result != NO_ERROR) {
+            BQ_LOGE("allocateBuffers: failed to allocate buffer (%u x %u, format"
+                    " %u, usage %#" PRIx64 ")", width, height, format, usage);
+            std::lock_guard<std::mutex> lock(mCore->mMutex);
+            mCore->mIsAllocating = false;
+            mCore->mIsAllocatingCondition.notify_all();
+            return;
         }
 
         { // Autolock scope
@@ -1614,15 +1610,13 @@
                 continue;
             }
 
-            for (size_t i = 0; i < newBufferCount; ++i) {
-                if (mCore->mFreeSlots.empty()) {
-                    BQ_LOGV("allocateBuffers: a slot was occupied while "
-                            "allocating. Dropping allocated buffer.");
-                    continue;
-                }
+            if (mCore->mFreeSlots.empty()) {
+                BQ_LOGV("allocateBuffers: a slot was occupied while "
+                        "allocating. Dropping allocated buffer.");
+            } else {
                 auto slot = mCore->mFreeSlots.begin();
                 mCore->clearBufferSlotLocked(*slot); // Clean up the slot first
-                mSlots[*slot].mGraphicBuffer = buffers[i];
+                mSlots[*slot].mGraphicBuffer = graphicBuffer;
                 mSlots[*slot].mFence = Fence::NO_FENCE;
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
                 mSlots[*slot].mAdditionalOptionsGenerationId = allocOptionsGenId;
diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp
index 6880678..6564534 100644
--- a/libs/gui/tests/BufferItemConsumer_test.cpp
+++ b/libs/gui/tests/BufferItemConsumer_test.cpp
@@ -28,6 +28,7 @@
 static constexpr int kHeight = 100;
 static constexpr int kMaxLockedBuffers = 3;
 static constexpr int kFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+static constexpr int kUsage = GRALLOC_USAGE_SW_READ_RARELY;
 static constexpr int kFrameSleepUs = 30 * 1000;
 
 class BufferItemConsumerTest : public ::testing::Test {
@@ -44,8 +45,7 @@
 
     void SetUp() override {
         BufferQueue::createBufferQueue(&mProducer, &mConsumer);
-        mBIC =
-            new BufferItemConsumer(mConsumer, kFormat, kMaxLockedBuffers, true);
+        mBIC = new BufferItemConsumer(mConsumer, kUsage, kMaxLockedBuffers, true);
         String8 name("BufferItemConsumer_Under_Test");
         mBIC->setName(name);
         mBFL = new BufferFreedListener(this);
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index 931c311..f19b908 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -19,10 +19,9 @@
 mod handle;
 mod surface;
 
-pub use handle::NativeHandle;
-pub use surface::Surface;
-
 pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+pub use handle::NativeHandle;
+pub use surface::{buffer::Buffer, Surface};
 
 use binder::{
     binder_impl::{BorrowedParcel, UnstructuredParcelable},
diff --git a/libs/nativewindow/rust/src/surface.rs b/libs/nativewindow/rust/src/surface.rs
index 9eddfcd..ed52471 100644
--- a/libs/nativewindow/rust/src/surface.rs
+++ b/libs/nativewindow/rust/src/surface.rs
@@ -14,6 +14,8 @@
 
 //! Rust wrapper for `ANativeWindow` and related types.
 
+pub(crate) mod buffer;
+
 use binder::{
     binder_impl::{BorrowedParcel, UnstructuredParcelable},
     impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
@@ -21,17 +23,18 @@
     StatusCode,
 };
 use bitflags::bitflags;
+use buffer::Buffer;
 use nativewindow_bindgen::{
     ADataSpace, AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire,
     ANativeWindow_getBuffersDataSpace, ANativeWindow_getBuffersDefaultDataSpace,
-    ANativeWindow_getFormat, ANativeWindow_getHeight, ANativeWindow_getWidth,
+    ANativeWindow_getFormat, ANativeWindow_getHeight, ANativeWindow_getWidth, ANativeWindow_lock,
     ANativeWindow_readFromParcel, ANativeWindow_release, ANativeWindow_setBuffersDataSpace,
     ANativeWindow_setBuffersGeometry, ANativeWindow_setBuffersTransform,
-    ANativeWindow_writeToParcel,
+    ANativeWindow_unlockAndPost, ANativeWindow_writeToParcel, ARect,
 };
 use std::error::Error;
 use std::fmt::{self, Debug, Display, Formatter};
-use std::ptr::{null_mut, NonNull};
+use std::ptr::{self, null_mut, NonNull};
 
 /// Wrapper around an opaque C `ANativeWindow`.
 #[derive(PartialEq, Eq)]
@@ -153,6 +156,43 @@
             Ok(ADataSpace(data_space))
         }
     }
+
+    /// Locks the window's next drawing surface for writing, and returns it.
+    pub fn lock(&mut self, bounds: Option<&mut ARect>) -> Result<Buffer, ErrorCode> {
+        let mut buffer = buffer::EMPTY;
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it. The other pointers must be valid because the come from
+        // references, and aren't retained after the function returns.
+        let status = unsafe {
+            ANativeWindow_lock(
+                self.0.as_ptr(),
+                &mut buffer,
+                bounds.map(ptr::from_mut).unwrap_or(null_mut()),
+            )
+        };
+        if status != 0 {
+            return Err(ErrorCode(status));
+        }
+
+        Ok(Buffer::new(buffer, self))
+    }
+
+    /// Unlocks the window's drawing surface which was previously locked, posting the new buffer to
+    /// the display.
+    ///
+    /// This shouldn't be called directly but via the [`Buffer`], hence is not public here.
+    fn unlock_and_post(&mut self) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status = unsafe { ANativeWindow_unlockAndPost(self.0.as_ptr()) };
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
 }
 
 impl Drop for Surface {
diff --git a/libs/nativewindow/rust/src/surface/buffer.rs b/libs/nativewindow/rust/src/surface/buffer.rs
new file mode 100644
index 0000000..a2d74d4
--- /dev/null
+++ b/libs/nativewindow/rust/src/surface/buffer.rs
@@ -0,0 +1,68 @@
+// Copyright (C) 2024 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.
+
+use super::{ErrorCode, Surface};
+use nativewindow_bindgen::{AHardwareBuffer_Format, ANativeWindow_Buffer};
+use std::ptr::null_mut;
+
+/// An empty `ANativeWindow_Buffer`.
+pub const EMPTY: ANativeWindow_Buffer = ANativeWindow_Buffer {
+    width: 0,
+    height: 0,
+    stride: 0,
+    format: 0,
+    bits: null_mut(),
+    reserved: [0; 6],
+};
+
+/// Rust wrapper for `ANativeWindow_Buffer`, representing a locked buffer from a [`Surface`].
+pub struct Buffer<'a> {
+    /// The wrapped `ANativeWindow_Buffer`.
+    pub buffer: ANativeWindow_Buffer,
+    surface: &'a mut Surface,
+}
+
+impl<'a> Buffer<'a> {
+    pub(crate) fn new(buffer: ANativeWindow_Buffer, surface: &'a mut Surface) -> Self {
+        Self { buffer, surface }
+    }
+
+    /// Unlocks the window's drawing surface which was previously locked to create this buffer,
+    /// posting the buffer to the display.
+    pub fn unlock_and_post(self) -> Result<(), ErrorCode> {
+        self.surface.unlock_and_post()
+    }
+
+    /// The number of pixels that are shown horizontally.
+    pub fn width(&self) -> i32 {
+        self.buffer.width
+    }
+
+    /// The number of pixels that are shown vertically.
+    pub fn height(&self) -> i32 {
+        self.buffer.height
+    }
+
+    /// The number of pixels that a line in the buffer takes in memory.
+    ///
+    /// This may be greater than the width.
+    pub fn stride(&self) -> i32 {
+        self.buffer.stride
+    }
+
+    /// The pixel format of the buffer.
+    pub fn format(&self) -> Result<AHardwareBuffer_Format::Type, ErrorCode> {
+        self.buffer.format.try_into().map_err(|_| ErrorCode(self.buffer.format))
+    }
+}
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 4a0889f..568d348 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -32,7 +32,8 @@
         CANCEL_POINTER_EVENTS = 1,
         CANCEL_NON_POINTER_EVENTS = 2,
         CANCEL_FALLBACK_EVENTS = 3,
-        ftl_last = CANCEL_FALLBACK_EVENTS,
+        CANCEL_HOVER_EVENTS = 4,
+        ftl_last = CANCEL_HOVER_EVENTS
     };
 
     // The criterion to use to determine which events should be canceled.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 2161e09..7eb7e36 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -746,7 +746,8 @@
             }
             touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
         }
-        touchedWindow.addHoveringPointer(entry.deviceId, pointer);
+        const auto [x, y] = resolveTouchedPosition(entry);
+        touchedWindow.addHoveringPointer(entry.deviceId, pointer, x, y);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
             touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
         }
@@ -873,6 +874,8 @@
             return {false, true};
         case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
             return {false, true};
+        case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
+            return {true, false};
     }
 }
 
@@ -2511,7 +2514,8 @@
 
             if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
-                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer);
+                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer, x,
+                                                          y);
             }
 
             // Set target flags.
@@ -5437,6 +5441,31 @@
         }
     }
 
+    // Check if the hovering should stop because the window is no longer eligible to receive it
+    // (for example, if the touchable region changed)
+    if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
+        TouchState& state = it->second;
+        for (TouchedWindow& touchedWindow : state.windows) {
+            std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf(
+                    [this, displayId, &touchedWindow](const PointerProperties& properties, float x,
+                                                      float y) REQUIRES(mLock) {
+                        const bool isStylus = properties.toolType == ToolType::STYLUS;
+                        const ui::Transform displayTransform = getTransformLocked(displayId);
+                        const bool stillAcceptsTouch =
+                                windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(),
+                                                     displayId, x, y, isStylus, displayTransform);
+                        return !stillAcceptsTouch;
+                    });
+
+            for (DeviceId deviceId : erasedDevices) {
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_HOVER_EVENTS,
+                                           "WindowInfo changed", traceContext.getTracker());
+                options.deviceId = deviceId;
+                synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
+            }
+        }
+    }
+
     // Release information for windows that are no longer present.
     // This ensures that unused input channels are released promptly.
     // Otherwise, they might stick around until the window handle is destroyed
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index dfbe02f..e283fc3 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -638,6 +638,8 @@
             return memento.source & AINPUT_SOURCE_CLASS_POINTER;
         case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
             return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+        case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
+            return memento.hovering;
         default:
             return false;
     }
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 0c9ad3c..2bf63be 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -112,17 +112,18 @@
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
-                                            DeviceId deviceId, const PointerProperties& pointer) {
+                                            DeviceId deviceId, const PointerProperties& pointer,
+                                            float x, float y) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.addHoveringPointer(deviceId, pointer);
+            touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
             return;
         }
     }
 
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
-    touchedWindow.addHoveringPointer(deviceId, pointer);
+    touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
     windows.push_back(touchedWindow);
 }
 
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 9d4bb3d..3fbe584 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -49,7 +49,8 @@
             DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers,
             std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                    DeviceId deviceId, const PointerProperties& pointer);
+                                    DeviceId deviceId, const PointerProperties& pointer, float x,
+                                    float y);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
     void clearHoveringPointers(DeviceId deviceId);
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 1f86f66..fa5be1a 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -36,6 +36,13 @@
                         }) != pointers.end();
 }
 
+bool hasPointerId(const std::vector<TouchedWindow::HoveringPointer>& pointers, int32_t pointerId) {
+    return std::find_if(pointers.begin(), pointers.end(),
+                        [&pointerId](const TouchedWindow::HoveringPointer& pointer) {
+                            return pointer.properties.id == pointerId;
+                        }) != pointers.end();
+}
+
 } // namespace
 
 bool TouchedWindow::hasHoveringPointers() const {
@@ -78,16 +85,18 @@
     return hasPointerId(state.hoveringPointers, pointerId);
 }
 
-void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) {
-    std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& properties,
+                                       float x, float y) {
+    std::vector<HoveringPointer>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
     const size_t initialSize = hoveringPointers.size();
-    std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) {
-        return properties.id == pointer.id;
+    std::erase_if(hoveringPointers, [&properties](const HoveringPointer& pointer) {
+        return pointer.properties.id == properties.id;
     });
     if (hoveringPointers.size() != initialSize) {
-        LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this;
+        LOG(ERROR) << __func__ << ": " << properties << ", device " << deviceId << " was in "
+                   << *this;
     }
-    hoveringPointers.push_back(pointer);
+    hoveringPointers.push_back({properties, x, y});
 }
 
 Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId,
@@ -173,8 +182,8 @@
                 return true;
             }
         }
-        for (const PointerProperties& properties : state.hoveringPointers) {
-            if (properties.toolType == ToolType::STYLUS) {
+        for (const HoveringPointer& pointer : state.hoveringPointers) {
+            if (pointer.properties.toolType == ToolType::STYLUS) {
                 return true;
             }
         }
@@ -270,8 +279,8 @@
     }
     DeviceState& state = stateIt->second;
 
-    std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) {
-        return properties.id == pointerId;
+    std::erase_if(state.hoveringPointers, [&pointerId](const HoveringPointer& pointer) {
+        return pointer.properties.id == pointerId;
     });
 
     if (!state.hasPointers()) {
@@ -279,6 +288,22 @@
     }
 }
 
+std::vector<DeviceId> TouchedWindow::eraseHoveringPointersIf(
+        std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition) {
+    std::vector<DeviceId> erasedDevices;
+    for (auto& [deviceId, state] : mDeviceStates) {
+        std::erase_if(state.hoveringPointers, [&](const HoveringPointer& pointer) {
+            if (condition(pointer.properties, pointer.x, pointer.y)) {
+                erasedDevices.push_back(deviceId);
+                return true;
+            }
+            return false;
+        });
+    }
+
+    return erasedDevices;
+}
+
 void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
@@ -312,6 +337,11 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow::HoveringPointer& pointer) {
+    out << pointer.properties << " at (" << pointer.x << ", " << pointer.y << ")";
+    return out;
+}
+
 std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
     out << window.dump();
     return out;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 4f0ad16..c38681e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -38,7 +38,7 @@
     bool hasHoveringPointers() const;
     bool hasHoveringPointers(DeviceId deviceId) const;
     bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
-    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer);
+    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer, float x, float y);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
 
     // Touching
@@ -69,6 +69,15 @@
     void clearHoveringPointers(DeviceId deviceId);
     std::string dump() const;
 
+    struct HoveringPointer {
+        PointerProperties properties;
+        float x;
+        float y;
+    };
+
+    std::vector<DeviceId> eraseHoveringPointersIf(
+            std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition);
+
 private:
     struct DeviceState {
         std::vector<PointerProperties> touchingPointers;
@@ -78,7 +87,7 @@
         // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
         // scenario.
         std::optional<nsecs_t> downTimeInTarget;
-        std::vector<PointerProperties> hoveringPointers;
+        std::vector<HoveringPointer> hoveringPointers;
 
         bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); };
     };
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 73ab0da..48930ef 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1923,6 +1923,99 @@
     window->consumeMotionEvent(WithMotionAction(ACTION_SCROLL));
 }
 
+/**
+ * Two windows: a trusted overlay and a regular window underneath. Both windows are visible.
+ * Mouse is hovered, and the hover event should only go to the overlay.
+ * However, next, the touchable region of the trusted overlay shrinks. The mouse position hasn't
+ * changed, but the cursor would now end up hovering above the regular window underneatch.
+ * If the mouse is now clicked, this would generate an ACTION_DOWN event, which would go to the
+ * regular window. However, the trusted overlay is also watching for outside touch.
+ * The trusted overlay should get two events:
+ * 1) The ACTION_OUTSIDE event, since the click is now not inside its touchable region
+ * 2) The HOVER_EXIT event, since the mouse pointer is no longer hovering inside this window
+ *
+ * This test reproduces a crash where there is an overlap between dispatch modes for the trusted
+ * overlay touch target, since the event is causing both an ACTION_OUTSIDE, and as a HOVER_EXIT.
+ */
+TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlay) {
+    std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
+                                                              ui::LogicalDisplayId::DEFAULT);
+    overlay->setTrustedOverlay(true);
+    overlay->setWatchOutsideTouch(true);
+    overlay->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+    // Hover the mouse into the overlay
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
+    // the regular window as the touch target
+    overlay->setTouchableRegion(Region({0, 0, 0, 0}));
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Now we can click with the mouse. The click should go into the regular window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
+/**
+ * Similar to above, but also has a spy on top that also catches the HOVER
+ * events. Also, instead of ACTION_DOWN, we are continuing to send the hovering
+ * stream to ensure that the spy receives hover events correctly.
+ */
+TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlayWithSpy) {
+    std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(app, mDispatcher, "Spy", ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
+                                                              ui::LogicalDisplayId::DEFAULT);
+    overlay->setTrustedOverlay(true);
+    overlay->setWatchOutsideTouch(true);
+    overlay->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+    // Hover the mouse into the overlay
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
+    // the regular window as the touch target
+    overlay->setTouchableRegion(Region({0, 0, 0, 0}));
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Now we can click with the mouse. The click should go into the regular window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+}
+
 using InputDispatcherMultiDeviceTest = InputDispatcherTest;
 
 /**
diff --git a/services/stats/Android.bp b/services/stats/Android.bp
index 6b99627..f698515 100644
--- a/services/stats/Android.bp
+++ b/services/stats/Android.bp
@@ -7,6 +7,11 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+vintf_fragment {
+    name: "android.frameworks.stats-service.xml",
+    src: "android.frameworks.stats-service.xml",
+}
+
 cc_library_shared {
     name: "libstatshidl",
     srcs: [
@@ -38,7 +43,7 @@
     local_include_dirs: [
         "include/stats",
     ],
-    vintf_fragments: [
+    vintf_fragment_modules: [
         "android.frameworks.stats-service.xml",
     ],
 }
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index 0284192..bfb7bd6 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -1169,7 +1169,7 @@
   return array;
 }
 
-template <typename T, unsigned int N>
+template <typename T, size_t N>
 inline Json::Value ToJsonValue(const T (&value)[N]) {
   return ArrayToJsonValue(N, value);
 }
@@ -1293,7 +1293,7 @@
   return true;
 }
 
-template <typename T, unsigned int N>
+template <typename T, size_t N>
 inline bool AsValue(Json::Value* json_value, T (*value)[N]) {
   return AsArray(json_value, N, *value);
 }