Home | History | Annotate | Download | only in utils
      1 package com.android.mail.utils;
      2 
      3 import android.content.Context;
      4 import android.os.SystemClock;
      5 
      6 import com.google.common.collect.Lists;
      7 
      8 import java.util.Deque;
      9 
     10 /**
     11  * Utility class to calculate a velocity using a moving average filter of recent input positions.
     12  * Intended to smooth out touch input events.
     13  */
     14 public class InputSmoother {
     15 
     16     /**
     17      * Some devices have significant sampling noise: it could be that samples come in too late,
     18      * or that the reported position doesn't quite match up with the time. Instantaneous velocity
     19      * on these devices is too jittery to be useful in deciding whether to instantly snap, so smooth
     20      * out the data using a moving average over this window size. A sample window size n will
     21      * effectively average the velocity over n-1 points, so n=2 is the minimum valid value (no
     22      * averaging at all).
     23      */
     24     private static final int SAMPLING_WINDOW_SIZE = 5;
     25 
     26     /**
     27      * The maximum elapsed time (in millis) between samples that we would consider "consecutive".
     28      * Only consecutive samples will factor into the rolling average sample window.
     29      * Any samples that are older than this maximum are continually purged from the sample window,
     30      * so as to avoid skewing the average with irrelevant older values.
     31      */
     32     private static final long MAX_SAMPLE_INTERVAL_MS = 200;
     33 
     34     /**
     35      * Sampling window to calculate rolling average of scroll velocity.
     36      */
     37     private final Deque<Sample> mRecentSamples = Lists.newLinkedList();
     38     private final float mDensity;
     39 
     40     private static class Sample {
     41         int pos;
     42         long millis;
     43     }
     44 
     45     public InputSmoother(Context context) {
     46         mDensity = context.getResources().getDisplayMetrics().density;
     47     }
     48 
     49     public void onInput(int pos) {
     50         Sample sample;
     51         final long nowMs = SystemClock.uptimeMillis();
     52 
     53         final Sample last = mRecentSamples.peekLast();
     54         if (last != null && nowMs - last.millis > MAX_SAMPLE_INTERVAL_MS) {
     55             mRecentSamples.clear();
     56         }
     57 
     58         if (mRecentSamples.size() == SAMPLING_WINDOW_SIZE) {
     59             sample = mRecentSamples.removeFirst();
     60         } else {
     61             sample = new Sample();
     62         }
     63         sample.pos = pos;
     64         sample.millis = nowMs;
     65 
     66         mRecentSamples.add(sample);
     67     }
     68 
     69     /**
     70      * Calculates velocity based on recent inputs from {@link #onInput(int)}, averaged together to
     71      * smooth out jitter.
     72      *
     73      * @return returns velocity in dp/s, or null if not enough samples have been collected
     74      */
     75     public Float getSmoothedVelocity() {
     76         if (mRecentSamples.size() < 2) {
     77             // need at least 2 position samples to determine a velocity
     78             return null;
     79         }
     80 
     81         // calculate moving average over current window
     82         int totalDistancePx = 0;
     83         int prevPos = mRecentSamples.getFirst().pos;
     84         final long totalTimeMs = mRecentSamples.getLast().millis - mRecentSamples.getFirst().millis;
     85 
     86         if (totalTimeMs <= 0) {
     87             // samples are really fast or bad. no answer.
     88             return null;
     89         }
     90 
     91         for (Sample s : mRecentSamples) {
     92             totalDistancePx += Math.abs(s.pos - prevPos);
     93             prevPos = s.pos;
     94         }
     95         final float distanceDp = totalDistancePx / mDensity;
     96         // velocity in dp per second
     97         return distanceDp * 1000 / totalTimeMs;
     98     }
     99 
    100 }
    101