Binder API for freeze state change notification.

Bug: 338097747
Change-Id: Iac340abc7a1a0700148cded9adb0451b8a4eae73
Test: atest BinderLibTest
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..c43cab2 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -557,6 +557,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 +803,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 94f947f..3d58b52 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
@@ -951,6 +982,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),
@@ -1485,6 +1543,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..47fd38f 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -57,6 +57,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;
@@ -472,27 +491,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) {
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..c473483 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,7 +138,10 @@
         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(const sp<RpcSession>& session, uint64_t address) {
@@ -146,12 +155,17 @@
         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>;
@@ -192,6 +206,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 +221,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 17248ce..61bab3e 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -198,9 +198,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
@@ -249,6 +258,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..3d5b754 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);
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 4c7684c..e3fb363 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 de9d42b..3b5239f 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;
@@ -361,6 +364,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 5582a96..8806fcc 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>
@@ -58,6 +61,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;
@@ -116,6 +120,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,
@@ -249,6 +255,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() {
@@ -293,6 +336,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;
 };
 
@@ -518,29 +606,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
@@ -837,6 +914,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];
@@ -1983,6 +2253,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);
@@ -2069,6 +2359,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({});