Switch to callback animation

Modified current animation logic to use callbacks from the controllers
to further clean and modularize code.
Test: Pixel 3XL device, atest PointerController_test, compile

Change-Id: I1073bd78687cca491663c0349751dab4b30aa8e2
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index 80b555b..45da008 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -168,7 +168,7 @@
         updatePointerLocked();
     } else {
         mLocked.pointerFadeDirection = -1;
-        mContext.startAnimation();
+        startAnimationLocked();
     }
 }
 
@@ -185,7 +185,7 @@
         updatePointerLocked();
     } else {
         mLocked.pointerFadeDirection = 1;
-        mContext.startAnimation();
+        startAnimationLocked();
     }
 }
 
@@ -312,10 +312,9 @@
     updatePointerLocked();
 }
 
-bool MouseCursorController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+bool MouseCursorController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
     nsecs_t frameDelay = timestamp - mContext.getAnimationTime();
-
-    std::scoped_lock lock(mLock);
+    bool keepAnimating = false;
 
     // Animate pointer fade.
     if (mLocked.pointerFadeDirection < 0) {
@@ -337,13 +336,10 @@
         }
         updatePointerLocked();
     }
-
     return keepAnimating;
 }
 
-bool MouseCursorController::doBitmapAnimation(nsecs_t timestamp) {
-    std::scoped_lock lock(mLock);
-
+bool MouseCursorController::doBitmapAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
     std::map<int32_t, PointerAnimation>::const_iterator iter =
             mLocked.animationResources.find(mLocked.requestedPointerType);
     if (iter == mLocked.animationResources.end()) {
@@ -364,7 +360,6 @@
 
         spriteController->closeTransaction();
     }
-
     // Keep animating.
     return true;
 }
@@ -399,7 +394,7 @@
                 if (anim_iter != mLocked.animationResources.end()) {
                     mLocked.animationFrameIndex = 0;
                     mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
-                    mContext.startAnimation();
+                    startAnimationLocked();
                 }
                 mLocked.pointerSprite->setIcon(iter->second);
             } else {
@@ -457,4 +452,38 @@
     return mLocked.resourcesLoaded;
 }
 
+bool MouseCursorController::doAnimations(nsecs_t timestamp) {
+    std::scoped_lock lock(mLock);
+    bool keepFading = doFadingAnimationLocked(timestamp);
+    bool keepBitmap = doBitmapAnimationLocked(timestamp);
+    bool keepAnimating = keepFading || keepBitmap;
+    if (!keepAnimating) {
+        /*
+         * We know that this callback will be removed before another
+         * is added. mLock in PointerAnimator will not be released
+         * until after this is removed, and adding another callback
+         * requires that lock. Thus it's safe to set mLocked.animating
+         * here.
+         */
+        mLocked.animating = false;
+    }
+    return keepAnimating;
+}
+
+void MouseCursorController::startAnimationLocked() REQUIRES(mLock) {
+    using namespace std::placeholders;
+
+    if (mLocked.animating) {
+        return;
+    }
+    mLocked.animating = true;
+
+    std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1);
+    /*
+     * Using -1 for displayId here to avoid removing the callback
+     * if a TouchSpotController with the same display is removed.
+     */
+    mContext.addAnimationCallback(-1, func);
+}
+
 } // namespace android
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index 448165b..e6dfc4c 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -25,6 +25,7 @@
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
 
+#include <functional>
 #include <map>
 #include <memory>
 #include <vector>
@@ -61,8 +62,7 @@
     void getAdditionalMouseResources();
     bool isViewportValid();
 
-    bool doBitmapAnimation(nsecs_t timestamp);
-    bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+    bool doAnimations(nsecs_t timestamp);
 
     bool resourcesLoaded();
 
@@ -96,6 +96,8 @@
 
         int32_t buttonState;
 
+        bool animating{false};
+
     } mLocked GUARDED_BY(mLock);
 
     bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
@@ -104,6 +106,11 @@
     void updatePointerLocked();
 
     void loadResourcesLocked(bool getAdditionalMouseResources);
+
+    bool doBitmapAnimationLocked(nsecs_t timestamp);
+    bool doFadingAnimationLocked(nsecs_t timestamp);
+
+    void startAnimationLocked();
 };
 
 } // namespace android
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 14c96ce..8f04cfb 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -57,7 +57,6 @@
 
     controller->mContext.setHandlerController(controller);
     controller->mContext.setCallbackController(controller);
-    controller->mContext.initializeDisplayEventReceiver();
     return controller;
 }
 
@@ -189,24 +188,6 @@
     mCursorController.setCustomPointerIcon(icon);
 }
 
-void PointerController::doAnimate(nsecs_t timestamp) {
-    std::scoped_lock lock(mLock);
-
-    mContext.setAnimationPending(false);
-
-    bool keepFading = false;
-    keepFading = mCursorController.doFadingAnimation(timestamp, keepFading);
-
-    for (auto& [displayID, spotController] : mLocked.spotControllers) {
-        keepFading = spotController.doFadingAnimation(timestamp, keepFading);
-    }
-
-    bool keepBitmapFlipping = mCursorController.doBitmapAnimation(timestamp);
-    if (keepFading || keepBitmapFlipping) {
-        mContext.startAnimation();
-    }
-}
-
 void PointerController::doInactivityTimeout() {
     fade(Transition::GRADUAL);
 }
@@ -221,6 +202,11 @@
     for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) {
         int32_t displayID = it->first;
         if (!displayIdSet.count(displayID)) {
+            /*
+             * Ensures that an in-progress animation won't dereference
+             * a null pointer to TouchSpotController.
+             */
+            mContext.removeAnimationCallback(displayID);
             it = mLocked.spotControllers.erase(it);
         } else {
             ++it;
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 1f561da..827fcf1 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -70,7 +70,6 @@
     void setCustomPointerIcon(const SpriteIcon& icon);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void doInactivityTimeout();
-    void doAnimate(nsecs_t timestamp);
     void reloadPointerResources();
     void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports);
 
diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp
index 2d7e22b..f30e8d8 100644
--- a/libs/input/PointerControllerContext.cpp
+++ b/libs/input/PointerControllerContext.cpp
@@ -38,10 +38,10 @@
         mSpriteController(spriteController),
         mHandler(new MessageHandler()),
         mCallback(new LooperCallback()),
-        mController(controller) {
+        mController(controller),
+        mAnimator(*this) {
     std::scoped_lock lock(mLock);
     mLocked.inactivityTimeout = InactivityTimeout::NORMAL;
-    mLocked.animationPending = false;
 }
 
 PointerControllerContext::~PointerControllerContext() {
@@ -57,15 +57,6 @@
     }
 }
 
-void PointerControllerContext::startAnimation() {
-    std::scoped_lock lock(mLock);
-    if (!mLocked.animationPending) {
-        mLocked.animationPending = true;
-        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
-        mDisplayEventReceiver.requestNextVsync();
-    }
-}
-
 void PointerControllerContext::resetInactivityTimeout() {
     std::scoped_lock lock(mLock);
     resetInactivityTimeoutLocked();
@@ -85,14 +76,8 @@
     mLooper->removeMessages(mHandler, MessageHandler::MSG_INACTIVITY_TIMEOUT);
 }
 
-void PointerControllerContext::setAnimationPending(bool animationPending) {
-    std::scoped_lock lock(mLock);
-    mLocked.animationPending = animationPending;
-}
-
-nsecs_t PointerControllerContext::getAnimationTime() {
-    std::scoped_lock lock(mLock);
-    return mLocked.animationTime;
+nsecs_t PointerControllerContext::getAnimationTime() REQUIRES(mAnimator.mLock) {
+    return mAnimator.getAnimationTimeLocked();
 }
 
 void PointerControllerContext::setHandlerController(std::shared_ptr<PointerController> controller) {
@@ -112,31 +97,8 @@
     return mSpriteController;
 }
 
-void PointerControllerContext::initializeDisplayEventReceiver() {
-    if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
-        mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK, Looper::EVENT_INPUT,
-                       mCallback, nullptr);
-    } else {
-        ALOGE("Failed to initialize DisplayEventReceiver.");
-    }
-}
-
 void PointerControllerContext::handleDisplayEvents() {
-    bool gotVsync = false;
-    ssize_t n;
-    nsecs_t timestamp;
-    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
-    while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
-        for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
-            if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
-                timestamp = buf[i].header.timestamp;
-                gotVsync = true;
-            }
-        }
-    }
-    if (gotVsync) {
-        mController.doAnimate(timestamp);
-    }
+    mAnimator.handleVsyncEvents();
 }
 
 void PointerControllerContext::MessageHandler::handleMessage(const Message& message) {
@@ -176,4 +138,91 @@
     return 1; // keep the callback
 }
 
+void PointerControllerContext::addAnimationCallback(int32_t displayId,
+                                                    std::function<bool(nsecs_t)> callback) {
+    mAnimator.addCallback(displayId, callback);
+}
+
+void PointerControllerContext::removeAnimationCallback(int32_t displayId) {
+    mAnimator.removeCallback(displayId);
+}
+
+PointerControllerContext::PointerAnimator::PointerAnimator(PointerControllerContext& context)
+      : mContext(context) {
+    initializeDisplayEventReceiver();
+}
+
+void PointerControllerContext::PointerAnimator::initializeDisplayEventReceiver() {
+    if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+        mContext.mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
+                                Looper::EVENT_INPUT, mContext.mCallback, nullptr);
+    } else {
+        ALOGE("Failed to initialize DisplayEventReceiver.");
+    }
+}
+
+void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId,
+                                                            std::function<bool(nsecs_t)> callback) {
+    std::scoped_lock lock(mLock);
+    mLocked.callbacks[displayId] = callback;
+    startAnimationLocked();
+}
+
+void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) {
+    std::scoped_lock lock(mLock);
+    auto it = mLocked.callbacks.find(displayId);
+    if (it == mLocked.callbacks.end()) {
+        return;
+    }
+    mLocked.callbacks.erase(it);
+}
+
+void PointerControllerContext::PointerAnimator::handleVsyncEvents() {
+    bool gotVsync = false;
+    ssize_t n;
+    nsecs_t timestamp;
+    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+    while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+        for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+            if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+                timestamp = buf[i].header.timestamp;
+                gotVsync = true;
+            }
+        }
+    }
+    if (gotVsync) {
+        std::scoped_lock lock(mLock);
+        mLocked.animationPending = false;
+        handleCallbacksLocked(timestamp);
+    }
+}
+
+nsecs_t PointerControllerContext::PointerAnimator::getAnimationTimeLocked() REQUIRES(mLock) {
+    return mLocked.animationTime;
+}
+
+void PointerControllerContext::PointerAnimator::startAnimationLocked() REQUIRES(mLock) {
+    if (!mLocked.animationPending) {
+        mLocked.animationPending = true;
+        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mDisplayEventReceiver.requestNextVsync();
+    }
+}
+
+void PointerControllerContext::PointerAnimator::handleCallbacksLocked(nsecs_t timestamp)
+        REQUIRES(mLock) {
+    for (auto it = mLocked.callbacks.begin(); it != mLocked.callbacks.end();) {
+        bool keepCallback = it->second(timestamp);
+        if (!keepCallback) {
+            it = mLocked.callbacks.erase(it);
+        } else {
+            ++it;
+        }
+    }
+
+    if (!mLocked.callbacks.empty()) {
+        startAnimationLocked();
+    }
+}
+
 } // namespace android
diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h
index 92e1bda..98073fe 100644
--- a/libs/input/PointerControllerContext.h
+++ b/libs/input/PointerControllerContext.h
@@ -26,6 +26,7 @@
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
 
+#include <functional>
 #include <map>
 #include <memory>
 #include <vector>
@@ -35,6 +36,8 @@
 namespace android {
 
 class PointerController;
+class MouseCursorController;
+class TouchSpotController;
 
 /*
  * Pointer resources.
@@ -96,7 +99,6 @@
     void startAnimation();
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
 
-    void setAnimationPending(bool animationPending);
     nsecs_t getAnimationTime();
 
     void clearSpotsByDisplay(int32_t displayId);
@@ -107,9 +109,11 @@
     sp<PointerControllerPolicyInterface> getPolicy();
     sp<SpriteController> getSpriteController();
 
-    void initializeDisplayEventReceiver();
     void handleDisplayEvents();
 
+    void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
+    void removeAnimationCallback(int32_t displayId);
+
     class MessageHandler : public virtual android::MessageHandler {
     public:
         enum {
@@ -127,22 +131,47 @@
     };
 
 private:
+    class PointerAnimator {
+    public:
+        PointerAnimator(PointerControllerContext& context);
+
+        void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback);
+        void removeCallback(int32_t displayId);
+        void handleVsyncEvents();
+        nsecs_t getAnimationTimeLocked();
+
+        mutable std::mutex mLock;
+
+    private:
+        struct Locked {
+            bool animationPending{false};
+            nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)};
+
+            std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks;
+        } mLocked GUARDED_BY(mLock);
+
+        DisplayEventReceiver mDisplayEventReceiver;
+
+        PointerControllerContext& mContext;
+
+        void initializeDisplayEventReceiver();
+        void startAnimationLocked();
+        void handleCallbacksLocked(nsecs_t timestamp);
+    };
+
     sp<PointerControllerPolicyInterface> mPolicy;
     sp<Looper> mLooper;
     sp<SpriteController> mSpriteController;
     sp<MessageHandler> mHandler;
     sp<LooperCallback> mCallback;
 
-    DisplayEventReceiver mDisplayEventReceiver;
-
     PointerController& mController;
 
+    PointerAnimator mAnimator;
+
     mutable std::mutex mLock;
 
     struct Locked {
-        bool animationPending;
-        nsecs_t animationTime;
-
         InactivityTimeout inactivityTimeout;
     } mLocked GUARDED_BY(mLock);
 
diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp
index c7430ce..f7c685f 100644
--- a/libs/input/TouchSpotController.cpp
+++ b/libs/input/TouchSpotController.cpp
@@ -142,7 +142,8 @@
 }
 
 TouchSpotController::Spot* TouchSpotController::createAndAddSpotLocked(uint32_t id,
-                                                                       std::vector<Spot*>& spots) {
+                                                                       std::vector<Spot*>& spots)
+        REQUIRES(mLock) {
     // Remove spots until we have fewer than MAX_SPOTS remaining.
     while (spots.size() >= MAX_SPOTS) {
         Spot* spot = removeFirstFadingSpotLocked(spots);
@@ -186,14 +187,13 @@
     if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
         mLocked.recycledSprites.push_back(spot->sprite);
     }
-
     delete spot;
 }
 
 void TouchSpotController::fadeOutAndReleaseSpotLocked(Spot* spot) REQUIRES(mLock) {
     if (spot->id != Spot::INVALID_ID) {
         spot->id = Spot::INVALID_ID;
-        mContext.startAnimation();
+        startAnimationLocked();
     }
 }
 
@@ -209,8 +209,24 @@
     mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId);
 }
 
-bool TouchSpotController::doFadingAnimation(nsecs_t timestamp, bool keepAnimating) {
+bool TouchSpotController::doAnimations(nsecs_t timestamp) {
     std::scoped_lock lock(mLock);
+    bool keepAnimating = doFadingAnimationLocked(timestamp);
+    if (!keepAnimating) {
+        /*
+         * We know that this callback will be removed before another
+         * is added. mLock in PointerAnimator will not be released
+         * until after this is removed, and adding another callback
+         * requires that lock. Thus it's safe to set mLocked.animating
+         * here.
+         */
+        mLocked.animating = false;
+    }
+    return keepAnimating;
+}
+
+bool TouchSpotController::doFadingAnimationLocked(nsecs_t timestamp) REQUIRES(mLock) {
+    bool keepAnimating = false;
     nsecs_t animationTime = mContext.getAnimationTime();
     nsecs_t frameDelay = timestamp - animationTime;
     size_t numSpots = mLocked.displaySpots.size();
@@ -233,4 +249,16 @@
     return keepAnimating;
 }
 
+void TouchSpotController::startAnimationLocked() REQUIRES(mLock) {
+    using namespace std::placeholders;
+
+    if (mLocked.animating) {
+        return;
+    }
+    mLocked.animating = true;
+
+    std::function<bool(nsecs_t)> func = std::bind(&TouchSpotController::doAnimations, this, _1);
+    mContext.addAnimationCallback(mDisplayId, func);
+}
+
 } // namespace android
diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h
index f3b3550..703de36 100644
--- a/libs/input/TouchSpotController.h
+++ b/libs/input/TouchSpotController.h
@@ -17,6 +17,8 @@
 #ifndef _UI_TOUCH_SPOT_CONTROLLER_H
 #define _UI_TOUCH_SPOT_CONTROLLER_H
 
+#include <functional>
+
 #include "PointerControllerContext.h"
 
 namespace android {
@@ -34,7 +36,7 @@
     void clearSpots();
 
     void reloadSpotResources();
-    bool doFadingAnimation(nsecs_t timestamp, bool keepAnimating);
+    bool doAnimations(nsecs_t timestamp);
 
 private:
     struct Spot {
@@ -76,6 +78,8 @@
         std::vector<Spot*> displaySpots;
         std::vector<sp<Sprite>> recycledSprites;
 
+        bool animating{false};
+
     } mLocked GUARDED_BY(mLock);
 
     Spot* getSpot(uint32_t id, const std::vector<Spot*>& spots);
@@ -84,6 +88,8 @@
     void releaseSpotLocked(Spot* spot);
     void fadeOutAndReleaseSpotLocked(Spot* spot);
     void fadeOutAndReleaseAllSpotsLocked();
+    bool doFadingAnimationLocked(nsecs_t timestamp);
+    void startAnimationLocked();
 };
 
 } // namespace android