Merge "Add BufferReleaseChannel" into main
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index ef2fa4d..fa7cb64 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -505,8 +505,9 @@
         return Status::fromExceptionCode(Status::EX_SECURITY, "App UIDs cannot add services.");
     }
 
-    if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canAddService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     if (binder == nullptr) {
@@ -888,8 +889,9 @@
     }
 
     auto ctx = mAccess->getCallingContext();
-    if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canAddService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     auto serviceIt = mNameToService.find(name);
@@ -1051,8 +1053,9 @@
     }
 
     auto ctx = mAccess->getCallingContext();
-    if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canAddService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     auto serviceIt = mNameToService.find(name);
@@ -1110,6 +1113,23 @@
     return Status::ok();
 }
 
+Status ServiceManager::canAddService(const Access::CallingContext& ctx, const std::string& name,
+                                     std::optional<std::string>* accessor) {
+    if (!mAccess->canAdd(ctx, name)) {
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied for service.");
+    }
+#ifndef VENDORSERVICEMANAGER
+    *accessor = getVintfAccessorName(name);
+#endif
+    if (accessor->has_value()) {
+        if (!mAccess->canAdd(ctx, accessor->value())) {
+            return Status::fromExceptionCode(Status::EX_SECURITY,
+                                             "SELinux denied for the accessor of the service.");
+        }
+    }
+    return Status::ok();
+}
+
 Status ServiceManager::canFindService(const Access::CallingContext& ctx, const std::string& name,
                                       std::optional<std::string>* accessor) {
     if (!mAccess->canFind(ctx, name)) {
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 0d666c6..c92141b 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -115,6 +115,8 @@
 
     os::Service tryGetService(const std::string& name, bool startIfNotFound);
     sp<IBinder> tryGetBinder(const std::string& name, bool startIfNotFound);
+    binder::Status canAddService(const Access::CallingContext& ctx, const std::string& name,
+                                 std::optional<std::string>* accessor);
     binder::Status canFindService(const Access::CallingContext& ctx, const std::string& name,
                                   std::optional<std::string>* accessor);
 
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index ae8de5f..65c2914 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <input/InputTransport.h>
+#include <input/Resampler.h>
 #include <utils/Looper.h>
 
 namespace android {
@@ -47,13 +48,13 @@
 /**
  * Consumes input events from an input channel.
  *
- * This is a re-implementation of InputConsumer that does not have resampling at the current moment.
- * A lot of the higher-level logic has been folded into this class, to make it easier to use.
- * In the legacy class, InputConsumer, the consumption logic was partially handled in the jni layer,
- * as well as various actions like adding the fd to the Choreographer.
+ * This is a re-implementation of InputConsumer. At the moment it only supports resampling for
+ * single pointer events. A lot of the higher-level logic has been folded into this class, to make
+ * it easier to use. In the legacy class, InputConsumer, the consumption logic was partially handled
+ * in the jni layer, as well as various actions like adding the fd to the Choreographer.
  *
  * TODO(b/297226446): use this instead of "InputConsumer":
- * - Add resampling to this class
+ * - Add resampling for multiple pointer events.
  * - Allow various resampling strategies to be specified
  * - Delete the old "InputConsumer" and use this class instead, renaming it to "InputConsumer".
  * - Add tracing
@@ -64,8 +65,18 @@
  */
 class InputConsumerNoResampling final {
 public:
+    /**
+     * @param callbacks are used to interact with InputConsumerNoResampling. They're called whenever
+     * the event is ready to consume.
+     * @param looper needs to be sp and not shared_ptr because it inherits from
+     * RefBase
+     * @param resampler the resampling strategy to use. If null, no resampling will be
+     * performed.
+     */
     explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
-                                       sp<Looper> looper, InputConsumerCallbacks& callbacks);
+                                       sp<Looper> looper, InputConsumerCallbacks& callbacks,
+                                       std::unique_ptr<Resampler> resampler);
+
     ~InputConsumerNoResampling();
 
     /**
@@ -99,6 +110,7 @@
     std::shared_ptr<InputChannel> mChannel;
     sp<Looper> mLooper;
     InputConsumerCallbacks& mCallbacks;
+    std::unique_ptr<Resampler> mResampler;
 
     // Looper-related infrastructure
     /**
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
new file mode 100644
index 0000000..ff9c4b0
--- /dev/null
+++ b/include/input/Resampler.h
@@ -0,0 +1,115 @@
+/**
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <chrono>
+#include <optional>
+
+#include <input/Input.h>
+#include <input/InputTransport.h>
+#include <input/RingBuffer.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+/**
+ * Resampler is an interface for resampling MotionEvents. Every resampling implementation
+ * must use this interface to enable resampling inside InputConsumer's logic.
+ */
+struct Resampler {
+    virtual ~Resampler() = default;
+
+    /**
+     * Tries to resample motionEvent at resampleTime. The provided resampleTime must be greater than
+     * the latest sample time of motionEvent. It is not guaranteed that resampling occurs at
+     * resampleTime. Interpolation may occur is futureSample is available. Otherwise, motionEvent
+     * may be resampled by another method, or not resampled at all. Furthermore, it is the
+     * implementer's responsibility to guarantee the following:
+     * - If resampling occurs, a single additional sample should be added to motionEvent. That is,
+     * if motionEvent had N samples before being passed to Resampler, then it will have N + 1
+     * samples by the end of the resampling. No other field of motionEvent should be modified.
+     * - If resampling does not occur, then motionEvent must not be modified in any way.
+     */
+    virtual void resampleMotionEvent(const std::chrono::nanoseconds resampleTime,
+                                     MotionEvent& motionEvent,
+                                     const InputMessage* futureSample) = 0;
+};
+
+class LegacyResampler final : public Resampler {
+public:
+    /**
+     * Tries to resample `motionEvent` at `resampleTime` by adding a resampled sample at the end of
+     * `motionEvent` with eventTime equal to `resampleTime` and pointer coordinates determined by
+     * linear interpolation or linear extrapolation. An earlier `resampleTime` will be used if
+     * extrapolation takes place and `resampleTime` is too far in the future. If `futureSample` is
+     * not null, interpolation will occur. If `futureSample` is null and there is enough historical
+     * data, LegacyResampler will extrapolate. Otherwise, no resampling takes place and
+     * `motionEvent` is unmodified.
+     */
+    void resampleMotionEvent(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
+                             const InputMessage* futureSample) override;
+
+private:
+    struct Pointer {
+        PointerProperties properties;
+        PointerCoords coords;
+    };
+
+    struct Sample {
+        std::chrono::nanoseconds eventTime;
+        Pointer pointer;
+
+        Sample(const std::chrono::nanoseconds eventTime, const PointerProperties& properties,
+               const PointerCoords& coords)
+              : eventTime{eventTime}, pointer{properties, coords} {}
+    };
+
+    /**
+     * Keeps track of the previous MotionEvent deviceId to enable comparison between the previous
+     * and the current deviceId.
+     */
+    std::optional<DeviceId> mPreviousDeviceId;
+
+    /**
+     * Up to two latest samples from MotionEvent. Updated every time resampleMotionEvent is called.
+     * Note: We store up to two samples in order to simplify the implementation. Although,
+     * calculations are possible with only one previous sample.
+     */
+    RingBuffer<Sample> mLatestSamples{/*capacity=*/2};
+
+    /**
+     * Adds up to mLatestSamples.capacity() of motionEvent's latest samples to mLatestSamples. (If
+     * motionEvent has fewer samples than mLatestSamples.capacity(), then the available samples are
+     * added to mLatestSamples.)
+     */
+    void updateLatestSamples(const MotionEvent& motionEvent);
+
+    /**
+     * May add a sample at the end of motionEvent with eventTime equal to resampleTime, and
+     * interpolated coordinates between the latest motionEvent sample and futureSample.
+     */
+    void interpolate(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
+                     const InputMessage& futureSample) const;
+
+    /**
+     * May add a sample at the end of motionEvent by extrapolating from the latest two samples. The
+     * added sample either has eventTime equal to resampleTime, or an earlier time if resampleTime
+     * is too far in the future.
+     */
+    void extrapolate(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent) const;
+};
+} // namespace android
\ No newline at end of file
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index c57c9cd..53bd08d 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -143,6 +143,22 @@
     return reply.readNullableStrongBinder(out);
 }
 
+status_t IBinder::addFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    BpBinder* proxy = this->remoteBinder();
+    if (proxy != nullptr) {
+        return proxy->addFrozenStateChangeCallback(callback);
+    }
+    return INVALID_OPERATION;
+}
+
+status_t IBinder::removeFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    BpBinder* proxy = this->remoteBinder();
+    if (proxy != nullptr) {
+        return proxy->removeFrozenStateChangeCallback(callback);
+    }
+    return INVALID_OPERATION;
+}
+
 status_t IBinder::getDebugPid(pid_t* out) {
     BBinder* local = this->localBinder();
     if (local != nullptr) {
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 6594aa6..eae844c 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -160,11 +160,12 @@
 
 // ---------------------------------------------------------------------------
 
-sp<BpBinder> BpBinder::create(int32_t handle) {
+sp<BpBinder> BpBinder::create(int32_t handle, std::function<void()>* postTask) {
     if constexpr (!kEnableKernelIpc) {
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
         return nullptr;
     }
+    LOG_ALWAYS_FATAL_IF(postTask == nullptr, "BAD STATE");
 
     int32_t trackedUid = -1;
     if (sCountByUidEnabled) {
@@ -183,7 +184,11 @@
                 ALOGE("Still too many binder proxy objects sent to uid %d from uid %d (%d proxies "
                       "held)",
                       getuid(), trackedUid, trackedValue);
-                if (sLimitCallback) sLimitCallback(trackedUid);
+
+                if (sLimitCallback) {
+                    *postTask = [=]() { sLimitCallback(trackedUid); };
+                }
+
                 sLastLimitCallbackMap[trackedUid] = trackedValue;
             }
         } else {
@@ -197,7 +202,11 @@
                 ALOGE("Too many binder proxy objects sent to uid %d from uid %d (%d proxies held)",
                       getuid(), trackedUid, trackedValue);
                 sTrackingMap[trackedUid] |= LIMIT_REACHED_MASK;
-                if (sLimitCallback) sLimitCallback(trackedUid);
+
+                if (sLimitCallback) {
+                    *postTask = [=]() { sLimitCallback(trackedUid); };
+                }
+
                 sLastLimitCallbackMap[trackedUid] = trackedValue & COUNTING_VALUE_MASK;
                 if (sBinderProxyThrottleCreate) {
                     ALOGI("Throttling binder proxy creates from uid %d in uid %d until binder proxy"
@@ -557,6 +566,123 @@
     }
 }
 
+status_t BpBinder::addFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    LOG_ALWAYS_FATAL_IF(isRpcBinder(),
+                        "addFrozenStateChangeCallback() is not supported for RPC Binder.");
+    LOG_ALWAYS_FATAL_IF(!kEnableKernelIpc, "Binder kernel driver disabled at build time");
+    LOG_ALWAYS_FATAL_IF(ProcessState::self()->getThreadPoolMaxTotalThreadCount() == 0,
+                        "addFrozenStateChangeCallback on %s but there are no threads "
+                        "(yet?) listening to incoming transactions. See "
+                        "ProcessState::startThreadPool "
+                        "and ProcessState::setThreadPoolMaxThreadCount. Generally you should "
+                        "setup the binder threadpool before other initialization steps.",
+                        String8(getInterfaceDescriptor()).c_str());
+    LOG_ALWAYS_FATAL_IF(callback == nullptr,
+                        "addFrozenStateChangeCallback(): callback must be non-NULL");
+
+    const sp<FrozenStateChangeCallback> strongCallback = callback.promote();
+    if (strongCallback == nullptr) {
+        return BAD_VALUE;
+    }
+
+    {
+        RpcMutexUniqueLock _l(mLock);
+        if (!mFrozen) {
+            ALOGV("Requesting freeze notification: %p handle %d\n", this, binderHandle());
+            IPCThreadState* self = IPCThreadState::self();
+            status_t status = self->addFrozenStateChangeCallback(binderHandle(), this);
+            if (status != NO_ERROR) {
+                // Avoids logspam if kernel does not support freeze
+                // notification.
+                if (status != INVALID_OPERATION) {
+                    ALOGE("IPCThreadState.addFrozenStateChangeCallback "
+                          "failed with %s. %p handle %d\n",
+                          statusToString(status).c_str(), this, binderHandle());
+                }
+                return status;
+            }
+            mFrozen = std::make_unique<FrozenStateChange>();
+            if (!mFrozen) {
+                std::ignore =
+                        IPCThreadState::self()->removeFrozenStateChangeCallback(binderHandle(),
+                                                                                this);
+                return NO_MEMORY;
+            }
+        }
+        if (mFrozen->initialStateReceived) {
+            strongCallback->onStateChanged(wp<BpBinder>::fromExisting(this),
+                                           mFrozen->isFrozen
+                                                   ? FrozenStateChangeCallback::State::FROZEN
+                                                   : FrozenStateChangeCallback::State::UNFROZEN);
+        }
+        ssize_t res = mFrozen->callbacks.add(callback);
+        if (res < 0) {
+            return res;
+        }
+        return NO_ERROR;
+    }
+}
+
+status_t BpBinder::removeFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    LOG_ALWAYS_FATAL_IF(isRpcBinder(),
+                        "removeFrozenStateChangeCallback() is not supported for RPC Binder.");
+    LOG_ALWAYS_FATAL_IF(!kEnableKernelIpc, "Binder kernel driver disabled at build time");
+
+    RpcMutexUniqueLock _l(mLock);
+
+    const size_t N = mFrozen ? mFrozen->callbacks.size() : 0;
+    for (size_t i = 0; i < N; i++) {
+        if (mFrozen->callbacks.itemAt(i) == callback) {
+            mFrozen->callbacks.removeAt(i);
+            if (mFrozen->callbacks.size() == 0) {
+                ALOGV("Clearing freeze notification: %p handle %d\n", this, binderHandle());
+                status_t status =
+                        IPCThreadState::self()->removeFrozenStateChangeCallback(binderHandle(),
+                                                                                this);
+                if (status != NO_ERROR) {
+                    ALOGE("Unexpected error from "
+                          "IPCThreadState.removeFrozenStateChangeCallback: %s. "
+                          "%p handle %d\n",
+                          statusToString(status).c_str(), this, binderHandle());
+                }
+                mFrozen.reset();
+            }
+            return NO_ERROR;
+        }
+    }
+
+    return NAME_NOT_FOUND;
+}
+
+void BpBinder::onFrozenStateChanged(bool isFrozen) {
+    LOG_ALWAYS_FATAL_IF(isRpcBinder(), "onFrozenStateChanged is not supported for RPC Binder.");
+    LOG_ALWAYS_FATAL_IF(!kEnableKernelIpc, "Binder kernel driver disabled at build time");
+
+    ALOGV("Sending frozen state change notification for proxy %p handle %d, isFrozen=%s\n", this,
+          binderHandle(), isFrozen ? "true" : "false");
+
+    RpcMutexUniqueLock _l(mLock);
+    if (!mFrozen) {
+        return;
+    }
+    bool stateChanged = !mFrozen->initialStateReceived || mFrozen->isFrozen != isFrozen;
+    if (stateChanged) {
+        mFrozen->isFrozen = isFrozen;
+        mFrozen->initialStateReceived = true;
+        for (size_t i = 0; i < mFrozen->callbacks.size();) {
+            sp<FrozenStateChangeCallback> callback = mFrozen->callbacks.itemAt(i).promote();
+            if (callback != nullptr) {
+                callback->onStateChanged(wp<BpBinder>::fromExisting(this),
+                                         isFrozen ? FrozenStateChangeCallback::State::FROZEN
+                                                  : FrozenStateChangeCallback::State::UNFROZEN);
+                i++;
+            } else {
+                mFrozen->callbacks.removeItemsAt(i);
+            }
+        }
+    }
+}
+
 void BpBinder::reportOneDeath(const Obituary& obit)
 {
     sp<DeathRecipient> recipient = obit.recipient.promote();
@@ -686,6 +812,10 @@
         if (ipc) ipc->clearDeathNotification(binderHandle(), this);
         mObituaries = nullptr;
     }
+    if (mFrozen != nullptr) {
+        std::ignore = IPCThreadState::self()->removeFrozenStateChangeCallback(binderHandle(), this);
+        mFrozen.reset();
+    }
     mLock.unlock();
 
     if (obits != nullptr) {
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 984c93d..1d26d85 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -89,26 +89,33 @@
         "BR_FROZEN_REPLY",
         "BR_ONEWAY_SPAM_SUSPECT",
         "BR_TRANSACTION_PENDING_FROZEN",
+        "BR_FROZEN_BINDER",
+        "BR_CLEAR_FREEZE_NOTIFICATION_DONE",
 };
 
-static const char *kCommandStrings[] = {
-    "BC_TRANSACTION",
-    "BC_REPLY",
-    "BC_ACQUIRE_RESULT",
-    "BC_FREE_BUFFER",
-    "BC_INCREFS",
-    "BC_ACQUIRE",
-    "BC_RELEASE",
-    "BC_DECREFS",
-    "BC_INCREFS_DONE",
-    "BC_ACQUIRE_DONE",
-    "BC_ATTEMPT_ACQUIRE",
-    "BC_REGISTER_LOOPER",
-    "BC_ENTER_LOOPER",
-    "BC_EXIT_LOOPER",
-    "BC_REQUEST_DEATH_NOTIFICATION",
-    "BC_CLEAR_DEATH_NOTIFICATION",
-    "BC_DEAD_BINDER_DONE"
+static const char* kCommandStrings[] = {
+        "BC_TRANSACTION",
+        "BC_REPLY",
+        "BC_ACQUIRE_RESULT",
+        "BC_FREE_BUFFER",
+        "BC_INCREFS",
+        "BC_ACQUIRE",
+        "BC_RELEASE",
+        "BC_DECREFS",
+        "BC_INCREFS_DONE",
+        "BC_ACQUIRE_DONE",
+        "BC_ATTEMPT_ACQUIRE",
+        "BC_REGISTER_LOOPER",
+        "BC_ENTER_LOOPER",
+        "BC_EXIT_LOOPER",
+        "BC_REQUEST_DEATH_NOTIFICATION",
+        "BC_CLEAR_DEATH_NOTIFICATION",
+        "BC_DEAD_BINDER_DONE",
+        "BC_TRANSACTION_SG",
+        "BC_REPLY_SG",
+        "BC_REQUEST_FREEZE_NOTIFICATION",
+        "BC_CLEAR_FREEZE_NOTIFICATION",
+        "BC_FREEZE_NOTIFICATION_DONE",
 };
 
 static const int64_t kWorkSourcePropagatedBitIndex = 32;
@@ -203,6 +210,18 @@
             out << ": death cookie " << (void*)(uint64_t)c;
         } break;
 
+        case BR_FROZEN_BINDER: {
+            const int32_t c = *cmd++;
+            const int32_t h = *cmd++;
+            const int32_t isFrozen = *cmd++;
+            out << ": freeze cookie " << (void*)(uint64_t)c << " isFrozen: " << isFrozen;
+        } break;
+
+        case BR_CLEAR_FREEZE_NOTIFICATION_DONE: {
+            const int32_t c = *cmd++;
+            out << ": freeze cookie " << (void*)(uint64_t)c;
+        } break;
+
         default:
             // no details to show for: BR_OK, BR_DEAD_REPLY,
             // BR_TRANSACTION_COMPLETE, BR_FINISHED
@@ -270,11 +289,23 @@
             out << ": handle=" << h << " (death cookie " << (void*)(uint64_t)c << ")";
         } break;
 
+        case BC_REQUEST_FREEZE_NOTIFICATION:
+        case BC_CLEAR_FREEZE_NOTIFICATION: {
+            const int32_t h = *cmd++;
+            const int32_t c = *cmd++;
+            out << ": handle=" << h << " (freeze cookie " << (void*)(uint64_t)c << ")";
+        } break;
+
         case BC_DEAD_BINDER_DONE: {
             const int32_t c = *cmd++;
             out << ": death cookie " << (void*)(uint64_t)c;
         } break;
 
+        case BC_FREEZE_NOTIFICATION_DONE: {
+            const int32_t c = *cmd++;
+            out << ": freeze cookie " << (void*)(uint64_t)c;
+        } break;
+
         default:
             // no details to show for: BC_REGISTER_LOOPER, BC_ENTER_LOOPER,
             // BC_EXIT_LOOPER
@@ -953,6 +984,33 @@
     return NO_ERROR;
 }
 
+status_t IPCThreadState::addFrozenStateChangeCallback(int32_t handle, BpBinder* proxy) {
+    static bool isSupported =
+            ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+    if (!isSupported) {
+        return INVALID_OPERATION;
+    }
+    proxy->getWeakRefs()->incWeak(proxy);
+    mOut.writeInt32(BC_REQUEST_FREEZE_NOTIFICATION);
+    mOut.writeInt32((int32_t)handle);
+    mOut.writePointer((uintptr_t)proxy);
+    flushCommands();
+    return NO_ERROR;
+}
+
+status_t IPCThreadState::removeFrozenStateChangeCallback(int32_t handle, BpBinder* proxy) {
+    static bool isSupported =
+            ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+    if (!isSupported) {
+        return INVALID_OPERATION;
+    }
+    mOut.writeInt32(BC_CLEAR_FREEZE_NOTIFICATION);
+    mOut.writeInt32((int32_t)handle);
+    mOut.writePointer((uintptr_t)proxy);
+    flushCommands();
+    return NO_ERROR;
+}
+
 IPCThreadState::IPCThreadState()
       : mProcess(ProcessState::self()),
         mServingStackPointer(nullptr),
@@ -1487,6 +1545,26 @@
             proxy->getWeakRefs()->decWeak(proxy);
         } break;
 
+        case BR_FROZEN_BINDER: {
+            const struct binder_frozen_state_info* data =
+                    reinterpret_cast<const struct binder_frozen_state_info*>(
+                            mIn.readInplace(sizeof(struct binder_frozen_state_info)));
+            if (data == nullptr) {
+                result = UNKNOWN_ERROR;
+                break;
+            }
+            BpBinder* proxy = (BpBinder*)data->cookie;
+            bool isFrozen = mIn.readInt32() > 0;
+            proxy->getPrivateAccessor().onFrozenStateChanged(data->is_frozen);
+            mOut.writeInt32(BC_FREEZE_NOTIFICATION_DONE);
+            mOut.writePointer(data->cookie);
+        } break;
+
+        case BR_CLEAR_FREEZE_NOTIFICATION_DONE: {
+            BpBinder* proxy = (BpBinder*)mIn.readPointer();
+            proxy->getWeakRefs()->decWeak(proxy);
+        } break;
+
     case BR_FINISHED:
         result = TIMED_OUT;
         break;
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index a42ede2..5e7f151 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -24,7 +24,6 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/Stability.h>
-#include <cutils/atomic.h>
 #include <utils/AndroidThreads.h>
 #include <utils/String8.h>
 #include <utils/Thread.h>
@@ -57,6 +56,25 @@
 
 // -------------------------------------------------------------------------
 
+namespace {
+bool readDriverFeatureFile(const char* filename) {
+    int fd = open(filename, O_RDONLY | O_CLOEXEC);
+    char on;
+    if (fd == -1) {
+        ALOGE_IF(errno != ENOENT, "%s: cannot open %s: %s", __func__, filename, strerror(errno));
+        return false;
+    }
+    if (read(fd, &on, sizeof(on)) == -1) {
+        ALOGE("%s: error reading to %s: %s", __func__, filename, strerror(errno));
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return on == '1';
+}
+
+} // namespace
+
 namespace android {
 
 using namespace android::binder::impl;
@@ -311,6 +329,7 @@
 sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
 {
     sp<IBinder> result;
+    std::function<void()> postTask;
 
     std::unique_lock<std::mutex> _l(mLock);
 
@@ -358,7 +377,7 @@
                    return nullptr;
             }
 
-            sp<BpBinder> b = BpBinder::PrivateAccessor::create(handle);
+            sp<BpBinder> b = BpBinder::PrivateAccessor::create(handle, &postTask);
             e->binder = b.get();
             if (b) e->refs = b->getWeakRefs();
             result = b;
@@ -371,6 +390,10 @@
         }
     }
 
+    _l.unlock();
+
+    if (postTask) postTask();
+
     return result;
 }
 
@@ -387,7 +410,7 @@
 }
 
 String8 ProcessState::makeBinderThreadName() {
-    int32_t s = android_atomic_add(1, &mThreadPoolSeq);
+    int32_t s = mThreadPoolSeq.fetch_add(1, std::memory_order_release);
     pid_t pid = getpid();
 
     std::string_view driverName = mDriverName.c_str();
@@ -429,8 +452,17 @@
 }
 
 size_t ProcessState::getThreadPoolMaxTotalThreadCount() const {
+    // Need to read `mKernelStartedThreads` before `mThreadPoolStarted` (with
+    // non-relaxed memory ordering) to avoid a race like the following:
+    //
+    // thread A: if (mThreadPoolStarted) { // evaluates false
+    // thread B: mThreadPoolStarted = true;
+    // thread B: mKernelStartedThreads++;
+    // thread A: size_t kernelStarted = mKernelStartedThreads;
+    // thread A: LOG_ALWAYS_FATAL_IF(kernelStarted != 0, ...);
+    size_t kernelStarted = mKernelStartedThreads;
+
     if (mThreadPoolStarted) {
-        size_t kernelStarted = mKernelStartedThreads;
         size_t max = mMaxThreads;
         size_t current = mCurrentThreads;
 
@@ -460,7 +492,6 @@
 
     // must not be initialized or maybe has poll thread setup, we
     // currently don't track this in libbinder
-    size_t kernelStarted = mKernelStartedThreads;
     LOG_ALWAYS_FATAL_IF(kernelStarted != 0, "Expecting 0 kernel started threads but have %zu",
                         kernelStarted);
     return mCurrentThreads;
@@ -472,27 +503,20 @@
 
 #define DRIVER_FEATURES_PATH "/dev/binderfs/features/"
 bool ProcessState::isDriverFeatureEnabled(const DriverFeature feature) {
-    static const char* const names[] = {
-        [static_cast<int>(DriverFeature::ONEWAY_SPAM_DETECTION)] =
-            DRIVER_FEATURES_PATH "oneway_spam_detection",
-        [static_cast<int>(DriverFeature::EXTENDED_ERROR)] =
-            DRIVER_FEATURES_PATH "extended_error",
-    };
-    int fd = open(names[static_cast<int>(feature)], O_RDONLY | O_CLOEXEC);
-    char on;
-    if (fd == -1) {
-        ALOGE_IF(errno != ENOENT, "%s: cannot open %s: %s", __func__,
-                 names[static_cast<int>(feature)], strerror(errno));
-        return false;
+    // Use static variable to cache the results.
+    if (feature == DriverFeature::ONEWAY_SPAM_DETECTION) {
+        static bool enabled = readDriverFeatureFile(DRIVER_FEATURES_PATH "oneway_spam_detection");
+        return enabled;
     }
-    if (read(fd, &on, sizeof(on)) == -1) {
-        ALOGE("%s: error reading to %s: %s", __func__,
-                 names[static_cast<int>(feature)], strerror(errno));
-        close(fd);
-        return false;
+    if (feature == DriverFeature::EXTENDED_ERROR) {
+        static bool enabled = readDriverFeatureFile(DRIVER_FEATURES_PATH "extended_error");
+        return enabled;
     }
-    close(fd);
-    return on == '1';
+    if (feature == DriverFeature::FREEZE_NOTIFICATION) {
+        static bool enabled = readDriverFeatureFile(DRIVER_FEATURES_PATH "freeze_notification");
+        return enabled;
+    }
+    return false;
 }
 
 status_t ProcessState::enableOnewaySpamDetection(bool enable) {
@@ -577,7 +601,7 @@
 #ifdef __ANDROID__
     LOG_ALWAYS_FATAL_IF(!opened.ok(),
                         "Binder driver '%s' could not be opened. Error: %s. Terminating.",
-                        error.c_str(), driver);
+                        driver, error.c_str());
 #endif
 
     if (opened.ok()) {
diff --git a/libs/binder/binder_module.h b/libs/binder/binder_module.h
index b3a2d9e..65cdcd7 100644
--- a/libs/binder/binder_module.h
+++ b/libs/binder/binder_module.h
@@ -32,4 +32,34 @@
 #include <linux/android/binder.h>
 #include <sys/ioctl.h>
 
+struct binder_frozen_state_info {
+    binder_uintptr_t cookie;
+    __u32 is_frozen;
+};
+
+#ifndef BR_FROZEN_BINDER
+// Temporary definition of BR_FROZEN_BINDER until UAPI binder.h includes it.
+#define BR_FROZEN_BINDER _IOR('r', 21, struct binder_frozen_state_info)
+#endif // BR_FROZEN_BINDER
+
+#ifndef BR_CLEAR_FREEZE_NOTIFICATION_DONE
+// Temporary definition of BR_CLEAR_FREEZE_NOTIFICATION_DONE until UAPI binder.h includes it.
+#define BR_CLEAR_FREEZE_NOTIFICATION_DONE _IOR('r', 22, binder_uintptr_t)
+#endif // BR_CLEAR_FREEZE_NOTIFICATION_DONE
+
+#ifndef BC_REQUEST_FREEZE_NOTIFICATION
+// Temporary definition of BC_REQUEST_FREEZE_NOTIFICATION until UAPI binder.h includes it.
+#define BC_REQUEST_FREEZE_NOTIFICATION _IOW('c', 19, struct binder_handle_cookie)
+#endif // BC_REQUEST_FREEZE_NOTIFICATION
+
+#ifndef BC_CLEAR_FREEZE_NOTIFICATION
+// Temporary definition of BC_CLEAR_FREEZE_NOTIFICATION until UAPI binder.h includes it.
+#define BC_CLEAR_FREEZE_NOTIFICATION _IOW('c', 20, struct binder_handle_cookie)
+#endif // BC_CLEAR_FREEZE_NOTIFICATION
+
+#ifndef BC_FREEZE_NOTIFICATION_DONE
+// Temporary definition of BC_FREEZE_NOTIFICATION_DONE until UAPI binder.h includes it.
+#define BC_FREEZE_NOTIFICATION_DONE _IOW('c', 21, binder_uintptr_t)
+#endif // BC_FREEZE_NOTIFICATION_DONE
+
 #endif // _BINDER_MODULE_H_
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index d7f74c4..7518044 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -29,6 +29,7 @@
 // ---------------------------------------------------------------------------
 namespace android {
 
+class IPCThreadState;
 class RpcSession;
 class RpcState;
 namespace internal {
@@ -66,6 +67,12 @@
                                                       void* cookie = nullptr, uint32_t flags = 0,
                                                       wp<DeathRecipient>* outRecipient = nullptr);
 
+    [[nodiscard]] status_t addFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& recipient);
+
+    [[nodiscard]] status_t removeFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& recipient);
+
     LIBBINDER_EXPORTED virtual void* attachObject(const void* objectID, void* object,
                                                   void* cleanupCookie,
                                                   object_cleanup_func func) final;
@@ -75,7 +82,6 @@
     LIBBINDER_EXPORTED sp<IBinder> lookupOrCreateWeak(const void* objectID,
                                                       IBinder::object_make_func make,
                                                       const void* makeArgs);
-
     LIBBINDER_EXPORTED virtual BpBinder* remoteBinder();
 
     LIBBINDER_EXPORTED void sendObituary();
@@ -132,9 +138,14 @@
         friend class ::android::ProcessState;
         friend class ::android::RpcSession;
         friend class ::android::RpcState;
-        explicit PrivateAccessor(const BpBinder* binder) : mBinder(binder) {}
+        friend class ::android::IPCThreadState;
+        explicit PrivateAccessor(const BpBinder* binder)
+              : mBinder(binder), mMutableBinder(nullptr) {}
+        explicit PrivateAccessor(BpBinder* binder) : mBinder(binder), mMutableBinder(binder) {}
 
-        static sp<BpBinder> create(int32_t handle) { return BpBinder::create(handle); }
+        static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask) {
+            return BpBinder::create(handle, postTask);
+        }
         static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address) {
             return BpBinder::create(session, address);
         }
@@ -146,17 +157,22 @@
         uint64_t rpcAddress() const { return mBinder->rpcAddress(); }
         const sp<RpcSession>& rpcSession() const { return mBinder->rpcSession(); }
 
+        void onFrozenStateChanged(bool isFrozen) { mMutableBinder->onFrozenStateChanged(isFrozen); }
         const BpBinder* mBinder;
+        BpBinder* mMutableBinder;
     };
+
     LIBBINDER_EXPORTED const PrivateAccessor getPrivateAccessor() const {
         return PrivateAccessor(this);
     }
 
+    PrivateAccessor getPrivateAccessor() { return PrivateAccessor(this); }
+
 private:
     friend PrivateAccessor;
     friend class sp<BpBinder>;
 
-    static sp<BpBinder> create(int32_t handle);
+    static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask);
     static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address);
 
     struct BinderHandle {
@@ -192,6 +208,14 @@
         uint32_t flags;
     };
 
+    void onFrozenStateChanged(bool isFrozen);
+
+    struct FrozenStateChange {
+        bool isFrozen = false;
+        Vector<wp<FrozenStateChangeCallback>> callbacks;
+        bool initialStateReceived = false;
+    };
+
     void reportOneDeath(const Obituary& obit);
     bool isDescriptorCached() const;
 
@@ -199,6 +223,7 @@
     volatile int32_t mAlive;
     volatile int32_t mObitsSent;
     Vector<Obituary>* mObituaries;
+    std::unique_ptr<FrozenStateChange> mFrozen;
     ObjectManager mObjects;
     mutable String16 mDescriptorCache;
     int32_t mTrackedUid;
diff --git a/libs/binder/include/binder/IBinder.h b/libs/binder/include/binder/IBinder.h
index 4eb1c08..1ed7c91 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -202,9 +202,18 @@
         virtual void binderDied(const wp<IBinder>& who) = 0;
     };
 
-    #if defined(__clang__)
-    #pragma clang diagnostic pop
-    #endif
+    class FrozenStateChangeCallback : public virtual RefBase {
+    public:
+        enum class State {
+            FROZEN,
+            UNFROZEN,
+        };
+        virtual void onStateChanged(const wp<IBinder>& who, State state) = 0;
+    };
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
 
     /**
      * Register the @a recipient for a notification if this binder
@@ -253,6 +262,48 @@
                                             uint32_t flags = 0,
                                             wp<DeathRecipient>* outRecipient = nullptr) = 0;
 
+    /**
+     * addFrozenStateChangeCallback provides a callback mechanism to notify
+     * about process frozen/unfrozen events. Upon registration and any
+     * subsequent state changes, the callback is invoked with the latest process
+     * frozen state.
+     *
+     * If the listener process (the one using this API) is itself frozen, state
+     * change events might be combined into a single one with the latest state.
+     * (meaning 'frozen, unfrozen' might just be 'unfrozen'). This single event
+     * would then be delivered when the listener process becomes unfrozen.
+     * Similarly, if an event happens before the previous event is consumed,
+     * they might be combined. This means the callback might not be called for
+     * every single state change, so don't rely on this API to count how many
+     * times the state has changed.
+     *
+     * @note When all references to the binder are dropped, the callback is
+     * automatically removed. So, you must hold onto a binder in order to
+     * receive notifications about it.
+     *
+     * @note You will only receive freeze notifications for remote binders, as
+     * local binders by definition can't be frozen without you being frozen as
+     * well. Trying to use this function on a local binder will result in an
+     * INVALID_OPERATION code being returned and nothing happening.
+     *
+     * @note This binder always holds a weak reference to the callback.
+     *
+     * @note You will only receive a weak reference to the binder object. You
+     * should not try to promote this to a strong reference. (Nor should you
+     * need to, as there is nothing useful you can directly do with it now that
+     * it has passed on.)
+     */
+    [[nodiscard]] status_t addFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& callback);
+
+    /**
+     * Remove a previously registered freeze callback.
+     * The @a callback will no longer be called if this object
+     * changes its frozen state.
+     */
+    [[nodiscard]] status_t removeFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& callback);
+
     virtual bool            checkSubclass(const void* subclassID) const;
 
     typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 09ab442..9ef4e69 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -174,6 +174,8 @@
     LIBBINDER_EXPORTED static void expungeHandle(int32_t handle, IBinder* binder);
     LIBBINDER_EXPORTED status_t requestDeathNotification(int32_t handle, BpBinder* proxy);
     LIBBINDER_EXPORTED status_t clearDeathNotification(int32_t handle, BpBinder* proxy);
+    [[nodiscard]] status_t addFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
+    [[nodiscard]] status_t removeFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
 
     LIBBINDER_EXPORTED static void shutdown();
 
@@ -210,13 +212,14 @@
     IPCThreadState();
     ~IPCThreadState();
 
-    status_t sendReply(const Parcel& reply, uint32_t flags);
-    status_t waitForResponse(Parcel* reply, status_t* acquireResult = nullptr);
-    status_t talkWithDriver(bool doReceive = true);
-    status_t writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code,
-                                  const Parcel& data, status_t* statusBuffer);
-    status_t getAndExecuteCommand();
-    status_t executeCommand(int32_t command);
+    [[nodiscard]] status_t sendReply(const Parcel& reply, uint32_t flags);
+    [[nodiscard]] status_t waitForResponse(Parcel* reply, status_t* acquireResult = nullptr);
+    [[nodiscard]] status_t talkWithDriver(bool doReceive = true);
+    [[nodiscard]] status_t writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle,
+                                                uint32_t code, const Parcel& data,
+                                                status_t* statusBuffer);
+    [[nodiscard]] status_t getAndExecuteCommand();
+    [[nodiscard]] status_t executeCommand(int32_t command);
     void processPendingDerefs();
     void processPostWriteDerefs();
 
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index 021bd58..21bfd42 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -133,6 +133,7 @@
     enum class DriverFeature {
         ONEWAY_SPAM_DETECTION,
         EXTENDED_ERROR,
+        FREEZE_NOTIFICATION,
     };
     // Determine whether a feature is supported by the binder driver.
     LIBBINDER_EXPORTED static bool isDriverFeatureEnabled(const DriverFeature feature);
@@ -188,8 +189,8 @@
     Vector<handle_entry> mHandleToObject;
 
     bool mForked;
-    bool mThreadPoolStarted;
-    volatile int32_t mThreadPoolSeq;
+    std::atomic_bool mThreadPoolStarted;
+    std::atomic_int32_t mThreadPoolSeq;
 
     CallRestriction mCallRestriction;
 };
diff --git a/libs/binder/rust/src/parcel/parcelable.rs b/libs/binder/rust/src/parcel/parcelable.rs
index 33dfe19..7f70396 100644
--- a/libs/binder/rust/src/parcel/parcelable.rs
+++ b/libs/binder/rust/src/parcel/parcelable.rs
@@ -1333,7 +1333,7 @@
         let vec = Vec::<u8>::deserialize(parcel.borrowed_ref()).unwrap();
         assert_eq!(vec, [-128i8 as u8, 127, 42, -117i8 as u8]);
 
-        let u16s = [u16::max_value(), 12_345, 42, 117];
+        let u16s = [u16::MAX, 12_345, 42, 117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1348,7 +1348,7 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0xffff); // u16::max_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0xffff); // u16::MAX
         assert_eq!(parcel.read::<u32>().unwrap(), 12345); // 12,345
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 117); // 117
@@ -1361,9 +1361,9 @@
 
         let vec = Vec::<u16>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [u16::max_value(), 12_345, 42, 117]);
+        assert_eq!(vec, [u16::MAX, 12_345, 42, 117]);
 
-        let i16s = [i16::max_value(), i16::min_value(), 42, -117];
+        let i16s = [i16::MAX, i16::MIN, 42, -117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1378,8 +1378,8 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fff); // i16::max_value()
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x8000); // i16::min_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fff); // i16::MAX
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x8000); // i16::MIN
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 0xff8b); // -117
 
@@ -1391,9 +1391,9 @@
 
         let vec = Vec::<i16>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [i16::max_value(), i16::min_value(), 42, -117]);
+        assert_eq!(vec, [i16::MAX, i16::MIN, 42, -117]);
 
-        let u32s = [u32::max_value(), 12_345, 42, 117];
+        let u32s = [u32::MAX, 12_345, 42, 117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1408,7 +1408,7 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0xffffffff); // u32::max_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0xffffffff); // u32::MAX
         assert_eq!(parcel.read::<u32>().unwrap(), 12345); // 12,345
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 117); // 117
@@ -1421,9 +1421,9 @@
 
         let vec = Vec::<u32>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [u32::max_value(), 12_345, 42, 117]);
+        assert_eq!(vec, [u32::MAX, 12_345, 42, 117]);
 
-        let i32s = [i32::max_value(), i32::min_value(), 42, -117];
+        let i32s = [i32::MAX, i32::MIN, 42, -117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1438,8 +1438,8 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fffffff); // i32::max_value()
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x80000000); // i32::min_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fffffff); // i32::MAX
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x80000000); // i32::MIN
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 0xffffff8b); // -117
 
@@ -1451,9 +1451,9 @@
 
         let vec = Vec::<i32>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [i32::max_value(), i32::min_value(), 42, -117]);
+        assert_eq!(vec, [i32::MAX, i32::MIN, 42, -117]);
 
-        let u64s = [u64::max_value(), 12_345, 42, 117];
+        let u64s = [u64::MAX, 12_345, 42, 117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1469,9 +1469,9 @@
 
         let vec = Vec::<u64>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [u64::max_value(), 12_345, 42, 117]);
+        assert_eq!(vec, [u64::MAX, 12_345, 42, 117]);
 
-        let i64s = [i64::max_value(), i64::min_value(), 42, -117];
+        let i64s = [i64::MAX, i64::MIN, 42, -117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1487,9 +1487,9 @@
 
         let vec = Vec::<i64>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [i64::max_value(), i64::min_value(), 42, -117]);
+        assert_eq!(vec, [i64::MAX, i64::MIN, 42, -117]);
 
-        let f32s = [std::f32::NAN, std::f32::INFINITY, 1.23456789, std::f32::EPSILON];
+        let f32s = [f32::NAN, f32::INFINITY, 1.23456789, f32::EPSILON];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1509,7 +1509,7 @@
         assert!(vec[0].is_nan());
         assert_eq!(vec[1..], f32s[1..]);
 
-        let f64s = [std::f64::NAN, std::f64::INFINITY, 1.234567890123456789, std::f64::EPSILON];
+        let f64s = [f64::NAN, f64::INFINITY, 1.234567890123456789, f64::EPSILON];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
diff --git a/libs/binder/rust/tests/serialization.rs b/libs/binder/rust/tests/serialization.rs
index 2b6c282..a902e96 100644
--- a/libs/binder/rust/tests/serialization.rs
+++ b/libs/binder/rust/tests/serialization.rs
@@ -124,7 +124,7 @@
         bindings::Transaction_TEST_BYTE => {
             assert_eq!(parcel.read::<i8>()?, 0);
             assert_eq!(parcel.read::<i8>()?, 1);
-            assert_eq!(parcel.read::<i8>()?, i8::max_value());
+            assert_eq!(parcel.read::<i8>()?, i8::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<i8>>()?, unsafe { bindings::TESTDATA_I8 });
             // SAFETY: Just reading an extern constant.
@@ -133,7 +133,7 @@
 
             reply.write(&0i8)?;
             reply.write(&1i8)?;
-            reply.write(&i8::max_value())?;
+            reply.write(&i8::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_I8 }[..])?;
             // SAFETY: Just reading an extern constant.
@@ -143,14 +143,14 @@
         bindings::Transaction_TEST_U16 => {
             assert_eq!(parcel.read::<u16>()?, 0);
             assert_eq!(parcel.read::<u16>()?, 1);
-            assert_eq!(parcel.read::<u16>()?, u16::max_value());
+            assert_eq!(parcel.read::<u16>()?, u16::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<u16>>()?, unsafe { bindings::TESTDATA_CHARS });
             assert_eq!(parcel.read::<Option<Vec<u16>>>()?, None);
 
             reply.write(&0u16)?;
             reply.write(&1u16)?;
-            reply.write(&u16::max_value())?;
+            reply.write(&u16::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_CHARS }[..])?;
             reply.write(&(None as Option<Vec<u16>>))?;
@@ -158,14 +158,14 @@
         bindings::Transaction_TEST_I32 => {
             assert_eq!(parcel.read::<i32>()?, 0);
             assert_eq!(parcel.read::<i32>()?, 1);
-            assert_eq!(parcel.read::<i32>()?, i32::max_value());
+            assert_eq!(parcel.read::<i32>()?, i32::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<i32>>()?, unsafe { bindings::TESTDATA_I32 });
             assert_eq!(parcel.read::<Option<Vec<i32>>>()?, None);
 
             reply.write(&0i32)?;
             reply.write(&1i32)?;
-            reply.write(&i32::max_value())?;
+            reply.write(&i32::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_I32 }[..])?;
             reply.write(&(None as Option<Vec<i32>>))?;
@@ -173,14 +173,14 @@
         bindings::Transaction_TEST_I64 => {
             assert_eq!(parcel.read::<i64>()?, 0);
             assert_eq!(parcel.read::<i64>()?, 1);
-            assert_eq!(parcel.read::<i64>()?, i64::max_value());
+            assert_eq!(parcel.read::<i64>()?, i64::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<i64>>()?, unsafe { bindings::TESTDATA_I64 });
             assert_eq!(parcel.read::<Option<Vec<i64>>>()?, None);
 
             reply.write(&0i64)?;
             reply.write(&1i64)?;
-            reply.write(&i64::max_value())?;
+            reply.write(&i64::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_I64 }[..])?;
             reply.write(&(None as Option<Vec<i64>>))?;
@@ -188,14 +188,14 @@
         bindings::Transaction_TEST_U64 => {
             assert_eq!(parcel.read::<u64>()?, 0);
             assert_eq!(parcel.read::<u64>()?, 1);
-            assert_eq!(parcel.read::<u64>()?, u64::max_value());
+            assert_eq!(parcel.read::<u64>()?, u64::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<u64>>()?, unsafe { bindings::TESTDATA_U64 });
             assert_eq!(parcel.read::<Option<Vec<u64>>>()?, None);
 
             reply.write(&0u64)?;
             reply.write(&1u64)?;
-            reply.write(&u64::max_value())?;
+            reply.write(&u64::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_U64 }[..])?;
             reply.write(&(None as Option<Vec<u64>>))?;
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index dd50fbd..21c32ac 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -42,6 +42,9 @@
     defaults: ["binder_test_defaults"],
     header_libs: ["libbinder_headers"],
     srcs: ["binderDriverInterfaceTest.cpp"],
+    shared_libs: [
+        "libbinder",
+    ],
     test_suites: [
         "device-tests",
         "vts",
@@ -127,6 +130,7 @@
         "libbase",
         "libbinder",
         "liblog",
+        "libprocessgroup",
         "libutils",
     ],
     static_libs: [
diff --git a/libs/binder/tests/binderDriverInterfaceTest.cpp b/libs/binder/tests/binderDriverInterfaceTest.cpp
index 7be4f21..af82860 100644
--- a/libs/binder/tests/binderDriverInterfaceTest.cpp
+++ b/libs/binder/tests/binderDriverInterfaceTest.cpp
@@ -20,12 +20,15 @@
 #include <stdlib.h>
 
 #include <binder/IBinder.h>
+#include <binder/ProcessState.h>
 #include <gtest/gtest.h>
 #include <linux/android/binder.h>
 #include <poll.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 
+#include "../binder_module.h"
+
 #define BINDER_DEV_NAME "/dev/binder"
 
 testing::Environment* binder_env;
@@ -365,6 +368,251 @@
     binderTestReadEmpty();
 }
 
+TEST_F(BinderDriverInterfaceTest, RequestFrozenNotification) {
+    if (!android::ProcessState::isDriverFeatureEnabled(
+                android::ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support freeze notification";
+        return;
+    }
+    binder_uintptr_t cookie = 1234;
+    struct {
+        uint32_t cmd0;
+        uint32_t arg0;
+        uint32_t cmd1;
+        struct binder_handle_cookie arg1;
+    } __attribute__((packed)) bc1 = {
+            .cmd0 = BC_INCREFS,
+            .arg0 = 0,
+            .cmd1 = BC_REQUEST_FREEZE_NOTIFICATION,
+            .arg1 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+    };
+    struct {
+        uint32_t cmd0;
+        // Expecting a BR_FROZEN_BINDER since BC_REQUEST_FREEZE_NOTIFICATION
+        // above should lead to an immediate notification of the current state.
+        uint32_t cmd1;
+        struct binder_frozen_state_info arg1;
+        uint32_t pad[16];
+    } __attribute__((packed)) br1;
+    struct {
+        uint32_t cmd2;
+        binder_uintptr_t arg2;
+        uint32_t cmd3;
+        struct binder_handle_cookie arg3;
+        uint32_t cmd4;
+        uint32_t arg4;
+    } __attribute__((packed)) bc2 = {
+            // Tell kernel that userspace has done handling BR_FROZEN_BINDER.
+            .cmd2 = BC_FREEZE_NOTIFICATION_DONE,
+            .arg2 = cookie,
+            .cmd3 = BC_CLEAR_FREEZE_NOTIFICATION,
+            .arg3 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+            .cmd4 = BC_DECREFS,
+            .arg4 = 0,
+    };
+    struct {
+        uint32_t cmd2;
+        uint32_t cmd3;
+        binder_uintptr_t arg3;
+        uint32_t pad[16];
+    } __attribute__((packed)) br2;
+
+    struct binder_write_read bwr1 = binder_write_read();
+    bwr1.write_buffer = (uintptr_t)&bc1;
+    bwr1.write_size = sizeof(bc1);
+    bwr1.read_buffer = (uintptr_t)&br1;
+    bwr1.read_size = sizeof(br1);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr1);
+    EXPECT_EQ(sizeof(bc1), bwr1.write_consumed);
+    EXPECT_EQ(sizeof(br1) - sizeof(br1.pad), bwr1.read_consumed);
+    EXPECT_EQ(BR_NOOP, br1.cmd0);
+    ASSERT_EQ(BR_FROZEN_BINDER, br1.cmd1);
+    EXPECT_FALSE(br1.arg1.is_frozen);
+
+    struct binder_write_read bwr2 = binder_write_read();
+    bwr2.write_buffer = (uintptr_t)&bc2;
+    bwr2.write_size = sizeof(bc2);
+    bwr2.read_buffer = (uintptr_t)&br2;
+    bwr2.read_size = sizeof(br2);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr2);
+    EXPECT_EQ(sizeof(bc2), bwr2.write_consumed);
+    EXPECT_EQ(sizeof(br2) - sizeof(br2.pad), bwr2.read_consumed);
+    EXPECT_EQ(BR_NOOP, br2.cmd2);
+    EXPECT_EQ(BR_CLEAR_FREEZE_NOTIFICATION_DONE, br2.cmd3);
+    EXPECT_EQ(cookie, br2.arg3);
+
+    binderTestReadEmpty();
+}
+
+TEST_F(BinderDriverInterfaceTest, OverwritePendingFrozenNotification) {
+    if (!android::ProcessState::isDriverFeatureEnabled(
+                android::ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support freeze notification";
+        return;
+    }
+    binder_uintptr_t cookie = 1234;
+    struct {
+        uint32_t cmd0;
+        uint32_t arg0;
+        uint32_t cmd1;
+        struct binder_handle_cookie arg1;
+        uint32_t cmd2;
+        struct binder_handle_cookie arg2;
+        uint32_t cmd3;
+        uint32_t arg3;
+    } __attribute__((packed)) bc = {
+            .cmd0 = BC_INCREFS,
+            .arg0 = 0,
+            .cmd1 = BC_REQUEST_FREEZE_NOTIFICATION,
+            // This BC_REQUEST_FREEZE_NOTIFICATION should lead to a pending
+            // frozen notification inserted into the queue.
+            .arg1 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+            // Send BC_CLEAR_FREEZE_NOTIFICATION before the above frozen
+            // notification has a chance of being sent. The notification should
+            // be overwritten. Userspace is expected to only receive
+            // BR_CLEAR_FREEZE_NOTIFICATION_DONE.
+            .cmd2 = BC_CLEAR_FREEZE_NOTIFICATION,
+            .arg2 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+            .cmd3 = BC_DECREFS,
+            .arg3 = 0,
+    };
+    struct {
+        uint32_t cmd0;
+        uint32_t cmd1;
+        binder_uintptr_t arg1;
+        uint32_t pad[16];
+    } __attribute__((packed)) br;
+    struct binder_write_read bwr = binder_write_read();
+
+    bwr.write_buffer = (uintptr_t)&bc;
+    bwr.write_size = sizeof(bc);
+    bwr.read_buffer = (uintptr_t)&br;
+    bwr.read_size = sizeof(br);
+
+    binderTestIoctl(BINDER_WRITE_READ, &bwr);
+    EXPECT_EQ(sizeof(bc), bwr.write_consumed);
+    EXPECT_EQ(sizeof(br) - sizeof(br.pad), bwr.read_consumed);
+    EXPECT_EQ(BR_NOOP, br.cmd0);
+    EXPECT_EQ(BR_CLEAR_FREEZE_NOTIFICATION_DONE, br.cmd1);
+    EXPECT_EQ(cookie, br.arg1);
+    binderTestReadEmpty();
+}
+
+TEST_F(BinderDriverInterfaceTest, ResendFrozenNotification) {
+    if (!android::ProcessState::isDriverFeatureEnabled(
+                android::ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support freeze notification";
+        return;
+    }
+    binder_uintptr_t cookie = 1234;
+    struct {
+        uint32_t cmd0;
+        uint32_t arg0;
+        uint32_t cmd1;
+        struct binder_handle_cookie arg1;
+    } __attribute__((packed)) bc1 = {
+            .cmd0 = BC_INCREFS,
+            .arg0 = 0,
+            .cmd1 = BC_REQUEST_FREEZE_NOTIFICATION,
+            .arg1 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+    };
+    struct {
+        uint32_t cmd0;
+        uint32_t cmd1;
+        struct binder_frozen_state_info arg1;
+        uint32_t pad[16];
+    } __attribute__((packed)) br1;
+    struct {
+        uint32_t cmd2;
+        struct binder_handle_cookie arg2;
+    } __attribute__((packed)) bc2 = {
+            // Clear the notification before acknowledging the in-flight
+            // BR_FROZEN_BINDER. Kernel should hold off sending
+            // BR_CLEAR_FREEZE_NOTIFICATION_DONE until the acknowledgement
+            // reaches kernel.
+            .cmd2 = BC_CLEAR_FREEZE_NOTIFICATION,
+            .arg2 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+    };
+    struct {
+        uint32_t pad[16];
+    } __attribute__((packed)) br2;
+    struct {
+        uint32_t cmd3;
+        binder_uintptr_t arg3;
+        uint32_t cmd4;
+        uint32_t arg4;
+    } __attribute__((packed)) bc3 = {
+            // Send the acknowledgement. Now the kernel should send out
+            // BR_CLEAR_FREEZE_NOTIFICATION_DONE.
+            .cmd3 = BC_FREEZE_NOTIFICATION_DONE,
+            .arg3 = cookie,
+            .cmd4 = BC_DECREFS,
+            .arg4 = 0,
+    };
+    struct {
+        uint32_t cmd2;
+        uint32_t cmd3;
+        binder_uintptr_t arg3;
+        uint32_t pad[16];
+    } __attribute__((packed)) br3;
+
+    struct binder_write_read bwr1 = binder_write_read();
+    bwr1.write_buffer = (uintptr_t)&bc1;
+    bwr1.write_size = sizeof(bc1);
+    bwr1.read_buffer = (uintptr_t)&br1;
+    bwr1.read_size = sizeof(br1);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr1);
+    EXPECT_EQ(sizeof(bc1), bwr1.write_consumed);
+    EXPECT_EQ(sizeof(br1) - sizeof(br1.pad), bwr1.read_consumed);
+    EXPECT_EQ(BR_NOOP, br1.cmd0);
+    ASSERT_EQ(BR_FROZEN_BINDER, br1.cmd1);
+    EXPECT_FALSE(br1.arg1.is_frozen);
+
+    struct binder_write_read bwr2 = binder_write_read();
+    bwr2.write_buffer = (uintptr_t)&bc2;
+    bwr2.write_size = sizeof(bc2);
+    bwr2.read_buffer = (uintptr_t)&br2;
+    bwr2.read_size = sizeof(br2);
+    binderTestIoctlSuccessOrError(BINDER_WRITE_READ, &bwr2, EAGAIN);
+    binderTestReadEmpty();
+
+    struct binder_write_read bwr3 = binder_write_read();
+    bwr3.write_buffer = (uintptr_t)&bc3;
+    bwr3.write_size = sizeof(bc3);
+    bwr3.read_buffer = (uintptr_t)&br3;
+    bwr3.read_size = sizeof(br3);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr3);
+    EXPECT_EQ(sizeof(bc3), bwr3.write_consumed);
+    EXPECT_EQ(sizeof(br3) - sizeof(br3.pad), bwr3.read_consumed);
+    EXPECT_EQ(BR_CLEAR_FREEZE_NOTIFICATION_DONE, br3.cmd3);
+    EXPECT_EQ(cookie, br3.arg3);
+    binderTestReadEmpty();
+}
+
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
 
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 9b1ba01..bcab6de 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -27,6 +27,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/result-gmock.h>
 #include <binder/Binder.h>
@@ -39,6 +40,8 @@
 #include <binder/RpcSession.h>
 #include <binder/Status.h>
 #include <binder/unique_fd.h>
+#include <input/BlockingQueue.h>
+#include <processgroup/processgroup.h>
 #include <utils/Flattenable.h>
 
 #include <linux/sched.h>
@@ -57,6 +60,7 @@
 using android::base::testing::HasValue;
 using android::binder::Status;
 using android::binder::unique_fd;
+using std::chrono_literals::operator""ms;
 using testing::ExplainMatchResult;
 using testing::Matcher;
 using testing::Not;
@@ -115,6 +119,8 @@
     BINDER_LIB_TEST_NOP_TRANSACTION_WAIT,
     BINDER_LIB_TEST_GETPID,
     BINDER_LIB_TEST_GETUID,
+    BINDER_LIB_TEST_LISTEN_FOR_FROZEN_STATE_CHANGE,
+    BINDER_LIB_TEST_CONSUME_STATE_CHANGE_EVENTS,
     BINDER_LIB_TEST_ECHO_VECTOR,
     BINDER_LIB_TEST_GET_NON_BLOCKING_FD,
     BINDER_LIB_TEST_REJECT_OBJECTS,
@@ -247,6 +253,43 @@
         sp<IBinder> m_server;
 };
 
+class TestFrozenStateChangeCallback : public IBinder::FrozenStateChangeCallback {
+public:
+    BlockingQueue<std::pair<const wp<IBinder>, State>> events;
+
+    virtual void onStateChanged(const wp<IBinder>& who, State state) {
+        events.push(std::make_pair(who, state));
+    }
+
+    void ensureFrozenEventReceived() {
+        auto event = events.popWithTimeout(500ms);
+        ASSERT_TRUE(event.has_value());
+        EXPECT_EQ(State::FROZEN, event->second); // isFrozen should be true
+        EXPECT_EQ(0u, events.size());
+    }
+
+    void ensureUnfrozenEventReceived() {
+        auto event = events.popWithTimeout(500ms);
+        ASSERT_TRUE(event.has_value());
+        EXPECT_EQ(State::UNFROZEN, event->second); // isFrozen should be false
+        EXPECT_EQ(0u, events.size());
+    }
+
+    std::vector<bool> getAllAndClear() {
+        std::vector<bool> results;
+        while (true) {
+            auto event = events.popWithTimeout(0ms);
+            if (!event.has_value()) {
+                break;
+            }
+            results.push_back(event->second == State::FROZEN);
+        }
+        return results;
+    }
+
+    sp<IBinder> binder;
+};
+
 class BinderLibTest : public ::testing::Test {
     public:
         virtual void SetUp() {
@@ -291,6 +334,51 @@
             EXPECT_EQ(1, ret);
         }
 
+        bool checkFreezeSupport() {
+            std::ifstream freezer_file("/sys/fs/cgroup/uid_0/cgroup.freeze");
+            // Pass test on devices where the cgroup v2 freezer is not supported
+            if (freezer_file.fail()) {
+                return false;
+            }
+            return IPCThreadState::self()->freeze(getpid(), false, 0) == NO_ERROR;
+        }
+
+        bool checkFreezeAndNotificationSupport() {
+            if (!checkFreezeSupport()) {
+                return false;
+            }
+            return ProcessState::isDriverFeatureEnabled(
+                    ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+        }
+
+        bool getBinderPid(int32_t* pid, sp<IBinder> server) {
+            Parcel data, replypid;
+            if (server->transact(BINDER_LIB_TEST_GETPID, data, &replypid) != NO_ERROR) {
+                ALOGE("BINDER_LIB_TEST_GETPID failed");
+                return false;
+            }
+            *pid = replypid.readInt32();
+            if (*pid <= 0) {
+                ALOGE("pid should be greater than zero");
+                return false;
+            }
+            return true;
+        }
+
+        void freezeProcess(int32_t pid) {
+            EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, true, 1000));
+        }
+
+        void unfreezeProcess(int32_t pid) {
+            EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, false, 0));
+        }
+
+        void removeCallbackAndValidateNoEvent(sp<IBinder> binder,
+                                              sp<TestFrozenStateChangeCallback> callback) {
+            EXPECT_THAT(binder->removeFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+            EXPECT_EQ(0u, callback->events.size());
+        }
+
         sp<IBinder> m_server;
 };
 
@@ -516,29 +604,18 @@
 }
 
 TEST_F(BinderLibTest, Freeze) {
-    Parcel data, reply, replypid;
-    std::ifstream freezer_file("/sys/fs/cgroup/uid_0/cgroup.freeze");
-
-    // Pass test on devices where the cgroup v2 freezer is not supported
-    if (freezer_file.fail()) {
-        GTEST_SKIP();
+    if (!checkFreezeSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support proceess freezing";
         return;
     }
-
+    Parcel data, reply, replypid;
     EXPECT_THAT(m_server->transact(BINDER_LIB_TEST_GETPID, data, &replypid), StatusEq(NO_ERROR));
     int32_t pid = replypid.readInt32();
     for (int i = 0; i < 10; i++) {
         EXPECT_EQ(NO_ERROR, m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION_WAIT, data, &reply, TF_ONE_WAY));
     }
 
-    // Pass test on devices where BINDER_FREEZE ioctl is not supported
-    int ret = IPCThreadState::self()->freeze(pid, false, 0);
-    if (ret == -EINVAL) {
-        GTEST_SKIP();
-        return;
-    }
-    EXPECT_EQ(NO_ERROR, ret);
-
+    EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, false, 0));
     EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0));
 
     // b/268232063 - succeeds ~0.08% of the time
@@ -835,6 +912,199 @@
     EXPECT_THAT(callback->getResult(), StatusEq(NO_ERROR));
 }
 
+TEST_F(BinderLibTest, ReturnErrorIfKernelDoesNotSupportFreezeNotification) {
+    if (ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    ASSERT_EQ(nullptr, binder->localBinder());
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(INVALID_OPERATION));
+}
+
+TEST_F(BinderLibTest, FrozenStateChangeNotificatiion) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback->ensureUnfrozenEventReceived();
+    // Check that the process hasn't died otherwise there's a risk of freezing
+    // the wrong process.
+    EXPECT_EQ(OK, binder->pingBinder());
+    freezeProcess(pid);
+    callback->ensureFrozenEventReceived();
+    unfreezeProcess(pid);
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+}
+
+TEST_F(BinderLibTest, AddFrozenCallbackWhenFrozen) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    // Check that the process hasn't died otherwise there's a risk of freezing
+    // the wrong process.
+    EXPECT_EQ(OK, binder->pingBinder());
+    freezeProcess(pid);
+    // Add the callback while the target process is frozen.
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    callback->ensureFrozenEventReceived();
+    unfreezeProcess(pid);
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+
+    // Check that the process hasn't died otherwise there's a risk of freezing
+    // the wrong process.
+    EXPECT_EQ(OK, binder->pingBinder());
+    freezeProcess(pid);
+    unfreezeProcess(pid);
+    // Make sure no callback happens since the listener has been removed.
+    EXPECT_EQ(0u, callback->events.size());
+}
+
+TEST_F(BinderLibTest, NoFrozenNotificationAfterCallbackRemoval) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+
+    // Make sure no callback happens after the listener is removed.
+    freezeProcess(pid);
+    unfreezeProcess(pid);
+    EXPECT_EQ(0u, callback->events.size());
+}
+
+TEST_F(BinderLibTest, MultipleFrozenStateChangeCallbacks) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback1 = sp<TestFrozenStateChangeCallback>::make();
+    sp<TestFrozenStateChangeCallback> callback2 = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback1), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback1->ensureUnfrozenEventReceived();
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback2), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback2->ensureUnfrozenEventReceived();
+
+    freezeProcess(pid);
+    callback1->ensureFrozenEventReceived();
+    callback2->ensureFrozenEventReceived();
+
+    removeCallbackAndValidateNoEvent(binder, callback1);
+    unfreezeProcess(pid);
+    EXPECT_EQ(0u, callback1->events.size());
+    callback2->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback2);
+
+    freezeProcess(pid);
+    EXPECT_EQ(0u, callback2->events.size());
+}
+
+TEST_F(BinderLibTest, RemoveThenAddFrozenStateChangeCallbacks) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    callback->ensureUnfrozenEventReceived();
+}
+
+TEST_F(BinderLibTest, CoalesceFreezeCallbacksWhenListenerIsFrozen) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<IBinder> binder = addServer();
+    sp<IBinder> listener = addServer();
+    ASSERT_NE(nullptr, binder);
+    ASSERT_NE(nullptr, listener);
+    int32_t pid, listenerPid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+    ASSERT_TRUE(getBinderPid(&listenerPid, listener));
+
+    // Ask the listener process to register for state change callbacks.
+    {
+        Parcel data, reply;
+        data.writeStrongBinder(binder);
+        ASSERT_THAT(listener->transact(BINDER_LIB_TEST_LISTEN_FOR_FROZEN_STATE_CHANGE, data,
+                                       &reply),
+                    StatusEq(NO_ERROR));
+    }
+    // Freeze the listener process.
+    freezeProcess(listenerPid);
+    createProcessGroup(getuid(), listenerPid);
+    ASSERT_TRUE(SetProcessProfiles(getuid(), listenerPid, {"Frozen"}));
+    // Repeatedly flip the target process between frozen and unfrozen states.
+    for (int i = 0; i < 1000; i++) {
+        usleep(50);
+        unfreezeProcess(pid);
+        usleep(50);
+        freezeProcess(pid);
+    }
+    // Unfreeze the listener process. Now it should receive the frozen state
+    // change notifications.
+    ASSERT_TRUE(SetProcessProfiles(getuid(), listenerPid, {"Unfrozen"}));
+    unfreezeProcess(listenerPid);
+    // Wait for 500ms to give the process enough time to wake up and handle
+    // notifications.
+    usleep(500 * 1000);
+    {
+        std::vector<bool> events;
+        Parcel data, reply;
+        ASSERT_THAT(listener->transact(BINDER_LIB_TEST_CONSUME_STATE_CHANGE_EVENTS, data, &reply),
+                    StatusEq(NO_ERROR));
+        reply.readBoolVector(&events);
+        // There should only be one single state change notifications delievered.
+        ASSERT_EQ(1u, events.size());
+        EXPECT_TRUE(events[0]);
+    }
+}
+
 TEST_F(BinderLibTest, PassFile) {
     int ret;
     int pipefd[2];
@@ -1981,6 +2251,26 @@
                 reply->writeInt32(param.sched_priority);
                 return NO_ERROR;
             }
+            case BINDER_LIB_TEST_LISTEN_FOR_FROZEN_STATE_CHANGE: {
+                sp<IBinder> binder = data.readStrongBinder();
+                frozenStateChangeCallback = sp<TestFrozenStateChangeCallback>::make();
+                // Hold an strong pointer to the binder object so it doesn't go
+                // away.
+                frozenStateChangeCallback->binder = binder;
+                int ret = binder->addFrozenStateChangeCallback(frozenStateChangeCallback);
+                if (ret != NO_ERROR) {
+                    return ret;
+                }
+                auto event = frozenStateChangeCallback->events.popWithTimeout(10ms);
+                if (!event.has_value()) {
+                    return NOT_ENOUGH_DATA;
+                }
+                return NO_ERROR;
+            }
+            case BINDER_LIB_TEST_CONSUME_STATE_CHANGE_EVENTS: {
+                reply->writeBoolVector(frozenStateChangeCallback->getAllAndClear());
+                return NO_ERROR;
+            }
             case BINDER_LIB_TEST_ECHO_VECTOR: {
                 std::vector<uint64_t> vector;
                 auto err = data.readUint64Vector(&vector);
@@ -2067,6 +2357,7 @@
     sp<IBinder> m_callback;
     bool m_exitOnDestroy;
     std::mutex m_blockMutex;
+    sp<TestFrozenStateChangeCallback> frozenStateChangeCallback;
 };
 
 int run_server(int index, int readypipefd, bool usePoll)
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index f480780..2cec243 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -323,6 +323,22 @@
 
 // END TESTS FOR LIMITATIONS OF SOCKET BINDER
 
+class TestFrozenStateChangeCallback : public IBinder::FrozenStateChangeCallback {
+public:
+    virtual void onStateChanged(const wp<IBinder>&, State) {}
+};
+
+TEST_P(BinderRpc, RpcBinderShouldFailOnFrozenStateCallbacks) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> a;
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
+    EXPECT_DEATH_IF_SUPPORTED(
+            { std::ignore = a->addFrozenStateChangeCallback(callback); },
+            "addFrozenStateChangeCallback\\(\\) is not supported for RPC Binder.");
+}
+
 TEST_P(BinderRpc, RepeatRootObject) {
     auto proc = createRpcTestSocketServerProcess({});
 
diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
index 23f583b..7f45581 100644
--- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
+++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
@@ -125,6 +125,11 @@
         VULKAN_DEVICE_EXTENSION = 9,
     };
 
+    enum GLTelemetryHints {
+        NO_HINT = 0,
+        SKIP_TELEMETRY = 1,
+    };
+
     GpuStatsInfo() = default;
     GpuStatsInfo(const GpuStatsInfo&) = default;
     virtual ~GpuStatsInfo() = default;
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 8fbf5c6..e4e81ad 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -232,6 +232,7 @@
         "MotionPredictorMetricsManager.cpp",
         "PrintTools.cpp",
         "PropertyMap.cpp",
+        "Resampler.cpp",
         "TfLiteMotionPredictor.cpp",
         "TouchVideoFrame.cpp",
         "VelocityControl.cpp",
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index c145d5c..99ffa68 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -17,6 +17,8 @@
 #define LOG_TAG "InputTransport"
 #define ATRACE_TAG ATRACE_TAG_INPUT
 
+#include <chrono>
+
 #include <inttypes.h>
 
 #include <android-base/logging.h>
@@ -168,6 +170,10 @@
     return msg;
 }
 
+bool isPointerEvent(const MotionEvent& motionEvent) {
+    return (motionEvent.getSource() & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
+}
+
 } // namespace
 
 using android::base::Result;
@@ -177,8 +183,13 @@
 
 InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
                                                      sp<Looper> looper,
-                                                     InputConsumerCallbacks& callbacks)
-      : mChannel(channel), mLooper(looper), mCallbacks(callbacks), mFdEvents(0) {
+                                                     InputConsumerCallbacks& callbacks,
+                                                     std::unique_ptr<Resampler> resampler)
+      : mChannel(channel),
+        mLooper(looper),
+        mCallbacks(callbacks),
+        mResampler(std::move(resampler)),
+        mFdEvents(0) {
     LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
     mCallback = sp<LooperEventCallback>::make(
             std::bind(&InputConsumerNoResampling::handleReceiveCallback, this,
@@ -463,6 +474,15 @@
         }
         messages.pop();
     }
+    // Check if resampling should be performed.
+    if (motionEvent != nullptr && isPointerEvent(*motionEvent) && mResampler != nullptr) {
+        InputMessage* futureSample = nullptr;
+        if (!messages.empty()) {
+            futureSample = &messages.front();
+        }
+        mResampler->resampleMotionEvent(static_cast<std::chrono::nanoseconds>(frameTime),
+                                        *motionEvent, futureSample);
+    }
     return std::make_pair(std::move(motionEvent), firstSeqForBatch);
 }
 
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
new file mode 100644
index 0000000..af8354c
--- /dev/null
+++ b/libs/input/Resampler.cpp
@@ -0,0 +1,151 @@
+/**
+ * Copyright 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.
+ */
+
+#define LOG_TAG "LegacyResampler"
+
+#include <algorithm>
+#include <chrono>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+
+#include <input/Resampler.h>
+#include <utils/Timers.h>
+
+using std::chrono::nanoseconds;
+
+namespace android {
+
+namespace {
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+        android::base::GetBoolProperty("ro.debuggable", false);
+#else
+        true;
+#endif
+
+bool debugResampling() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_TRANSPORT_RESAMPLING =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+                                          ANDROID_LOG_INFO);
+        return DEBUG_TRANSPORT_RESAMPLING;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
+
+constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5};
+
+constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2};
+
+constexpr std::chrono::milliseconds RESAMPLE_MAX_DELTA{20};
+
+constexpr std::chrono::milliseconds RESAMPLE_MAX_PREDICTION{8};
+
+inline float lerp(float a, float b, float alpha) {
+    return a + alpha * (b - a);
+}
+
+const PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoords& b,
+                                             const float alpha) {
+    // Ensure the struct PointerCoords is initialized.
+    PointerCoords resampledCoords{};
+    resampledCoords.isResampled = true;
+    resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, lerp(a.getX(), b.getX(), alpha));
+    resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha));
+    return resampledCoords;
+}
+} // namespace
+
+void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
+    const size_t motionEventSampleSize = motionEvent.getHistorySize() + 1;
+    for (size_t i = 0; i < motionEventSampleSize; ++i) {
+        Sample sample{static_cast<nanoseconds>(motionEvent.getHistoricalEventTime(i)),
+                      *motionEvent.getPointerProperties(0),
+                      motionEvent.getSamplePointerCoords()[i]};
+        mLatestSamples.pushBack(sample);
+    }
+}
+
+void LegacyResampler::interpolate(const nanoseconds resampleTime, MotionEvent& motionEvent,
+                                  const InputMessage& futureSample) const {
+    const Sample pastSample = mLatestSamples.back();
+    const nanoseconds delta =
+            static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
+    if (delta < RESAMPLE_MIN_DELTA) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+        return;
+    }
+    const float alpha =
+            std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
+
+    const PointerCoords resampledCoords =
+            calculateResampledCoords(pastSample.pointer.coords,
+                                     futureSample.body.motion.pointers[0].coords, alpha);
+    motionEvent.addSample(resampleTime.count(), &resampledCoords, motionEvent.getId());
+}
+
+void LegacyResampler::extrapolate(const nanoseconds resampleTime, MotionEvent& motionEvent) const {
+    if (mLatestSamples.size() < 2) {
+        return;
+    }
+    const Sample pastSample = *(mLatestSamples.end() - 2);
+    const Sample presentSample = *(mLatestSamples.end() - 1);
+    const nanoseconds delta =
+            static_cast<nanoseconds>(presentSample.eventTime - pastSample.eventTime);
+    if (delta < RESAMPLE_MIN_DELTA) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+        return;
+    } else if (delta > RESAMPLE_MAX_DELTA) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns.";
+        return;
+    }
+    // The farthest future time to which we can extrapolate. If the given resampleTime exceeds this,
+    // we use this value as the resample time target.
+    const nanoseconds farthestPrediction = static_cast<nanoseconds>(presentSample.eventTime) +
+            std::min<nanoseconds>(delta / 2, RESAMPLE_MAX_PREDICTION);
+    const nanoseconds newResampleTime =
+            (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
+    LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
+            << "Resample time is too far in the future. Adjusting prediction from "
+            << (resampleTime - presentSample.eventTime) << " to "
+            << (farthestPrediction - presentSample.eventTime) << "ns.";
+    const float alpha =
+            std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
+            delta;
+
+    const PointerCoords resampledCoords =
+            calculateResampledCoords(pastSample.pointer.coords, presentSample.pointer.coords,
+                                     alpha);
+    motionEvent.addSample(newResampleTime.count(), &resampledCoords, motionEvent.getId());
+}
+
+void LegacyResampler::resampleMotionEvent(const nanoseconds resampleTime, MotionEvent& motionEvent,
+                                          const InputMessage* futureSample) {
+    if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
+        mLatestSamples.clear();
+    }
+    mPreviousDeviceId = motionEvent.getDeviceId();
+    updateLatestSamples(motionEvent);
+    if (futureSample) {
+        interpolate(resampleTime, motionEvent, *futureSample);
+    } else {
+        extrapolate(resampleTime, motionEvent);
+    }
+    LOG_IF(INFO, debugResampling()) << "Not resampled. Not enough data.";
+}
+} // namespace android
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index e9d799e..132866b 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -23,6 +23,7 @@
         "InputVerifier_test.cpp",
         "MotionPredictor_test.cpp",
         "MotionPredictorMetricsManager_test.cpp",
+        "Resampler_test.cpp",
         "RingBuffer_test.cpp",
         "TfLiteMotionPredictor_test.cpp",
         "TouchResampling_test.cpp",
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index e710613..467c3b4 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -396,8 +396,9 @@
             break;
         }
         case LooperMessage::CREATE_CONSUMER: {
-            mConsumer = std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel),
-                                                                    mLooper, *this);
+            mConsumer =
+                    std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel), mLooper,
+                                                                *this, /*resampler=*/nullptr);
             break;
         }
         case LooperMessage::DESTROY_CONSUMER: {
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
new file mode 100644
index 0000000..e160ca0
--- /dev/null
+++ b/libs/input/tests/Resampler_test.cpp
@@ -0,0 +1,417 @@
+/**
+ * Copyright 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.
+ */
+
+#include <input/Resampler.h>
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <memory>
+#include <vector>
+
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <input/InputTransport.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+namespace {
+
+using namespace std::literals::chrono_literals;
+
+constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
+struct Pointer {
+    int32_t id{0};
+    ToolType toolType{ToolType::FINGER};
+    float x{0.0f};
+    float y{0.0f};
+    bool isResampled{false};
+    /**
+     * Converts from Pointer to PointerCoords. Enables calling LegacyResampler methods and
+     * assertions only with the relevant data for tests.
+     */
+    operator PointerCoords() const;
+};
+
+Pointer::operator PointerCoords() const {
+    PointerCoords pointerCoords;
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+    pointerCoords.isResampled = isResampled;
+    return pointerCoords;
+}
+
+struct InputSample {
+    std::chrono::milliseconds eventTime{0};
+    std::vector<Pointer> pointers{};
+    /**
+     * Converts from InputSample to InputMessage. Enables calling LegacyResampler methods only with
+     * the relevant data for tests.
+     */
+    operator InputMessage() const;
+};
+
+InputSample::operator InputMessage() const {
+    InputMessage message;
+    message.header.type = InputMessage::Type::MOTION;
+    message.body.motion.pointerCount = pointers.size();
+    message.body.motion.eventTime = static_cast<std::chrono::nanoseconds>(eventTime).count();
+    message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
+    message.body.motion.downTime = 0;
+    const uint32_t pointerCount = message.body.motion.pointerCount;
+    for (uint32_t i = 0; i < pointerCount; ++i) {
+        message.body.motion.pointers[i].properties.id = pointers[i].id;
+        message.body.motion.pointers[i].properties.toolType = pointers[i].toolType;
+        message.body.motion.pointers[i].coords.setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        message.body.motion.pointers[i].coords.setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
+        message.body.motion.pointers[i].coords.isResampled = pointers[i].isResampled;
+    }
+    return message;
+}
+
+struct InputStream {
+    std::vector<InputSample> samples{};
+    int32_t action{0};
+    DeviceId deviceId{0};
+    /**
+     * Converts from InputStream to MotionEvent. Enables calling LegacyResampler methods only with
+     * the relevant data for tests.
+     */
+    operator MotionEvent() const;
+};
+
+InputStream::operator MotionEvent() const {
+    const InputSample& firstSample{*samples.begin()};
+    MotionEventBuilder motionEventBuilder =
+            MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
+                    .downTime(0)
+                    .eventTime(static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count())
+                    .deviceId(deviceId);
+    for (const Pointer& pointer : firstSample.pointers) {
+        const PointerBuilder pointerBuilder =
+                PointerBuilder(pointer.id, pointer.toolType).x(pointer.x).y(pointer.y);
+        motionEventBuilder.pointer(pointerBuilder);
+    }
+    MotionEvent motionEvent = motionEventBuilder.build();
+    const size_t numSamples = samples.size();
+    for (size_t i = 1; i < numSamples; ++i) {
+        std::vector<PointerCoords> pointersCoords{samples[i].pointers.begin(),
+                                                  samples[i].pointers.end()};
+        motionEvent.addSample(static_cast<std::chrono::nanoseconds>(samples[i].eventTime).count(),
+                              pointersCoords.data(), motionEvent.getId());
+    }
+    return motionEvent;
+}
+
+} // namespace
+
+class ResamplerTest : public testing::Test {
+protected:
+    ResamplerTest() : mResampler(std::make_unique<LegacyResampler>()) {}
+
+    ~ResamplerTest() override {}
+
+    void SetUp() override {}
+
+    void TearDown() override {}
+
+    std::unique_ptr<Resampler> mResampler;
+
+    MotionEvent buildMotionEvent(const int32_t action, const nsecs_t eventTime,
+                                 const std::vector<PointerBuilder>& pointers);
+
+    InputMessage createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
+                               const int32_t action,
+                               const std::vector<PointerProperties>& properties,
+                               const std::vector<PointerCoords>& coords);
+
+    /**
+     * Checks that beforeCall and afterCall are equal except for the mutated attributes by addSample
+     * member function.
+     * @param beforeCall MotionEvent before passing it to resampleMotionEvent
+     * @param afterCall MotionEvent after passing it to resampleMotionEvent
+     */
+    void assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
+                                               const MotionEvent& afterCall);
+
+    /**
+     * Asserts the MotionEvent is resampled by checking an increment in history size and that the
+     * resampled coordinates are near the expected ones.
+     */
+    void assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
+                                                   const MotionEvent& resampled,
+                                                   const PointerCoords& expectedCoords);
+
+    void assertMotionEventIsNotResampled(const MotionEvent& original,
+                                         const MotionEvent& notResampled);
+};
+
+MotionEvent ResamplerTest::buildMotionEvent(const int32_t action, const nsecs_t eventTime,
+                                            const std::vector<PointerBuilder>& pointerBuilders) {
+    MotionEventBuilder motionEventBuilder = MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
+                                                    .downTime(0)
+                                                    .eventTime(eventTime);
+    for (const PointerBuilder& pointerBuilder : pointerBuilders) {
+        motionEventBuilder.pointer(pointerBuilder);
+    }
+    return motionEventBuilder.build();
+}
+
+InputMessage ResamplerTest::createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
+                                          const int32_t action,
+                                          const std::vector<PointerProperties>& properties,
+                                          const std::vector<PointerCoords>& coords) {
+    InputMessage message;
+    message.header.type = InputMessage::Type::MOTION;
+    message.body.motion.pointerCount = pointerCount;
+    message.body.motion.eventTime = eventTime;
+    message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
+    message.body.motion.downTime = 0;
+    for (uint32_t i = 0; i < pointerCount; ++i) {
+        message.body.motion.pointers[i].properties = properties[i];
+        message.body.motion.pointers[i].coords = coords[i];
+    }
+    return message;
+}
+
+void ResamplerTest::assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
+                                                          const MotionEvent& afterCall) {
+    EXPECT_EQ(beforeCall.getDeviceId(), afterCall.getDeviceId());
+    EXPECT_EQ(beforeCall.getAction(), afterCall.getAction());
+    EXPECT_EQ(beforeCall.getActionButton(), afterCall.getActionButton());
+    EXPECT_EQ(beforeCall.getButtonState(), afterCall.getButtonState());
+    EXPECT_EQ(beforeCall.getFlags(), afterCall.getFlags());
+    EXPECT_EQ(beforeCall.getEdgeFlags(), afterCall.getEdgeFlags());
+    EXPECT_EQ(beforeCall.getClassification(), afterCall.getClassification());
+    EXPECT_EQ(beforeCall.getPointerCount(), afterCall.getPointerCount());
+    EXPECT_EQ(beforeCall.getMetaState(), afterCall.getMetaState());
+    EXPECT_EQ(beforeCall.getSource(), afterCall.getSource());
+    EXPECT_EQ(beforeCall.getXPrecision(), afterCall.getXPrecision());
+    EXPECT_EQ(beforeCall.getYPrecision(), afterCall.getYPrecision());
+    EXPECT_EQ(beforeCall.getDownTime(), afterCall.getDownTime());
+    EXPECT_EQ(beforeCall.getDisplayId(), afterCall.getDisplayId());
+}
+
+void ResamplerTest::assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
+                                                              const MotionEvent& resampled,
+                                                              const PointerCoords& expectedCoords) {
+    assertMotionEventMetaDataDidNotMutate(original, resampled);
+    const size_t originalSampleSize = original.getHistorySize() + 1;
+    const size_t resampledSampleSize = resampled.getHistorySize() + 1;
+    EXPECT_EQ(originalSampleSize + 1, resampledSampleSize);
+    const PointerCoords& resampledCoords =
+            resampled.getSamplePointerCoords()[resampled.getHistorySize()];
+    EXPECT_TRUE(resampledCoords.isResampled);
+    EXPECT_NEAR(expectedCoords.getX(), resampledCoords.getX(), EPSILON);
+    EXPECT_NEAR(expectedCoords.getY(), resampledCoords.getY(), EPSILON);
+}
+
+void ResamplerTest::assertMotionEventIsNotResampled(const MotionEvent& original,
+                                                    const MotionEvent& notResampled) {
+    assertMotionEventMetaDataDidNotMutate(original, notResampled);
+    const size_t originalSampleSize = original.getHistorySize() + 1;
+    const size_t notResampledSampleSize = notResampled.getHistorySize() + 1;
+    EXPECT_EQ(originalSampleSize, notResampledSampleSize);
+}
+
+TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) {
+    MotionEvent motionEvent =
+            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE,
+                        .deviceId = 0};
+    const MotionEvent originalMotionEvent = motionEvent;
+    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
+    MotionEvent motionFromFirstDevice =
+            InputStream{{{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         {8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE,
+                        .deviceId = 0};
+    mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
+    MotionEvent motionFromSecondDevice =
+            InputStream{{{11ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE,
+                        .deviceId = 1};
+    const MotionEvent originalMotionEvent = motionFromSecondDevice;
+    mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
+    // The MotionEvent should not be resampled because the second event came from a different device
+    // than the previous event.
+    assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
+}
+
+// Increments of 16 ms for display refresh rate
+// Increments of 6 ms for input frequency
+// Resampling latency is known to be 5 ms
+// Therefore, first resampling time will be 11 ms
+
+/**
+ * Timeline
+ * ----+----------------------+---------+---------+---------+----------
+ *     0ms                   10ms      11ms      15ms      16ms
+ *    DOWN                   MOVE       |        MSG        |
+ *                                  resample              frame
+ * Resampling occurs at 11ms. It is possible to interpolate because there is a sample available
+ * after the resample time. It is assumed that the InputMessage frequency is 100Hz, and the frame
+ * frequency is 60Hz. This means the time between InputMessage samples is 10ms, and the time between
+ * frames is ~16ms. Resample time is frameTime - RESAMPLE_LATENCY. The resampled sample must be the
+ * last one in the batch to consume.
+ */
+TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              Pointer{.id = 0,
+                                                      .x = 1.2f,
+                                                      .y = 1.2f,
+                                                      .isResampled = true});
+}
+
+TEST_F(ResamplerTest, SinglePointerDeltaTooSmallInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(10'500'000ns, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+/**
+ * Tests extrapolation given two MotionEvents with a single sample.
+ */
+TEST_F(ResamplerTest, SinglePointerSingleSampleExtrapolation) {
+    MotionEvent previousMotionEvent =
+            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(10ms, previousMotionEvent, nullptr);
+
+    MotionEvent motionEvent =
+            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              Pointer{.id = 0,
+                                                      .x = 1.0f,
+                                                      .y = 1.0f,
+                                                      .isResampled = true});
+    // Integrity of the whole motionEvent
+    // History size should increment by 1
+    // Check if the resampled value is the last one
+    // Check if the resampleTime is correct
+    // Check if the PointerCoords are consistent with the other computations
+}
+
+TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              Pointer{.id = 0,
+                                                      .x = 2.2f,
+                                                      .y = 2.2f,
+                                                      .isResampled = true});
+}
+
+TEST_F(ResamplerTest, SinglePointerMultipleSampleExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              Pointer{.id = 0,
+                                                      .x = 2.2f,
+                                                      .y = 2.2f,
+                                                      .isResampled = true});
+}
+
+TEST_F(ResamplerTest, SinglePointerDeltaTooSmallExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{{9ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, SinglePointerDeltaTooLargeExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         {26ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(27ms, motionEvent, nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, SinglePointerResampleTimeTooFarExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         {25ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(43ms, motionEvent, nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              Pointer{.id = 0,
+                                                      .x = 2.4f,
+                                                      .y = 2.4f,
+                                                      .isResampled = true});
+}
+} // namespace android
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index ec7b190..bf0e38e 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -270,7 +270,7 @@
         hnd = attempt_to_load_updated_driver(cnx);
 
         // If updated driver apk is set but fail to load, abort here.
-        LOG_ALWAYS_FATAL_IF(android::GraphicsEnv::getInstance().getDriverNamespace(),
+        LOG_ALWAYS_FATAL_IF(android::GraphicsEnv::getInstance().getDriverNamespace() && !hnd,
                             "couldn't find an OpenGL ES implementation from %s",
                             android::GraphicsEnv::getInstance().getDriverPath().c_str());
     }
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 37db05c..6e35041 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -916,42 +916,72 @@
             egl_context_t* const c = get_context(share_list);
             share_list = c->context;
         }
+
+        bool skip_telemetry = false;
+
+        auto findAttribute = [](const EGLint* attrib_ptr, GLint attribute, GLint* value) {
+            while (attrib_ptr && *attrib_ptr != EGL_NONE) {
+                GLint attr = *attrib_ptr++;
+                GLint val = *attrib_ptr++;
+                if (attr == attribute) {
+                    if (value) {
+                        *value = val;
+                    }
+                    return true;
+                }
+            }
+            return false;
+        };
+
+        std::vector<EGLint> replacement_attrib_list;
+        GLint telemetry_value;
+        if (findAttribute(attrib_list, EGL_TELEMETRY_HINT_ANDROID, &telemetry_value)) {
+            skip_telemetry = (telemetry_value == android::GpuStatsInfo::SKIP_TELEMETRY);
+
+            // We need to remove EGL_TELEMETRY_HINT_ANDROID or the underlying drivers will
+            // complain about an unexpected attribute
+            const EGLint* attrib_ptr = attrib_list;
+            while (attrib_ptr && *attrib_ptr != EGL_NONE) {
+                GLint attr = *attrib_ptr++;
+                GLint val = *attrib_ptr++;
+                if (attr != EGL_TELEMETRY_HINT_ANDROID) {
+                    replacement_attrib_list.push_back(attr);
+                    replacement_attrib_list.push_back(val);
+                }
+            }
+            replacement_attrib_list.push_back(EGL_NONE);
+            attrib_list = replacement_attrib_list.data();
+        }
         // b/111083885 - If we are presenting EGL 1.4 interface to apps
         // error out on robust access attributes that are invalid
         // in EGL 1.4 as the driver may be fine with them but dEQP expects
         // tests to fail according to spec.
         if (attrib_list && (cnx->driverVersion < EGL_MAKE_VERSION(1, 5, 0))) {
-            const EGLint* attrib_ptr = attrib_list;
-            while (*attrib_ptr != EGL_NONE) {
-                GLint attr = *attrib_ptr++;
-                GLint value = *attrib_ptr++;
-                if (attr == EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR) {
-                    // We are GL ES context with EGL 1.4, this is an invalid
-                    // attribute
-                    return setError(EGL_BAD_ATTRIBUTE, EGL_NO_CONTEXT);
-                }
-            };
+            if (findAttribute(attrib_list, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR,
+                              nullptr)) {
+                // We are GL ES context with EGL 1.4, this is an invalid attribute
+                return setError(EGL_BAD_ATTRIBUTE, EGL_NO_CONTEXT);
+            }
         }
         EGLContext context =
                 cnx->egl.eglCreateContext(dp->disp.dpy, config, share_list, attrib_list);
         if (context != EGL_NO_CONTEXT) {
             // figure out if it's a GLESv1 or GLESv2
             int version = egl_connection_t::GLESv1_INDEX;
-            if (attrib_list) {
-                while (*attrib_list != EGL_NONE) {
-                    GLint attr = *attrib_list++;
-                    GLint value = *attrib_list++;
-                    if (attr == EGL_CONTEXT_CLIENT_VERSION && (value == 2 || value == 3)) {
-                        version = egl_connection_t::GLESv2_INDEX;
-                    }
-                };
+            GLint version_value;
+            if (findAttribute(attrib_list, EGL_CONTEXT_CLIENT_VERSION, &version_value)) {
+                if (version_value == 2 || version_value == 3) {
+                    version = egl_connection_t::GLESv2_INDEX;
+                }
             }
             if (version == egl_connection_t::GLESv1_INDEX) {
                 android::GraphicsEnv::getInstance().setTargetStats(
                         android::GpuStatsInfo::Stats::GLES_1_IN_USE);
             }
-            android::GraphicsEnv::getInstance().setTargetStats(
-                    android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT);
+            if (!skip_telemetry) {
+                android::GraphicsEnv::getInstance().setTargetStats(
+                        android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT);
+            }
             egl_context_t* c = new egl_context_t(dpy, context, config, cnx, version);
             return c;
         }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index faafa50..fcea0f3 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2683,7 +2683,7 @@
 
                 // Check if the wallpaper window should deliver the corresponding event.
                 slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
-                                   tempTouchState, entry.deviceId, pointer, targets);
+                                   tempTouchState, entry, targets);
                 tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointer.id,
                                                                oldTouchedWindowHandle);
             }
@@ -7100,9 +7100,11 @@
 void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
-                                         TouchState& state, DeviceId deviceId,
-                                         const PointerProperties& pointerProperties,
+                                         TouchState& state, const MotionEntry& entry,
                                          std::vector<InputTarget>& targets) const {
+    LOG_IF(FATAL, entry.getPointerCount() != 1) << "Entry not eligible for slip: " << entry;
+    const DeviceId deviceId = entry.deviceId;
+    const PointerProperties& pointerProperties = entry.pointerProperties[0];
     std::vector<PointerProperties> pointers{pointerProperties};
     const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -7129,7 +7131,7 @@
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER,
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
                                         InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
-                                deviceId, pointers);
+                                deviceId, pointers, entry.eventTime);
     }
 }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 698bdba..87dfd1d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -709,11 +709,23 @@
 
     sp<InputReporterInterface> mReporter;
 
+    /**
+     * Slip the wallpaper touch if necessary.
+     *
+     * @param targetFlags the target flags
+     * @param oldWindowHandle the old window that the touch slipped out of
+     * @param newWindowHandle the new window that the touch is slipping into
+     * @param state the current touch state. This will be updated if necessary to reflect the new
+     *        windows that are receiving touch.
+     * @param deviceId the device id of the current motion being processed
+     * @param pointerProperties the pointer properties of the current motion being processed
+     * @param targets the current targets to add the walpaper ones to
+     * @param eventTime the new downTime for the wallpaper target
+     */
     void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                             const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
                             const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                            TouchState& state, DeviceId deviceId,
-                            const PointerProperties& pointerProperties,
+                            TouchState& state, const MotionEntry& entry,
                             std::vector<InputTarget>& targets) const REQUIRES(mLock);
     void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 9d4bb3d..5a70dd5 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -47,7 +47,7 @@
             const sp<android::gui::WindowInfoHandle>& windowHandle,
             InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
             DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers,
-            std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
+            std::optional<nsecs_t> firstDownTimeInTarget);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                     DeviceId deviceId, const PointerProperties& pointer);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index d418a9b..c2f174f 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -5711,6 +5711,273 @@
 }
 
 /**
+ * Three windows:
+ * 1) A window on the left, with flag dup_to_wallpaper
+ * 2) A window on the right, with flag slippery
+ * 3) A wallpaper window  under the left window
+ * When touch slips from right window to left, the wallpaper should receive a similar slippery
+ * enter event. Later on, when another device becomes active, the wallpaper should receive
+ * consistent streams from the new device, and also from the old device.
+ * This test attempts to reproduce a crash in the dispatcher where the wallpaper target's downTime
+ * was not getting set during slippery entrance.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlipperyAndMultiWindowMultiTouch) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application1 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application2 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application3 = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> wallpaper =
+            sp<FakeWindowHandle>::make(application1, mDispatcher, "wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
+    wallpaper->setIsWallpaper(true);
+    wallpaper->setPreventSplitting(true);
+    wallpaper->setTouchable(false);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application2, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setTouchableRegion(Region{{0, 0, 100, 100}});
+    leftWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application3, mDispatcher, "Right",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setTouchableRegion(Region{{100, 0, 200, 100}});
+    rightWindow->setSlippery(true);
+    rightWindow->setWatchOutsideTouch(true);
+    rightWindow->setTrustedOverlay(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*rightWindow->getInfo(), *leftWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 3;
+    const DeviceId deviceB = 9;
+
+    // First finger from device A into right window
+    NotifyMotionArgs deviceADownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                    .deviceId(deviceA)
+                    .build();
+
+    mDispatcher->notifyMotion(deviceADownArgs);
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Move the finger of device A from right window into left window. It should slip.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(80).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    wallpaper->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Finger from device B down into left window
+    NotifyMotionArgs deviceBDownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40))
+                    .deviceId(deviceB)
+                    .build();
+    mDispatcher->notifyMotion(deviceBDownArgs);
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_OUTSIDE)));
+
+    // Move finger from device B, still keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+
+    // Lift the finger from device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+
+    // Move the finger of device A, keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_MOVE)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_MOVE)));
+
+    // Second finger down from device A, into the right window. It should be split into:
+    // MOVE for the left window (due to existing implementation) + a DOWN into the right window
+    // Wallpaper will not receive this new pointer, and it will only get the MOVE event.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    auto firstFingerMoveFromDeviceA = AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_MOVE),
+                                            WithPointerCount(1), WithPointerId(0, 0));
+    leftWindow->consumeMotionEvent(firstFingerMoveFromDeviceA);
+    wallpaper->consumeMotionEvent(firstFingerMoveFromDeviceA);
+    rightWindow->consumeMotionEvent(
+            AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_DOWN), WithPointerId(0, 1)));
+
+    // Lift up the second finger.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+    leftWindow->consumeMotionEvent(firstFingerMoveFromDeviceA);
+    wallpaper->consumeMotionEvent(firstFingerMoveFromDeviceA);
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * Same test as above, but with enable_multi_device_same_window_stream flag set to false.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlipperyAndMultiWindowMultiTouch_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+    std::shared_ptr<FakeApplicationHandle> application1 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application2 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application3 = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> wallpaper =
+            sp<FakeWindowHandle>::make(application1, mDispatcher, "wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
+    wallpaper->setIsWallpaper(true);
+    wallpaper->setPreventSplitting(true);
+    wallpaper->setTouchable(false);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application2, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setTouchableRegion(Region{{0, 0, 100, 100}});
+    leftWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application3, mDispatcher, "Right",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setTouchableRegion(Region{{100, 0, 200, 100}});
+    rightWindow->setSlippery(true);
+    rightWindow->setWatchOutsideTouch(true);
+    rightWindow->setTrustedOverlay(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*rightWindow->getInfo(), *leftWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 3;
+    const DeviceId deviceB = 9;
+
+    // First finger from device A into right window
+    NotifyMotionArgs deviceADownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                    .deviceId(deviceA)
+                    .build();
+
+    mDispatcher->notifyMotion(deviceADownArgs);
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Move the finger of device A from right window into left window. It should slip.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(80).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    wallpaper->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Finger from device B down into left window
+    NotifyMotionArgs deviceBDownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40))
+                    .deviceId(deviceB)
+                    .build();
+    mDispatcher->notifyMotion(deviceBDownArgs);
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_CANCEL)));
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_CANCEL)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_OUTSIDE)));
+
+    // Move finger from device B, still keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+
+    // Lift the finger from device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+
+    // Move the finger of device A, keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    // This device was already canceled, so MOVE events will not be arriving to the windows from it.
+
+    // Second finger down from device A, into the right window. It should be split into:
+    // MOVE for the left window (due to existing implementation) + a DOWN into the right window
+    // Wallpaper will not receive this new pointer, and it will only get the MOVE event.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_DOWN), WithPointerId(0, 1)));
+
+    // Lift up the second finger.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Two windows: left and right. The left window has PREVENT_SPLITTING input config. Device A sends a
  * down event to the right window. Device B sends a down event to the left window, and then a
  * POINTER_DOWN event to the right window. However, since the left window prevents splitting, the
diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp
index 555b80a..33724a9 100644
--- a/services/sensorservice/SensorDirectConnection.cpp
+++ b/services/sensorservice/SensorDirectConnection.cpp
@@ -26,12 +26,17 @@
 
 using util::ProtoOutputStream;
 
-SensorService::SensorDirectConnection::SensorDirectConnection(const sp<SensorService>& service,
-        uid_t uid, const sensors_direct_mem_t *mem, int32_t halChannelHandle,
-        const String16& opPackageName, int deviceId)
-        : mService(service), mUid(uid), mMem(*mem),
+SensorService::SensorDirectConnection::SensorDirectConnection(
+        const sp<SensorService>& service, uid_t uid, pid_t pid, const sensors_direct_mem_t* mem,
+        int32_t halChannelHandle, const String16& opPackageName, int deviceId)
+      : mService(service),
+        mUid(uid),
+        mPid(pid),
+        mMem(*mem),
         mHalChannelHandle(halChannelHandle),
-        mOpPackageName(opPackageName), mDeviceId(deviceId), mDestroyed(false) {
+        mOpPackageName(opPackageName),
+        mDeviceId(deviceId),
+        mDestroyed(false) {
     mUserId = multiuser_get_user_id(mUid);
     ALOGD_IF(DEBUG_CONNECTIONS, "Created SensorDirectConnection");
 }
@@ -62,10 +67,21 @@
 
 void SensorService::SensorDirectConnection::dump(String8& result) const {
     Mutex::Autolock _l(mConnectionLock);
-    result.appendFormat("\tPackage %s, HAL channel handle %d, total sensor activated %zu\n",
-            String8(mOpPackageName).c_str(), getHalChannelHandle(), mActivated.size());
-    for (auto &i : mActivated) {
-        result.appendFormat("\t\tSensor %#08x, rate %d\n", i.first, i.second);
+    result.appendFormat("\t%s | HAL channel handle %d | uid %d | pid %d\n",
+                        String8(mOpPackageName).c_str(), getHalChannelHandle(), mUid, mPid);
+    result.appendFormat("\tActivated sensor count: %zu\n", mActivated.size());
+    dumpSensorInfoWithLock(result, mActivated);
+
+    result.appendFormat("\tBackup sensor (opened but UID idle) count: %zu\n",
+                        mActivatedBackup.size());
+    dumpSensorInfoWithLock(result, mActivatedBackup);
+}
+
+void SensorService::SensorDirectConnection::dumpSensorInfoWithLock(
+        String8& result, std::unordered_map<int, int> sensors) const {
+    for (auto& i : sensors) {
+        result.appendFormat("\t\t%s 0x%08x | rate %d\n", mService->getSensorName(i.first).c_str(),
+                            i.first, i.second);
     }
 }
 
diff --git a/services/sensorservice/SensorDirectConnection.h b/services/sensorservice/SensorDirectConnection.h
index bfaf811..9f21731 100644
--- a/services/sensorservice/SensorDirectConnection.h
+++ b/services/sensorservice/SensorDirectConnection.h
@@ -17,9 +17,10 @@
 #ifndef ANDROID_SENSOR_DIRECT_CONNECTION_H
 #define ANDROID_SENSOR_DIRECT_CONNECTION_H
 
-#include <optional>
+#include <android-base/thread_annotations.h>
 #include <stdint.h>
 #include <sys/types.h>
+#include <optional>
 
 #include <binder/BinderService.h>
 
@@ -37,15 +38,15 @@
 
 class SensorService::SensorDirectConnection: public BnSensorEventConnection {
 public:
-    SensorDirectConnection(const sp<SensorService>& service, uid_t uid,
-            const sensors_direct_mem_t *mem, int32_t halChannelHandle,
-            const String16& opPackageName, int deviceId);
+    SensorDirectConnection(const sp<SensorService>& service, uid_t uid, pid_t pid,
+                           const sensors_direct_mem_t* mem, int32_t halChannelHandle,
+                           const String16& opPackageName, int deviceId);
     void dump(String8& result) const;
     void dump(util::ProtoOutputStream* proto) const;
     uid_t getUid() const { return mUid; }
     const String16& getOpPackageName() const { return mOpPackageName; }
     int32_t getHalChannelHandle() const;
-    bool isEquivalent(const sensors_direct_mem_t *mem) const;
+    bool isEquivalent(const sensors_direct_mem_t* mem) const;
 
     // Invoked when access to sensors for this connection has changed, e.g. lost or
     // regained due to changes in the sensor restricted/privacy mode or the
@@ -94,8 +95,13 @@
     // Recover sensor requests previously capped by capRates().
     void uncapRates();
 
+    // Dumps a set of sensor infos.
+    void dumpSensorInfoWithLock(String8& result, std::unordered_map<int, int> sensors) const
+            EXCLUSIVE_LOCKS_REQUIRED(mConnectionLock);
+
     const sp<SensorService> mService;
     const uid_t mUid;
+    const pid_t mPid;
     const sensors_direct_mem_t mMem;
     const int32_t mHalChannelHandle;
     const String16 mOpPackageName;
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index 3446f58..130c112 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -90,15 +90,14 @@
         result.append("NORMAL\n");
     }
     result.appendFormat("\t %s | WakeLockRefCount %d | uid %d | cache size %d | "
-            "max cache size %d\n", mPackageName.c_str(), mWakeLockRefCount, mUid, mCacheSize,
-            mMaxCacheSize);
+                        "max cache size %d | has sensor access: %s\n",
+                        mPackageName.c_str(), mWakeLockRefCount, mUid, mCacheSize, mMaxCacheSize,
+                        hasSensorAccess() ? "true" : "false");
     for (auto& it : mSensorInfo) {
         const FlushInfo& flushInfo = it.second;
-        result.appendFormat("\t %s 0x%08x | status: %s | pending flush events %d \n",
-                            mService->getSensorName(it.first).c_str(),
-                            it.first,
-                            flushInfo.mFirstFlushPending ? "First flush pending" :
-                                                           "active",
+        result.appendFormat("\t %s 0x%08x | first flush pending: %s | pending flush events %d \n",
+                            mService->getSensorName(it.first).c_str(), it.first,
+                            flushInfo.mFirstFlushPending ? "true" : "false",
                             flushInfo.mPendingFlushEventsToSend);
     }
 #if DEBUG_CONNECTIONS
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 31b7f88..3895ffe 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -682,14 +682,14 @@
                     mSensorPrivacyPolicy->isSensorPrivacyEnabled() ? "enabled" : "disabled");
 
             const auto& activeConnections = connLock.getActiveConnections();
-            result.appendFormat("%zd active connections\n", activeConnections.size());
+            result.appendFormat("%zd open event connections\n", activeConnections.size());
             for (size_t i=0 ; i < activeConnections.size() ; i++) {
                 result.appendFormat("Connection Number: %zu \n", i);
                 activeConnections[i]->dump(result);
             }
 
             const auto& directConnections = connLock.getDirectConnections();
-            result.appendFormat("%zd direct connections\n", directConnections.size());
+            result.appendFormat("%zd open direct connections\n", directConnections.size());
             for (size_t i = 0 ; i < directConnections.size() ; i++) {
                 result.appendFormat("Direct connection %zu:\n", i);
                 directConnections[i]->dump(result);
@@ -1729,7 +1729,10 @@
         ALOGE("SensorDevice::registerDirectChannel returns %d", channelHandle);
     } else {
         mem.handle = clone;
-        conn = new SensorDirectConnection(this, uid, &mem, channelHandle, opPackageName, deviceId);
+        IPCThreadState* thread = IPCThreadState::self();
+        pid_t pid = (thread != nullptr) ? thread->getCallingPid() : -1;
+        conn = new SensorDirectConnection(this, uid, pid, &mem, channelHandle, opPackageName,
+                                          deviceId);
     }
 
     if (conn == nullptr) {
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 04491a2..4a7cff5 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -89,7 +89,9 @@
 }
 
 bool VSyncPredictor::validate(nsecs_t timestamp) const {
+    SFTRACE_CALL();
     if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
+        SFTRACE_INSTANT("timestamp valid (first)");
         return true;
     }
 
@@ -98,7 +100,11 @@
             (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent &&
         percent <= (kMaxPercent - kOutlierTolerancePercent)) {
-        SFTRACE_FORMAT_INSTANT("timestamp is not aligned with model");
+        SFTRACE_FORMAT_INSTANT("timestamp not aligned with model. aValidTimestamp %.2fms ago"
+                               ", timestamp %.2fms ago, idealPeriod=%.2 percent=%d",
+                               (mClock->now() - aValidTimestamp) / 1e6f,
+                               (mClock->now() - timestamp) / 1e6f,
+                               idealPeriod() / 1e6f, percent);
         return false;
     }
 
@@ -148,7 +154,10 @@
             // Add the timestamp to mTimestamps before clearing it so we could
             // update mKnownTimestamp based on the new timestamp.
             mTimestamps.push_back(timestamp);
-            clearTimestamps();
+
+            // Do not clear timelines as we don't want to break the phase while
+            // we are still learning.
+            clearTimestamps(/* clearTimelines */ false);
         } else if (!mTimestamps.empty()) {
             mKnownTimestamp =
                     std::max(timestamp, *std::max_element(mTimestamps.begin(), mTimestamps.end()));
@@ -235,7 +244,7 @@
 
     if (CC_UNLIKELY(bottom == 0)) {
         it->second = {idealPeriod(), 0};
-        clearTimestamps();
+        clearTimestamps(/* clearTimelines */ true);
         return false;
     }
 
@@ -245,7 +254,7 @@
     auto const percent = std::abs(anticipatedPeriod - idealPeriod()) * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent) {
         it->second = {idealPeriod(), 0};
-        clearTimestamps();
+        clearTimestamps(/* clearTimelines */ true);
         return false;
     }
 
@@ -425,6 +434,9 @@
           timeout ? std::to_string(timeout->timeoutNs).c_str() : "N/A");
     std::lock_guard lock(mMutex);
 
+    // do not clear the timelines on VRR displays if we didn't change the mode
+    const bool isVrr = modePtr->getVrrConfig().has_value();
+    const bool clearTimelines = !isVrr || mDisplayModePtr->getId() != modePtr->getId();
     mDisplayModePtr = modePtr;
     mNumVsyncsForFrame = numVsyncsPerFrame(mDisplayModePtr);
     traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());
@@ -438,8 +450,10 @@
         mRateMap[idealPeriod()] = {idealPeriod(), 0};
     }
 
-    mTimelines.clear();
-    clearTimestamps();
+    if (clearTimelines) {
+      mTimelines.clear();
+    }
+    clearTimestamps(clearTimelines);
 }
 
 Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
@@ -556,15 +570,19 @@
     return mRateMap.find(idealPeriod())->second;
 }
 
-void VSyncPredictor::clearTimestamps() {
-    SFTRACE_CALL();
+void VSyncPredictor::clearTimestamps(bool clearTimelines) {
+    SFTRACE_FORMAT("%s: clearTimelines=%d", __func__, clearTimelines);
 
     if (!mTimestamps.empty()) {
         auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
         if (mKnownTimestamp) {
             mKnownTimestamp = std::max(*mKnownTimestamp, maxRb);
+            SFTRACE_FORMAT_INSTANT("mKnownTimestamp was %.2fms ago",
+                               (mClock->now() - *mKnownTimestamp) / 1e6f);
         } else {
             mKnownTimestamp = maxRb;
+            SFTRACE_FORMAT_INSTANT("mKnownTimestamp (maxRb) was %.2fms ago",
+                               (mClock->now() - *mKnownTimestamp) / 1e6f);
         }
 
         mTimestamps.clear();
@@ -575,7 +593,7 @@
     if (mTimelines.empty()) {
         mLastCommittedVsync = TimePoint::fromNs(0);
         mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
-    } else {
+    } else if (clearTimelines) {
         while (mTimelines.size() > 1) {
             mTimelines.pop_front();
         }
@@ -596,9 +614,10 @@
 }
 
 void VSyncPredictor::resetModel() {
+    SFTRACE_CALL();
     std::lock_guard lock(mMutex);
     mRateMap[idealPeriod()] = {idealPeriod(), 0};
-    clearTimestamps();
+    clearTimestamps(/* clearTimelines */ true);
 }
 
 void VSyncPredictor::dump(std::string& result) const {
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 6c8a2f2..2df3d04 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -127,7 +127,7 @@
 
     VSyncPredictor(VSyncPredictor const&) = delete;
     VSyncPredictor& operator=(VSyncPredictor const&) = delete;
-    void clearTimestamps() REQUIRES(mMutex);
+    void clearTimestamps(bool clearTimelines) REQUIRES(mMutex);
 
     const std::unique_ptr<Clock> mClock;
     const PhysicalDisplayId mId;
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 2455822..b974cd2 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -140,7 +140,9 @@
     std::lock_guard lock(mMutex);
     mLastHwVsync.reset();
 
-    if (!mSupportKernelIdleTimer && mTracker.isCurrentMode(modePtr) && !force) {
+    // kernel idle timer is not applicable for VRR
+    const bool supportKernelIdleTimer = mSupportKernelIdleTimer && !modePtr->getVrrConfig();
+    if (!supportKernelIdleTimer && mTracker.isCurrentMode(modePtr) && !force) {
         endPeriodTransition();
         setIgnorePresentFencesInternal(false);
         mMoreSamplesNeeded = false;
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 3f89960..ef213f0 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -339,10 +339,13 @@
 
     ALOGD("Unload builtin Vulkan driver.");
 
-    // Close the opened device
-    int err = hal_.dev_->common.close(
-        const_cast<struct hw_device_t*>(&hal_.dev_->common));
-    ALOG_ASSERT(!err, "hw_device_t::close() failed.");
+    if (hal_.dev_->common.close != nullptr)
+    {
+        // Close the opened device
+        int err = hal_.dev_->common.close(
+            const_cast<struct hw_device_t*>(&hal_.dev_->common));
+        ALOG_ASSERT(!err, "hw_device_t::close() failed.");
+    }
 
     // Close the opened shared library in the hw_module_t
     android_unload_sphal_library(hal_.dev_->common.module->dso);