Merge "Use std::filesystem for input event devices scan."
diff --git a/include/input/Input.h b/include/input/Input.h
index 194db1c..40d655f 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -342,6 +342,8 @@
     void scale(float globalScale, float windowXScale, float windowYScale);
     void applyOffset(float xOffset, float yOffset);
 
+    void transform(const ui::Transform& transform);
+
     inline float getX() const {
         return getAxisValue(AMOTION_EVENT_AXIS_X);
     }
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 0219cf7..09cebef 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -41,7 +41,6 @@
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
-#include <utils/Vector.h>
 
 #include <android-base/unique_fd.h>
 
@@ -71,10 +70,7 @@
 
     struct Header {
         Type type; // 4 bytes
-        // We don't need this field in order to align the body below but we
-        // leave it here because InputMessage::size() and other functions
-        // compute the size of this structure as sizeof(Header) + sizeof(Body).
-        uint32_t padding;
+        uint32_t seq;
     } header;
 
     // Body *must* be 8 byte aligned.
@@ -83,8 +79,8 @@
     static_assert(sizeof(std::array<uint8_t, 32>) == 32);
     union Body {
         struct Key {
-            uint32_t seq;
             int32_t eventId;
+            uint32_t empty1;
             nsecs_t eventTime __attribute__((aligned(8)));
             int32_t deviceId;
             int32_t source;
@@ -103,8 +99,8 @@
         } key;
 
         struct Motion {
-            uint32_t seq;
             int32_t eventId;
+            uint32_t empty1;
             nsecs_t eventTime __attribute__((aligned(8)));
             int32_t deviceId;
             int32_t source;
@@ -153,16 +149,14 @@
         } motion;
 
         struct Finished {
-            uint32_t seq;
+            uint32_t empty1;
             uint32_t handled; // actually a bool, but we must maintain 8-byte alignment
 
             inline size_t size() const { return sizeof(Finished); }
         } finished;
 
         struct Focus {
-            uint32_t seq;
             int32_t eventId;
-            uint32_t empty1;
             // The following two fields take up 4 bytes total
             uint16_t hasFocus;    // actually a bool
             uint16_t inTouchMode; // actually a bool, but we must maintain 8-byte alignment
@@ -174,6 +168,19 @@
     bool isValid(size_t actualSize) const;
     size_t size() const;
     void getSanitizedCopy(InputMessage* msg) const;
+
+    static const char* typeToString(Type type) {
+        switch (type) {
+            case Type::KEY:
+                return "KEY";
+            case Type::MOTION:
+                return "MOTION";
+            case Type::FINISHED:
+                return "FINISHED";
+            case Type::FOCUS:
+                return "FOCUS";
+        }
+    }
 };
 
 struct InputChannelInfo : public Parcelable {
@@ -438,6 +445,8 @@
      */
     int32_t getPendingBatchSource() const;
 
+    std::string dump() const;
+
 private:
     // True if touch resampling is enabled.
     const bool mResampleTouch;
@@ -454,9 +463,9 @@
 
     // Batched motion events per device and source.
     struct Batch {
-        Vector<InputMessage> samples;
+        std::vector<InputMessage> samples;
     };
-    Vector<Batch> mBatches;
+    std::vector<Batch> mBatches;
 
     // Touch state per device and source, only for sources of class pointer.
     struct History {
@@ -543,7 +552,7 @@
             return false;
         }
     };
-    Vector<TouchState> mTouchStates;
+    std::vector<TouchState> mTouchStates;
 
     // Chain of batched sequence numbers.  When multiple input messages are combined into
     // a batch, we append a record here that associates the last sequence number in the
@@ -553,7 +562,7 @@
         uint32_t seq;   // sequence number of batched input message
         uint32_t chain; // sequence number of previous batched input message
     };
-    Vector<SeqChain> mSeqChains;
+    std::vector<SeqChain> mSeqChains;
 
     status_t consumeBatch(InputEventFactoryInterface* factory,
             nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 05f43e3..25c0b19 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -326,6 +326,11 @@
 
     while(true) {
         {
+            // It would be really nice if we could read binder commands on this
+            // thread instead of needing a threadpool to be started, but for
+            // instance, if we call getAndExecuteCommand, it might be the case
+            // that another thread serves the callback, and we never get a
+            // command, so we hang indefinitely.
             std::unique_lock<std::mutex> lock(waiter->mMutex);
             using std::literals::chrono_literals::operator""s;
             waiter->mCv.wait_for(lock, 1s, [&] {
@@ -334,6 +339,8 @@
             if (waiter->mBinder != nullptr) return waiter->mBinder;
         }
 
+        ALOGW("Waited one second for %s (is service started? are binder threads started and available?)", name.c_str());
+
         // Handle race condition for lazy services. Here is what can happen:
         // - the service dies (not processed by init yet).
         // - sm processes death notification.
@@ -347,8 +354,6 @@
             return nullptr;
         }
         if (out != nullptr) return out;
-
-        ALOGW("Waited one second for %s", name.c_str());
     }
 }
 
diff --git a/libs/binder/include/binder/TextOutput.h b/libs/binder/include/binder/TextOutput.h
index f66406f..c7e1e14 100644
--- a/libs/binder/include/binder/TextOutput.h
+++ b/libs/binder/include/binder/TextOutput.h
@@ -50,12 +50,18 @@
 
 // ---------------------------------------------------------------------------
 
+// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
+// be allocated.
 // Text output stream for printing to the log (via utils/Log.h).
 extern TextOutput& alog;
 
+// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
+// be allocated.
 // Text output stream for printing to stdout.
 extern TextOutput& aout;
 
+// DO NOT USE: prefer libutils/libbase logs, which don't require static data to
+// be allocated.
 // Text output stream for printing to stderr.
 extern TextOutput& aerr;
 
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index 7540cae..d287290 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -698,3 +698,14 @@
 const char* AIBinder_getCallingSid() {
     return ::android::IPCThreadState::self()->getCallingSid();
 }
+
+android::sp<android::IBinder> AIBinder_toPlatformBinder(AIBinder* binder) {
+    if (binder == nullptr) return nullptr;
+    return binder->getBinder();
+}
+
+AIBinder* AIBinder_fromPlatformBinder(const android::sp<android::IBinder>& binder) {
+    sp<AIBinder> ndkBinder = ABpBinder::lookupOrCreateFromBinder(binder);
+    AIBinder_incStrong(ndkBinder.get());
+    return ndkBinder.get();
+}
diff --git a/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h b/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
index 1f5a237..d4feaba 100644
--- a/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
+++ b/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
@@ -18,6 +18,10 @@
 
 #include <android/binder_ibinder.h>
 
+#if !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)
+#include <binder/IBinder.h>
+#endif
+
 __BEGIN_DECLS
 
 /**
@@ -44,3 +48,33 @@
 __attribute__((warn_unused_result)) const char* AIBinder_getCallingSid() __INTRODUCED_IN(31);
 
 __END_DECLS
+
+#if !defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__)
+
+/**
+ * Get libbinder version of binder from AIBinder.
+ *
+ * WARNING: function calls to a local object on the other side of this function
+ * will parcel. When converting between binders, keep in mind it is not as
+ * efficient as a direct function call.
+ *
+ * \param binder binder with ownership retained by the client
+ * \return platform binder object
+ */
+android::sp<android::IBinder> AIBinder_toPlatformBinder(AIBinder* binder);
+
+/**
+ * Get libbinder_ndk version of binder from platform binder.
+ *
+ * WARNING: function calls to a local object on the other side of this function
+ * will parcel. When converting between binders, keep in mind it is not as
+ * efficient as a direct function call.
+ *
+ * \param binder platform binder which may be from anywhere (doesn't have to be
+ * created with libbinder_ndK)
+ * \return binder with one reference count of ownership given to the client. See
+ * AIBinder_decStrong
+ */
+AIBinder* AIBinder_fromPlatformBinder(const android::sp<android::IBinder>& binder);
+
+#endif
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index d58b75c..9b5fa26 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -95,8 +95,6 @@
     AServiceManager_addService; # apex llndk
     AServiceManager_checkService; # apex llndk
     AServiceManager_getService; # apex llndk
-  local:
-    *;
 };
 
 LIBBINDER_NDK30 { # introduced=30
@@ -111,19 +109,21 @@
     AIBinder_markVendorStability; # llndk
     AIBinder_markVintfStability; # apex llndk
     AIBinder_Class_setHandleShellCommand; # apex llndk
-  local:
-    *;
 };
 
 LIBBINDER_NDK31 { # introduced=31
   global:
     AIBinder_getCallingSid; # apex
     AIBinder_setRequestingSid; # apex
-  local:
-    *;
 };
 
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
+    extern "C++" {
+        AIBinder_fromPlatformBinder*;
+        AIBinder_toPlatformBinder*;
+    };
+  local:
+    *;
 };
diff --git a/libs/binder/ndk/tests/IBinderNdkUnitTest.aidl b/libs/binder/ndk/tests/IBinderNdkUnitTest.aidl
index 4bba9e4..dc77467d 100644
--- a/libs/binder/ndk/tests/IBinderNdkUnitTest.aidl
+++ b/libs/binder/ndk/tests/IBinderNdkUnitTest.aidl
@@ -22,6 +22,8 @@
 import IEmpty;
 
 interface IBinderNdkUnitTest {
+    int repeatInt(int a);
+
     void takeInterface(IEmpty test);
     void forceFlushCommands();
 
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 6869220..e3fdb4b 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -44,6 +44,10 @@
 constexpr char kBinderNdkUnitTestService[] = "BinderNdkUnitTest";
 
 class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest {
+    ndk::ScopedAStatus repeatInt(int32_t in, int32_t* out) {
+        *out = in;
+        return ndk::ScopedAStatus::ok();
+    }
     ndk::ScopedAStatus takeInterface(const std::shared_ptr<aidl::IEmpty>& empty) {
         (void)empty;
         return ndk::ScopedAStatus::ok();
@@ -329,6 +333,30 @@
     EXPECT_TRUE(destroyed);
 }
 
+TEST(NdkBinder, ConvertToPlatformBinder) {
+    for (const ndk::SpAIBinder& binder :
+         {// remote
+          ndk::SpAIBinder(AServiceManager_getService(kBinderNdkUnitTestService)),
+          // local
+          ndk::SharedRefBase::make<MyBinderNdkUnitTest>()->asBinder()}) {
+        // convert to platform binder
+        EXPECT_NE(binder.get(), nullptr);
+        sp<IBinder> platformBinder = AIBinder_toPlatformBinder(binder.get());
+        EXPECT_NE(platformBinder.get(), nullptr);
+        auto proxy = interface_cast<IBinderNdkUnitTest>(platformBinder);
+        EXPECT_NE(proxy, nullptr);
+
+        // use platform binder
+        int out;
+        EXPECT_TRUE(proxy->repeatInt(4, &out).isOk());
+        EXPECT_EQ(out, 4);
+
+        // convert back
+        ndk::SpAIBinder backBinder = ndk::SpAIBinder(AIBinder_fromPlatformBinder(platformBinder));
+        EXPECT_EQ(backBinder.get(), binder.get());
+    }
+}
+
 class MyResultReceiver : public BnResultReceiver {
    public:
     Mutex mMutex;
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 31aa685..2ac079e 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -301,6 +301,11 @@
     }
 }
 
+void PointerCoords::transform(const ui::Transform& transform) {
+    vec2 newCoords = transform.transform(getX(), getY());
+    setAxisValue(AMOTION_EVENT_AXIS_X, newCoords.x);
+    setAxisValue(AMOTION_EVENT_AXIS_Y, newCoords.y);
+}
 
 // --- PointerProperties ---
 
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index c6043ac..60ad578 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -133,12 +133,11 @@
 
     // Write the header
     msg->header.type = header.type;
+    msg->header.seq = header.seq;
 
     // Write the body
     switch(header.type) {
         case InputMessage::Type::KEY: {
-            // uint32_t seq
-            msg->body.key.seq = body.key.seq;
             // int32_t eventId
             msg->body.key.eventId = body.key.eventId;
             // nsecs_t eventTime
@@ -168,8 +167,6 @@
             break;
         }
         case InputMessage::Type::MOTION: {
-            // uint32_t seq
-            msg->body.motion.seq = body.motion.seq;
             // int32_t eventId
             msg->body.motion.eventId = body.motion.eventId;
             // nsecs_t eventTime
@@ -232,12 +229,10 @@
             break;
         }
         case InputMessage::Type::FINISHED: {
-            msg->body.finished.seq = body.finished.seq;
             msg->body.finished.handled = body.finished.handled;
             break;
         }
         case InputMessage::Type::FOCUS: {
-            msg->body.focus.seq = body.focus.seq;
             msg->body.focus.eventId = body.focus.eventId;
             msg->body.focus.hasFocus = body.focus.hasFocus;
             msg->body.focus.inTouchMode = body.focus.inTouchMode;
@@ -468,7 +463,7 @@
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::KEY;
-    msg.body.key.seq = seq;
+    msg.header.seq = seq;
     msg.body.key.eventId = eventId;
     msg.body.key.deviceId = deviceId;
     msg.body.key.source = source;
@@ -526,7 +521,7 @@
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::MOTION;
-    msg.body.motion.seq = seq;
+    msg.header.seq = seq;
     msg.body.motion.eventId = eventId;
     msg.body.motion.deviceId = deviceId;
     msg.body.motion.source = source;
@@ -570,7 +565,7 @@
 
     InputMessage msg;
     msg.header.type = InputMessage::Type::FOCUS;
-    msg.body.focus.seq = seq;
+    msg.header.seq = seq;
     msg.body.focus.eventId = eventId;
     msg.body.focus.hasFocus = hasFocus ? 1 : 0;
     msg.body.focus.inTouchMode = inTouchMode ? 1 : 0;
@@ -594,7 +589,7 @@
                 mChannel->getName().c_str(), msg.header.type);
         return UNKNOWN_ERROR;
     }
-    *outSeq = msg.body.finished.seq;
+    *outSeq = msg.header.seq;
     *outHandled = msg.body.finished.handled == 1;
     return OK;
 }
@@ -655,7 +650,7 @@
                 if (!keyEvent) return NO_MEMORY;
 
                 initializeKeyEvent(keyEvent, &mMsg);
-                *outSeq = mMsg.body.key.seq;
+                *outSeq = mMsg.header.seq;
                 *outEvent = keyEvent;
                 if (DEBUG_TRANSPORT_ACTIONS) {
                     ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
@@ -667,9 +662,9 @@
             case InputMessage::Type::MOTION: {
                 ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
                 if (batchIndex >= 0) {
-                    Batch& batch = mBatches.editItemAt(batchIndex);
+                    Batch& batch = mBatches[batchIndex];
                     if (canAddSample(batch, &mMsg)) {
-                        batch.samples.push(mMsg);
+                        batch.samples.push_back(mMsg);
                         if (DEBUG_TRANSPORT_ACTIONS) {
                             ALOGD("channel '%s' consumer ~ appended to batch event",
                                   mChannel->getName().c_str());
@@ -680,18 +675,18 @@
                         // No need to process events that we are going to cancel anyways
                         const size_t count = batch.samples.size();
                         for (size_t i = 0; i < count; i++) {
-                            const InputMessage& msg = batch.samples.itemAt(i);
-                            sendFinishedSignal(msg.body.motion.seq, false);
+                            const InputMessage& msg = batch.samples[i];
+                            sendFinishedSignal(msg.header.seq, false);
                         }
-                        batch.samples.removeItemsAt(0, count);
-                        mBatches.removeAt(batchIndex);
+                        batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
+                        mBatches.erase(mBatches.begin() + batchIndex);
                     } else {
                         // We cannot append to the batch in progress, so we need to consume
                         // the previous batch right now and defer the new message until later.
                         mMsgDeferred = true;
                         status_t result = consumeSamples(factory, batch, batch.samples.size(),
                                                          outSeq, outEvent);
-                        mBatches.removeAt(batchIndex);
+                        mBatches.erase(mBatches.begin() + batchIndex);
                         if (result) {
                             return result;
                         }
@@ -707,9 +702,9 @@
                 // Start a new batch if needed.
                 if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
                     mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
-                    mBatches.push();
-                    Batch& batch = mBatches.editTop();
-                    batch.samples.push(mMsg);
+                    Batch batch;
+                    batch.samples.push_back(mMsg);
+                    mBatches.push_back(batch);
                     if (DEBUG_TRANSPORT_ACTIONS) {
                         ALOGD("channel '%s' consumer ~ started batch event",
                               mChannel->getName().c_str());
@@ -722,7 +717,7 @@
 
                 updateTouchState(mMsg);
                 initializeMotionEvent(motionEvent, &mMsg);
-                *outSeq = mMsg.body.motion.seq;
+                *outSeq = mMsg.header.seq;
                 *outEvent = motionEvent;
 
                 if (DEBUG_TRANSPORT_ACTIONS) {
@@ -743,7 +738,7 @@
                 if (!focusEvent) return NO_MEMORY;
 
                 initializeFocusEvent(focusEvent, &mMsg);
-                *outSeq = mMsg.body.focus.seq;
+                *outSeq = mMsg.header.seq;
                 *outEvent = focusEvent;
                 break;
             }
@@ -757,10 +752,10 @@
     status_t result;
     for (size_t i = mBatches.size(); i > 0; ) {
         i--;
-        Batch& batch = mBatches.editItemAt(i);
+        Batch& batch = mBatches[i];
         if (frameTime < 0) {
             result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
-            mBatches.removeAt(i);
+            mBatches.erase(mBatches.begin() + i);
             return result;
         }
 
@@ -775,11 +770,11 @@
 
         result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
         const InputMessage* next;
-        if (batch.samples.isEmpty()) {
-            mBatches.removeAt(i);
+        if (batch.samples.empty()) {
+            mBatches.erase(mBatches.begin() + i);
             next = nullptr;
         } else {
-            next = &batch.samples.itemAt(0);
+            next = &batch.samples[0];
         }
         if (!result && mResampleTouch) {
             resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
@@ -797,20 +792,20 @@
 
     uint32_t chain = 0;
     for (size_t i = 0; i < count; i++) {
-        InputMessage& msg = batch.samples.editItemAt(i);
+        InputMessage& msg = batch.samples[i];
         updateTouchState(msg);
         if (i) {
             SeqChain seqChain;
-            seqChain.seq = msg.body.motion.seq;
+            seqChain.seq = msg.header.seq;
             seqChain.chain = chain;
-            mSeqChains.push(seqChain);
+            mSeqChains.push_back(seqChain);
             addSample(motionEvent, &msg);
         } else {
             initializeMotionEvent(motionEvent, &msg);
         }
-        chain = msg.body.motion.seq;
+        chain = msg.header.seq;
     }
-    batch.samples.removeItemsAt(0, count);
+    batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
 
     *outSeq = chain;
     *outEvent = motionEvent;
@@ -832,10 +827,10 @@
     case AMOTION_EVENT_ACTION_DOWN: {
         ssize_t index = findTouchState(deviceId, source);
         if (index < 0) {
-            mTouchStates.push();
+            mTouchStates.push_back({});
             index = mTouchStates.size() - 1;
         }
-        TouchState& touchState = mTouchStates.editItemAt(index);
+        TouchState& touchState = mTouchStates[index];
         touchState.initialize(deviceId, source);
         touchState.addHistory(msg);
         break;
@@ -844,7 +839,7 @@
     case AMOTION_EVENT_ACTION_MOVE: {
         ssize_t index = findTouchState(deviceId, source);
         if (index >= 0) {
-            TouchState& touchState = mTouchStates.editItemAt(index);
+            TouchState& touchState = mTouchStates[index];
             touchState.addHistory(msg);
             rewriteMessage(touchState, msg);
         }
@@ -854,7 +849,7 @@
     case AMOTION_EVENT_ACTION_POINTER_DOWN: {
         ssize_t index = findTouchState(deviceId, source);
         if (index >= 0) {
-            TouchState& touchState = mTouchStates.editItemAt(index);
+            TouchState& touchState = mTouchStates[index];
             touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
             rewriteMessage(touchState, msg);
         }
@@ -864,7 +859,7 @@
     case AMOTION_EVENT_ACTION_POINTER_UP: {
         ssize_t index = findTouchState(deviceId, source);
         if (index >= 0) {
-            TouchState& touchState = mTouchStates.editItemAt(index);
+            TouchState& touchState = mTouchStates[index];
             rewriteMessage(touchState, msg);
             touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
         }
@@ -874,7 +869,7 @@
     case AMOTION_EVENT_ACTION_SCROLL: {
         ssize_t index = findTouchState(deviceId, source);
         if (index >= 0) {
-            TouchState& touchState = mTouchStates.editItemAt(index);
+            TouchState& touchState = mTouchStates[index];
             rewriteMessage(touchState, msg);
         }
         break;
@@ -884,9 +879,9 @@
     case AMOTION_EVENT_ACTION_CANCEL: {
         ssize_t index = findTouchState(deviceId, source);
         if (index >= 0) {
-            TouchState& touchState = mTouchStates.editItemAt(index);
+            TouchState& touchState = mTouchStates[index];
             rewriteMessage(touchState, msg);
-            mTouchStates.removeAt(index);
+            mTouchStates.erase(mTouchStates.begin() + index);
         }
         break;
     }
@@ -943,7 +938,7 @@
         return;
     }
 
-    TouchState& touchState = mTouchStates.editItemAt(index);
+    TouchState& touchState = mTouchStates[index];
     if (touchState.historySize < 1) {
 #if DEBUG_RESAMPLING
         ALOGD("Not resampled, no history for device.");
@@ -1089,11 +1084,11 @@
         size_t chainIndex = 0;
         for (size_t i = seqChainCount; i > 0; ) {
              i--;
-             const SeqChain& seqChain = mSeqChains.itemAt(i);
+             const SeqChain& seqChain = mSeqChains[i];
              if (seqChain.seq == currentSeq) {
                  currentSeq = seqChain.chain;
                  chainSeqs[chainIndex++] = currentSeq;
-                 mSeqChains.removeAt(i);
+                 mSeqChains.erase(mSeqChains.begin() + i);
              }
         }
         status_t status = OK;
@@ -1107,7 +1102,7 @@
                 SeqChain seqChain;
                 seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
                 seqChain.chain = chainSeqs[chainIndex];
-                mSeqChains.push(seqChain);
+                mSeqChains.push_back(seqChain);
                 if (!chainIndex) break;
                 chainIndex--;
             }
@@ -1122,7 +1117,7 @@
 status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
     InputMessage msg;
     msg.header.type = InputMessage::Type::FINISHED;
-    msg.body.finished.seq = seq;
+    msg.header.seq = seq;
     msg.body.finished.handled = handled ? 1 : 0;
     return mChannel->sendMessage(&msg);
 }
@@ -1132,23 +1127,23 @@
 }
 
 bool InputConsumer::hasPendingBatch() const {
-    return !mBatches.isEmpty();
+    return !mBatches.empty();
 }
 
 int32_t InputConsumer::getPendingBatchSource() const {
-    if (mBatches.isEmpty()) {
+    if (mBatches.empty()) {
         return AINPUT_SOURCE_CLASS_NONE;
     }
 
-    const Batch& batch = mBatches.itemAt(0);
-    const InputMessage& head = batch.samples.itemAt(0);
+    const Batch& batch = mBatches[0];
+    const InputMessage& head = batch.samples[0];
     return head.body.motion.source;
 }
 
 ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
     for (size_t i = 0; i < mBatches.size(); i++) {
-        const Batch& batch = mBatches.itemAt(i);
-        const InputMessage& head = batch.samples.itemAt(0);
+        const Batch& batch = mBatches[i];
+        const InputMessage& head = batch.samples[0];
         if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
             return i;
         }
@@ -1158,7 +1153,7 @@
 
 ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
     for (size_t i = 0; i < mTouchStates.size(); i++) {
-        const TouchState& touchState = mTouchStates.itemAt(i);
+        const TouchState& touchState = mTouchStates[i];
         if (touchState.deviceId == deviceId && touchState.source == source) {
             return i;
         }
@@ -1212,7 +1207,7 @@
 }
 
 bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
-    const InputMessage& head = batch.samples.itemAt(0);
+    const InputMessage& head = batch.samples[0];
     uint32_t pointerCount = msg->body.motion.pointerCount;
     if (head.body.motion.pointerCount != pointerCount
             || head.body.motion.action != msg->body.motion.action) {
@@ -1230,11 +1225,72 @@
 ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
     size_t numSamples = batch.samples.size();
     size_t index = 0;
-    while (index < numSamples
-            && batch.samples.itemAt(index).body.motion.eventTime <= time) {
+    while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
         index += 1;
     }
     return ssize_t(index) - 1;
 }
 
+std::string InputConsumer::dump() const {
+    std::string out;
+    out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
+    out = out + "mChannel = " + mChannel->getName() + "\n";
+    out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
+    if (mMsgDeferred) {
+        out = out + "mMsg : " + InputMessage::typeToString(mMsg.header.type) + "\n";
+    }
+    out += "Batches:\n";
+    for (const Batch& batch : mBatches) {
+        out += "    Batch:\n";
+        for (const InputMessage& msg : batch.samples) {
+            out += android::base::StringPrintf("        Message %" PRIu32 ": %s ", msg.header.seq,
+                                               InputMessage::typeToString(msg.header.type));
+            switch (msg.header.type) {
+                case InputMessage::Type::KEY: {
+                    out += android::base::StringPrintf("action=%s keycode=%" PRId32,
+                                                       KeyEvent::actionToString(
+                                                               msg.body.key.action),
+                                                       msg.body.key.keyCode);
+                    break;
+                }
+                case InputMessage::Type::MOTION: {
+                    out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
+                    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+                        const float x = msg.body.motion.pointers[i].coords.getX();
+                        const float y = msg.body.motion.pointers[i].coords.getY();
+                        out += android::base::StringPrintf("\n            Pointer %" PRIu32
+                                                           " : x=%.1f y=%.1f",
+                                                           i, x, y);
+                    }
+                    break;
+                }
+                case InputMessage::Type::FINISHED: {
+                    out += android::base::StringPrintf("handled=%s",
+                                                       toString(msg.body.finished.handled));
+                    break;
+                }
+                case InputMessage::Type::FOCUS: {
+                    out += android::base::StringPrintf("hasFocus=%s inTouchMode=%s",
+                                                       toString(msg.body.focus.hasFocus),
+                                                       toString(msg.body.focus.inTouchMode));
+                    break;
+                }
+            }
+            out += "\n";
+        }
+    }
+    if (mBatches.empty()) {
+        out += "    <empty>\n";
+    }
+    out += "mSeqChains:\n";
+    for (const SeqChain& chain : mSeqChains) {
+        out += android::base::StringPrintf("    chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
+                                           chain.chain);
+    }
+    if (mSeqChains.empty()) {
+        out += "    <empty>\n";
+    }
+    return out;
+}
+
 } // namespace android
diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp
index 7f4bd6e..4187ca9 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -103,7 +103,7 @@
     InputMessage clientReply;
     memset(&clientReply, 0, sizeof(InputMessage));
     clientReply.header.type = InputMessage::Type::FINISHED;
-    clientReply.body.finished.seq = 0x11223344;
+    clientReply.header.seq = 0x11223344;
     clientReply.body.finished.handled = true;
     EXPECT_EQ(OK, clientChannel->sendMessage(&clientReply))
             << "client channel should be able to send message to server channel";
@@ -113,7 +113,7 @@
             << "server channel should be able to receive message from client channel";
     EXPECT_EQ(clientReply.header.type, serverReply.header.type)
             << "server channel should receive the correct message from client channel";
-    EXPECT_EQ(clientReply.body.finished.seq, serverReply.body.finished.seq)
+    EXPECT_EQ(clientReply.header.seq, serverReply.header.seq)
             << "server channel should receive the correct message from client channel";
     EXPECT_EQ(clientReply.body.finished.handled, serverReply.body.finished.handled)
             << "server channel should receive the correct message from client channel";
@@ -181,7 +181,7 @@
 
     InputMessage serverMsg = {}, clientMsg;
     serverMsg.header.type = InputMessage::Type::MOTION;
-    serverMsg.body.motion.seq = 1;
+    serverMsg.header.seq = 1;
     serverMsg.body.motion.pointerCount = 1;
 
     for (MotionClassification classification : classifications) {
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 1fe7bb9..50be9ad 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -34,8 +34,7 @@
 void TestInputMessageAlignment() {
   CHECK_OFFSET(InputMessage, body, 8);
 
-  CHECK_OFFSET(InputMessage::Body::Key, seq, 0);
-  CHECK_OFFSET(InputMessage::Body::Key, eventId, 4);
+  CHECK_OFFSET(InputMessage::Body::Key, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Key, eventTime, 8);
   CHECK_OFFSET(InputMessage::Body::Key, deviceId, 16);
   CHECK_OFFSET(InputMessage::Body::Key, source, 20);
@@ -49,8 +48,7 @@
   CHECK_OFFSET(InputMessage::Body::Key, repeatCount, 80);
   CHECK_OFFSET(InputMessage::Body::Key, downTime, 88);
 
-  CHECK_OFFSET(InputMessage::Body::Motion, seq, 0);
-  CHECK_OFFSET(InputMessage::Body::Motion, eventId, 4);
+  CHECK_OFFSET(InputMessage::Body::Motion, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Motion, eventTime, 8);
   CHECK_OFFSET(InputMessage::Body::Motion, deviceId, 16);
   CHECK_OFFSET(InputMessage::Body::Motion, source, 20);
@@ -75,16 +73,16 @@
   CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 128);
   CHECK_OFFSET(InputMessage::Body::Motion, pointers, 136);
 
-  CHECK_OFFSET(InputMessage::Body::Focus, seq, 0);
-  CHECK_OFFSET(InputMessage::Body::Focus, eventId, 4);
-  CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 12);
-  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 14);
+  CHECK_OFFSET(InputMessage::Body::Focus, eventId, 0);
+  CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 4);
+  CHECK_OFFSET(InputMessage::Body::Focus, inTouchMode, 6);
 
-  CHECK_OFFSET(InputMessage::Body::Finished, seq, 0);
   CHECK_OFFSET(InputMessage::Body::Finished, handled, 4);
 }
 
 void TestHeaderSize() {
+    CHECK_OFFSET(InputMessage::Header, type, 0);
+    CHECK_OFFSET(InputMessage::Header, seq, 4);
     static_assert(sizeof(InputMessage::Header) == 8);
 }
 
@@ -98,7 +96,7 @@
                   offsetof(InputMessage::Body::Motion, pointers) +
                           sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
     static_assert(sizeof(InputMessage::Body::Finished) == 8);
-    static_assert(sizeof(InputMessage::Body::Focus) == 16);
+    static_assert(sizeof(InputMessage::Body::Focus) == 8);
 }
 
 // --- VerifiedInputEvent ---
diff --git a/libs/ui/Transform.cpp b/libs/ui/Transform.cpp
index 06b6bfe..3bf3903 100644
--- a/libs/ui/Transform.cpp
+++ b/libs/ui/Transform.cpp
@@ -55,7 +55,6 @@
             mMatrix[1][1] == other.mMatrix[1][1] && mMatrix[1][2] == other.mMatrix[1][2] &&
             mMatrix[2][0] == other.mMatrix[2][0] && mMatrix[2][1] == other.mMatrix[2][1] &&
             mMatrix[2][2] == other.mMatrix[2][2];
-    ;
 }
 
 Transform Transform::operator * (const Transform& rhs) const
@@ -87,6 +86,19 @@
     return r;
 }
 
+Transform Transform::operator * (float value) const {
+    Transform r(*this);
+    const mat33& M(mMatrix);
+    mat33& R(r.mMatrix);
+    for (size_t i = 0; i < 3; i++) {
+        for (size_t j = 0; j < 2; j++) {
+            R[i][j] = M[i][j] * value;
+        }
+    }
+    r.type();
+    return r;
+}
+
 Transform& Transform::operator=(const Transform& other) {
     mMatrix = other.mMatrix;
     mType = other.mType;
@@ -105,11 +117,19 @@
     return mMatrix[2][1];
 }
 
-float Transform::sx() const {
+float Transform::dsdx() const {
     return mMatrix[0][0];
 }
 
-float Transform::sy() const {
+float Transform::dtdx() const {
+    return mMatrix[1][0];
+}
+
+float Transform::dtdy() const {
+    return mMatrix[0][1];
+}
+
+float Transform::dsdy() const {
     return mMatrix[1][1];
 }
 
@@ -187,6 +207,15 @@
     return NO_ERROR;
 }
 
+void Transform::set(const std::array<float, 9>& matrix) {
+    mat33& M(mMatrix);
+    M[0][0] = matrix[0];  M[1][0] = matrix[1];  M[2][0] = matrix[2];
+    M[0][1] = matrix[3];  M[1][1] = matrix[4];  M[2][1] = matrix[5];
+    M[0][2] = matrix[6];  M[1][2] = matrix[7];  M[2][2] = matrix[8];
+    mType = UNKNOWN_TYPE;
+    type();
+}
+
 vec2 Transform::transform(const vec2& v) const {
     vec2 r;
     const mat33& M(mMatrix);
@@ -204,9 +233,8 @@
     return r;
 }
 
-vec2 Transform::transform(int x, int y) const
-{
-    return transform(vec2(x,y));
+vec2 Transform::transform(float x, float y) const {
+    return transform(vec2(x, y));
 }
 
 Rect Transform::makeBounds(int w, int h) const
diff --git a/libs/ui/include/ui/Transform.h b/libs/ui/include/ui/Transform.h
index 3e8caf4..cf59467 100644
--- a/libs/ui/include/ui/Transform.h
+++ b/libs/ui/include/ui/Transform.h
@@ -18,6 +18,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <array>
 #include <ostream>
 #include <string>
 
@@ -68,24 +69,28 @@
     const vec3& operator [] (size_t i) const;  // returns column i
     float   tx() const;
     float   ty() const;
-    float   sx() const;
-    float   sy() const;
+    float dsdx() const;
+    float dtdx() const;
+    float dtdy() const;
+    float dsdy() const;
 
     // modify the transform
     void        reset();
     void        set(float tx, float ty);
     void        set(float a, float b, float c, float d);
     status_t    set(uint32_t flags, float w, float h);
+    void        set(const std::array<float, 9>& matrix);
 
     // transform data
     Rect    makeBounds(int w, int h) const;
-    vec2    transform(int x, int y) const;
+    vec2    transform(float x, float y) const;
     Region  transform(const Region& reg) const;
     Rect    transform(const Rect& bounds,
                       bool roundOutwards = false) const;
     FloatRect transform(const FloatRect& bounds) const;
     Transform& operator = (const Transform& other);
     Transform operator * (const Transform& rhs) const;
+    Transform operator * (float value) const;
     // assumes the last row is < 0 , 0 , 1 >
     vec2 transform(const vec2& v) const;
     vec3 transform(const vec3& v) const;
diff --git a/libs/vr/libpdx/fuzz/Android.bp b/libs/vr/libpdx/fuzz/Android.bp
index 8ab09f3..b36e0de 100644
--- a/libs/vr/libpdx/fuzz/Android.bp
+++ b/libs/vr/libpdx/fuzz/Android.bp
@@ -1,5 +1,5 @@
 cc_fuzz {
-    name: "libpdx_fuzz",
+    name: "libpdx_service_dispatcher_fuzzer",
     clang: true,
     srcs: [
         "service_dispatcher_fuzzer.cpp",
@@ -15,6 +15,48 @@
     shared_libs: [
         "libutils",
         "liblog",
-	"libcutils",
+        "libcutils"
+    ],
+}
+
+cc_fuzz {
+    name: "libpdx_message_fuzzer",
+    clang: true,
+    srcs: [
+        "message_fuzzer.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    static_libs: [
+        "libpdx",
+    ],
+    shared_libs: [
+        "libutils",
+        "liblog",
+        "libcutils"
+    ],
+}
+
+cc_fuzz {
+    name: "libpdx_serialization_fuzzer",
+    clang: true,
+    srcs: [
+        "serialization_fuzzer.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+    ],
+    static_libs: [
+        "libpdx",
+    ],
+    shared_libs: [
+        "libutils",
+        "liblog",
+        "libcutils"
     ],
 }
diff --git a/libs/vr/libpdx/fuzz/message_fuzzer.cpp b/libs/vr/libpdx/fuzz/message_fuzzer.cpp
new file mode 100644
index 0000000..b627045
--- /dev/null
+++ b/libs/vr/libpdx/fuzz/message_fuzzer.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2020 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.
+ */
+// Authors: corbin.souffrant@leviathansecurity.com
+//          brian.balling@leviathansecurity.com
+
+#include <fuzzer/FuzzedDataProvider.h>
+#include <helpers.h>
+#include <pdx/client_channel.h>
+#include <pdx/service.h>
+#include <pdx/service_dispatcher.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/eventfd.h>
+#include <thread>
+
+using namespace android::pdx;
+
+// Fuzzer for Message object functions.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider fdp = FuzzedDataProvider(data, size);
+
+  FuzzEndpoint* endpoint = new FuzzEndpoint(&fdp);
+  std::shared_ptr<Service> service(
+      new Service("FuzzService", std::unique_ptr<Endpoint>(endpoint)));
+  std::shared_ptr<Channel> channel(nullptr);
+
+  // Generate a random Message object to call functions in.
+  MessageInfo info;
+  info.pid = fdp.ConsumeIntegral<int>();
+  info.tid = fdp.ConsumeIntegral<int>();
+  info.cid = fdp.ConsumeIntegral<int>();
+  info.mid = fdp.ConsumeIntegral<int>();
+  info.euid = fdp.ConsumeIntegral<int>();
+  info.egid = fdp.ConsumeIntegral<int>();
+  info.op = fdp.ConsumeIntegral<int32_t>();
+  info.flags = fdp.ConsumeIntegral<uint32_t>();
+  info.service = service.get();
+  info.channel = channel.get();
+  info.send_len = fdp.ConsumeIntegral<size_t>();
+  info.recv_len = fdp.ConsumeIntegral<size_t>();
+  info.fd_count = fdp.ConsumeIntegral<size_t>();
+  if (fdp.remaining_bytes() >= 32) {
+    std::vector<uint8_t> impulse_vec = fdp.ConsumeBytes<uint8_t>(32);
+    memcpy(info.impulse, impulse_vec.data(), 32);
+  }
+
+  Message message = Message(info);
+
+  // A bunch of getters that probably won't do much, but might as well
+  // get coverage, while we are here.
+  message.GetProcessId();
+  message.GetThreadId();
+  message.GetEffectiveUserId();
+  message.GetEffectiveGroupId();
+  message.GetChannelId();
+  message.GetMessageId();
+  message.GetOp();
+  message.GetFlags();
+  message.GetSendLength();
+  message.GetReceiveLength();
+  message.GetFileDescriptorCount();
+  message.ImpulseEnd();
+  message.replied();
+  message.IsChannelExpired();
+  message.IsServiceExpired();
+  message.GetState();
+  message.GetState();
+
+  // Some misc. functions.
+  unsigned int fd = fdp.ConsumeIntegral<unsigned int>();
+  int clear_mask = fdp.ConsumeIntegral<int>();
+  int set_mask = fdp.ConsumeIntegral<int>();
+  Status<void> status = {};
+  message.ModifyChannelEvents(clear_mask, set_mask);
+
+  // Fuzz the handle functions.
+  LocalHandle l_handle = {};
+  BorrowedHandle b_handle = {};
+  RemoteHandle r_handle = {};
+  LocalChannelHandle lc_handle = {};
+  BorrowedChannelHandle bc_handle = {};
+  RemoteChannelHandle rc_handle = {};
+  FileReference f_ref = fdp.ConsumeIntegral<int32_t>();
+  ChannelReference c_ref = fdp.ConsumeIntegral<int32_t>();
+
+  // These don't actually modify any state in the Message or params.
+  // They can be called in any order.
+  message.PushFileHandle(b_handle);
+  message.PushFileHandle(r_handle);
+  message.PushChannelHandle(lc_handle);
+  message.PushChannelHandle(bc_handle);
+  message.PushChannelHandle(rc_handle);
+  message.GetFileHandle(f_ref, &l_handle);
+  message.GetChannelHandle(c_ref, &lc_handle);
+
+  // Can only reply once, pick at random.
+  switch (fdp.ConsumeIntegral<uint8_t>()) {
+    case 0:
+      message.ReplyFileDescriptor(fd);
+      break;
+    case 1:
+      message.Reply(status);
+      break;
+    case 2:
+      message.Reply(l_handle);
+      break;
+    case 3:
+      message.Reply(b_handle);
+      break;
+    case 4:
+      message.Reply(r_handle);
+      break;
+    case 5:
+      message.Reply(lc_handle);
+      break;
+    case 6:
+      message.Reply(bc_handle);
+      break;
+    case 7:
+      message.Reply(rc_handle);
+  }
+
+  // Fuzz the channel functions.
+  int flags = fdp.ConsumeIntegral<int>();
+  int channel_id = 0;
+  message.PushChannel(flags, channel, &channel_id);
+  message.CheckChannel(service.get(), c_ref, &channel);
+  message.CheckChannel(c_ref, &channel);
+  message.PushChannel(service.get(), flags, channel, &channel_id);
+  size_t iovec_size = sizeof(iovec);
+  struct iovec* iovecs = nullptr;
+
+  // Fuzz the read/write functions. Needs at least one iovec, plus one byte.
+  if (fdp.remaining_bytes() >= iovec_size + 1) {
+    std::vector<uint8_t> tmp_vec = fdp.ConsumeBytes<uint8_t>(iovec_size);
+    struct iovec* vector = reinterpret_cast<struct iovec*>(tmp_vec.data());
+    std::vector<uint8_t> tmp_buf =
+        fdp.ConsumeBytes<uint8_t>(fdp.remaining_bytes());
+    void* buf = reinterpret_cast<void*>(tmp_buf.data());
+    size_t buf_size = fdp.ConsumeIntegral<size_t>();
+
+    // Capping num_vecs to 1024 so it doesn't allocate too much memory.
+    size_t num_vecs = fdp.ConsumeIntegralInRange<size_t>(0, 1024);
+
+    if (num_vecs > 0)
+      iovecs = new struct iovec[num_vecs];
+    for (size_t i = 0; i < num_vecs; i++) {
+      iovecs[i] = *vector;
+    }
+
+    message.ReadAll(vector, buf_size);
+    message.WriteAll(buf, buf_size);
+    message.ReadVectorAll(vector, num_vecs);
+    message.WriteVectorAll(vector, num_vecs);
+    message.ReadVector(vector, buf_size);
+    message.WriteVector(vector, buf_size);
+  }
+
+  if (iovecs != nullptr)
+    delete[] iovecs;
+  return 0;
+}
diff --git a/libs/vr/libpdx/fuzz/serialization_fuzzer.cpp b/libs/vr/libpdx/fuzz/serialization_fuzzer.cpp
new file mode 100644
index 0000000..afde5f7
--- /dev/null
+++ b/libs/vr/libpdx/fuzz/serialization_fuzzer.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 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.
+ */
+// Authors: corbin.souffrant@leviathansecurity.com
+//          brian.balling@leviathansecurity.com
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include <fuzzer/FuzzedDataProvider.h>
+#include <pdx/rpc/argument_encoder.h>
+#include <pdx/rpc/array_wrapper.h>
+#include <pdx/rpc/default_initialization_allocator.h>
+#include <pdx/rpc/payload.h>
+#include <pdx/rpc/serializable.h>
+#include <pdx/rpc/serialization.h>
+#include <pdx/rpc/string_wrapper.h>
+#include <pdx/utility.h>
+
+using namespace android::pdx;
+using namespace android::pdx::rpc;
+
+struct FuzzType {
+  int a;
+  float b;
+  std::string c;
+
+  FuzzType() {}
+  FuzzType(int a, float b, const std::string& c) : a(a), b(b), c(c) {}
+
+ private:
+  PDX_SERIALIZABLE_MEMBERS(FuzzType, a, b, c);
+};
+
+// Fuzzer for Serialization operations, this is mostly just lifted from the
+// existing test cases to use fuzzed values as inputs.
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  FuzzedDataProvider fdp = FuzzedDataProvider(data, size);
+  Payload result;
+
+  // Currently, only fuzzing subset of types. In the future, may want
+  // to add more difficult to generate types like array, map, enum, etc...
+  bool b_val = fdp.ConsumeBool();
+  uint8_t u8_val = fdp.ConsumeIntegral<uint8_t>();
+  uint16_t u16_val = fdp.ConsumeIntegral<uint16_t>();
+  uint32_t u32_val = fdp.ConsumeIntegral<uint32_t>();
+  uint64_t u64_val = fdp.ConsumeIntegral<uint64_t>();
+  int8_t i8_val = fdp.ConsumeIntegral<int8_t>();
+  int16_t i16_val = fdp.ConsumeIntegral<uint16_t>();
+  int32_t i32_val = fdp.ConsumeIntegral<uint32_t>();
+  int64_t i64_val = fdp.ConsumeIntegral<uint64_t>();
+  float f_val = fdp.ConsumeFloatingPoint<float>();
+  double d_val = fdp.ConsumeFloatingPoint<double>();
+  std::string s_val = fdp.ConsumeRandomLengthString(fdp.remaining_bytes());
+  std::vector<uint8_t> vec_val =
+      fdp.ConsumeBytes<uint8_t>(fdp.remaining_bytes());
+  FuzzType t1_val{reinterpret_cast<int>(i32_val), f_val, s_val};
+
+  // Types need to be individually fuzzed because code path changes depending
+  // on which type is being serialized/deserialized.
+  Serialize(b_val, &result);
+  Deserialize(&b_val, &result);
+  Serialize(u8_val, &result);
+  Deserialize(&u8_val, &result);
+  Serialize(u16_val, &result);
+  Deserialize(&u16_val, &result);
+  Serialize(u32_val, &result);
+  Deserialize(&u32_val, &result);
+  Serialize(u64_val, &result);
+  Deserialize(&u64_val, &result);
+  Serialize(i8_val, &result);
+  Deserialize(&i8_val, &result);
+  Serialize(i16_val, &result);
+  Deserialize(&i16_val, &result);
+  Serialize(i32_val, &result);
+  Deserialize(&i32_val, &result);
+  Serialize(i64_val, &result);
+  Deserialize(&i64_val, &result);
+  Serialize(f_val, &result);
+  Deserialize(&f_val, &result);
+  Serialize(d_val, &result);
+  Deserialize(&d_val, &result);
+  Serialize(s_val, &result);
+  Deserialize(&s_val, &result);
+  Serialize(WrapString(s_val), &result);
+  Deserialize(&s_val, &result);
+  Serialize(vec_val, &result);
+  Deserialize(&vec_val, &result);
+  Serialize(t1_val, &result);
+  Deserialize(&t1_val, &result);
+
+  return 0;
+}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 0fa2332..5df4aec 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1584,7 +1584,8 @@
     // Update the touch state as needed based on the properties of the touch event.
     int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
     InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
-    sp<InputWindowHandle> newHoverWindowHandle;
+    sp<InputWindowHandle> newHoverWindowHandle(mLastHoverWindowHandle);
+    sp<InputWindowHandle> newTouchedWindowHandle;
 
     // Copy current touch state into tempTouchState.
     // This state will be used to update mTouchStatesByDisplay at the end of this function.
@@ -1653,7 +1654,7 @@
             y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));
         }
         bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
-        sp<InputWindowHandle> newTouchedWindowHandle =
+        newTouchedWindowHandle =
                 findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
                                           isDown /*addOutsideTargets*/, true /*addPortalWindows*/);
 
@@ -1722,10 +1723,10 @@
             }
 
             // Update hover state.
-            if (isHoverAction) {
+            if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+                newHoverWindowHandle = nullptr;
+            } else if (isHoverAction) {
                 newHoverWindowHandle = newTouchedWindowHandle;
-            } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
-                newHoverWindowHandle = mLastHoverWindowHandle;
             }
 
             // Update the temporary touch state.
@@ -1760,8 +1761,7 @@
 
             sp<InputWindowHandle> oldTouchedWindowHandle =
                     tempTouchState.getFirstForegroundWindowHandle();
-            sp<InputWindowHandle> newTouchedWindowHandle =
-                    findTouchedWindowAtLocked(displayId, x, y, &tempTouchState);
+            newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState);
             if (oldTouchedWindowHandle != newTouchedWindowHandle &&
                 oldTouchedWindowHandle != nullptr && newTouchedWindowHandle != nullptr) {
                 if (DEBUG_FOCUS) {
@@ -1798,8 +1798,11 @@
     }
 
     if (newHoverWindowHandle != mLastHoverWindowHandle) {
-        // Let the previous window know that the hover sequence is over.
-        if (mLastHoverWindowHandle != nullptr) {
+        // Let the previous window know that the hover sequence is over, unless we already did it
+        // when dispatching it as is to newTouchedWindowHandle.
+        if (mLastHoverWindowHandle != nullptr &&
+            (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT ||
+             mLastHoverWindowHandle != newTouchedWindowHandle)) {
 #if DEBUG_HOVER
             ALOGD("Sending hover exit event to window %s.",
                   mLastHoverWindowHandle->getName().c_str());
@@ -1808,8 +1811,11 @@
                                              InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
         }
 
-        // Let the new window know that the hover sequence is starting.
-        if (newHoverWindowHandle != nullptr) {
+        // Let the new window know that the hover sequence is starting, unless we already did it
+        // when dispatching it as is to newTouchedWindowHandle.
+        if (newHoverWindowHandle != nullptr &&
+            (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER ||
+             newHoverWindowHandle != newTouchedWindowHandle)) {
 #if DEBUG_HOVER
             ALOGD("Sending hover enter event to window %s.",
                   newHoverWindowHandle->getName().c_str());
@@ -4263,10 +4269,10 @@
                     dump += INDENT4;
                     entry->eventEntry->appendDescription(dump);
                     dump += StringPrintf(", targetFlags=0x%08x, resolvedAction=%d, "
-                                         "age=%" PRId64 "ms, wait=%" PRId64 "ms\n",
+                                         "age=%" PRId64 "ms, wait=%" PRId64 "ms seq=%" PRIu32 "\n",
                                          entry->targetFlags, entry->resolvedAction,
                                          ns2ms(currentTime - entry->eventEntry->eventTime),
-                                         ns2ms(currentTime - entry->deliveryTime));
+                                         ns2ms(currentTime - entry->deliveryTime), entry->seq);
                 }
             } else {
                 dump += INDENT3 "WaitQueue: <empty>\n";
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index cf5a3ab..86b4868 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -939,6 +939,126 @@
     return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /* repeatCount */ 0, displayId);
 }
 
+class PointerBuilder {
+public:
+    PointerBuilder(int32_t id, int32_t toolType) {
+        mProperties.clear();
+        mProperties.id = id;
+        mProperties.toolType = toolType;
+        mCoords.clear();
+    }
+
+    PointerBuilder& x(float x) { return axis(AMOTION_EVENT_AXIS_X, x); }
+
+    PointerBuilder& y(float y) { return axis(AMOTION_EVENT_AXIS_Y, y); }
+
+    PointerBuilder& axis(int32_t axis, float value) {
+        mCoords.setAxisValue(axis, value);
+        return *this;
+    }
+
+    PointerProperties buildProperties() const { return mProperties; }
+
+    PointerCoords buildCoords() const { return mCoords; }
+
+private:
+    PointerProperties mProperties;
+    PointerCoords mCoords;
+};
+
+class MotionEventBuilder {
+public:
+    MotionEventBuilder(int32_t action, int32_t source) {
+        mAction = action;
+        mSource = source;
+        mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    }
+
+    MotionEventBuilder& eventTime(nsecs_t eventTime) {
+        mEventTime = eventTime;
+        return *this;
+    }
+
+    MotionEventBuilder& displayId(int32_t displayId) {
+        mDisplayId = displayId;
+        return *this;
+    }
+
+    MotionEventBuilder& actionButton(int32_t actionButton) {
+        mActionButton = actionButton;
+        return *this;
+    }
+
+    MotionEventBuilder& buttonState(int32_t actionButton) {
+        mActionButton = actionButton;
+        return *this;
+    }
+
+    MotionEventBuilder& rawXCursorPosition(float rawXCursorPosition) {
+        mRawXCursorPosition = rawXCursorPosition;
+        return *this;
+    }
+
+    MotionEventBuilder& rawYCursorPosition(float rawYCursorPosition) {
+        mRawYCursorPosition = rawYCursorPosition;
+        return *this;
+    }
+
+    MotionEventBuilder& pointer(PointerBuilder pointer) {
+        mPointers.push_back(pointer);
+        return *this;
+    }
+
+    MotionEvent build() {
+        std::vector<PointerProperties> pointerProperties;
+        std::vector<PointerCoords> pointerCoords;
+        for (const PointerBuilder& pointer : mPointers) {
+            pointerProperties.push_back(pointer.buildProperties());
+            pointerCoords.push_back(pointer.buildCoords());
+        }
+
+        // Set mouse cursor position for the most common cases to avoid boilerplate.
+        if (mSource == AINPUT_SOURCE_MOUSE &&
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
+            mPointers.size() == 1) {
+            mRawXCursorPosition = pointerCoords[0].getX();
+            mRawYCursorPosition = pointerCoords[0].getY();
+        }
+
+        MotionEvent event;
+        event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC,
+                         mAction, mActionButton, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
+                         mButtonState, MotionClassification::NONE, /* xScale */ 1, /* yScale */ 1,
+                         /* xOffset */ 0,
+                         /* yOffset */ 0, /* xPrecision */ 0, /* yPrecision */ 0,
+                         mRawXCursorPosition, mRawYCursorPosition, mEventTime, mEventTime,
+                         mPointers.size(), pointerProperties.data(), pointerCoords.data());
+
+        return event;
+    }
+
+private:
+    int32_t mAction;
+    int32_t mSource;
+    nsecs_t mEventTime;
+    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    int32_t mActionButton{0};
+    int32_t mButtonState{0};
+    float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+
+    std::vector<PointerBuilder> mPointers;
+};
+
+static int32_t injectMotionEvent(
+        const sp<InputDispatcher>& dispatcher, const MotionEvent& event,
+        std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
+        int32_t injectionMode = INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT) {
+    return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, injectionMode,
+                                        injectionTimeout,
+                                        POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+}
+
 static int32_t injectMotionEvent(
         const sp<InputDispatcher>& dispatcher, int32_t action, int32_t source, int32_t displayId,
         const PointF& position,
@@ -947,32 +1067,18 @@
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
         int32_t injectionMode = INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT,
         nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC)) {
-    MotionEvent event;
-    PointerProperties pointerProperties[1];
-    PointerCoords pointerCoords[1];
-
-    pointerProperties[0].clear();
-    pointerProperties[0].id = 0;
-    pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-
-    pointerCoords[0].clear();
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
-    pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
-
-    // Define a valid motion down event.
-    event.initialize(InputEvent::nextId(), DEVICE_ID, source, displayId, INVALID_HMAC, action,
-                     /* actionButton */ 0,
-                     /* flags */ 0,
-                     /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
-                     /* xScale */ 1, /* yScale */ 1, /* xOffset */ 0, /* yOffset */ 0,
-                     /* xPrecision */ 0, /* yPrecision */ 0, cursorPosition.x, cursorPosition.y,
-                     eventTime, eventTime,
-                     /*pointerCount*/ 1, pointerProperties, pointerCoords);
+    MotionEvent event = MotionEventBuilder(action, source)
+                                .displayId(displayId)
+                                .eventTime(eventTime)
+                                .rawXCursorPosition(cursorPosition.x)
+                                .rawYCursorPosition(cursorPosition.y)
+                                .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                                 .x(position.x)
+                                                 .y(position.y))
+                                .build();
 
     // Inject event until dispatch out.
-    return dispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID, injectionMode,
-                                        injectionTimeout,
-                                        POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+    return injectMotionEvent(dispatcher, event);
 }
 
 static int32_t injectMotionDown(const sp<InputDispatcher>& dispatcher, int32_t source,
@@ -1187,6 +1293,198 @@
     windowSecond->consumeKeyDown(ADISPLAY_ID_NONE);
 }
 
+TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> windowLeft =
+            new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    windowLeft->setFrame(Rect(0, 0, 600, 800));
+    windowLeft->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
+    sp<FakeWindowHandle> windowRight =
+            new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    windowRight->setFrame(Rect(600, 0, 1200, 800));
+    windowRight->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowLeft, windowRight}}});
+
+    // Start cursor position in right window so that we can move the cursor to left window.
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(900)
+                                                         .y(400))
+                                        .build()));
+    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
+                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE,
+                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+
+    // Move cursor into left window
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT,
+                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
+                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE,
+                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+
+    // Inject a series of mouse events for a mouse click
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                        .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .buttonState(0)
+                                        .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                        .buttonState(0)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    windowLeft->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+    // Move mouse cursor back to right window
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(900)
+                                                         .y(400))
+                                        .build()));
+    windowLeft->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT,
+                             ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
+                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+    windowRight->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_MOVE,
+                              ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+}
+
+// This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected
+// directly in this test.
+TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) {
+    sp<FakeApplicationHandle> application = new FakeApplicationHandle();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 1200, 800));
+    window->setLayoutParamFlags(InputWindowInfo::FLAG_NOT_TOUCH_MODAL);
+
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_ENTER,
+                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+
+    // Inject a series of mouse events for a mouse click
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                        .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_PRESS,
+                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .buttonState(0)
+                                        .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                        .buttonState(0)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(INPUT_EVENT_INJECTION_SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT,
+                                                   AINPUT_SOURCE_MOUSE)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+                                                         .x(300)
+                                                         .y(400))
+                                        .build()));
+    window->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_HOVER_EXIT,
+                         ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
+}
+
 TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
     sp<FakeApplicationHandle> application = new FakeApplicationHandle();
 
diff --git a/services/inputflinger/tests/InputFlingerService_test.cpp b/services/inputflinger/tests/InputFlingerService_test.cpp
index fd3f562..6d50519 100644
--- a/services/inputflinger/tests/InputFlingerService_test.cpp
+++ b/services/inputflinger/tests/InputFlingerService_test.cpp
@@ -402,7 +402,9 @@
     } else {
         android::ProcessState::self()->startThreadPool();
         ::testing::InitGoogleTest(&argc, argv);
-        return RUN_ALL_TESTS();
+        int result = RUN_ALL_TESTS();
+        kill(forkPid, SIGKILL);
+        return result;
     }
     return 0;
 }
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index 1156f55..89284f2 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -324,9 +324,6 @@
         }
 
         uint64_t bufferID = mQueueItems[0].mGraphicBuffer->getId();
-        mFlinger->mFrameTracer->traceFence(layerId, bufferID, currentFrameNumber,
-                                           mQueueItems[0].mFenceTime,
-                                           FrameTracer::FrameEvent::ACQUIRE_FENCE);
         mFlinger->mTimeStats->setLatchTime(layerId, currentFrameNumber, latchTime);
         mFlinger->mFrameTracer->traceTimestamp(layerId, bufferID, currentFrameNumber, latchTime,
                                                FrameTracer::FrameEvent::LATCH);
@@ -393,8 +390,12 @@
 
 void BufferQueueLayer::onFrameAvailable(const BufferItem& item) {
     const int32_t layerId = getSequence();
-    mFlinger->mFrameTracer->traceTimestamp(layerId, item.mGraphicBuffer->getId(), item.mFrameNumber,
-                                           systemTime(), FrameTracer::FrameEvent::QUEUE);
+    const uint64_t bufferId = item.mGraphicBuffer->getId();
+    mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, item.mFrameNumber, systemTime(),
+                                           FrameTracer::FrameEvent::QUEUE);
+    mFlinger->mFrameTracer->traceFence(layerId, bufferId, item.mFrameNumber,
+                                       std::make_shared<FenceTime>(item.mFence),
+                                       FrameTracer::FrameEvent::ACQUIRE_FENCE);
 
     ATRACE_CALL();
     // Add this buffer from our internal queue tracker
@@ -460,8 +461,12 @@
     }
 
     const int32_t layerId = getSequence();
-    mFlinger->mFrameTracer->traceTimestamp(layerId, item.mGraphicBuffer->getId(), item.mFrameNumber,
-                                           systemTime(), FrameTracer::FrameEvent::QUEUE);
+    const uint64_t bufferId = item.mGraphicBuffer->getId();
+    mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, item.mFrameNumber, systemTime(),
+                                           FrameTracer::FrameEvent::QUEUE);
+    mFlinger->mFrameTracer->traceFence(layerId, bufferId, item.mFrameNumber,
+                                       std::make_shared<FenceTime>(item.mFence),
+                                       FrameTracer::FrameEvent::ACQUIRE_FENCE);
     mConsumer->onBufferAvailable(item);
 }
 
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 8cfb908..67becab 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2385,8 +2385,8 @@
     }
 
     ui::Transform t = getTransform();
-    const float xScale = t.sx();
-    const float yScale = t.sy();
+    const float xScale = t.dsdx();
+    const float yScale = t.dsdy();
     int32_t xSurfaceInset = info.surfaceInset;
     int32_t ySurfaceInset = info.surfaceInset;
     if (xScale != 1.0f || yScale != 1.0f) {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index ecf2597..36433c2 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -20,6 +20,7 @@
 
 #include "LayerHistory.h"
 
+#include <android-base/stringprintf.h>
 #include <cutils/properties.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
@@ -183,4 +184,11 @@
 
     mActiveLayersEnd = 0;
 }
+
+std::string LayerHistory::dump() const {
+    std::lock_guard lock(mLock);
+    return base::StringPrintf("LayerHistory{size=%zu, active=%zu}", mLayerInfos.size(),
+                              mActiveLayersEnd);
+}
+
 } // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 228b8a0..128699b 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -22,6 +22,7 @@
 
 #include <memory>
 #include <mutex>
+#include <string>
 #include <utility>
 #include <vector>
 
@@ -71,6 +72,7 @@
     virtual Summary summarize(nsecs_t now) = 0;
 
     virtual void clear() = 0;
+    virtual std::string dump() const = 0;
 };
 
 namespace impl {
@@ -97,6 +99,7 @@
     android::scheduler::LayerHistory::Summary summarize(nsecs_t now) override;
 
     void clear() override;
+    std::string dump() const override;
 
 private:
     friend class android::scheduler::LayerHistoryTest;
@@ -155,6 +158,7 @@
     android::scheduler::LayerHistory::Summary summarize(nsecs_t /*now*/) override;
 
     void clear() override;
+    std::string dump() const override;
 
 private:
     friend android::scheduler::LayerHistoryTestV2;
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
index aa04bd7..37e67e1 100644
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
@@ -20,6 +20,7 @@
 
 #include "LayerHistory.h"
 
+#include <android-base/stringprintf.h>
 #include <cutils/properties.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
@@ -31,9 +32,8 @@
 #include <utility>
 
 #include "../Layer.h"
-#include "SchedulerUtils.h"
-
 #include "LayerInfoV2.h"
+#include "SchedulerUtils.h"
 
 namespace android::scheduler::impl {
 
@@ -214,4 +214,10 @@
     }
 }
 
+std::string LayerHistoryV2::dump() const {
+    std::lock_guard lock(mLock);
+    return base::StringPrintf("LayerHistoryV2{size=%zu, active=%zu}", mLayerInfos.size(),
+                              mActiveLayersEnd);
+}
+
 } // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 27bf0ec..eed8486 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -280,6 +280,11 @@
     RefreshRateConfigs(const std::vector<std::shared_ptr<const HWC2::Display::Config>>& configs,
                        HwcConfigIndexType currentConfigId);
 
+    // Returns whether switching configs (refresh rate or resolution) is possible.
+    // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the configs only
+    // differ in resolution.
+    bool canSwitch() const { return mRefreshRates.size() > 1; }
+
     // Class to enumerate options around toggling the kernel timer on and off. We have an option
     // for no change to avoid extra calls to kernel.
     enum class KernelIdleTimerAction {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 5c0ba01..ab63d64 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -61,6 +61,7 @@
     } while (false)
 
 namespace android {
+namespace {
 
 std::unique_ptr<DispSync> createDispSync(bool supportKernelTimer) {
     // TODO (140302863) remove this and use the vsync_reactor system.
@@ -97,25 +98,27 @@
     }
 }
 
+const char* toContentDetectionString(bool useContentDetection, bool useContentDetectionV2) {
+    if (!useContentDetection) return "off";
+    return useContentDetectionV2 ? "V2" : "V1";
+}
+
+} // namespace
+
 Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
-                     const scheduler::RefreshRateConfigs& refreshRateConfig,
+                     const scheduler::RefreshRateConfigs& refreshRateConfigs,
                      ISchedulerCallback& schedulerCallback, bool useContentDetectionV2,
                      bool useContentDetection)
       : mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
         mPrimaryDispSync(createDispSync(mSupportKernelTimer)),
         mEventControlThread(new impl::EventControlThread(std::move(function))),
+        mLayerHistory(createLayerHistory(refreshRateConfigs, useContentDetectionV2)),
         mSchedulerCallback(schedulerCallback),
-        mRefreshRateConfigs(refreshRateConfig),
+        mRefreshRateConfigs(refreshRateConfigs),
         mUseContentDetection(useContentDetection),
         mUseContentDetectionV2(useContentDetectionV2) {
     using namespace sysprop;
 
-    if (mUseContentDetectionV2) {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(refreshRateConfig);
-    } else {
-        mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
-    }
-
     const int setIdleTimerMs = property_get_int32("debug.sf.set_idle_timer_ms", 0);
 
     if (const auto millis = setIdleTimerMs ? setIdleTimerMs : set_idle_timer_ms(0); millis > 0) {
@@ -149,11 +152,13 @@
 Scheduler::Scheduler(std::unique_ptr<DispSync> primaryDispSync,
                      std::unique_ptr<EventControlThread> eventControlThread,
                      const scheduler::RefreshRateConfigs& configs,
-                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2,
+                     ISchedulerCallback& schedulerCallback,
+                     std::unique_ptr<LayerHistory> layerHistory, bool useContentDetectionV2,
                      bool useContentDetection)
       : mSupportKernelTimer(false),
         mPrimaryDispSync(std::move(primaryDispSync)),
         mEventControlThread(std::move(eventControlThread)),
+        mLayerHistory(std::move(layerHistory)),
         mSchedulerCallback(schedulerCallback),
         mRefreshRateConfigs(configs),
         mUseContentDetection(useContentDetection),
@@ -166,6 +171,17 @@
     mIdleTimer.reset();
 }
 
+std::unique_ptr<LayerHistory> Scheduler::createLayerHistory(
+        const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2) {
+    if (!configs.canSwitch()) return nullptr;
+
+    if (useContentDetectionV2) {
+        return std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
+    }
+
+    return std::make_unique<scheduler::impl::LayerHistory>();
+}
+
 DispSync& Scheduler::getPrimaryDispSync() {
     return *mPrimaryDispSync;
 }
@@ -499,16 +515,7 @@
 }
 
 void Scheduler::notifyTouchEvent() {
-    if (!mTouchTimer) return;
-
-    // Touch event will boost the refresh rate to performance.
-    // Clear Layer History to get fresh FPS detection.
-    // NOTE: Instead of checking all the layers, we should be checking the layer
-    // that is currently on top. b/142507166 will give us this capability.
-    std::lock_guard<std::mutex> lock(mFeatureStateLock);
-    if (mLayerHistory) {
-        // Layer History will be cleared based on RefreshRateConfigs::getBestRefreshRate
-
+    if (mTouchTimer) {
         mTouchTimer->reset();
 
         if (mSupportKernelTimer && mIdleTimer) {
@@ -564,8 +571,14 @@
 
 void Scheduler::touchTimerCallback(TimerState state) {
     const TouchState touch = state == TimerState::Reset ? TouchState::Active : TouchState::Inactive;
+    // Touch event will boost the refresh rate to performance.
+    // Clear layer history to get fresh FPS detection.
+    // NOTE: Instead of checking all the layers, we should be checking the layer
+    // that is currently on top. b/142507166 will give us this capability.
     if (handleTimerStateChanged(&mFeatures.touch, touch)) {
-        mLayerHistory->clear();
+        if (mLayerHistory) {
+            mLayerHistory->clear();
+        }
     }
     ATRACE_INT("TouchState", static_cast<int>(touch));
 }
@@ -577,14 +590,13 @@
 
 void Scheduler::dump(std::string& result) const {
     using base::StringAppendF;
-    const char* const states[] = {"off", "on"};
 
-    StringAppendF(&result, "+  Idle timer: %s\n",
-                  mIdleTimer ? mIdleTimer->dump().c_str() : states[0]);
+    StringAppendF(&result, "+  Idle timer: %s\n", mIdleTimer ? mIdleTimer->dump().c_str() : "off");
     StringAppendF(&result, "+  Touch timer: %s\n",
-                  mTouchTimer ? mTouchTimer->dump().c_str() : states[0]);
-    StringAppendF(&result, "+  Use content detection: %s\n\n",
-                  sysprop::use_content_detection_for_refresh_rate(false) ? "on" : "off");
+                  mTouchTimer ? mTouchTimer->dump().c_str() : "off");
+    StringAppendF(&result, "+  Content detection: %s %s\n\n",
+                  toContentDetectionString(mUseContentDetection, mUseContentDetectionV2),
+                  mLayerHistory ? mLayerHistory->dump().c_str() : "(no layer history)");
 }
 
 template <class T>
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 730ea8f..5fee4b4 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -174,8 +174,11 @@
 
     // Used by tests to inject mocks.
     Scheduler(std::unique_ptr<DispSync>, std::unique_ptr<EventControlThread>,
-              const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
-              bool useContentDetectionV2, bool useContentDetection);
+              const scheduler::RefreshRateConfigs&, ISchedulerCallback&,
+              std::unique_ptr<LayerHistory>, bool useContentDetectionV2, bool useContentDetection);
+
+    static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&,
+                                                            bool useContentDetectionV2);
 
     std::unique_ptr<VSyncSource> makePrimaryDispSyncSource(const char* name, nsecs_t phaseOffsetNs);
 
@@ -231,7 +234,7 @@
     std::unique_ptr<EventControlThread> mEventControlThread;
 
     // Used to choose refresh rate if content detection is enabled.
-    std::unique_ptr<LayerHistory> mLayerHistory;
+    const std::unique_ptr<LayerHistory> mLayerHistory;
 
     // Timer that records time between requests for next vsync.
     std::optional<scheduler::OneShotTimer> mIdleTimer;
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 9355e61..9add6a5 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -150,9 +150,14 @@
                 .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_REFRESH_RATE));
         EXPECT_CALL(*primaryDispSync, expectedPresentTime(_)).WillRepeatedly(Return(0));
 
+        constexpr bool kHasMultipleConfigs = true;
         mFlinger.setupScheduler(std::move(primaryDispSync),
                                 std::make_unique<mock::EventControlThread>(),
-                                std::move(eventThread), std::move(sfEventThread));
+                                std::move(eventThread), std::move(sfEventThread),
+                                kHasMultipleConfigs);
+
+        // Layer history should be created if there are multiple configs.
+        ASSERT_TRUE(mFlinger.scheduler()->hasLayerHistory());
     }
 
     void setupForceGeometryDirty() {
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index cae317b..3ab22d3 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -49,6 +49,8 @@
 
     LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
 
+    void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
+
     impl::LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
     const impl::LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
 
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
index afd2b71..1705835 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
@@ -50,6 +50,8 @@
 
     LayerHistoryTestV2() { mFlinger.resetScheduler(mScheduler); }
 
+    void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
+
     impl::LayerHistoryV2& history() { return *mScheduler->mutableLayerHistoryV2(); }
     const impl::LayerHistoryV2& history() const { return *mScheduler->mutableLayerHistoryV2(); }
 
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 1aa7320..382199d 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -31,8 +31,10 @@
 #include "Scheduler/EventThread.h"
 #include "Scheduler/RefreshRateConfigs.h"
 #include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockDisplay.h"
 #include "mock/MockEventThread.h"
+#include "mock/MockLayer.h"
 
 using testing::_;
 using testing::Return;
@@ -58,13 +60,18 @@
     SchedulerTest();
     ~SchedulerTest() override;
 
-    std::unique_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
-    std::unique_ptr<TestableScheduler> mScheduler;
+    Hwc2::mock::Display mDisplay;
+    const scheduler::RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
+                                                          .setVsyncPeriod(16'666'667)
+                                                          .setConfigGroup(0)
+                                                          .build()},
+                                                 HwcConfigIndexType(0)};
+
+    TestableScheduler mScheduler{mConfigs, false};
 
     Scheduler::ConnectionHandle mConnectionHandle;
     mock::EventThread* mEventThread;
     sp<MockEventThreadConnection> mEventThreadConnection;
-    Hwc2::mock::Display mDisplay;
 };
 
 SchedulerTest::SchedulerTest() {
@@ -72,16 +79,6 @@
             ::testing::UnitTest::GetInstance()->current_test_info();
     ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
 
-    std::vector<std::shared_ptr<const HWC2::Display::Config>> configs{
-            HWC2::Display::Config::Builder(mDisplay, 0)
-                    .setVsyncPeriod(int32_t(16666667))
-                    .setConfigGroup(0)
-                    .build()};
-    mRefreshRateConfigs = std::make_unique<
-            scheduler::RefreshRateConfigs>(configs, /*currentConfig=*/HwcConfigIndexType(0));
-
-    mScheduler = std::make_unique<TestableScheduler>(*mRefreshRateConfigs, false);
-
     auto eventThread = std::make_unique<mock::EventThread>();
     mEventThread = eventThread.get();
     EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
@@ -93,7 +90,7 @@
     EXPECT_CALL(*mEventThread, createEventConnection(_, _))
             .WillRepeatedly(Return(mEventThreadConnection));
 
-    mConnectionHandle = mScheduler->createConnection(std::move(eventThread));
+    mConnectionHandle = mScheduler.createConnection(std::move(eventThread));
     EXPECT_TRUE(mConnectionHandle);
 }
 
@@ -103,7 +100,6 @@
     ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
 }
 
-namespace {
 /* ------------------------------------------------------------------------
  * Test cases
  */
@@ -111,67 +107,84 @@
 TEST_F(SchedulerTest, invalidConnectionHandle) {
     Scheduler::ConnectionHandle handle;
 
-    sp<IDisplayEventConnection> connection;
-    ASSERT_NO_FATAL_FAILURE(
-            connection = mScheduler->createDisplayEventConnection(handle,
-                                                                  ISurfaceComposer::
-                                                                          eConfigChangedSuppress));
+    const sp<IDisplayEventConnection> connection =
+            mScheduler.createDisplayEventConnection(handle,
+                                                    ISurfaceComposer::eConfigChangedSuppress);
+
     EXPECT_FALSE(connection);
-    EXPECT_FALSE(mScheduler->getEventConnection(handle));
+    EXPECT_FALSE(mScheduler.getEventConnection(handle));
 
     // The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads.
     EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false));
+    mScheduler.onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->onScreenAcquired(handle));
+    mScheduler.onScreenAcquired(handle);
 
     EXPECT_CALL(*mEventThread, onScreenReleased()).Times(0);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->onScreenReleased(handle));
+    mScheduler.onScreenReleased(handle);
 
     std::string output;
     EXPECT_CALL(*mEventThread, dump(_)).Times(0);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->dump(handle, output));
+    mScheduler.dump(handle, output);
     EXPECT_TRUE(output.empty());
 
     EXPECT_CALL(*mEventThread, setPhaseOffset(_)).Times(0);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->setPhaseOffset(handle, 10));
+    mScheduler.setPhaseOffset(handle, 10);
 }
 
 TEST_F(SchedulerTest, validConnectionHandle) {
-    sp<IDisplayEventConnection> connection;
-    ASSERT_NO_FATAL_FAILURE(
-            connection = mScheduler->createDisplayEventConnection(mConnectionHandle,
-                                                                  ISurfaceComposer::
-                                                                          eConfigChangedSuppress));
+    const sp<IDisplayEventConnection> connection =
+            mScheduler.createDisplayEventConnection(mConnectionHandle,
+                                                    ISurfaceComposer::eConfigChangedSuppress);
+
     ASSERT_EQ(mEventThreadConnection, connection);
-    EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle));
+    EXPECT_TRUE(mScheduler.getEventConnection(mConnectionHandle));
 
     EXPECT_CALL(*mEventThread, onHotplugReceived(PHYSICAL_DISPLAY_ID, false)).Times(1);
-    ASSERT_NO_FATAL_FAILURE(
-            mScheduler->onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false));
+    mScheduler.onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false);
 
     EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->onScreenAcquired(mConnectionHandle));
+    mScheduler.onScreenAcquired(mConnectionHandle);
 
     EXPECT_CALL(*mEventThread, onScreenReleased()).Times(1);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->onScreenReleased(mConnectionHandle));
+    mScheduler.onScreenReleased(mConnectionHandle);
 
     std::string output("dump");
     EXPECT_CALL(*mEventThread, dump(output)).Times(1);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->dump(mConnectionHandle, output));
+    mScheduler.dump(mConnectionHandle, output);
     EXPECT_FALSE(output.empty());
 
     EXPECT_CALL(*mEventThread, setPhaseOffset(10)).Times(1);
-    ASSERT_NO_FATAL_FAILURE(mScheduler->setPhaseOffset(mConnectionHandle, 10));
+    mScheduler.setPhaseOffset(mConnectionHandle, 10);
 
     static constexpr size_t kEventConnections = 5;
     ON_CALL(*mEventThread, getEventThreadConnectionCount())
             .WillByDefault(Return(kEventConnections));
-    EXPECT_EQ(kEventConnections, mScheduler->getEventThreadConnectionCount(mConnectionHandle));
+    EXPECT_EQ(kEventConnections, mScheduler.getEventThreadConnectionCount(mConnectionHandle));
 }
 
-} // namespace
+TEST_F(SchedulerTest, noLayerHistory) {
+    // Layer history should not be created if there is a single config.
+    ASSERT_FALSE(mScheduler.hasLayerHistory());
+
+    TestableSurfaceFlinger flinger;
+    mock::MockLayer layer(flinger.flinger());
+
+    // Content detection should be no-op.
+    mScheduler.registerLayer(&layer);
+    mScheduler.recordLayerHistory(&layer, 0, LayerHistory::LayerUpdateType::Buffer);
+
+    constexpr bool kPowerStateNormal = true;
+    mScheduler.setDisplayPowerState(kPowerStateNormal);
+
+    constexpr uint32_t kDisplayArea = 999'999;
+    mScheduler.onPrimaryDisplayAreaChanged(kDisplayArea);
+
+    mScheduler.chooseRefreshRateForContent();
+    EXPECT_EQ(0, mScheduler.refreshRateChangeCount());
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index b7067a6..c463294 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -29,41 +29,20 @@
 class TestableScheduler : public Scheduler, private ISchedulerCallback {
 public:
     TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
-          : Scheduler([](bool) {}, configs, *this, useContentDetectionV2, true) {
-        if (mUseContentDetectionV2) {
-            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
-        } else {
-            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
-        }
-    }
+          : Scheduler([](bool) {}, configs, *this, useContentDetectionV2, true) {}
 
     TestableScheduler(std::unique_ptr<DispSync> primaryDispSync,
                       std::unique_ptr<EventControlThread> eventControlThread,
                       const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
           : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
-                      useContentDetectionV2, true) {
-        if (mUseContentDetectionV2) {
-            mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
-        } else {
-            mLayerHistory = std::make_unique<scheduler::impl::LayerHistory>();
-        }
-    }
+                      createLayerHistory(configs, useContentDetectionV2), useContentDetectionV2,
+                      true) {}
 
     // Used to inject mock event thread.
     ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
         return Scheduler::createConnection(std::move(eventThread));
     }
 
-    size_t layerHistorySize() const NO_THREAD_SAFETY_ANALYSIS {
-        if (mUseContentDetectionV2) {
-            return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get())
-                    ->mLayerInfos.size();
-        } else {
-            return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get())
-                    ->mLayerInfos.size();
-        }
-    }
-
     /* ------------------------------------------------------------------------
      * Read-write access to private data to set up preconditions and assert
      * post-conditions.
@@ -73,14 +52,26 @@
     auto& mutablePrimaryDispSync() { return mPrimaryDispSync; }
     auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
 
-    auto mutableLayerHistory() {
+    size_t refreshRateChangeCount() const { return mRefreshRateChangeCount; }
+
+    bool hasLayerHistory() const { return static_cast<bool>(mLayerHistory); }
+
+    auto* mutableLayerHistory() {
+        LOG_ALWAYS_FATAL_IF(mUseContentDetectionV2);
         return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get());
     }
 
-    auto mutableLayerHistoryV2() {
+    auto* mutableLayerHistoryV2() {
+        LOG_ALWAYS_FATAL_IF(!mUseContentDetectionV2);
         return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get());
     }
 
+    size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
+        if (!mLayerHistory) return 0;
+        return mUseContentDetectionV2 ? mutableLayerHistoryV2()->mLayerInfos.size()
+                                      : mutableLayerHistory()->mLayerInfos.size();
+    }
+
     void replaceTouchTimer(int64_t millis) {
         if (mTouchTimer) {
             mTouchTimer.reset();
@@ -108,9 +99,12 @@
     }
 
 private:
-    void changeRefreshRate(const RefreshRate&, ConfigEvent) override {}
+    void changeRefreshRate(const RefreshRate&, ConfigEvent) override { mRefreshRateChangeCount++; }
+
     void repaintEverythingForHWC() override {}
     void kernelTimerChanged(bool /*expired*/) override {}
+
+    size_t mRefreshRateChangeCount = 0;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index f630103..256e048 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -202,13 +202,20 @@
                         std::unique_ptr<EventControlThread> eventControlThread,
                         std::unique_ptr<EventThread> appEventThread,
                         std::unique_ptr<EventThread> sfEventThread,
-                        bool useContentDetectionV2 = false) {
+                        bool hasMultipleConfigs = false) {
         std::vector<std::shared_ptr<const HWC2::Display::Config>> configs{
                 HWC2::Display::Config::Builder(mDisplay, 0)
-                        .setVsyncPeriod(int32_t(16666667))
+                        .setVsyncPeriod(16'666'667)
                         .setConfigGroup(0)
                         .build()};
 
+        if (hasMultipleConfigs) {
+            configs.emplace_back(HWC2::Display::Config::Builder(mDisplay, 1)
+                                         .setVsyncPeriod(11'111'111)
+                                         .setConfigGroup(0)
+                                         .build());
+        }
+
         mFlinger->mRefreshRateConfigs = std::make_unique<
                 scheduler::RefreshRateConfigs>(configs, /*currentConfig=*/HwcConfigIndexType(0));
         mFlinger->mRefreshRateStats = std::make_unique<
@@ -218,9 +225,10 @@
         mFlinger->mPhaseConfiguration =
                 mFactory.createPhaseConfiguration(*mFlinger->mRefreshRateConfigs);
 
+        constexpr bool kUseContentDetectionV2 = false;
         mScheduler =
                 new TestableScheduler(std::move(primaryDispSync), std::move(eventControlThread),
-                                      *mFlinger->mRefreshRateConfigs, useContentDetectionV2);
+                                      *mFlinger->mRefreshRateConfigs, kUseContentDetectionV2);
 
         mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
         mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
index 896c162..03e51ae 100644
--- a/services/vibratorservice/VibratorHalController.cpp
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -47,105 +47,77 @@
 
 static constexpr int MAX_RETRIES = 1;
 
-template <typename T>
-using hal_connect_fn = std::function<sp<T>()>;
-
-template <typename T>
-sp<T> connectToHal(bool* halExists, const hal_connect_fn<T>& connectFn, const char* halName) {
-    if (!*halExists) {
+std::shared_ptr<HalWrapper> HalConnector::connect(std::shared_ptr<CallbackScheduler> scheduler) {
+    static bool gHalExists = true;
+    if (!gHalExists) {
+        // We already tried to connect to all of the vibrator HAL versions and none was available.
         return nullptr;
     }
-    sp<T> hal = connectFn();
-    if (hal) {
-        ALOGV("Successfully connected to Vibrator HAL %s service.", halName);
-    } else {
-        ALOGV("Vibrator HAL %s service not available.", halName);
-        *halExists = false;
-    }
-    return hal;
-}
 
-sp<Aidl::IVibrator> connectToAidl() {
-    static bool gHalExists = true;
-    static hal_connect_fn<Aidl::IVibrator> connectFn = []() {
-        return waitForVintfService<Aidl::IVibrator>();
-    };
-    return connectToHal(&gHalExists, connectFn, "AIDL");
-}
-
-sp<V1_0::IVibrator> connectToHidl() {
-    static bool gHalExists = true;
-    static hal_connect_fn<V1_0::IVibrator> connectFn = []() {
-        return V1_0::IVibrator::getService();
-    };
-    return connectToHal(&gHalExists, connectFn, "v1.0");
-}
-
-// -------------------------------------------------------------------------------------------------
-
-std::shared_ptr<HalWrapper> HalConnector::connect(std::shared_ptr<CallbackScheduler> scheduler) {
-    sp<Aidl::IVibrator> aidlHal = connectToAidl();
+    sp<Aidl::IVibrator> aidlHal = waitForVintfService<Aidl::IVibrator>();
     if (aidlHal) {
+        ALOGV("Successfully connected to Vibrator HAL AIDL service.");
         return std::make_shared<AidlHalWrapper>(std::move(scheduler), aidlHal);
     }
-    sp<V1_0::IVibrator> halV1_0 = connectToHidl();
+
+    sp<V1_0::IVibrator> halV1_0 = V1_0::IVibrator::getService();
     if (halV1_0 == nullptr) {
-        // No Vibrator HAL service available.
+        ALOGV("Vibrator HAL service not available.");
+        gHalExists = false;
         return nullptr;
     }
+
     sp<V1_3::IVibrator> halV1_3 = V1_3::IVibrator::castFrom(halV1_0);
     if (halV1_3) {
-        ALOGV("Successfully converted to Vibrator HAL v1.3 service.");
+        ALOGV("Successfully connected to Vibrator HAL v1.3 service.");
         return std::make_shared<HidlHalWrapperV1_3>(std::move(scheduler), halV1_3);
     }
     sp<V1_2::IVibrator> halV1_2 = V1_2::IVibrator::castFrom(halV1_0);
     if (halV1_2) {
-        ALOGV("Successfully converted to Vibrator HAL v1.2 service.");
+        ALOGV("Successfully connected to Vibrator HAL v1.2 service.");
         return std::make_shared<HidlHalWrapperV1_2>(std::move(scheduler), halV1_2);
     }
     sp<V1_1::IVibrator> halV1_1 = V1_1::IVibrator::castFrom(halV1_0);
     if (halV1_1) {
-        ALOGV("Successfully converted to Vibrator HAL v1.1 service.");
+        ALOGV("Successfully connected to Vibrator HAL v1.1 service.");
         return std::make_shared<HidlHalWrapperV1_1>(std::move(scheduler), halV1_1);
     }
+    ALOGV("Successfully connected to Vibrator HAL v1.0 service.");
     return std::make_shared<HidlHalWrapperV1_0>(std::move(scheduler), halV1_0);
 }
 
 // -------------------------------------------------------------------------------------------------
 
-std::shared_ptr<HalWrapper> HalController::initHal() {
-    std::lock_guard<std::mutex> lock(mConnectedHalMutex);
-    if (mConnectedHal == nullptr) {
-        mConnectedHal = mHalConnector->connect(mCallbackScheduler);
-    }
-    return mConnectedHal;
-}
-
 template <typename T>
 HalResult<T> HalController::processHalResult(HalResult<T> result, const char* functionName) {
     if (result.isFailed()) {
         ALOGE("%s failed: Vibrator HAL not available", functionName);
         std::lock_guard<std::mutex> lock(mConnectedHalMutex);
-        // Drop HAL handle. This will force future api calls to reconnect.
-        mConnectedHal = nullptr;
+        mConnectedHal->tryReconnect();
     }
     return result;
 }
 
 template <typename T>
 HalResult<T> HalController::apply(HalController::hal_fn<T>& halFn, const char* functionName) {
-    std::shared_ptr<HalWrapper> hal = initHal();
-    if (hal == nullptr) {
-        ALOGV("Skipped %s because Vibrator HAL is not available", functionName);
-        return HalResult<T>::unsupported();
+    std::shared_ptr<HalWrapper> hal = nullptr;
+    {
+        std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+        if (mConnectedHal == nullptr) {
+            // Init was never called, so connect to HAL for the first time during this call.
+            mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+
+            if (mConnectedHal == nullptr) {
+                ALOGV("Skipped %s because Vibrator HAL is not available", functionName);
+                return HalResult<T>::unsupported();
+            }
+        }
+        hal = mConnectedHal;
     }
 
     HalResult<T> ret = processHalResult(halFn(hal), functionName);
     for (int i = 0; i < MAX_RETRIES && ret.isFailed(); i++) {
-        hal = initHal();
-        if (hal) {
-            ret = processHalResult(halFn(hal), functionName);
-        }
+        ret = processHalResult(halFn(hal), functionName);
     }
 
     return ret;
@@ -153,11 +125,27 @@
 
 // -------------------------------------------------------------------------------------------------
 
+void HalController::init() {
+    std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+    if (mConnectedHal == nullptr) {
+        mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+    }
+}
+
 HalResult<void> HalController::ping() {
     hal_fn<void> pingFn = [](std::shared_ptr<HalWrapper> hal) { return hal->ping(); };
     return apply(pingFn, "ping");
 }
 
+void HalController::tryReconnect() {
+    std::lock_guard<std::mutex> lock(mConnectedHalMutex);
+    if (mConnectedHal == nullptr) {
+        mConnectedHal = mHalConnector->connect(mCallbackScheduler);
+    } else {
+        mConnectedHal->tryReconnect();
+    }
+}
+
 HalResult<void> HalController::on(milliseconds timeout,
                                   const std::function<void()>& completionCallback) {
     hal_fn<void> onFn = [&](std::shared_ptr<HalWrapper> hal) {
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index 94db538..9d219ff 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -149,8 +149,16 @@
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> AidlHalWrapper::ping() {
-    return IInterface::asBinder(mHandle)->pingBinder() ? HalResult<void>::ok()
-                                                       : HalResult<void>::failed();
+    return IInterface::asBinder(getHal())->pingBinder() ? HalResult<void>::ok()
+                                                        : HalResult<void>::failed();
+}
+
+void AidlHalWrapper::tryReconnect() {
+    sp<Aidl::IVibrator> newHandle = checkVintfService<Aidl::IVibrator>();
+    if (newHandle) {
+        std::lock_guard<std::mutex> lock(mHandleMutex);
+        mHandle = std::move(newHandle);
+    }
 }
 
 HalResult<void> AidlHalWrapper::on(milliseconds timeout,
@@ -160,7 +168,7 @@
             static_cast<int32_t>(capabilities.value() & Capabilities::ON_CALLBACK);
     auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
 
-    auto ret = HalResult<void>::fromStatus(mHandle->on(timeout.count(), cb));
+    auto ret = HalResult<void>::fromStatus(getHal()->on(timeout.count(), cb));
     if (!supportsCallback && ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, timeout);
     }
@@ -169,24 +177,24 @@
 }
 
 HalResult<void> AidlHalWrapper::off() {
-    return HalResult<void>::fromStatus(mHandle->off());
+    return HalResult<void>::fromStatus(getHal()->off());
 }
 
 HalResult<void> AidlHalWrapper::setAmplitude(int32_t amplitude) {
     float convertedAmplitude = static_cast<float>(amplitude) / std::numeric_limits<uint8_t>::max();
-    return HalResult<void>::fromStatus(mHandle->setAmplitude(convertedAmplitude));
+    return HalResult<void>::fromStatus(getHal()->setAmplitude(convertedAmplitude));
 }
 
 HalResult<void> AidlHalWrapper::setExternalControl(bool enabled) {
-    return HalResult<void>::fromStatus(mHandle->setExternalControl(enabled));
+    return HalResult<void>::fromStatus(getHal()->setExternalControl(enabled));
 }
 
 HalResult<void> AidlHalWrapper::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) {
-    return HalResult<void>::fromStatus(mHandle->alwaysOnEnable(id, effect, strength));
+    return HalResult<void>::fromStatus(getHal()->alwaysOnEnable(id, effect, strength));
 }
 
 HalResult<void> AidlHalWrapper::alwaysOnDisable(int32_t id) {
-    return HalResult<void>::fromStatus(mHandle->alwaysOnDisable(id));
+    return HalResult<void>::fromStatus(getHal()->alwaysOnDisable(id));
 }
 
 HalResult<Capabilities> AidlHalWrapper::getCapabilities() {
@@ -210,7 +218,7 @@
     auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
 
     int32_t lengthMs;
-    auto result = mHandle->perform(effect, strength, cb, &lengthMs);
+    auto result = getHal()->perform(effect, strength, cb, &lengthMs);
     milliseconds length = milliseconds(lengthMs);
 
     auto ret = HalResult<milliseconds>::fromStatus(result, length);
@@ -226,31 +234,47 @@
         const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
     auto cb = new HalCallbackWrapper(completionCallback);
-    return HalResult<void>::fromStatus(mHandle->compose(primitiveEffects, cb));
+    return HalResult<void>::fromStatus(getHal()->compose(primitiveEffects, cb));
 }
 
 HalResult<Capabilities> AidlHalWrapper::getCapabilitiesInternal() {
     int32_t capabilities = 0;
-    auto result = mHandle->getCapabilities(&capabilities);
+    auto result = getHal()->getCapabilities(&capabilities);
     return HalResult<Capabilities>::fromStatus(result, static_cast<Capabilities>(capabilities));
 }
 
 HalResult<std::vector<Effect>> AidlHalWrapper::getSupportedEffectsInternal() {
     std::vector<Effect> supportedEffects;
-    auto result = mHandle->getSupportedEffects(&supportedEffects);
+    auto result = getHal()->getSupportedEffects(&supportedEffects);
     return HalResult<std::vector<Effect>>::fromStatus(result, supportedEffects);
 }
 
+sp<Aidl::IVibrator> AidlHalWrapper::getHal() {
+    std::lock_guard<std::mutex> lock(mHandleMutex);
+    return mHandle;
+}
+
 // -------------------------------------------------------------------------------------------------
 
-HalResult<void> HidlHalWrapperV1_0::ping() {
-    auto result = mHandleV1_0->ping();
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::ping() {
+    auto result = getHal()->ping();
     return HalResult<void>::fromReturn(result);
 }
 
-HalResult<void> HidlHalWrapperV1_0::on(milliseconds timeout,
-                                       const std::function<void()>& completionCallback) {
-    auto result = mHandleV1_0->on(timeout.count());
+template <typename I>
+void HidlHalWrapper<I>::tryReconnect() {
+    sp<I> newHandle = I::tryGetService();
+    if (newHandle) {
+        std::lock_guard<std::mutex> lock(mHandleMutex);
+        mHandle = std::move(newHandle);
+    }
+}
+
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::on(milliseconds timeout,
+                                      const std::function<void()>& completionCallback) {
+    auto result = getHal()->on(timeout.count());
     auto ret = HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
     if (ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, timeout);
@@ -258,69 +282,68 @@
     return ret;
 }
 
-HalResult<void> HidlHalWrapperV1_0::off() {
-    auto result = mHandleV1_0->off();
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::off() {
+    auto result = getHal()->off();
     return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
-HalResult<void> HidlHalWrapperV1_0::setAmplitude(int32_t amplitude) {
-    auto result = mHandleV1_0->setAmplitude(static_cast<uint8_t>(amplitude));
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::setAmplitude(int32_t amplitude) {
+    auto result = getHal()->setAmplitude(static_cast<uint8_t>(amplitude));
     return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
-HalResult<void> HidlHalWrapperV1_0::setExternalControl(bool) {
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::setExternalControl(bool) {
     ALOGV("Skipped setExternalControl because Vibrator HAL does not support it");
     return HalResult<void>::unsupported();
 }
 
-HalResult<void> HidlHalWrapperV1_0::alwaysOnEnable(int32_t, Effect, EffectStrength) {
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::alwaysOnEnable(int32_t, Effect, EffectStrength) {
     ALOGV("Skipped alwaysOnEnable because Vibrator HAL AIDL is not available");
     return HalResult<void>::unsupported();
 }
 
-HalResult<void> HidlHalWrapperV1_0::alwaysOnDisable(int32_t) {
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::alwaysOnDisable(int32_t) {
     ALOGV("Skipped alwaysOnDisable because Vibrator HAL AIDL is not available");
     return HalResult<void>::unsupported();
 }
 
-HalResult<Capabilities> HidlHalWrapperV1_0::getCapabilities() {
+template <typename I>
+HalResult<Capabilities> HidlHalWrapper<I>::getCapabilities() {
     std::lock_guard<std::mutex> lock(mCapabilitiesMutex);
-    return loadCached<Capabilities>(std::bind(&HidlHalWrapperV1_0::getCapabilitiesInternal, this),
+    return loadCached<Capabilities>(std::bind(&HidlHalWrapper<I>::getCapabilitiesInternal, this),
                                     mCapabilities);
 }
 
-HalResult<std::vector<Effect>> HidlHalWrapperV1_0::getSupportedEffects() {
+template <typename I>
+HalResult<std::vector<Effect>> HidlHalWrapper<I>::getSupportedEffects() {
     ALOGV("Skipped getSupportedEffects because Vibrator HAL AIDL is not available");
     return HalResult<std::vector<Effect>>::unsupported();
 }
 
-HalResult<milliseconds> HidlHalWrapperV1_0::performEffect(
-        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
-    if (isStaticCastValid<V1_0::Effect>(effect)) {
-        return performInternalV1_0(effect, strength, completionCallback);
-    }
-
-    ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
-          Aidl::toString(effect).c_str());
-    return HalResult<milliseconds>::unsupported();
-}
-
-HalResult<void> HidlHalWrapperV1_0::performComposedEffect(const std::vector<CompositeEffect>&,
-                                                          const std::function<void()>&) {
+template <typename I>
+HalResult<void> HidlHalWrapper<I>::performComposedEffect(const std::vector<CompositeEffect>&,
+                                                         const std::function<void()>&) {
     ALOGV("Skipped composed effect because Vibrator HAL AIDL is not available");
     return HalResult<void>::unsupported();
 }
 
-HalResult<Capabilities> HidlHalWrapperV1_0::getCapabilitiesInternal() {
-    hardware::Return<bool> result = mHandleV1_0->supportsAmplitudeControl();
+template <typename I>
+HalResult<Capabilities> HidlHalWrapper<I>::getCapabilitiesInternal() {
+    hardware::Return<bool> result = getHal()->supportsAmplitudeControl();
     Capabilities capabilities =
             result.withDefault(false) ? Capabilities::AMPLITUDE_CONTROL : Capabilities::NONE;
     return HalResult<Capabilities>::fromReturn(result, capabilities);
 }
 
-template <class I, class T>
-HalResult<milliseconds> HidlHalWrapperV1_0::performInternal(
-        perform_fn<I, T> performFn, sp<I> handle, T effect, EffectStrength strength,
+template <typename I>
+template <typename T>
+HalResult<milliseconds> HidlHalWrapper<I>::performInternal(
+        perform_fn<T> performFn, sp<I> handle, T effect, EffectStrength strength,
         const std::function<void()>& completionCallback) {
     V1_0::Status status;
     int32_t lengthMs;
@@ -341,10 +364,24 @@
     return ret;
 }
 
-HalResult<milliseconds> HidlHalWrapperV1_0::performInternalV1_0(
+template <typename I>
+sp<I> HidlHalWrapper<I>::getHal() {
+    std::lock_guard<std::mutex> lock(mHandleMutex);
+    return mHandle;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<milliseconds> HidlHalWrapperV1_0::performEffect(
         Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
-    V1_0::Effect e = static_cast<V1_0::Effect>(effect);
-    return performInternal(&V1_0::IVibrator::perform, mHandleV1_0, e, strength, completionCallback);
+    if (isStaticCastValid<V1_0::Effect>(effect)) {
+        return performInternal(&V1_0::IVibrator::perform, getHal(),
+                               static_cast<V1_0::Effect>(effect), strength, completionCallback);
+    }
+
+    ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
+          Aidl::toString(effect).c_str());
+    return HalResult<milliseconds>::unsupported();
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -352,10 +389,12 @@
 HalResult<milliseconds> HidlHalWrapperV1_1::performEffect(
         Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
     if (isStaticCastValid<V1_0::Effect>(effect)) {
-        return performInternalV1_0(effect, strength, completionCallback);
+        return performInternal(&V1_1::IVibrator::perform, getHal(),
+                               static_cast<V1_0::Effect>(effect), strength, completionCallback);
     }
     if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
-        return performInternalV1_1(effect, strength, completionCallback);
+        return performInternal(&V1_1::IVibrator::perform_1_1, getHal(),
+                               static_cast<V1_1::Effect_1_1>(effect), strength, completionCallback);
     }
 
     ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
@@ -363,25 +402,21 @@
     return HalResult<milliseconds>::unsupported();
 }
 
-HalResult<milliseconds> HidlHalWrapperV1_1::performInternalV1_1(
-        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
-    V1_1::Effect_1_1 e = static_cast<V1_1::Effect_1_1>(effect);
-    return performInternal(&V1_1::IVibrator::perform_1_1, mHandleV1_1, e, strength,
-                           completionCallback);
-}
-
 // -------------------------------------------------------------------------------------------------
 
 HalResult<milliseconds> HidlHalWrapperV1_2::performEffect(
         Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
     if (isStaticCastValid<V1_0::Effect>(effect)) {
-        return performInternalV1_0(effect, strength, completionCallback);
+        return performInternal(&V1_2::IVibrator::perform, getHal(),
+                               static_cast<V1_0::Effect>(effect), strength, completionCallback);
     }
     if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
-        return performInternalV1_1(effect, strength, completionCallback);
+        return performInternal(&V1_2::IVibrator::perform_1_1, getHal(),
+                               static_cast<V1_1::Effect_1_1>(effect), strength, completionCallback);
     }
     if (isStaticCastValid<V1_2::Effect>(effect)) {
-        return performInternalV1_2(effect, strength, completionCallback);
+        return performInternal(&V1_2::IVibrator::perform_1_2, getHal(),
+                               static_cast<V1_2::Effect>(effect), strength, completionCallback);
     }
 
     ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
@@ -389,33 +424,30 @@
     return HalResult<milliseconds>::unsupported();
 }
 
-HalResult<milliseconds> HidlHalWrapperV1_2::performInternalV1_2(
-        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
-    V1_2::Effect e = static_cast<V1_2::Effect>(effect);
-    return performInternal(&V1_2::IVibrator::perform_1_2, mHandleV1_2, e, strength,
-                           completionCallback);
-}
-
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> HidlHalWrapperV1_3::setExternalControl(bool enabled) {
-    auto result = mHandleV1_3->setExternalControl(static_cast<uint32_t>(enabled));
+    auto result = getHal()->setExternalControl(static_cast<uint32_t>(enabled));
     return HalResult<void>::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 HalResult<milliseconds> HidlHalWrapperV1_3::performEffect(
         Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
     if (isStaticCastValid<V1_0::Effect>(effect)) {
-        return performInternalV1_0(effect, strength, completionCallback);
+        return performInternal(&V1_3::IVibrator::perform, getHal(),
+                               static_cast<V1_0::Effect>(effect), strength, completionCallback);
     }
     if (isStaticCastValid<V1_1::Effect_1_1>(effect)) {
-        return performInternalV1_1(effect, strength, completionCallback);
+        return performInternal(&V1_3::IVibrator::perform_1_1, getHal(),
+                               static_cast<V1_1::Effect_1_1>(effect), strength, completionCallback);
     }
     if (isStaticCastValid<V1_2::Effect>(effect)) {
-        return performInternalV1_2(effect, strength, completionCallback);
+        return performInternal(&V1_3::IVibrator::perform_1_2, getHal(),
+                               static_cast<V1_2::Effect>(effect), strength, completionCallback);
     }
     if (isStaticCastValid<V1_3::Effect>(effect)) {
-        return performInternalV1_3(effect, strength, completionCallback);
+        return performInternal(&V1_3::IVibrator::perform_1_3, getHal(),
+                               static_cast<V1_3::Effect>(effect), strength, completionCallback);
     }
 
     ALOGV("Skipped performEffect because Vibrator HAL does not support effect %s",
@@ -424,23 +456,23 @@
 }
 
 HalResult<Capabilities> HidlHalWrapperV1_3::getCapabilitiesInternal() {
-    HalResult<Capabilities> parentResult = HidlHalWrapperV1_2::getCapabilitiesInternal();
-    if (!parentResult.isOk()) {
-        // Loading for previous HAL versions already failed, so propagate failure.
-        return parentResult;
+    sp<V1_3::IVibrator> hal = getHal();
+    auto amplitudeResult = hal->supportsAmplitudeControl();
+    if (!amplitudeResult.isOk()) {
+        return HalResult<Capabilities>::failed();
     }
 
-    Capabilities capabilities = parentResult.value();
-    auto result = mHandleV1_3->supportsExternalControl();
-    capabilities |= result.withDefault(false) ? Capabilities::EXTERNAL_CONTROL : Capabilities::NONE;
-    return HalResult<Capabilities>::fromReturn(result, capabilities);
-}
+    auto externalControlResult = hal->supportsExternalControl();
+    Capabilities capabilities = Capabilities::NONE;
 
-HalResult<milliseconds> HidlHalWrapperV1_3::performInternalV1_3(
-        Effect effect, EffectStrength strength, const std::function<void()>& completionCallback) {
-    V1_3::Effect e = static_cast<V1_3::Effect>(effect);
-    return performInternal(&V1_3::IVibrator::perform_1_3, mHandleV1_3, e, strength,
-                           completionCallback);
+    if (amplitudeResult.withDefault(false)) {
+        capabilities |= Capabilities::AMPLITUDE_CONTROL;
+    }
+    if (externalControlResult.withDefault(false)) {
+        capabilities |= Capabilities::EXTERNAL_CONTROL;
+    }
+
+    return HalResult<Capabilities>::fromReturn(externalControlResult, capabilities);
 }
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index 1fb7d05..daf2c8c 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -51,7 +51,10 @@
             mConnectedHal(nullptr) {}
     virtual ~HalController() = default;
 
+    void init();
+
     HalResult<void> ping() final override;
+    void tryReconnect() final override;
 
     HalResult<void> on(std::chrono::milliseconds timeout,
                        const std::function<void()>& completionCallback) final override;
@@ -81,8 +84,6 @@
     // Shared pointer to allow local copies to be used by different threads.
     std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
 
-    std::shared_ptr<HalWrapper> initHal();
-
     template <typename T>
     HalResult<T> processHalResult(HalResult<T> result, const char* functionName);
 
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 5e3c275..a4fa869 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -20,6 +20,7 @@
 #include <android-base/thread_annotations.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
 #include <android/hardware/vibrator/IVibrator.h>
+#include <binder/IServiceManager.h>
 
 #include <vibratorservice/VibratorCallbackScheduler.h>
 
@@ -131,6 +132,7 @@
     virtual ~HalWrapper() = default;
 
     virtual HalResult<void> ping() = 0;
+    virtual void tryReconnect() = 0;
 
     virtual HalResult<void> on(std::chrono::milliseconds timeout,
                                const std::function<void()>& completionCallback) = 0;
@@ -167,34 +169,36 @@
           : HalWrapper(std::move(scheduler)), mHandle(std::move(handle)) {}
     virtual ~AidlHalWrapper() = default;
 
-    virtual HalResult<void> ping() override;
+    HalResult<void> ping() override final;
+    void tryReconnect() override final;
 
-    virtual HalResult<void> on(std::chrono::milliseconds timeout,
-                               const std::function<void()>& completionCallback) override;
-    virtual HalResult<void> off() override;
+    HalResult<void> on(std::chrono::milliseconds timeout,
+                       const std::function<void()>& completionCallback) override final;
+    HalResult<void> off() override final;
 
-    virtual HalResult<void> setAmplitude(int32_t amplitude) override;
-    virtual HalResult<void> setExternalControl(bool enabled) override;
+    HalResult<void> setAmplitude(int32_t amplitude) override final;
+    HalResult<void> setExternalControl(bool enabled) override final;
 
-    virtual HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                           hardware::vibrator::EffectStrength strength) override;
-    virtual HalResult<void> alwaysOnDisable(int32_t id) override;
+    HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
+                                   hardware::vibrator::EffectStrength strength) override final;
+    HalResult<void> alwaysOnDisable(int32_t id) override final;
 
-    virtual HalResult<Capabilities> getCapabilities() override;
-    virtual HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() override;
+    HalResult<Capabilities> getCapabilities() override final;
+    HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() override final;
 
-    virtual HalResult<std::chrono::milliseconds> performEffect(
+    HalResult<std::chrono::milliseconds> performEffect(
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback) override;
+            const std::function<void()>& completionCallback) override final;
 
-    virtual HalResult<void> performComposedEffect(
+    HalResult<void> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
-            const std::function<void()>& completionCallback) override;
+            const std::function<void()>& completionCallback) override final;
 
 private:
-    const sp<hardware::vibrator::IVibrator> mHandle;
+    std::mutex mHandleMutex;
     std::mutex mCapabilitiesMutex;
     std::mutex mSupportedEffectsMutex;
+    sp<hardware::vibrator::IVibrator> mHandle GUARDED_BY(mHandleMutex);
     std::optional<Capabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
     std::optional<std::vector<hardware::vibrator::Effect>> mSupportedEffects
             GUARDED_BY(mSupportedEffectsMutex);
@@ -202,125 +206,120 @@
     // Loads directly from IVibrator handle, skipping caches.
     HalResult<Capabilities> getCapabilitiesInternal();
     HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffectsInternal();
+    sp<hardware::vibrator::IVibrator> getHal();
 };
 
-// Wrapper for the HDIL Vibrator HAL v1.0.
-class HidlHalWrapperV1_0 : public HalWrapper {
+// Wrapper for the HDIL Vibrator HALs.
+template <typename I>
+class HidlHalWrapper : public HalWrapper {
 public:
-    HidlHalWrapperV1_0(std::shared_ptr<CallbackScheduler> scheduler,
-                       sp<hardware::vibrator::V1_0::IVibrator> handle)
-          : HalWrapper(std::move(scheduler)), mHandleV1_0(std::move(handle)) {}
-    virtual ~HidlHalWrapperV1_0() = default;
+    HidlHalWrapper(std::shared_ptr<CallbackScheduler> scheduler, sp<I> handle)
+          : HalWrapper(std::move(scheduler)), mHandle(std::move(handle)) {}
+    virtual ~HidlHalWrapper() = default;
 
-    virtual HalResult<void> ping() override;
+    HalResult<void> ping() override final;
+    void tryReconnect() override final;
 
-    virtual HalResult<void> on(std::chrono::milliseconds timeout,
-                               const std::function<void()>& completionCallback) override;
-    virtual HalResult<void> off() override;
+    HalResult<void> on(std::chrono::milliseconds timeout,
+                       const std::function<void()>& completionCallback) override final;
+    HalResult<void> off() override final;
 
-    virtual HalResult<void> setAmplitude(int32_t amplitude) override;
+    HalResult<void> setAmplitude(int32_t amplitude) override final;
     virtual HalResult<void> setExternalControl(bool enabled) override;
 
-    virtual HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                           hardware::vibrator::EffectStrength strength) override;
-    virtual HalResult<void> alwaysOnDisable(int32_t id) override;
+    HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
+                                   hardware::vibrator::EffectStrength strength) override final;
+    HalResult<void> alwaysOnDisable(int32_t id) override final;
 
-    virtual HalResult<Capabilities> getCapabilities() override;
-    virtual HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() override;
+    HalResult<Capabilities> getCapabilities() override final;
+    HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffects() override final;
 
-    virtual HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback) override;
-
-    virtual HalResult<void> performComposedEffect(
+    HalResult<void> performComposedEffect(
             const std::vector<hardware::vibrator::CompositeEffect>& primitiveEffects,
-            const std::function<void()>& completionCallback) override;
+            const std::function<void()>& completionCallback) override final;
 
 protected:
-    const sp<hardware::vibrator::V1_0::IVibrator> mHandleV1_0;
+    std::mutex mHandleMutex;
     std::mutex mCapabilitiesMutex;
+    sp<I> mHandle GUARDED_BY(mHandleMutex);
     std::optional<Capabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
 
     // Loads directly from IVibrator handle, skipping the mCapabilities cache.
     virtual HalResult<Capabilities> getCapabilitiesInternal();
 
-    template <class I, class T>
+    template <class T>
     using perform_fn =
             hardware::Return<void> (I::*)(T, hardware::vibrator::V1_0::EffectStrength,
                                           hardware::vibrator::V1_0::IVibrator::perform_cb);
 
-    template <class I, class T>
+    template <class T>
     HalResult<std::chrono::milliseconds> performInternal(
-            perform_fn<I, T> performFn, sp<I> handle, T effect,
+            perform_fn<T> performFn, sp<I> handle, T effect,
             hardware::vibrator::EffectStrength strength,
             const std::function<void()>& completionCallback);
 
-    HalResult<std::chrono::milliseconds> performInternalV1_0(
+    sp<I> getHal();
+};
+
+// Wrapper for the HDIL Vibrator HAL v1.0.
+class HidlHalWrapperV1_0 : public HidlHalWrapper<hardware::vibrator::V1_0::IVibrator> {
+public:
+    HidlHalWrapperV1_0(std::shared_ptr<CallbackScheduler> scheduler,
+                       sp<hardware::vibrator::V1_0::IVibrator> handle)
+          : HidlHalWrapper<hardware::vibrator::V1_0::IVibrator>(std::move(scheduler),
+                                                                std::move(handle)) {}
+    virtual ~HidlHalWrapperV1_0() = default;
+
+    HalResult<std::chrono::milliseconds> performEffect(
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback);
+            const std::function<void()>& completionCallback) override final;
 };
 
 // Wrapper for the HDIL Vibrator HAL v1.1.
-class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 {
+class HidlHalWrapperV1_1 : public HidlHalWrapper<hardware::vibrator::V1_1::IVibrator> {
 public:
     HidlHalWrapperV1_1(std::shared_ptr<CallbackScheduler> scheduler,
                        sp<hardware::vibrator::V1_1::IVibrator> handle)
-          : HidlHalWrapperV1_0(std::move(scheduler), handle), mHandleV1_1(handle) {}
+          : HidlHalWrapper<hardware::vibrator::V1_1::IVibrator>(std::move(scheduler),
+                                                                std::move(handle)) {}
     virtual ~HidlHalWrapperV1_1() = default;
 
-    virtual HalResult<std::chrono::milliseconds> performEffect(
+    HalResult<std::chrono::milliseconds> performEffect(
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback) override;
-
-protected:
-    const sp<hardware::vibrator::V1_1::IVibrator> mHandleV1_1;
-
-    HalResult<std::chrono::milliseconds> performInternalV1_1(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback);
+            const std::function<void()>& completionCallback) override final;
 };
 
 // Wrapper for the HDIL Vibrator HAL v1.2.
-class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 {
+class HidlHalWrapperV1_2 : public HidlHalWrapper<hardware::vibrator::V1_2::IVibrator> {
 public:
     HidlHalWrapperV1_2(std::shared_ptr<CallbackScheduler> scheduler,
                        sp<hardware::vibrator::V1_2::IVibrator> handle)
-          : HidlHalWrapperV1_1(std::move(scheduler), handle), mHandleV1_2(handle) {}
+          : HidlHalWrapper<hardware::vibrator::V1_2::IVibrator>(std::move(scheduler),
+                                                                std::move(handle)) {}
     virtual ~HidlHalWrapperV1_2() = default;
 
-    virtual HalResult<std::chrono::milliseconds> performEffect(
+    HalResult<std::chrono::milliseconds> performEffect(
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback) override;
-
-protected:
-    const sp<hardware::vibrator::V1_2::IVibrator> mHandleV1_2;
-
-    HalResult<std::chrono::milliseconds> performInternalV1_2(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback);
+            const std::function<void()>& completionCallback) override final;
 };
 
 // Wrapper for the HDIL Vibrator HAL v1.3.
-class HidlHalWrapperV1_3 : public HidlHalWrapperV1_2 {
+class HidlHalWrapperV1_3 : public HidlHalWrapper<hardware::vibrator::V1_3::IVibrator> {
 public:
     HidlHalWrapperV1_3(std::shared_ptr<CallbackScheduler> scheduler,
                        sp<hardware::vibrator::V1_3::IVibrator> handle)
-          : HidlHalWrapperV1_2(std::move(scheduler), handle), mHandleV1_3(handle) {}
+          : HidlHalWrapper<hardware::vibrator::V1_3::IVibrator>(std::move(scheduler),
+                                                                std::move(handle)) {}
     virtual ~HidlHalWrapperV1_3() = default;
 
-    virtual HalResult<void> setExternalControl(bool enabled) override;
+    HalResult<void> setExternalControl(bool enabled) override final;
 
-    virtual HalResult<std::chrono::milliseconds> performEffect(
+    HalResult<std::chrono::milliseconds> performEffect(
             hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback) override;
+            const std::function<void()>& completionCallback) override final;
 
 protected:
-    const sp<hardware::vibrator::V1_3::IVibrator> mHandleV1_3;
-
-    virtual HalResult<Capabilities> getCapabilitiesInternal() override;
-    HalResult<std::chrono::milliseconds> performInternalV1_3(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
-            const std::function<void()>& completionCallback);
+    HalResult<Capabilities> getCapabilitiesInternal() override final;
 };
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 24e6a1e..2d55549 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -53,6 +53,7 @@
     virtual ~MockHalWrapper() = default;
 
     MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
+    MOCK_METHOD(void, tryReconnect, (), (override));
     MOCK_METHOD(vibrator::HalResult<void>, on,
                 (milliseconds timeout, const std::function<void()>& completionCallback),
                 (override));
@@ -132,7 +133,6 @@
                             vibrator::HalResult<vibrator::Capabilities> capabilitiesResult,
                             vibrator::HalResult<std::vector<Effect>> effectsResult,
                             vibrator::HalResult<milliseconds> durationResult) {
-        InSequence seq;
         EXPECT_CALL(*mMockHal.get(), ping())
                 .Times(Exactly(cardinality))
                 .WillRepeatedly(Return(voidResult));
@@ -167,11 +167,25 @@
         EXPECT_CALL(*mMockHal.get(), performComposedEffect(Eq(compositeEffects), _))
                 .Times(Exactly(cardinality))
                 .WillRepeatedly(Return(voidResult));
+
+        if (cardinality > 1) {
+            // One reconnection call after each failure.
+            EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(11 * cardinality));
+        }
     }
 };
 
 // -------------------------------------------------------------------------------------------------
 
+TEST_F(VibratorHalControllerTest, TestInit) {
+    mController->init();
+    ASSERT_EQ(1, mConnectCounter);
+
+    // Noop when wrapper was already initialized.
+    mController->init();
+    ASSERT_EQ(1, mConnectCounter);
+}
+
 TEST_F(VibratorHalControllerTest, TestApiCallsAreForwardedToHal) {
     std::vector<Effect> supportedEffects;
     supportedEffects.push_back(Effect::CLICK);
@@ -211,7 +225,6 @@
 
     ASSERT_TRUE(mController->performComposedEffect(compositeEffects, []() {}).isOk());
 
-    // No reconnection attempt was made after the first one.
     ASSERT_EQ(1, mConnectCounter);
 }
 
@@ -239,7 +252,6 @@
     ASSERT_TRUE(mController->performComposedEffect(std::vector<CompositeEffect>(), []() {})
                         .isUnsupported());
 
-    // No reconnection attempt was made after the first one.
     ASSERT_EQ(1, mConnectCounter);
 }
 
@@ -266,23 +278,24 @@
     ASSERT_TRUE(
             mController->performComposedEffect(std::vector<CompositeEffect>(), []() {}).isFailed());
 
-    // One reconnection attempt + retry attempts per api call.
-    ASSERT_EQ(11 * MAX_ATTEMPTS, mConnectCounter);
+    ASSERT_EQ(1, mConnectCounter);
 }
 
 TEST_F(VibratorHalControllerTest, TestFailedApiResultReturnsSuccessAfterRetries) {
     {
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), ping())
-                .Times(Exactly(2))
-                .WillOnce(Return(vibrator::HalResult<void>::failed()))
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(vibrator::HalResult<void>::failed()));
+        EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), ping())
+                .Times(Exactly(1))
                 .WillRepeatedly(Return(vibrator::HalResult<void>::ok()));
     }
 
     ASSERT_EQ(0, mConnectCounter);
     ASSERT_TRUE(mController->ping().isOk());
-    // One connect + one retry.
-    ASSERT_EQ(2, mConnectCounter);
+    ASSERT_EQ(1, mConnectCounter);
 }
 
 TEST_F(VibratorHalControllerTest, TestMultiThreadConnectsOnlyOnce) {
@@ -323,7 +336,7 @@
     ASSERT_TRUE(mController->performComposedEffect(std::vector<CompositeEffect>(), []() {})
                         .isUnsupported());
 
-    // One reconnection attempt per api call, no retry.
+    // One connection attempt per api call.
     ASSERT_EQ(11, mConnectCounter);
 }
 
@@ -337,19 +350,24 @@
                     return vibrator::HalResult<void>::ok();
                 });
         EXPECT_CALL(*mMockHal.get(), ping())
-                .Times(Exactly(MAX_ATTEMPTS))
+                .Times(Exactly(1))
                 .WillRepeatedly(Return(vibrator::HalResult<void>::failed()));
+        EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), ping())
+                .Times(Exactly(1))
+                .WillRepeatedly(Return(vibrator::HalResult<void>::failed()));
+        EXPECT_CALL(*mMockHal.get(), tryReconnect()).Times(Exactly(1));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
     auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
 
     ASSERT_TRUE(mController->on(10ms, callback).isOk());
-    ASSERT_TRUE(mController->ping().isFailed()); // Delete connected HAL pointer from controller.
-    mMockHal.reset();                            // Delete mock HAL pointer from test class.
+    ASSERT_TRUE(mController->ping().isFailed());
+    mMockHal.reset();
     ASSERT_EQ(0, *callbackCounter.get());
 
-    // Callback triggered even after HalWrapper was completely destroyed.
+    // Callback triggered even after HalWrapper was reconnected.
     std::this_thread::sleep_for(15ms);
     ASSERT_EQ(1, *callbackCounter.get());
 }