Skip to content

Commit 0007b5f

Browse files
RingController: accessibility, gesture, and action updates
Made-with: Cursor
1 parent 084c5e6 commit 0007b5f

3 files changed

Lines changed: 54 additions & 19 deletions

File tree

src/RingController/RingAccessibilityService.cs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public class RingAccessibilityService : AccessibilityService, ISensorEventListen
4444
/// <summary> ThresholdCrossing: last time we executed after a successful <see cref="RingGestureInterpreter.Interpret"/> (rate limit via <see cref="RingModeProfile.AccumulationTimeoutMs"/>). </summary>
4545
long lastThresholdTriggerAtMs;
4646

47+
/// <summary> Last sample where ring reported motion or off-center integral (see <see cref="TryResetInterpreterAfterRingIdle"/>). </summary>
48+
long lastMeaningfulRingActivityMs;
49+
50+
const long MeaningfulRingIdleResetMs = 400;
51+
const int MeaningfulRingSumAbsQuiet = 24;
52+
4753
const string SensorStringTypeOpticalTracking = "xiaomi.sensor.optical_tracking";
4854

4955
public override void OnCreate()
@@ -102,6 +108,7 @@ public override void OnDestroy()
102108
/// <summary> Sets foreground package for per-app config and whether a camera app is on top. </summary>
103109
void UpdateForegroundPackage(string packageName)
104110
{
111+
var priorResolved = foregroundResolvedPackage;
105112
var resolvedPackageName = packageName;
106113

107114
// Home / system UI can be the event package while another app is actually focused — prefer root window.
@@ -137,6 +144,14 @@ void UpdateForegroundPackage(string packageName)
137144
}
138145

139146
foregroundResolvedPackage = resolvedPackageName;
147+
if (!string.IsNullOrEmpty(priorResolved) &&
148+
!string.Equals(priorResolved, foregroundResolvedPackage, StringComparison.Ordinal))
149+
{
150+
interpreter.ResetProcessingStateAfterIdle();
151+
lastSumDeltaXForAccum = null;
152+
accumBaselineSum = null;
153+
lastMeaningfulRingActivityMs = 0;
154+
}
140155
}
141156

142157
RingConfig ResolveEffectiveRingConfig(RingConfig? loadedRoot = null)
@@ -182,6 +197,28 @@ static int AccumulateEffectiveAbsMag(int sum, int delta, int baseline, int minAb
182197
return Math.Max(integralAbs, ThresholdAlignedBlendedAbs(sum, delta, minAbsToTrigger));
183198
}
184199

200+
/// <summary>
201+
/// Optical tracking can stream events continuously; inter-sample time never shows "idle". After quiet motion/integral,
202+
/// reset interpreter + accum baselines so stale driver state cannot block triggers.
203+
/// </summary>
204+
void TryResetInterpreterAfterRingIdle(long nowMs, int sumDeltaX, int deltaX)
205+
{
206+
var meaningful = deltaX != 0 || Math.Abs(sumDeltaX) > MeaningfulRingSumAbsQuiet;
207+
if (meaningful)
208+
{
209+
lastMeaningfulRingActivityMs = nowMs;
210+
return;
211+
}
212+
213+
if (lastMeaningfulRingActivityMs <= 0) return;
214+
if (nowMs - lastMeaningfulRingActivityMs <= MeaningfulRingIdleResetMs) return;
215+
216+
interpreter.ResetProcessingStateAfterIdle();
217+
lastSumDeltaXForAccum = null;
218+
accumBaselineSum = null;
219+
lastMeaningfulRingActivityMs = 0;
220+
}
221+
185222
public void OnSensorChanged(SensorEvent? e)
186223
{
187224
if (e?.Values == null || e.Values.Count < 8) return;
@@ -192,14 +229,14 @@ public void OnSensorChanged(SensorEvent? e)
192229
if (isCameraForeground) return;
193230

194231
var sensorData = ringSensor.CreateRingSensorData(e);
232+
var now = SystemClock.UptimeMillis();
233+
TryResetInterpreterAfterRingIdle(now, sensorData.sum_delta_x, sensorData.delta_x);
234+
195235
var config = ResolveEffectiveRingConfig(root);
196236
var modeProfile = config.GetProfile(config.ExecutionMode);
197237

198-
// Every sensor event: zero all timeout / interval bases so nothing carries over from prior samples.
199-
lastThresholdTriggerAtMs = 0;
200-
lastAccumExecuteAtMs = 0;
201-
lastSensorEventMsForAccum = 0;
202-
interpreter.ResetTimeoutClocksForSensorEvent();
238+
// Timing state (threshold / accum idle / gesture gap & cooldown) must persist across sensor callbacks.
239+
// Resetting clocks every sample broke MaxGapMs, idle baseline snap, and cooldowns after pauses (e.g. app scroll).
203240

204241
if (config.ExecutionMode != RingExecutionMode.ThresholdAccumulatedRepeat)
205242
{
@@ -212,7 +249,6 @@ public void OnSensorChanged(SensorEvent? e)
212249

213250
if (config.ExecutionMode == RingExecutionMode.Gesture)
214251
{
215-
var now = SystemClock.UptimeMillis();
216252
var sum = sensorData.sum_delta_x;
217253
var gestureAction = interpreter.InterpretGesture(
218254
sumDeltaX: sum,
@@ -238,7 +274,6 @@ public void OnSensorChanged(SensorEvent? e)
238274
}
239275
else if (config.ExecutionMode == RingExecutionMode.ThresholdAccumulatedRepeat)
240276
{
241-
var now = SystemClock.UptimeMillis();
242277
long resetAfter = modeProfile.AccumulationTimeoutMs > 0 ? modeProfile.AccumulationTimeoutMs : 100;
243278
long minIntervalMs = resetAfter;
244279

src/RingController/RingActionExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ void DispatchCenterVerticalSwipe(bool swipeUp)
412412
var cy = h * 0.5f;
413413
cy = Math.Clamp(cy, h * 0.15f, h * 0.85f);
414414

415-
var half = Math.Min(h * 0.38f, 560f) * 0.5f;
415+
var half = Math.Min(h * 0.38f, 560f) * 0.25f;
416416
float y1, y2;
417417
if (swipeUp)
418418
{

src/RingController/RingGestureInterpreter.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,10 @@ sealed record GestureEvent(long TimeMs, RingDirection Direction, int AbsDeltaX);
1212
readonly List<GestureEvent> buffer = new();
1313
readonly List<GestureSequenceState> gestureSequenceStates = new();
1414
int? lastGestureSumForDriver;
15-
/// <summary>
16-
/// Time of the last sensor callback. After <see cref="ResetTimeoutClocksForSensorEvent"/> resets to 0,
17-
/// updated in <see cref="InterpretGesture"/>'s <c>finally</c> (elapsed time restarts per event).
18-
/// </summary>
15+
/// <summary> Time of the last <see cref="InterpretGesture"/> sample; used for <c>MaxGapMs</c> between callbacks. </summary>
1916
long lastGestureSensorMs;
2017
long lastActionAtMs;
2118

22-
/// <summary> Resets internal timeout/cooldown clocks to 0 on each sensor event (call from the service every time). </summary>
23-
internal void ResetTimeoutClocksForSensorEvent()
24-
{
25-
lastGestureSensorMs = 0;
26-
lastActionAtMs = 0;
27-
}
28-
2919
struct GestureSequenceState
3020
{
3121
/// <summary> Accumulation for the current step in the active direction only (left: positive delta_x; right: absolute of negative delta_x). </summary>
@@ -59,6 +49,16 @@ static int GestureLeftIncrement(int deltaX, int dSum) =>
5949
static int GestureRightIncrement(int deltaX, int dSum) =>
6050
DirectionalGestureIncrement(Math.Max(0, -deltaX), Math.Max(0, -dSum));
6151

52+
/// <summary> Clears driver/gesture/bookkeeping state after user idle or app switch (high-rate sensor never "gaps" in time). </summary>
53+
public void ResetProcessingStateAfterIdle()
54+
{
55+
lastGestureSumForDriver = null;
56+
lastGestureSensorMs = 0;
57+
lastActionAtMs = 0;
58+
buffer.Clear();
59+
gestureSequenceStates.Clear();
60+
}
61+
6262
/// <summary>
6363
/// Gesture mode: accumulates per-frame <c>delta_x</c> and sum deltas per direction (<see cref="DirectionalGestureIncrement"/> dampens release overshoot).
6464
/// </summary>

0 commit comments

Comments
 (0)