Merge "Return zero delta when delta is not computed"
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index b743f49..0389e57 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -39,6 +39,12 @@
  */
 typedef struct AChoreographer AChoreographer;
 
+struct AChoreographerFrameCallbackData;
+/**
+ * Opaque type that provides access to an AChoreographerFrameCallbackData object.
+ */
+typedef struct AChoreographerFrameCallbackData AChoreographerFrameCallbackData;
+
 /**
  * Prototype of the function that is called when a new frame is being rendered.
  * It's passed the time that the frame is being rendered as nanoseconds in the
@@ -60,6 +66,14 @@
 typedef void (*AChoreographer_frameCallback64)(int64_t frameTimeNanos, void* data);
 
 /**
+ * Prototype of the function that is called when a new frame is being rendered.
+ * It's passed the frame data that should not outlive the callback, as well as the data pointer
+ * provided by the application that registered a callback.
+ */
+typedef void (*AChoreographer_extendedFrameCallback)(
+        const AChoreographerFrameCallbackData* callbackData, void* data);
+
+/**
  * Prototype of the function that is called when the display refresh rate
  * changes. It's passed the new vsync period in nanoseconds, as well as the data
  * pointer provided by the application that registered a callback.
@@ -111,6 +125,14 @@
                                                uint32_t delayMillis) __INTRODUCED_IN(29);
 
 /**
+ * Posts a callback to run on the next frame. The data pointer provided will
+ * be passed to the callback function when it's called.
+ */
+void AChoreographer_postExtendedFrameCallback(AChoreographer* choreographer,
+                                        AChoreographer_extendedFrameCallback callback, void* data)
+        __INTRODUCED_IN(33);
+
+/**
  * Registers a callback to be run when the display refresh rate changes. The
  * data pointer provided will be passed to the callback function when it's
  * called. The same callback may be registered multiple times, provided that a
@@ -160,6 +182,42 @@
                                                   AChoreographer_refreshRateCallback, void* data)
         __INTRODUCED_IN(30);
 
+/**
+ * The time in nanoseconds when the frame started being rendered.
+ */
+int64_t AChoreographerFrameCallbackData_getFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
+
+/**
+ * The number of possible frame timelines.
+ */
+size_t AChoreographerFrameCallbackData_getFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
+
+/**
+ * Get index of the platform-preferred FrameTimeline.
+ */
+size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data) __INTRODUCED_IN(33);
+
+/**
+ * The vsync ID token used to map Choreographer data.
+ */
+int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
+
+/**
+ * The time in nanoseconds which the frame at given index is expected to be presented.
+ */
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime(
+        const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
+
+/**
+ * The time in nanoseconds which the frame at given index needs to be ready by.
+ */
+int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+        const AChoreographerFrameCallbackData* data, size_t index) __INTRODUCED_IN(33);
+
 __END_DECLS
 
 #endif // ANDROID_CHOREOGRAPHER_H
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 4c61a59..ba2920e 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -278,7 +278,7 @@
     RpcConnectionHeader header;
     if (status == OK) {
         status = client->interruptableReadFully(server->mShutdownTrigger.get(), &header,
-                                                sizeof(header));
+                                                sizeof(header), {});
         if (status != OK) {
             ALOGE("Failed to read ID for client connecting to RPC server: %s",
                   statusToString(status).c_str());
@@ -291,7 +291,7 @@
         if (header.sessionIdSize > 0) {
             sessionId.resize(header.sessionIdSize);
             status = client->interruptableReadFully(server->mShutdownTrigger.get(),
-                                                    sessionId.data(), sessionId.size());
+                                                    sessionId.data(), sessionId.size(), {});
             if (status != OK) {
                 ALOGE("Failed to read session ID for client connecting to RPC server: %s",
                       statusToString(status).c_str());
@@ -316,7 +316,7 @@
             };
 
             status = client->interruptableWriteFully(server->mShutdownTrigger.get(), &response,
-                                                     sizeof(response));
+                                                     sizeof(response), {});
             if (status != OK) {
                 ALOGE("Failed to send new session response: %s", statusToString(status).c_str());
                 // still need to cleanup before we can return
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index dafb339..65f6bc6 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -560,7 +560,7 @@
     }
 
     auto sendHeaderStatus =
-            server->interruptableWriteFully(mShutdownTrigger.get(), &header, sizeof(header));
+            server->interruptableWriteFully(mShutdownTrigger.get(), &header, sizeof(header), {});
     if (sendHeaderStatus != OK) {
         ALOGE("Could not write connection header to socket: %s",
               statusToString(sendHeaderStatus).c_str());
@@ -570,7 +570,7 @@
     if (sessionId.size() > 0) {
         auto sendSessionIdStatus =
                 server->interruptableWriteFully(mShutdownTrigger.get(), sessionId.data(),
-                                                sessionId.size());
+                                                sessionId.size(), {});
         if (sendSessionIdStatus != OK) {
             ALOGE("Could not write session ID ('%s') to socket: %s",
                   base::HexString(sessionId.data(), sessionId.size()).c_str(),
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 3ff13bc..86cc91c 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -307,7 +307,7 @@
 
 status_t RpcState::rpcSend(const sp<RpcSession::RpcConnection>& connection,
                            const sp<RpcSession>& session, const char* what, const void* data,
-                           size_t size) {
+                           size_t size, const std::function<status_t()>& altPoll) {
     LOG_RPC_DETAIL("Sending %s on RpcTransport %p: %s", what, connection->rpcTransport.get(),
                    android::base::HexString(data, size).c_str());
 
@@ -319,7 +319,7 @@
 
     if (status_t status =
                 connection->rpcTransport->interruptableWriteFully(session->mShutdownTrigger.get(),
-                                                                  data, size);
+                                                                  data, size, altPoll);
         status != OK) {
         LOG_RPC_DETAIL("Failed to write %s (%zu bytes) on RpcTransport %p, error: %s", what, size,
                        connection->rpcTransport.get(), statusToString(status).c_str());
@@ -341,7 +341,7 @@
 
     if (status_t status =
                 connection->rpcTransport->interruptableReadFully(session->mShutdownTrigger.get(),
-                                                                 data, size);
+                                                                 data, size, {});
         status != OK) {
         LOG_RPC_DETAIL("Failed to read %s (%zu bytes) on RpcTransport %p, error: %s", what, size,
                        connection->rpcTransport.get(), statusToString(status).c_str());
@@ -523,21 +523,44 @@
     memcpy(transactionData.data() + sizeof(RpcWireHeader) + sizeof(RpcWireTransaction), data.data(),
            data.dataSize());
 
+    constexpr size_t kWaitMaxUs = 1000000;
+    constexpr size_t kWaitLogUs = 10000;
+    size_t waitUs = 0;
+
+    // Oneway calls have no sync point, so if many are sent before, whether this
+    // is a twoway or oneway transaction, they may have filled up the socket.
+    // So, make sure we drain them before polling.
+    std::function<status_t()> drainRefs = [&] {
+        if (waitUs > kWaitLogUs) {
+            ALOGE("Cannot send command, trying to process pending refcounts. Waiting %zuus. Too "
+                  "many oneway calls?",
+                  waitUs);
+        }
+
+        if (waitUs > 0) {
+            usleep(waitUs);
+            waitUs = std::min(kWaitMaxUs, waitUs * 2);
+        } else {
+            waitUs = 1;
+        }
+
+        return drainCommands(connection, session, CommandType::CONTROL_ONLY);
+    };
+
     if (status_t status = rpcSend(connection, session, "transaction", transactionData.data(),
-                                  transactionData.size());
-        status != OK)
+                                  transactionData.size(), drainRefs);
+        status != OK) {
         // TODO(b/167966510): need to undo onBinderLeaving - we know the
         // refcount isn't successfully transferred.
         return status;
+    }
 
     if (flags & IBinder::FLAG_ONEWAY) {
         LOG_RPC_DETAIL("Oneway command, so no longer waiting on RpcTransport %p",
                        connection->rpcTransport.get());
 
         // Do not wait on result.
-        // However, too many oneway calls may cause refcounts to build up and fill up the socket,
-        // so process those.
-        return drainCommands(connection, session, CommandType::CONTROL_ONLY);
+        return OK;
     }
 
     LOG_ALWAYS_FATAL_IF(reply == nullptr, "Reply parcel must be used for synchronous transaction.");
@@ -723,7 +746,7 @@
     // for 'recursive' calls to this, we have already read and processed the
     // binder from the transaction data and taken reference counts into account,
     // so it is cached here.
-    sp<IBinder> targetRef;
+    sp<IBinder> target;
 processTransactInternalTailCall:
 
     if (transactionData.size() < sizeof(RpcWireTransaction)) {
@@ -738,12 +761,9 @@
     bool oneway = transaction->flags & IBinder::FLAG_ONEWAY;
 
     status_t replyStatus = OK;
-    sp<IBinder> target;
     if (addr != 0) {
-        if (!targetRef) {
+        if (!target) {
             replyStatus = onBinderEntering(session, addr, &target);
-        } else {
-            target = targetRef;
         }
 
         if (replyStatus != OK) {
@@ -910,7 +930,8 @@
 
                 // reset up arguments
                 transactionData = std::move(todo.data);
-                targetRef = std::move(todo.ref);
+                LOG_ALWAYS_FATAL_IF(target != todo.ref,
+                                    "async list should be associated with a binder");
 
                 it->second.asyncTodo.pop();
                 goto processTransactInternalTailCall;
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index 42e95e0..50de22b 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -177,7 +177,8 @@
 
     [[nodiscard]] status_t rpcSend(const sp<RpcSession::RpcConnection>& connection,
                                    const sp<RpcSession>& session, const char* what,
-                                   const void* data, size_t size);
+                                   const void* data, size_t size,
+                                   const std::function<status_t()>& altPoll = nullptr);
     [[nodiscard]] status_t rpcRec(const sp<RpcSession::RpcConnection>& connection,
                                   const sp<RpcSession>& session, const char* what, void* data,
                                   size_t size);
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index 41f4a9f..7669518 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -43,56 +43,72 @@
         return ret;
     }
 
-    status_t interruptableWriteFully(FdTrigger* fdTrigger, const void* data, size_t size) override {
-        const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data);
-        const uint8_t* end = buffer + size;
+    template <typename Buffer, typename SendOrReceive>
+    status_t interruptableReadOrWrite(FdTrigger* fdTrigger, Buffer buffer, size_t size,
+                                      SendOrReceive sendOrReceiveFun, const char* funName,
+                                      int16_t event, const std::function<status_t()>& altPoll) {
+        const Buffer end = buffer + size;
 
         MAYBE_WAIT_IN_FLAKE_MODE;
 
-        status_t status;
-        while ((status = fdTrigger->triggerablePoll(mSocket.get(), POLLOUT)) == OK) {
-            ssize_t writeSize =
-                    TEMP_FAILURE_RETRY(::send(mSocket.get(), buffer, end - buffer, MSG_NOSIGNAL));
-            if (writeSize < 0) {
+        // Since we didn't poll, we need to manually check to see if it was triggered. Otherwise, we
+        // may never know we should be shutting down.
+        if (fdTrigger->isTriggered()) {
+            return DEAD_OBJECT;
+        }
+
+        bool havePolled = false;
+        while (true) {
+            ssize_t processSize = TEMP_FAILURE_RETRY(
+                    sendOrReceiveFun(mSocket.get(), buffer, end - buffer, MSG_NOSIGNAL));
+
+            if (processSize < 0) {
                 int savedErrno = errno;
-                LOG_RPC_DETAIL("RpcTransport send(): %s", strerror(savedErrno));
-                return -savedErrno;
+
+                // Still return the error on later passes, since it would expose
+                // a problem with polling
+                if (havePolled ||
+                    (!havePolled && savedErrno != EAGAIN && savedErrno != EWOULDBLOCK)) {
+                    LOG_RPC_DETAIL("RpcTransport %s(): %s", funName, strerror(savedErrno));
+                    return -savedErrno;
+                }
+            } else if (processSize == 0) {
+                return DEAD_OBJECT;
+            } else {
+                buffer += processSize;
+                if (buffer == end) {
+                    return OK;
+                }
             }
 
-            if (writeSize == 0) return DEAD_OBJECT;
-
-            buffer += writeSize;
-            if (buffer == end) return OK;
+            if (altPoll) {
+                if (status_t status = altPoll(); status != OK) return status;
+                if (fdTrigger->isTriggered()) {
+                    return DEAD_OBJECT;
+                }
+            } else {
+                if (status_t status = fdTrigger->triggerablePoll(mSocket.get(), event);
+                    status != OK)
+                    return status;
+                if (!havePolled) havePolled = true;
+            }
         }
-        return status;
     }
 
-    status_t interruptableReadFully(FdTrigger* fdTrigger, void* data, size_t size) override {
-        uint8_t* buffer = reinterpret_cast<uint8_t*>(data);
-        uint8_t* end = buffer + size;
+    status_t interruptableWriteFully(FdTrigger* fdTrigger, const void* data, size_t size,
+                                     const std::function<status_t()>& altPoll) override {
+        return interruptableReadOrWrite(fdTrigger, reinterpret_cast<const uint8_t*>(data), size,
+                                        send, "send", POLLOUT, altPoll);
+    }
 
-        MAYBE_WAIT_IN_FLAKE_MODE;
-
-        status_t status;
-        while ((status = fdTrigger->triggerablePoll(mSocket.get(), POLLIN)) == OK) {
-            ssize_t readSize =
-                    TEMP_FAILURE_RETRY(::recv(mSocket.get(), buffer, end - buffer, MSG_NOSIGNAL));
-            if (readSize < 0) {
-                int savedErrno = errno;
-                LOG_RPC_DETAIL("RpcTransport recv(): %s", strerror(savedErrno));
-                return -savedErrno;
-            }
-
-            if (readSize == 0) return DEAD_OBJECT; // EOF
-
-            buffer += readSize;
-            if (buffer == end) return OK;
-        }
-        return status;
+    status_t interruptableReadFully(FdTrigger* fdTrigger, void* data, size_t size,
+                                    const std::function<status_t()>& altPoll) override {
+        return interruptableReadOrWrite(fdTrigger, reinterpret_cast<uint8_t*>(data), size, recv,
+                                        "recv", POLLIN, altPoll);
     }
 
 private:
-    android::base::unique_fd mSocket;
+    base::unique_fd mSocket;
 };
 
 // RpcTransportCtx with TLS disabled.
diff --git a/libs/binder/RpcTransportTls.cpp b/libs/binder/RpcTransportTls.cpp
index f8cd71d..7f810b1 100644
--- a/libs/binder/RpcTransportTls.cpp
+++ b/libs/binder/RpcTransportTls.cpp
@@ -169,12 +169,13 @@
     // If |sslError| is WANT_READ / WANT_WRITE, poll for POLLIN / POLLOUT respectively. Otherwise
     // return error. Also return error if |fdTrigger| is triggered before or during poll().
     status_t pollForSslError(android::base::borrowed_fd fd, int sslError, FdTrigger* fdTrigger,
-                             const char* fnString, int additionalEvent = 0) {
+                             const char* fnString, int additionalEvent,
+                             const std::function<status_t()>& altPoll) {
         switch (sslError) {
             case SSL_ERROR_WANT_READ:
-                return handlePoll(POLLIN | additionalEvent, fd, fdTrigger, fnString);
+                return handlePoll(POLLIN | additionalEvent, fd, fdTrigger, fnString, altPoll);
             case SSL_ERROR_WANT_WRITE:
-                return handlePoll(POLLOUT | additionalEvent, fd, fdTrigger, fnString);
+                return handlePoll(POLLOUT | additionalEvent, fd, fdTrigger, fnString, altPoll);
             case SSL_ERROR_SYSCALL: {
                 auto queue = toString();
                 LOG_TLS_DETAIL("%s(): %s. Treating as DEAD_OBJECT. Error queue: %s", fnString,
@@ -194,11 +195,17 @@
     bool mHandled = false;
 
     status_t handlePoll(int event, android::base::borrowed_fd fd, FdTrigger* fdTrigger,
-                        const char* fnString) {
-        status_t ret = fdTrigger->triggerablePoll(fd, event);
+                        const char* fnString, const std::function<status_t()>& altPoll) {
+        status_t ret;
+        if (altPoll) {
+            ret = altPoll();
+            if (fdTrigger->isTriggered()) ret = DEAD_OBJECT;
+        } else {
+            ret = fdTrigger->triggerablePoll(fd, event);
+        }
+
         if (ret != OK && ret != DEAD_OBJECT) {
-            ALOGE("triggerablePoll error while poll()-ing after %s(): %s", fnString,
-                  statusToString(ret).c_str());
+            ALOGE("poll error while after %s(): %s", fnString, statusToString(ret).c_str());
         }
         clear();
         return ret;
@@ -268,8 +275,10 @@
     RpcTransportTls(android::base::unique_fd socket, Ssl ssl)
           : mSocket(std::move(socket)), mSsl(std::move(ssl)) {}
     Result<size_t> peek(void* buf, size_t size) override;
-    status_t interruptableWriteFully(FdTrigger* fdTrigger, const void* data, size_t size) override;
-    status_t interruptableReadFully(FdTrigger* fdTrigger, void* data, size_t size) override;
+    status_t interruptableWriteFully(FdTrigger* fdTrigger, const void* data, size_t size,
+                                     const std::function<status_t()>& altPoll) override;
+    status_t interruptableReadFully(FdTrigger* fdTrigger, void* data, size_t size,
+                                    const std::function<status_t()>& altPoll) override;
 
 private:
     android::base::unique_fd mSocket;
@@ -295,7 +304,8 @@
 }
 
 status_t RpcTransportTls::interruptableWriteFully(FdTrigger* fdTrigger, const void* data,
-                                                  size_t size) {
+                                                  size_t size,
+                                                  const std::function<status_t()>& altPoll) {
     auto buffer = reinterpret_cast<const uint8_t*>(data);
     const uint8_t* end = buffer + size;
 
@@ -317,8 +327,8 @@
         int sslError = mSsl.getError(writeSize);
         // TODO(b/195788248): BIO should contain the FdTrigger, and send(2) / recv(2) should be
         //   triggerablePoll()-ed. Then additionalEvent is no longer necessary.
-        status_t pollStatus =
-                errorQueue.pollForSslError(mSocket.get(), sslError, fdTrigger, "SSL_write", POLLIN);
+        status_t pollStatus = errorQueue.pollForSslError(mSocket.get(), sslError, fdTrigger,
+                                                         "SSL_write", POLLIN, altPoll);
         if (pollStatus != OK) return pollStatus;
         // Do not advance buffer. Try SSL_write() again.
     }
@@ -326,7 +336,8 @@
     return OK;
 }
 
-status_t RpcTransportTls::interruptableReadFully(FdTrigger* fdTrigger, void* data, size_t size) {
+status_t RpcTransportTls::interruptableReadFully(FdTrigger* fdTrigger, void* data, size_t size,
+                                                 const std::function<status_t()>& altPoll) {
     auto buffer = reinterpret_cast<uint8_t*>(data);
     uint8_t* end = buffer + size;
 
@@ -350,8 +361,8 @@
             return DEAD_OBJECT;
         }
         int sslError = mSsl.getError(readSize);
-        status_t pollStatus =
-                errorQueue.pollForSslError(mSocket.get(), sslError, fdTrigger, "SSL_read");
+        status_t pollStatus = errorQueue.pollForSslError(mSocket.get(), sslError, fdTrigger,
+                                                         "SSL_read", 0, altPoll);
         if (pollStatus != OK) return pollStatus;
         // Do not advance buffer. Try SSL_read() again.
     }
@@ -382,7 +393,7 @@
         }
         int sslError = ssl->getError(ret);
         status_t pollStatus =
-                errorQueue.pollForSslError(fd, sslError, fdTrigger, "SSL_do_handshake");
+                errorQueue.pollForSslError(fd, sslError, fdTrigger, "SSL_do_handshake", 0, {});
         if (pollStatus != OK) return false;
     }
 }
diff --git a/libs/binder/include/binder/RpcTransport.h b/libs/binder/include/binder/RpcTransport.h
index 4fe2324..db8b5e9 100644
--- a/libs/binder/include/binder/RpcTransport.h
+++ b/libs/binder/include/binder/RpcTransport.h
@@ -18,6 +18,7 @@
 
 #pragma once
 
+#include <functional>
 #include <memory>
 #include <string>
 
@@ -43,14 +44,20 @@
     /**
      * Read (or write), but allow to be interrupted by a trigger.
      *
+     * altPoll - function to be called instead of polling, when needing to wait
+     * to read/write data. If this returns an error, that error is returned from
+     * this function.
+     *
      * Return:
      *   OK - succeeded in completely processing 'size'
      *   error - interrupted (failure or trigger)
      */
-    [[nodiscard]] virtual status_t interruptableWriteFully(FdTrigger *fdTrigger, const void *buf,
-                                                           size_t size) = 0;
-    [[nodiscard]] virtual status_t interruptableReadFully(FdTrigger *fdTrigger, void *buf,
-                                                          size_t size) = 0;
+    [[nodiscard]] virtual status_t interruptableWriteFully(
+            FdTrigger *fdTrigger, const void *buf, size_t size,
+            const std::function<status_t()> &altPoll) = 0;
+    [[nodiscard]] virtual status_t interruptableReadFully(
+            FdTrigger *fdTrigger, void *buf, size_t size,
+            const std::function<status_t()> &altPoll) = 0;
 
 protected:
     RpcTransport() = default;
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index e7c3396..a91092e 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -441,6 +441,8 @@
 ///
 /// Registers the given binder object with the given identifier. If successful,
 /// this service can then be retrieved using that identifier.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
 pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
     let instance = CString::new(identifier).unwrap();
     let status = unsafe {
@@ -462,6 +464,8 @@
 ///
 /// If any service in the process is registered as lazy, all should be, otherwise
 /// the process may be shut down while a service is in use.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
 pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
     let instance = CString::new(identifier).unwrap();
     let status = unsafe {
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index a2558f5..a1058bc 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -1573,7 +1573,7 @@
                                                   FdTrigger* fdTrigger) {
             std::string message(kMessage);
             auto status = serverTransport->interruptableWriteFully(fdTrigger, message.data(),
-                                                                   message.size());
+                                                                   message.size(), {});
             if (status != OK) return AssertionFailure() << statusToString(status);
             return AssertionSuccess();
         }
@@ -1606,7 +1606,7 @@
             std::string readMessage(expectedMessage.size(), '\0');
             status_t readStatus =
                     mClientTransport->interruptableReadFully(mFdTrigger.get(), readMessage.data(),
-                                                             readMessage.size());
+                                                             readMessage.size(), {});
             if (readStatus != OK) {
                 return AssertionFailure() << statusToString(readStatus);
             }
@@ -1800,8 +1800,8 @@
     bool shouldContinueWriting = false;
     auto serverPostConnect = [&](RpcTransport* serverTransport, FdTrigger* fdTrigger) {
         std::string message(RpcTransportTestUtils::kMessage);
-        auto status =
-                serverTransport->interruptableWriteFully(fdTrigger, message.data(), message.size());
+        auto status = serverTransport->interruptableWriteFully(fdTrigger, message.data(),
+                                                               message.size(), {});
         if (status != OK) return AssertionFailure() << statusToString(status);
 
         {
@@ -1811,7 +1811,7 @@
             }
         }
 
-        status = serverTransport->interruptableWriteFully(fdTrigger, msg2.data(), msg2.size());
+        status = serverTransport->interruptableWriteFully(fdTrigger, msg2.data(), msg2.size(), {});
         if (status != DEAD_OBJECT)
             return AssertionFailure() << "When FdTrigger is shut down, interruptableWriteFully "
                                          "should return DEAD_OBJECT, but it is "
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index e1b1efc..6f1a7ae 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -153,6 +153,7 @@
                     outVsyncEventData->id = ev.vsync.vsyncId;
                     outVsyncEventData->deadlineTimestamp = ev.vsync.deadlineTimestamp;
                     outVsyncEventData->frameInterval = ev.vsync.frameInterval;
+                    outVsyncEventData->expectedPresentTime = ev.vsync.expectedVSyncTimestamp;
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
                     dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index 4ade240..f3bd139 100644
--- a/libs/gui/include/gui/DisplayEventDispatcher.h
+++ b/libs/gui/include/gui/DisplayEventDispatcher.h
@@ -33,6 +33,9 @@
 
     // The current frame interval in ns when this frame was scheduled.
     int64_t frameInterval = 0;
+
+    // The anticipated Vsync present time.
+    int64_t expectedPresentTime = 0;
 };
 
 class DisplayEventDispatcher : public LooperCallback {
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index ba71960..4c4a34f 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -77,6 +77,7 @@
 struct FrameCallback {
     AChoreographer_frameCallback callback;
     AChoreographer_frameCallback64 callback64;
+    AChoreographer_extendedFrameCallback extendedCallback;
     void* data;
     nsecs_t dueTime;
 
@@ -95,6 +96,27 @@
 
 class Choreographer;
 
+/**
+ * Implementation of AChoreographerFrameCallbackData.
+ */
+struct ChoreographerFrameCallbackDataImpl {
+    struct FrameTimeline {
+        int64_t vsyncId{0};
+        int64_t expectedPresentTimeNanos{0};
+        int64_t deadlineNanos{0};
+    };
+
+    int64_t frameTimeNanos{0};
+
+    size_t frameTimelinesLength;
+
+    std::vector<FrameTimeline> frameTimelines;
+
+    size_t preferredFrameTimelineIndex;
+
+    const Choreographer* choreographer;
+};
+
 struct {
     std::mutex lock;
     std::vector<Choreographer*> ptrs GUARDED_BY(lock);
@@ -107,7 +129,9 @@
 public:
     explicit Choreographer(const sp<Looper>& looper) EXCLUDES(gChoreographers.lock);
     void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
-                                  AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay);
+                                  AChoreographer_frameCallback64 cb64,
+                                  AChoreographer_extendedFrameCallback extendedCallback, void* data,
+                                  nsecs_t delay);
     void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
             EXCLUDES(gChoreographers.lock);
     void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
@@ -130,6 +154,7 @@
     int64_t getVsyncId() const;
     int64_t getFrameDeadline() const;
     int64_t getFrameInterval() const;
+    bool inCallback() const;
 
 private:
     Choreographer(const Choreographer&) = delete;
@@ -145,6 +170,8 @@
 
     void scheduleCallbacks();
 
+    ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const;
+
     std::mutex mLock;
     // Protected by mLock
     std::priority_queue<FrameCallback> mFrameCallbacks;
@@ -152,6 +179,7 @@
 
     nsecs_t mLatestVsyncPeriod = -1;
     VsyncEventData mLastVsyncEventData;
+    bool mInCallback = false;
 
     const sp<Looper> mLooper;
     const std::thread::id mThreadId;
@@ -211,10 +239,12 @@
     }
 }
 
-void Choreographer::postFrameCallbackDelayed(
-        AChoreographer_frameCallback cb, AChoreographer_frameCallback64 cb64, void* data, nsecs_t delay) {
+void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb,
+                                             AChoreographer_frameCallback64 cb64,
+                                             AChoreographer_extendedFrameCallback extendedCallback,
+                                             void* data, nsecs_t delay) {
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-    FrameCallback callback{cb, cb64, data, now + delay};
+    FrameCallback callback{cb, cb64, extendedCallback, data, now + delay};
     {
         std::lock_guard<std::mutex> _l{mLock};
         mFrameCallbacks.push(callback);
@@ -370,7 +400,15 @@
     }
     mLastVsyncEventData = vsyncEventData;
     for (const auto& cb : callbacks) {
-        if (cb.callback64 != nullptr) {
+        if (cb.extendedCallback != nullptr) {
+            const ChoreographerFrameCallbackDataImpl frameCallbackData =
+                    createFrameCallbackData(timestamp);
+            mInCallback = true;
+            cb.extendedCallback(reinterpret_cast<const AChoreographerFrameCallbackData*>(
+                                        &frameCallbackData),
+                                cb.data);
+            mInCallback = false;
+        } else if (cb.callback64 != nullptr) {
             cb.callback64(timestamp, cb.data);
         } else if (cb.callback != nullptr) {
             cb.callback(timestamp, cb.data);
@@ -379,8 +417,8 @@
 }
 
 void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) {
-    ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.",
-            this, to_string(displayId).c_str(), toString(connected));
+    ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this,
+          to_string(displayId).c_str(), toString(connected));
 }
 
 void Choreographer::dispatchModeChanged(nsecs_t, PhysicalDisplayId, int32_t, nsecs_t) {
@@ -399,15 +437,15 @@
 
 void Choreographer::handleMessage(const Message& message) {
     switch (message.what) {
-    case MSG_SCHEDULE_CALLBACKS:
-        scheduleCallbacks();
-        break;
-    case MSG_SCHEDULE_VSYNC:
-        scheduleVsync();
-        break;
-    case MSG_HANDLE_REFRESH_RATE_UPDATES:
-        handleRefreshRateUpdates();
-        break;
+        case MSG_SCHEDULE_CALLBACKS:
+            scheduleCallbacks();
+            break;
+        case MSG_SCHEDULE_VSYNC:
+            scheduleVsync();
+            break;
+        case MSG_HANDLE_REFRESH_RATE_UPDATES:
+            handleRefreshRateUpdates();
+            break;
     }
 }
 
@@ -423,6 +461,22 @@
     return mLastVsyncEventData.frameInterval;
 }
 
+bool Choreographer::inCallback() const {
+    return mInCallback;
+}
+
+ChoreographerFrameCallbackDataImpl Choreographer::createFrameCallbackData(nsecs_t timestamp) const {
+    std::vector<ChoreographerFrameCallbackDataImpl::FrameTimeline> frameTimelines;
+    frameTimelines.push_back({.vsyncId = mLastVsyncEventData.id,
+                              .expectedPresentTimeNanos = mLastVsyncEventData.expectedPresentTime,
+                              .deadlineNanos = mLastVsyncEventData.deadlineTimestamp});
+    return {.frameTimeNanos = timestamp,
+            .frameTimelinesLength = 1,
+            .preferredFrameTimelineIndex = 0,
+            .frameTimelines = frameTimelines,
+            .choreographer = this};
+}
+
 } // namespace android
 using namespace android;
 
@@ -435,6 +489,12 @@
     return reinterpret_cast<const Choreographer*>(choreographer);
 }
 
+static inline const ChoreographerFrameCallbackDataImpl*
+AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(
+        const AChoreographerFrameCallbackData* data) {
+    return reinterpret_cast<const ChoreographerFrameCallbackDataImpl*>(data);
+}
+
 // Glue for private C api
 namespace android {
 void AChoreographer_signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock) {
@@ -487,6 +547,11 @@
                                                     void* data, uint32_t delayMillis) {
     return AChoreographer_postFrameCallbackDelayed64(choreographer, callback, data, delayMillis);
 }
+void AChoreographer_routePostExtendedFrameCallback(AChoreographer* choreographer,
+                                                   AChoreographer_extendedFrameCallback callback,
+                                                   void* data) {
+    return AChoreographer_postExtendedFrameCallback(choreographer, callback, data);
+}
 void AChoreographer_routeRegisterRefreshRateCallback(AChoreographer* choreographer,
                                                      AChoreographer_refreshRateCallback callback,
                                                      void* data) {
@@ -497,6 +562,30 @@
                                                        void* data) {
     return AChoreographer_unregisterRefreshRateCallback(choreographer, callback, data);
 }
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data) {
+    return AChoreographerFrameCallbackData_getFrameTimeNanos(data);
+}
+size_t AChoreographerFrameCallbackData_routeGetFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data) {
+    return AChoreographerFrameCallbackData_getFrameTimelinesLength(data);
+}
+size_t AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data) {
+    return AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(data);
+}
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    return AChoreographerFrameCallbackData_getFrameTimelineVsyncId(data, index);
+}
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTime(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    return AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime(data, index);
+}
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineDeadline(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    return AChoreographerFrameCallbackData_getFrameTimelineDeadline(data, index);
+}
 
 int64_t AChoreographer_getVsyncId(const AChoreographer* choreographer) {
     return AChoreographer_to_Choreographer(choreographer)->getVsyncId();
@@ -523,24 +612,32 @@
 }
 
 void AChoreographer_postFrameCallback(AChoreographer* choreographer,
-        AChoreographer_frameCallback callback, void* data) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            callback, nullptr, data, 0);
+                                      AChoreographer_frameCallback callback, void* data) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0);
 }
 void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
-        AChoreographer_frameCallback callback, void* data, long delayMillis) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            callback, nullptr, data, ms2ns(delayMillis));
+                                             AChoreographer_frameCallback callback, void* data,
+                                             long delayMillis) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis));
+}
+void AChoreographer_postExtendedFrameCallback(AChoreographer* choreographer,
+                                              AChoreographer_extendedFrameCallback callback,
+                                              void* data) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0);
 }
 void AChoreographer_postFrameCallback64(AChoreographer* choreographer,
-        AChoreographer_frameCallback64 callback, void* data) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            nullptr, callback, data, 0);
+                                        AChoreographer_frameCallback64 callback, void* data) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0);
 }
 void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer,
-        AChoreographer_frameCallback64 callback, void* data, uint32_t delayMillis) {
-    AChoreographer_to_Choreographer(choreographer)->postFrameCallbackDelayed(
-            nullptr, callback, data, ms2ns(delayMillis));
+                                               AChoreographer_frameCallback64 callback, void* data,
+                                               uint32_t delayMillis) {
+    AChoreographer_to_Choreographer(choreographer)
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis));
 }
 void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
                                                 AChoreographer_refreshRateCallback callback,
@@ -553,6 +650,58 @@
     AChoreographer_to_Choreographer(choreographer)->unregisterRefreshRateCallback(callback, data);
 }
 
+int64_t AChoreographerFrameCallbackData_getFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    return frameCallbackData->frameTimeNanos;
+}
+size_t AChoreographerFrameCallbackData_getFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    return frameCallbackData->frameTimelinesLength;
+}
+size_t AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    return frameCallbackData->preferredFrameTimelineIndex;
+}
+int64_t AChoreographerFrameCallbackData_getFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    LOG_ALWAYS_FATAL_IF(index >= frameCallbackData->frameTimelinesLength, "Index out of bounds");
+    return frameCallbackData->frameTimelines[index].vsyncId;
+}
+int64_t AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    LOG_ALWAYS_FATAL_IF(index >= frameCallbackData->frameTimelinesLength, "Index out of bounds");
+    return frameCallbackData->frameTimelines[index].expectedPresentTimeNanos;
+}
+int64_t AChoreographerFrameCallbackData_getFrameTimelineDeadline(
+        const AChoreographerFrameCallbackData* data, size_t index) {
+    const ChoreographerFrameCallbackDataImpl* frameCallbackData =
+            AChoreographerFrameCallbackData_to_ChoreographerFrameCallbackDataImpl(data);
+    LOG_ALWAYS_FATAL_IF(!frameCallbackData->choreographer->inCallback(),
+                        "Data is only valid in callback");
+    LOG_ALWAYS_FATAL_IF(index >= frameCallbackData->frameTimelinesLength, "Index out of bounds");
+    return frameCallbackData->frameTimelines[index].deadlineNanos;
+}
+
 AChoreographer* AChoreographer_create() {
     Choreographer* choreographer = new Choreographer(nullptr);
     status_t result = choreographer->initialize();
diff --git a/libs/nativedisplay/include-private/private/android/choreographer.h b/libs/nativedisplay/include-private/private/android/choreographer.h
index 7d25ce8..6e90853 100644
--- a/libs/nativedisplay/include-private/private/android/choreographer.h
+++ b/libs/nativedisplay/include-private/private/android/choreographer.h
@@ -63,11 +63,26 @@
 void AChoreographer_routePostFrameCallbackDelayed64(AChoreographer* choreographer,
                                                     AChoreographer_frameCallback64 callback,
                                                     void* data, uint32_t delayMillis);
+void AChoreographer_routePostExtendedFrameCallback(AChoreographer* choreographer,
+                                                   AChoreographer_extendedFrameCallback callback,
+                                                   void* data);
 void AChoreographer_routeRegisterRefreshRateCallback(AChoreographer* choreographer,
                                                      AChoreographer_refreshRateCallback callback,
                                                      void* data);
 void AChoreographer_routeUnregisterRefreshRateCallback(AChoreographer* choreographer,
                                                        AChoreographer_refreshRateCallback callback,
                                                        void* data);
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimeNanos(
+        const AChoreographerFrameCallbackData* data);
+size_t AChoreographerFrameCallbackData_routeGetFrameTimelinesLength(
+        const AChoreographerFrameCallbackData* data);
+size_t AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex(
+        const AChoreographerFrameCallbackData* data);
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId(
+        const AChoreographerFrameCallbackData* data, size_t index);
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTime(
+        const AChoreographerFrameCallbackData* data, size_t index);
+int64_t AChoreographerFrameCallbackData_routeGetFrameTimelineDeadline(
+        const AChoreographerFrameCallbackData* data, size_t index);
 
 } // namespace android
diff --git a/libs/nativedisplay/libnativedisplay.map.txt b/libs/nativedisplay/libnativedisplay.map.txt
index 9ed4915..b4a70e8 100644
--- a/libs/nativedisplay/libnativedisplay.map.txt
+++ b/libs/nativedisplay/libnativedisplay.map.txt
@@ -7,6 +7,13 @@
     AChoreographer_postFrameCallbackDelayed64; # apex # introduced=30
     AChoreographer_registerRefreshRateCallback; # apex # introduced=30
     AChoreographer_unregisterRefreshRateCallback; # apex # introduced=30
+    AChoreographer_postExtendedFrameCallback; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimeNanos; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelinesLength; # apex # introduced=33
+    AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineVsyncId; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTime; # apex # introduced=33
+    AChoreographerFrameCallbackData_getFrameTimelineDeadline; # apex # introduced=33
     AChoreographer_create; # apex # introduced=30
     AChoreographer_destroy; # apex # introduced=30
     AChoreographer_getFd; # apex # introduced=30
@@ -28,6 +35,13 @@
       android::AChoreographer_routePostFrameCallbackDelayed64*;
       android::AChoreographer_routeRegisterRefreshRateCallback*;
       android::AChoreographer_routeUnregisterRefreshRateCallback*;
+      android::AChoreographer_routePostExtendedFrameCallback*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimeNanos*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelinesLength*;
+      android::AChoreographerFrameCallbackData_routeGetPreferredFrameTimelineIndex*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelineVsyncId*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelineExpectedPresentTime*;
+      android::AChoreographerFrameCallbackData_routeGetFrameTimelineDeadline*;
       android::AChoreographer_signalRefreshRateCallbacks*;
       android::AChoreographer_getVsyncId*;
       android::AChoreographer_getFrameDeadline*;
diff --git a/libs/ui/Transform.cpp b/libs/ui/Transform.cpp
index cd68c1c..b34d906 100644
--- a/libs/ui/Transform.cpp
+++ b/libs/ui/Transform.cpp
@@ -396,6 +396,11 @@
         result.mMatrix[1][0] = -b*idet;
         result.mMatrix[1][1] =  a*idet;
         result.mType = mType;
+        if (getOrientation() & ROT_90) {
+            // Recalculate the type if there is a 90-degree rotation component, since the inverse
+            // of ROT_90 is ROT_270 and vice versa.
+            result.mType |= UNKNOWN_TYPE;
+        }
 
         vec2 T(-x, -y);
         T = result.transform(T);
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index a87740a..0ee15f2 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -27,28 +27,40 @@
     name: "Region_test",
     shared_libs: ["libui"],
     srcs: ["Region_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
     name: "colorspace_test",
     shared_libs: ["libui"],
     srcs: ["colorspace_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
     name: "DisplayId_test",
     shared_libs: ["libui"],
     srcs: ["DisplayId_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
     name: "FlattenableHelpers_test",
     shared_libs: ["libui"],
     srcs: ["FlattenableHelpers_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
@@ -68,7 +80,10 @@
         "GraphicBufferAllocator_test.cpp",
         "mock/MockGrallocAllocator.cpp",
     ],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
@@ -83,14 +98,20 @@
         "libutils",
     ],
     srcs: ["GraphicBuffer_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 // This test has a main method, and requires a separate binary to be built.
 cc_test {
     name: "GraphicBufferOverBinder_test",
     srcs: ["GraphicBufferOverBinder_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     shared_libs: [
         "libbinder",
         "libgui",
@@ -105,7 +126,10 @@
     test_suites: ["device-tests"],
     shared_libs: ["libui"],
     srcs: ["Rect_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
@@ -113,7 +137,10 @@
     test_suites: ["device-tests"],
     shared_libs: ["libui"],
     srcs: ["Size_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
 cc_test {
@@ -121,6 +148,18 @@
     shared_libs: ["libui"],
     static_libs: ["libgmock"],
     srcs: ["MockFence_test.cpp"],
-    cflags: ["-Wall", "-Werror"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
 }
 
+cc_test {
+    name: "Transform_test",
+    shared_libs: ["libui"],
+    srcs: ["Transform_test.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
diff --git a/libs/ui/tests/Transform_test.cpp b/libs/ui/tests/Transform_test.cpp
new file mode 100644
index 0000000..6964284
--- /dev/null
+++ b/libs/ui/tests/Transform_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ui/Transform.h>
+
+#include <gtest/gtest.h>
+
+namespace android::ui {
+
+TEST(TransformTest, inverseRotation_hasCorrectType) {
+    const auto testRotationFlagsForInverse = [](Transform::RotationFlags rotation,
+                                                Transform::RotationFlags expectedInverse,
+                                                bool isRotation) {
+        const Transform t(rotation, 0, 0);
+        EXPECT_EQ(t.getOrientation(), rotation);
+        const Transform inverse = t.inverse();
+        EXPECT_EQ(inverse.getOrientation(), expectedInverse);
+
+        if (isRotation) {
+            EXPECT_TRUE(t.getType() & Transform::ROTATE);
+            EXPECT_TRUE(inverse.getType() & Transform::ROTATE);
+        } else {
+            EXPECT_FALSE(t.getType() & Transform::ROTATE);
+            EXPECT_FALSE(inverse.getType() & Transform::ROTATE);
+        }
+    };
+
+    testRotationFlagsForInverse(Transform::ROT_0, Transform::ROT_0, false);
+    testRotationFlagsForInverse(Transform::ROT_90, Transform::ROT_270, true);
+    testRotationFlagsForInverse(Transform::ROT_180, Transform::ROT_180, true);
+    testRotationFlagsForInverse(Transform::ROT_270, Transform::ROT_90, true);
+    testRotationFlagsForInverse(Transform::FLIP_H, Transform::FLIP_H, false);
+    testRotationFlagsForInverse(Transform::FLIP_V, Transform::FLIP_V, false);
+}
+
+} // namespace android::ui