Touch pad UX improvements.

Fade the pointer spot when swiping or performing a freeform
gesture.

Support configuring the gesture mode in the device IDC file.

Added workaround for devices that report individual finger
movements one at a time instead of all at once.

Bug: 4124987
Change-Id: I44628b00382ad59306e4ec5c4717d69cc6fbebb8
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 853ceb3..cb69008 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -1627,7 +1627,7 @@
                     mPointerController->setButtonState(mLocked.buttonState);
                 }
 
-                mPointerController->unfade();
+                mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
             }
 
             float x, y;
@@ -1686,7 +1686,7 @@
     { // acquire lock
         AutoMutex _l(mLock);
         if (mPointerController != NULL) {
-            mPointerController->fade();
+            mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
         }
     } // release lock
 }
@@ -1873,10 +1873,22 @@
     mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
     mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime();
 
-    // TODO: Make this configurable.
-    //mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+    // TODO: select the default gesture mode based on whether the device supports
+    // distinct multitouch
     mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
 
+    String8 gestureModeString;
+    if (getDevice()->getConfiguration().tryGetProperty(String8("touch.gestureMode"),
+            gestureModeString)) {
+        if (gestureModeString == "pointer") {
+            mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+        } else if (gestureModeString == "spots") {
+            mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
+        } else if (gestureModeString != "default") {
+            LOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
+        }
+    }
+
     if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
             || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
         // The device is a cursor device with a touch pad attached.
@@ -1897,7 +1909,7 @@
             mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
         } else if (deviceTypeString == "pointer") {
             mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
-        } else {
+        } else if (deviceTypeString != "default") {
             LOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
         }
     }
@@ -1915,6 +1927,17 @@
 void TouchInputMapper::dumpParameters(String8& dump) {
     dump.append(INDENT3 "Parameters:\n");
 
+    switch (mParameters.gestureMode) {
+    case Parameters::GESTURE_MODE_POINTER:
+        dump.append(INDENT4 "GestureMode: pointer\n");
+        break;
+    case Parameters::GESTURE_MODE_SPOTS:
+        dump.append(INDENT4 "GestureMode: spots\n");
+        break;
+    default:
+        assert(false);
+    }
+
     switch (mParameters.deviceType) {
     case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
         dump.append(INDENT4 "DeviceType: touchScreen\n");
@@ -3251,10 +3274,36 @@
         return;
     }
 
-    // Show the pointer if needed.
-    if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL
-            && mPointerGesture.currentGestureMode != PointerGesture::QUIET) {
-        mPointerController->unfade();
+    // Show or hide the pointer if needed.
+    switch (mPointerGesture.currentGestureMode) {
+    case PointerGesture::NEUTRAL:
+    case PointerGesture::QUIET:
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+                && (mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+                        || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)) {
+            // Remind the user of where the pointer is after finishing a gesture with spots.
+            mPointerController->unfade(PointerControllerInterface::TRANSITION_GRADUAL);
+        }
+        break;
+    case PointerGesture::TAP:
+    case PointerGesture::TAP_DRAG:
+    case PointerGesture::BUTTON_CLICK_OR_DRAG:
+    case PointerGesture::HOVER:
+    case PointerGesture::PRESS:
+        // Unfade the pointer when the current gesture manipulates the
+        // area directly under the pointer.
+        mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+        break;
+    case PointerGesture::SWIPE:
+    case PointerGesture::FREEFORM:
+        // Fade the pointer when the current gesture manipulates a different
+        // area and there are spots to guide the user experience.
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+        } else {
+            mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+        }
+        break;
     }
 
     // Send events!
@@ -3808,6 +3857,7 @@
             *outFinishPreviousGesture = true;
             mPointerGesture.currentGestureMode = PointerGesture::PRESS;
             mPointerGesture.activeGestureId = 0;
+            mPointerGesture.referenceIdBits.clear();
 
             if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
                     && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) {
@@ -3938,6 +3988,17 @@
             }
         }
 
+        // Clear the reference deltas for fingers not yet included in the reference calculation.
+        for (BitSet32 idBits(mCurrentTouch.idBits.value & ~mPointerGesture.referenceIdBits.value);
+                !idBits.isEmpty(); ) {
+            uint32_t id = idBits.firstMarkedBit();
+            idBits.clearBit(id);
+
+            mPointerGesture.referenceDeltas[id].dx = 0;
+            mPointerGesture.referenceDeltas[id].dy = 0;
+        }
+        mPointerGesture.referenceIdBits = mCurrentTouch.idBits;
+
         // Move the reference points based on the overall group motion of the fingers.
         // The objective is to calculate a vector delta that is common to the movement
         // of all fingers.
@@ -3951,27 +4012,39 @@
 
                 const PointerData& cpd = mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
                 const PointerData& lpd = mLastTouch.pointers[mLastTouch.idToIndex[id]];
-                float deltaX = cpd.x - lpd.x;
-                float deltaY = cpd.y - lpd.y;
+                PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+                delta.dx += cpd.x - lpd.x;
+                delta.dy += cpd.y - lpd.y;
 
                 if (first) {
-                    commonDeltaX = deltaX;
-                    commonDeltaY = deltaY;
+                    commonDeltaX = delta.dx;
+                    commonDeltaY = delta.dy;
                 } else {
-                    commonDeltaX = calculateCommonVector(commonDeltaX, deltaX);
-                    commonDeltaY = calculateCommonVector(commonDeltaY, deltaY);
+                    commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx);
+                    commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy);
                 }
             }
 
-            mPointerGesture.referenceTouchX += commonDeltaX;
-            mPointerGesture.referenceTouchY += commonDeltaY;
-            mPointerGesture.referenceGestureX +=
-                    commonDeltaX * mLocked.pointerGestureXMovementScale;
-            mPointerGesture.referenceGestureY +=
-                    commonDeltaY * mLocked.pointerGestureYMovementScale;
-            clampPositionUsingPointerBounds(mPointerController,
-                    &mPointerGesture.referenceGestureX,
-                    &mPointerGesture.referenceGestureY);
+            if (commonDeltaX || commonDeltaY) {
+                for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+                    uint32_t id = idBits.firstMarkedBit();
+                    idBits.clearBit(id);
+
+                    PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+                    delta.dx = 0;
+                    delta.dy = 0;
+                }
+
+                mPointerGesture.referenceTouchX += commonDeltaX;
+                mPointerGesture.referenceTouchY += commonDeltaY;
+                mPointerGesture.referenceGestureX +=
+                        commonDeltaX * mLocked.pointerGestureXMovementScale;
+                mPointerGesture.referenceGestureY +=
+                        commonDeltaY * mLocked.pointerGestureYMovementScale;
+                clampPositionUsingPointerBounds(mPointerController,
+                        &mPointerGesture.referenceGestureX,
+                        &mPointerGesture.referenceGestureY);
+            }
         }
 
         // Report gestures.
@@ -4255,7 +4328,7 @@
     { // acquire lock
         AutoMutex _l(mLock);
         if (mPointerController != NULL) {
-            mPointerController->fade();
+            mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
         }
     } // release lock
 }
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 0485617..0fbc93c 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -1021,6 +1021,14 @@
         float referenceGestureX;  // reference gesture X/Y coordinates in pixels
         float referenceGestureY;
 
+        // Distance that each pointer has traveled which has not yet been
+        // subsumed into the reference gesture position.
+        BitSet32 referenceIdBits;
+        struct Delta {
+            float dx, dy;
+        };
+        Delta referenceDeltas[MAX_POINTER_ID + 1];
+
         // Describes how touch ids are mapped to gesture ids for freeform gestures.
         uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
 
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index ffef720..eecb76f 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -69,10 +69,10 @@
 
     mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
 
-    mLocked.pointerIsFading = true; // keep the pointer initially faded
+    mLocked.pointerFadeDirection = 0;
     mLocked.pointerX = 0;
     mLocked.pointerY = 0;
-    mLocked.pointerAlpha = 0.0f;
+    mLocked.pointerAlpha = 0.0f; // pointer is initially faded
     mLocked.pointerSprite = mSpriteController->createSprite();
     mLocked.pointerIconChanged = false;
 
@@ -191,23 +191,37 @@
     *outY = mLocked.pointerY;
 }
 
-void PointerController::fade() {
+void PointerController::fade(Transition transition) {
     AutoMutex _l(mLock);
 
-    sendImmediateInactivityTimeoutLocked();
+    // Remove the inactivity timeout, since we are fading now.
+    removeInactivityTimeoutLocked();
+
+    // Start fading.
+    if (transition == TRANSITION_IMMEDIATE) {
+        mLocked.pointerFadeDirection = 0;
+        mLocked.pointerAlpha = 0.0f;
+        updatePointerLocked();
+    } else {
+        mLocked.pointerFadeDirection = -1;
+        startAnimationLocked();
+    }
 }
 
-void PointerController::unfade() {
+void PointerController::unfade(Transition transition) {
     AutoMutex _l(mLock);
 
     // Always reset the inactivity timer.
     resetInactivityTimeoutLocked();
 
-    // Unfade immediately if needed.
-    if (mLocked.pointerIsFading) {
-        mLocked.pointerIsFading = false;
+    // Start unfading.
+    if (transition == TRANSITION_IMMEDIATE) {
+        mLocked.pointerFadeDirection = 0;
         mLocked.pointerAlpha = 1.0f;
         updatePointerLocked();
+    } else {
+        mLocked.pointerFadeDirection = 1;
+        startAnimationLocked();
     }
 }
 
@@ -401,10 +415,20 @@
     nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
 
     // Animate pointer fade.
-    if (mLocked.pointerIsFading) {
+    if (mLocked.pointerFadeDirection < 0) {
         mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
-        if (mLocked.pointerAlpha <= 0) {
-            mLocked.pointerAlpha = 0;
+        if (mLocked.pointerAlpha <= 0.0f) {
+            mLocked.pointerAlpha = 0.0f;
+            mLocked.pointerFadeDirection = 0;
+        } else {
+            keepAnimating = true;
+        }
+        updatePointerLocked();
+    } else if (mLocked.pointerFadeDirection > 0) {
+        mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
+        if (mLocked.pointerAlpha >= 1.0f) {
+            mLocked.pointerAlpha = 1.0f;
+            mLocked.pointerFadeDirection = 0;
         } else {
             keepAnimating = true;
         }
@@ -432,12 +456,7 @@
 }
 
 void PointerController::doInactivityTimeout() {
-    AutoMutex _l(mLock);
-
-    if (!mLocked.pointerIsFading) {
-        mLocked.pointerIsFading = true;
-        startAnimationLocked();
-    }
+    fade(TRANSITION_GRADUAL);
 }
 
 void PointerController::startAnimationLocked() {
@@ -456,9 +475,8 @@
     mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
 }
 
-void PointerController::sendImmediateInactivityTimeoutLocked() {
+void PointerController::removeInactivityTimeoutLocked() {
     mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
-    mLooper->sendMessage(mHandler, MSG_INACTIVITY_TIMEOUT);
 }
 
 void PointerController::updatePointerLocked() {
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index b9184ac..d6e58c2 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -64,14 +64,21 @@
     /* Gets the absolute location of the pointer. */
     virtual void getPosition(float* outX, float* outY) const = 0;
 
+    enum Transition {
+        // Fade/unfade immediately.
+        TRANSITION_IMMEDIATE,
+        // Fade/unfade gradually.
+        TRANSITION_GRADUAL,
+    };
+
     /* Fades the pointer out now. */
-    virtual void fade() = 0;
+    virtual void fade(Transition transition) = 0;
 
     /* Makes the pointer visible if it has faded out.
      * The pointer never unfades itself automatically.  This method must be called
      * by the client whenever the pointer is moved or a button is pressed and it
      * wants to ensure that the pointer becomes visible again. */
-    virtual void unfade() = 0;
+    virtual void unfade(Transition transition) = 0;
 
     enum Presentation {
         // Show the mouse pointer.
@@ -187,8 +194,8 @@
     virtual uint32_t getButtonState() const;
     virtual void setPosition(float x, float y);
     virtual void getPosition(float* outX, float* outY) const;
-    virtual void fade();
-    virtual void unfade();
+    virtual void fade(Transition transition);
+    virtual void unfade(Transition transition);
 
     virtual void setPresentation(Presentation presentation);
     virtual void setSpots(SpotGesture spotGesture,
@@ -250,7 +257,7 @@
         Presentation presentation;
         bool presentationChanged;
 
-        bool pointerIsFading;
+        int32_t pointerFadeDirection;
         float pointerX;
         float pointerY;
         float pointerAlpha;
@@ -274,7 +281,7 @@
     void startAnimationLocked();
 
     void resetInactivityTimeoutLocked();
-    void sendImmediateInactivityTimeoutLocked();
+    void removeInactivityTimeoutLocked();
     void updatePointerLocked();
 
     Spot* getSpotLocked(uint32_t id);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 1596e54..8b3e731 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -92,10 +92,10 @@
         *outY = 0;
     }
 
-    virtual void fade() {
+    virtual void fade(Transition transition) {
     }
 
-    virtual void unfade() {
+    virtual void unfade(Transition transition) {
     }
 
     virtual void setPresentation(Presentation presentation) {