4-finger swipe and 4-finger swipe lift gestures added

Gesture library supports 4-finger swipe and 4-finger swipe lift gesture.
The gestures are called FourFingerSwipe and FourFingerSwipeList,
respectively. The gesture detection is similar to three finger swipe,
but the detection are not merge, since the three finger detection needs
some modifications.

BUG=none
TEST=CL:323291
CQ-DEPEND=CL:323340

Change-Id: I6f157347518defbc80589c25f8320add0b3907f3
Reviewed-on: https://chromium-review.googlesource.com/323162
Commit-Ready: Amirhossein Simjour <asimjour@chromium.org>
Tested-by: Amirhossein Simjour <asimjour@chromium.org>
Reviewed-by: Andrew de los Reyes <adlr@chromium.org>
diff --git a/include/activity_log.h b/include/activity_log.h
index dc37f25..6c40c5f 100644
--- a/include/activity_log.h
+++ b/include/activity_log.h
@@ -126,6 +126,8 @@
   static const char kValueGestureTypeFling[];
   static const char kValueGestureTypeSwipe[];
   static const char kValueGestureTypeSwipeLift[];
+  static const char kValueGestureTypeFourFingerSwipe[];
+  static const char kValueGestureTypeFourFingerSwipeLift[];
   static const char kValueGestureTypeMetrics[];
   static const char kKeyGestureStartTime[];
   static const char kKeyGestureEndTime[];
@@ -150,6 +152,10 @@
   static const char kKeyGestureSwipeDY[];
   static const char kKeyGestureSwipeOrdinalDX[];
   static const char kKeyGestureSwipeOrdinalDY[];
+  static const char kKeyGestureFourFingerSwipeDX[];
+  static const char kKeyGestureFourFingerSwipeDY[];
+  static const char kKeyGestureFourFingerSwipeOrdinalDX[];
+  static const char kKeyGestureFourFingerSwipeOrdinalDY[];
   static const char kKeyGestureMetricsType[];
   static const char kKeyGestureMetricsData1[];
   static const char kKeyGestureMetricsData2[];
diff --git a/include/finger_metrics.h b/include/finger_metrics.h
index 5440e19..42ecb9e 100644
--- a/include/finger_metrics.h
+++ b/include/finger_metrics.h
@@ -12,7 +12,7 @@
 namespace gestures {
 
 static const size_t kMaxFingers = 10;
-static const size_t kMaxGesturingFingers = 3;
+static const size_t kMaxGesturingFingers = 4;
 static const size_t kMaxTapFingers = 10;
 
 // A datastructure describing a 2D vector in the mathematical sense.
diff --git a/include/gestures.h b/include/gestures.h
index d79b6da..96d4340 100644
--- a/include/gestures.h
+++ b/include/gestures.h
@@ -225,6 +225,17 @@
 } GestureSwipe;
 
 typedef struct {
+  float dx, dy;
+  float ordinal_dx, ordinal_dy;
+} GestureFourFingerSwipe;
+
+typedef struct {
+  char __dummy;
+  // Remove this when there is a member in this struct. http://crbug.com/341155
+  // Currently no members
+} GestureFourFingerSwipeLift;
+
+typedef struct {
   char __dummy;
   // Remove this when there is a member in this struct. http://crbug.com/341155
   // Currently no members
@@ -266,6 +277,8 @@
   kGestureTypePinch,
   kGestureTypeSwipeLift,
   kGestureTypeMetrics,
+  kGestureTypeFourFingerSwipe,
+  kGestureTypeFourFingerSwipeLift,
 };
 
 #ifdef __cplusplus
@@ -275,8 +288,10 @@
 extern const GestureButtonsChange kGestureButtonsChange;
 extern const GestureFling kGestureFling;
 extern const GestureSwipe kGestureSwipe;
+extern const GestureFourFingerSwipe kGestureFourFingerSwipe;
 extern const GesturePinch kGesturePinch;
 extern const GestureSwipeLift kGestureSwipeLift;
+extern const GestureFourFingerSwipeLift kGestureFourFingerSwipeLift;
 extern const GestureMetrics kGestureMetrics;
 #endif  // __cplusplus
 
@@ -324,6 +339,14 @@
     details.swipe.ordinal_dx = details.swipe.dx = dx;
     details.swipe.ordinal_dy = details.swipe.dy = dy;
   }
+  Gesture(const GestureFourFingerSwipe&,
+          stime_t start, stime_t end, float dx, float dy)
+      : start_time(start),
+        end_time(end),
+        type(kGestureTypeFourFingerSwipe) {
+    details.four_finger_swipe.ordinal_dx = details.four_finger_swipe.dx = dx;
+    details.four_finger_swipe.ordinal_dy = details.four_finger_swipe.dy = dy;
+  }
   Gesture(const GesturePinch&,
           stime_t start, stime_t end, float dz, unsigned state)
       : start_time(start),
@@ -336,6 +359,10 @@
       : start_time(start),
         end_time(end),
         type(kGestureTypeSwipeLift) {}
+  Gesture(const GestureFourFingerSwipeLift&, stime_t start, stime_t end)
+      : start_time(start),
+        end_time(end),
+        type(kGestureTypeFourFingerSwipeLift) {}
   Gesture(const GestureMetrics&,
           stime_t start, stime_t end, GestureMetricsType m_type,
           float d1, float d2)
@@ -359,6 +386,8 @@
     GesturePinch pinch;
     GestureSwipeLift swipe_lift;
     GestureMetrics metrics;
+    GestureFourFingerSwipe four_finger_swipe;
+    GestureFourFingerSwipeLift four_finger_swipe_lift;
   } details;
 };
 
diff --git a/include/immediate_interpreter.h b/include/immediate_interpreter.h
index 315e159..0ee6137 100644
--- a/include/immediate_interpreter.h
+++ b/include/immediate_interpreter.h
@@ -493,9 +493,14 @@
   // To reset the state machine call with reset=true
   bool UpdatePinchState(const HardwareState& hwstate, bool reset);
 
-  // Returns the current three-finger gesture, or kGestureTypeNull if no gesture
-  // should be produced.
-  GestureType GetThreeFingerGestureType(const FingerState* const fingers[3]);
+  // Returns a gesture assuming that at least one of the fingers performing
+  // current_gesture_type has left
+  GestureType GetFingerLiftGesture(GestureType current_gesture_type);
+
+  // Returns the current multi-finger gesture, or kGestureTypeNull if no gesture
+  // should be produced. num_fingers can be 3 or 4.
+  GestureType GetMultiFingerGestureType(const FingerState* const fingers[],
+                                        const int num_fingers);
 
   const char* TapToClickStateName(TapToClickState state);
 
@@ -756,9 +761,15 @@
   // Maximum distance [mm] between the outermost fingers while performing a
   // three-finger gesture.
   DoubleProperty three_finger_close_distance_thresh_;
+  // Maximum distance [mm] between the outermost fingers while performing a
+  // four-finger gesture.
+  DoubleProperty four_finger_close_distance_thresh_;
   // Minimum distance [mm] one of the three fingers must move to perform a
   // swipe gesture.
   DoubleProperty three_finger_swipe_distance_thresh_;
+  // Minimum distance [mm] one of the four fingers must move to perform a
+  // four finger swipe gesture.
+  DoubleProperty four_finger_swipe_distance_thresh_;
   // If three-finger swipe should be enabled
   BoolProperty three_finger_swipe_enable_;
   // During a scroll one finger determines scroll speed and direction.
diff --git a/src/accel_filter_interpreter.cc b/src/accel_filter_interpreter.cc
index f63d876..647e4a0 100644
--- a/src/accel_filter_interpreter.cc
+++ b/src/accel_filter_interpreter.cc
@@ -182,12 +182,16 @@
   switch (copy.type) {
     case kGestureTypeMove:
     case kGestureTypeSwipe:
+    case kGestureTypeFourFingerSwipe:
       if (copy.type == kGestureTypeMove) {
         scale_out_x = dx = &copy.details.move.dx;
         scale_out_y = dy = &copy.details.move.dy;
-      } else {
+      } else if (copy.type == kGestureTypeSwipe) {
         scale_out_x = dx = &copy.details.swipe.dx;
         scale_out_y = dy = &copy.details.swipe.dy;
+      } else {
+        scale_out_x = dx = &copy.details.four_finger_swipe.dx;
+        scale_out_y = dy = &copy.details.four_finger_swipe.dy;
       }
       if (use_mouse_point_curves_.val_ && use_custom_mouse_curve_.val_) {
         segs = mouse_custom_point_;
diff --git a/src/activity_log.cc b/src/activity_log.cc
index b00ce9d..24ae3a4 100644
--- a/src/activity_log.cc
+++ b/src/activity_log.cc
@@ -304,6 +304,24 @@
       ret[kKeyGestureType] =
           Json::Value(kValueGestureTypeSwipeLift);
       break;
+    case kGestureTypeFourFingerSwipe:
+      handled = true;
+      ret[kKeyGestureType] =
+          Json::Value(kValueGestureTypeFourFingerSwipe);
+      ret[kKeyGestureFourFingerSwipeDX] =
+          Json::Value(gesture.details.four_finger_swipe.dx);
+      ret[kKeyGestureFourFingerSwipeDY] =
+          Json::Value(gesture.details.four_finger_swipe.dy);
+      ret[kKeyGestureFourFingerSwipeOrdinalDX] =
+          Json::Value(gesture.details.four_finger_swipe.ordinal_dx);
+      ret[kKeyGestureFourFingerSwipeOrdinalDY] =
+          Json::Value(gesture.details.four_finger_swipe.ordinal_dy);
+      break;
+    case kGestureTypeFourFingerSwipeLift:
+      handled = true;
+      ret[kKeyGestureType] =
+          Json::Value(kValueGestureTypeFourFingerSwipeLift);
+      break;
     case kGestureTypeMetrics:
       handled = true;
       ret[kKeyGestureType] =
@@ -453,6 +471,9 @@
 const char ActivityLog::kValueGestureTypeFling[] = "fling";
 const char ActivityLog::kValueGestureTypeSwipe[] = "swipe";
 const char ActivityLog::kValueGestureTypeSwipeLift[] = "swipeLift";
+const char ActivityLog::kValueGestureTypeFourFingerSwipe[] = "fourFingerSwipe";
+const char ActivityLog::kValueGestureTypeFourFingerSwipeLift[] =
+    "fourFingerSwipeLift";
 const char ActivityLog::kValueGestureTypeMetrics[] = "metrics";
 const char ActivityLog::kKeyGestureStartTime[] = "startTime";
 const char ActivityLog::kKeyGestureEndTime[] = "endTime";
@@ -477,6 +498,10 @@
 const char ActivityLog::kKeyGestureSwipeDY[] = "dy";
 const char ActivityLog::kKeyGestureSwipeOrdinalDX[] = "ordinalDx";
 const char ActivityLog::kKeyGestureSwipeOrdinalDY[] = "ordinalDy";
+const char ActivityLog::kKeyGestureFourFingerSwipeDX[] = "dx";
+const char ActivityLog::kKeyGestureFourFingerSwipeDY[] = "dy";
+const char ActivityLog::kKeyGestureFourFingerSwipeOrdinalDX[] = "ordinalDx";
+const char ActivityLog::kKeyGestureFourFingerSwipeOrdinalDY[] = "ordinalDy";
 const char ActivityLog::kKeyGestureMetricsType[] = "metricsType";
 const char ActivityLog::kKeyGestureMetricsData1[] = "data1";
 const char ActivityLog::kKeyGestureMetricsData2[] = "data2";
diff --git a/src/gestures.cc b/src/gestures.cc
index 7d2f638..286c3df 100644
--- a/src/gestures.cc
+++ b/src/gestures.cc
@@ -253,6 +253,17 @@
     case kGestureTypeSwipeLift:
       return StringPrintf("(Gesture type: swipeLift start: %f stop: %f)",
                           start_time, end_time);
+    case kGestureTypeFourFingerSwipe:
+      return StringPrintf("(Gesture type: fourFingerSwipe start: %f stop: %f "
+                          "dx: %f dy: %f ordinal_dx: %f ordinal_dy: %f)",
+                          start_time, end_time,
+                          details.four_finger_swipe.dx,
+                          details.four_finger_swipe.dy,
+                          details.four_finger_swipe.ordinal_dx,
+                          details.four_finger_swipe.ordinal_dy);
+    case kGestureTypeFourFingerSwipeLift:
+      return StringPrintf("(Gesture type: fourFingerSwipeLift start: %f "
+                          "stop: %f)", start_time, end_time);
     case kGestureTypeMetrics:
       return StringPrintf("(Gesture type: metrics start: %f stop: %f "
                           "type: %d d1: %f d2: %f)", start_time, end_time,
@@ -300,6 +311,14 @@
     case kGestureTypeSwipeLift:
       return gestures::DoubleEq(start_time, that.start_time) &&
           gestures::DoubleEq(end_time, that.end_time);
+    case kGestureTypeFourFingerSwipe:
+      return gestures::DoubleEq(start_time, that.start_time) &&
+          gestures::DoubleEq(end_time, that.end_time) &&
+          gestures::FloatEq(details.four_finger_swipe.dx,
+              that.details.four_finger_swipe.dx);
+    case kGestureTypeFourFingerSwipeLift:
+      return gestures::DoubleEq(start_time, that.start_time) &&
+          gestures::DoubleEq(end_time, that.end_time);
     case kGestureTypeMetrics:
       return gestures::DoubleEq(start_time, that.start_time) &&
           gestures::DoubleEq(end_time, that.end_time) &&
@@ -625,5 +644,6 @@
 const GestureButtonsChange kGestureButtonsChange = { 0, 0 };
 const GestureFling kGestureFling = { 0, 0, 0, 0, 0 };
 const GestureSwipe kGestureSwipe = { 0, 0, 0, 0 };
+const GestureFourFingerSwipe kGestureFourFingerSwipe = { 0, 0, 0, 0 };
 const GesturePinch kGesturePinch = { 0, 0, 0 };
 const GestureMetrics kGestureMetrics = { kGestureMetricsTypeUnknown, {0, 0} };
diff --git a/src/immediate_interpreter.cc b/src/immediate_interpreter.cc
index 2e27b82..2068ed0 100644
--- a/src/immediate_interpreter.cc
+++ b/src/immediate_interpreter.cc
@@ -1045,9 +1045,15 @@
       three_finger_close_distance_thresh_(prop_reg,
                                           "Three Finger Close Distance Thresh",
                                           50.0),
+      four_finger_close_distance_thresh_(prop_reg,
+                                         "Four Finger Close Distance Thresh",
+                                         60.0),
       three_finger_swipe_distance_thresh_(prop_reg,
                                           "Three Finger Swipe Distance Thresh",
-                                          1.0),
+                                          2.0),
+      four_finger_swipe_distance_thresh_(prop_reg,
+                                         "Four Finger Swipe Distance Thresh",
+                                         2.0),
       three_finger_swipe_enable_(prop_reg, "Three Finger Swipe EnableX", 1),
       scroll_stationary_finger_max_distance_(
           prop_reg, "Scroll Stationary Finger Max Distance", 1.0),
@@ -1417,16 +1423,16 @@
 
     case kGestureTypeScroll:
     case kGestureTypeSwipe:
+    case kGestureTypeFourFingerSwipe:
       // If a gesturing finger just left, do fling/lift
       if (AnyGesturingFingerLeft(*state_buffer_.Get(0),
                                  prev_gs_fingers_)) {
-        current_gesture_type_ =
-            current_gesture_type_ == kGestureTypeScroll ?
-            kGestureTypeFling : kGestureTypeSwipeLift;
+        current_gesture_type_ = GetFingerLiftGesture(current_gesture_type_);
         return;
       }
       // fallthrough
     case kGestureTypeSwipeLift:
+    case kGestureTypeFourFingerSwipeLift:
     case kGestureTypeFling:
     case kGestureTypeMove:
     case kGestureTypeNull:
@@ -1509,11 +1515,23 @@
                 Err("Unable to find gesturing fingers!");
                 return;
               }
-              current_gesture_type_ = GetThreeFingerGestureType(fingers);
+              current_gesture_type_ = GetMultiFingerGestureType(fingers, 3);
               if (current_gesture_type_ == kGestureTypeSwipe)
                 last_swipe_timestamp_ = hwstate.timestamp;
-            } else {
-              Log("TODO(adlr): support > 3 finger gestures.");
+            } else if (sorted_ids.size() == 4) {
+              const FingerState* fingers[] = {
+                hwstate.GetFingerState(*sorted_ids.begin()),
+                hwstate.GetFingerState(*(sorted_ids.begin() + 1)),
+                hwstate.GetFingerState(*(sorted_ids.begin() + 2)),
+                hwstate.GetFingerState(*(sorted_ids.begin() + 3))
+              };
+              if (!fingers[0] || !fingers[1] || !fingers[2] || !fingers[3]) {
+                Err("Unable to find gesturing fingers!");
+                return;
+              }
+              current_gesture_type_ = GetMultiFingerGestureType(fingers, 4);
+              if (current_gesture_type_ == kGestureTypeFourFingerSwipe)
+                current_gesture_type_ = kGestureTypeFourFingerSwipe;
             }
             if (current_gesture_type_ != kGestureTypeNull) {
               active_gs_fingers->clear();
@@ -1988,54 +2006,83 @@
   }
 }
 
-GestureType ImmediateInterpreter::GetThreeFingerGestureType(
-    const FingerState* const fingers[3]) {
-  const FingerState* x_fingers[] = { fingers[0], fingers[1], fingers[2] };
-  const FingerState* y_fingers[] = { fingers[0], fingers[1], fingers[2] };
-  qsort(x_fingers, 3, sizeof(*x_fingers), CompareX<FingerState>);
-  qsort(y_fingers, 3, sizeof(*y_fingers), CompareY<FingerState>);
+GestureType ImmediateInterpreter::GetFingerLiftGesture(
+    GestureType current_gesture_type) {
+  switch(current_gesture_type) {
+    case kGestureTypeScroll: return kGestureTypeFling;
+    case kGestureTypeSwipe: return kGestureTypeSwipeLift;
+    case kGestureTypeFourFingerSwipe: return kGestureTypeFourFingerSwipeLift;
+    default: return kGestureTypeNull;
+  }
+}
 
-  bool horizontal =
-      (x_fingers[2]->position_x - x_fingers[0]->position_x) >=
-      (y_fingers[2]->position_y - y_fingers[0]->position_y);
-  const FingerState* min_finger = horizontal ? x_fingers[0] : y_fingers[0];
-  const FingerState* center_finger = horizontal ? x_fingers[1] : y_fingers[1];
-  const FingerState* max_finger = horizontal ? x_fingers[2] : y_fingers[2];
-
-  if (DistSq(*min_finger, *max_finger) >
-      three_finger_close_distance_thresh_.val_ *
-      three_finger_close_distance_thresh_.val_) {
+GestureType ImmediateInterpreter::GetMultiFingerGestureType(
+    const FingerState* const fingers[], const int num_fingers) {
+  float close_distance_thresh;
+  float swipe_distance_thresh;
+  GestureType gesture_type;
+  if (num_fingers == 4) {
+    close_distance_thresh = four_finger_close_distance_thresh_.val_;
+    swipe_distance_thresh = four_finger_swipe_distance_thresh_.val_;
+    gesture_type = kGestureTypeFourFingerSwipe;
+  } else if (num_fingers == 3) {
+    close_distance_thresh = three_finger_close_distance_thresh_.val_;
+    swipe_distance_thresh = three_finger_swipe_distance_thresh_.val_;
+    gesture_type = kGestureTypeSwipe;
+  } else {
     return kGestureTypeNull;
   }
 
-  float dx[] = {
-    min_finger->position_x - start_positions_[min_finger->tracking_id].x_,
-    center_finger->position_x - start_positions_[center_finger->tracking_id].x_,
-    max_finger->position_x - start_positions_[max_finger->tracking_id].x_
-  };
-  float dy[] = {
-    min_finger->position_y - start_positions_[min_finger->tracking_id].y_,
-    center_finger->position_y - start_positions_[center_finger->tracking_id].y_,
-    max_finger->position_y - start_positions_[max_finger->tracking_id].y_
-  };
+  const FingerState* x_fingers[num_fingers];
+  const FingerState* y_fingers[num_fingers];
+  for (int i = 0; i < num_fingers; i++) {
+    x_fingers[i] = fingers[i];
+    y_fingers[i] = fingers[i];
+  }
+  std::sort(x_fingers, x_fingers + num_fingers,
+            [] (const FingerState* a, const FingerState* b) ->
+                bool { return a->position_x < b->position_x; });
+  std::sort(y_fingers, y_fingers + num_fingers,
+            [] (const FingerState* a, const FingerState* b) ->
+                bool { return a->position_y < b->position_y; });
+  bool horizontal =
+      (x_fingers[num_fingers - 1]->position_x - x_fingers[0]->position_x) >=
+      (y_fingers[num_fingers -1]->position_y - y_fingers[0]->position_y);
+  const FingerState* sorted_fingers[num_fingers];
+  for (int i = 0; i < num_fingers; i++) {
+    sorted_fingers[i] = horizontal ? x_fingers[i] : y_fingers[i];
+  }
+  if (DistSq(*sorted_fingers[0], *sorted_fingers[num_fingers - 1]) >
+      close_distance_thresh * close_distance_thresh) {
+    return kGestureTypeNull;
+  }
+
+  float dx[num_fingers];
+  float dy[num_fingers];
+  for (int i = 0; i < num_fingers; i++) {
+    dx[i] = sorted_fingers[i]->position_x -
+            start_positions_[sorted_fingers[i]->tracking_id].x_;
+    dy[i] = sorted_fingers[i]->position_y -
+            start_positions_[sorted_fingers[i]->tracking_id].y_;
+  }
   // pick horizontal or vertical
   float *deltas = fabsf(dx[0]) > fabsf(dy[0]) ? dx : dy;
   swipe_is_vertical_ = deltas == dy;
 
-  // All three fingers must move in the same direction.
-  if ((deltas[0] > 0 && !(deltas[1] > 0 && deltas[2] > 0)) ||
-      (deltas[0] < 0 && !(deltas[1] < 0 && deltas[2] < 0))) {
-    return kGestureTypeNull;
+  // All fingers must move in the same direction.
+  for (int i = 1; i < num_fingers; i++) {
+    if (deltas[i] * deltas[0] <= 0.0) {
+      return kGestureTypeNull;
+    }
   }
 
   // One finger must have traveled far enough.
-  if (fabsf(deltas[0]) < three_finger_swipe_distance_thresh_.val_ &&
-      fabsf(deltas[1]) < three_finger_swipe_distance_thresh_.val_ &&
-      fabsf(deltas[2]) < three_finger_swipe_distance_thresh_.val_) {
-    return kGestureTypeNull;
+  for (int i = 0; i < num_fingers; i++) {
+    if (fabsf(deltas[i]) >= swipe_distance_thresh) {
+      return gesture_type;
+    }
   }
-
-  return kGestureTypeSwipe;
+  return kGestureTypeNull;
 }
 
 const char* ImmediateInterpreter::TapToClickStateName(TapToClickState state) {
@@ -2740,7 +2787,8 @@
       scroll_manager_.ComputeFling(state_buffer_, scroll_buffer_, &result_);
       break;
     }
-    case kGestureTypeSwipe: {
+    case kGestureTypeSwipe:
+    case kGestureTypeFourFingerSwipe: {
       if (!three_finger_swipe_enable_.val_)
         break;
       float sum_delta[] = { 0.0, 0.0 };
@@ -2773,13 +2821,23 @@
           }
         }
       }
-      result_ = Gesture(
-          kGestureSwipe, state_buffer_.Get(1)->timestamp,
-          hwstate.timestamp,
-          (!swipe_is_vertical_ && finger_cnt[0]) ?
-          sum_delta[0] / finger_cnt[0] : 0.0,
-          (swipe_is_vertical_ && finger_cnt[1]) ?
-          sum_delta[1] / finger_cnt[1] : 0.0);
+      if (current_gesture_type_ == kGestureTypeSwipe) {
+        result_ = Gesture(
+            kGestureSwipe, state_buffer_.Get(1)->timestamp,
+            hwstate.timestamp,
+            (!swipe_is_vertical_ && finger_cnt[0]) ?
+            sum_delta[0] / finger_cnt[0] : 0.0,
+            (swipe_is_vertical_ && finger_cnt[1]) ?
+            sum_delta[1] / finger_cnt[1] : 0.0);
+      } else if (current_gesture_type_ == kGestureTypeFourFingerSwipe) {
+        result_ = Gesture(
+            kGestureFourFingerSwipe, state_buffer_.Get(1)->timestamp,
+            hwstate.timestamp,
+            (!swipe_is_vertical_ && finger_cnt[0]) ?
+            sum_delta[0] / finger_cnt[0] : 0.0,
+            (swipe_is_vertical_ && finger_cnt[1]) ?
+            sum_delta[1] / finger_cnt[1] : 0.0);
+      }
       break;
     }
     case kGestureTypeSwipeLift: {
@@ -2789,6 +2847,12 @@
       break;
     }
 
+    case kGestureTypeFourFingerSwipeLift: {
+      result_ = Gesture(kGestureFourFingerSwipeLift,
+                        state_buffer_.Get(1)->timestamp,
+                        hwstate.timestamp);
+      break;
+    }
     case kGestureTypePinch: {
       float current_dist = sqrtf(TwoFingerDistanceSq(hwstate));
       result_ = Gesture(kGesturePinch, changed_time_, hwstate.timestamp,
diff --git a/src/immediate_interpreter_unittest.cc b/src/immediate_interpreter_unittest.cc
index cb6f39e..b3c4df5 100644
--- a/src/immediate_interpreter_unittest.cc
+++ b/src/immediate_interpreter_unittest.cc
@@ -1485,7 +1485,8 @@
   ii.ResetSameFingersState(hardware_state[0]);
   ii.UpdatePointingFingers(hardware_state[4]);
   ids = ii.GetGesturingFingers(hardware_state[4]);
-  EXPECT_EQ(3, ids.size());
+  EXPECT_EQ(4, ids.size());
+  EXPECT_TRUE(ids.end() != ids.find(91));
   EXPECT_TRUE(ids.end() != ids.find(92));
   EXPECT_TRUE(ids.end() != ids.find(93));
   EXPECT_TRUE(ids.end() != ids.find(94));
diff --git a/src/util.cc b/src/util.cc
index eeea23e..b68ef8f 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -24,11 +24,13 @@
     case kGestureTypeContactInitiated: priority++;  // fallthrough
 
     // Midrange, equal priority
+    case kGestureTypeFourFingerSwipe:  // fallthrough
     case kGestureTypePinch:  // fallthrough
     case kGestureTypeSwipe:  // fallthrough
     case kGestureTypeMove:  // fallthrough
     case kGestureTypeScroll: priority++;  // fallthrough
 
+    case kGestureTypeFourFingerSwipeLift: priority++;  // fallthrough
     case kGestureTypeFling: priority++;  // fallthrough
     case kGestureTypeSwipeLift: priority++;  // fallthrough
     case kGestureTypeButtonsChange: priority++;  // fallthrough
@@ -57,6 +59,7 @@
       case kGestureTypeContactInitiated:  // fallthrough
       case kGestureTypeFling:
       case kGestureTypeSwipeLift:
+      case kGestureTypeFourFingerSwipeLift:
       case kGestureTypeMetrics:
         break;
       case kGestureTypeButtonsChange:
@@ -73,6 +76,13 @@
         break;
       case kGestureTypeSwipe:
         gesture->details.swipe.dx += addend->details.swipe.dx;
+        gesture->details.swipe.dy += addend->details.swipe.dy;
+        break;
+      case kGestureTypeFourFingerSwipe:
+        gesture->details.four_finger_swipe.dx +=
+            addend->details.four_finger_swipe.dx;
+        gesture->details.four_finger_swipe.dy +=
+            addend->details.four_finger_swipe.dy;
         break;
       case kGestureTypePinch:
         gesture->details.pinch.dz += addend->details.pinch.dz;