content: package uk.co.jarofgreen.lib;
  
  import android.hardware.Sensor;
  import android.hardware.SensorEvent;
  import android.hardware.SensorEventListener;
  import android.hardware.SensorManager;
  import android.os.SystemClock;
  import androidx.annotation.NonNull;
  
  import com.achep.base.tests.Check;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  
  /**
   * A shake detection library.
   *
   * @author James
   * @copyright 2013 JMB Technology Limited
   * @license Open Source; 3-clause BSD
   */
  /*
   * Edited by Artem Chepurnoy
   *
   * Copyright (C) 2015 AChep@xda <artemchep@gmail.com>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   * MA  02110-1301, USA.
   */
  // TODO: Filter slow shakes by the velocity
  public class ShakeDetector implements SensorEventListener {
  
      private static final int SHAKE_CHECK_THRESHOLD = 200;
  
      /**
       * After we detect a shake, we ignore any events for a bit of time.
       * We don't want two shakes to close together.
       */
      private static final int IGNORE_EVENTS_AFTER_SHAKE = 1500; // 1.5 sec.
  
      private static final long KEEP_DATA_POINTS_FOR = 1400;
      private static final long MINIMUM_EACH_DIRECTION = 7;
      private static final float POSITIVE_COUNTER_THRESHOLD = 2.0f;
      private static final float NEGATIVE_COUNTER_THRESHOLD = -2.0f;
  
      private final List<DataPoint> mDataPoints = new ArrayList<>();
      private final Listener mListener;
      private SensorManager mSensorManager;
  
      private long lastUpdate;
      private long lastShake = 0;
  
      private float last_x = 0, last_y = 0, last_z = 0;
  
      private final int[] pos = new int[3];
      private final int[] neg = new int[3];
      private final int[] dir = new int[3];
  
      public interface Listener {
  
          /**
           * Called on shake detected.
           */
          void onShakeDetected();
      }
  
      private static class DataPoint {
          public float x, y, z;
          public long time;
  
          public DataPoint(float x, float y, float z, long time) {
              this.x = x;
              this.y = y;
              this.z = z;
              this.time = time;
          }
  
          public float getAxis(int i) {
              switch (i) {
                  case 0:
                      return x;
                  case 1:
                      return y;
                  default:
                      return z;
              }
          }
      }
  
      public ShakeDetector(@NonNull Listener listener) {
          mListener = listener;
      }
  
      public void start(@NonNull SensorManager sensorManager) {
          mSensorManager = sensorManager;
          Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
          Check.getInstance().isNonNull(sensor);
          mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
      }
  
      public void stop() {
          mSensorManager.unregisterListener(this);
          mSensorManager = null;
      }
  
      @Override
      public void onSensorChanged(@NonNull SensorEvent event) {
          Check.getInstance().isTrue(event.sensor.getType() == Sensor.TYPE_ACCELEROMETER);
          long now = SystemClock.elapsedRealtime();
          // If a shake in last X seconds ignore.
          if (lastShake != 0 && (now - lastShake) < IGNORE_EVENTS_AFTER_SHAKE) return;
  
          float x = event.values[0];
          float y = event.values[1];
          float z = event.values[2];
          if (last_x != 0 && last_y != 0 && last_z != 0 && (last_x != x || last_y != y || last_z != z)) {
              DataPoint dp = new DataPoint(last_x - x, last_y - y, last_z - z, now);
              mDataPoints.add(dp);
  
              if ((now - lastUpdate) > SHAKE_CHECK_THRESHOLD) {
                  lastUpdate = now;
                  checkShake();
              }
          }
  
          last_x = x;
          last_y = y;
          last_z = z;
      }
  
      private void checkShake() {
          long curTime = SystemClock.elapsedRealtime();
          long cutOffTime = curTime - KEEP_DATA_POINTS_FOR;
  
          // Remove outdated data points.
          while (mDataPoints.size() > 0 && mDataPoints.get(0).time < cutOffTime) {
              mDataPoints.remove(0);
          }
  
          Arrays.fill(pos, 0);
          Arrays.fill(neg, 0);
          Arrays.fill(dir, 0);
          for (DataPoint dp : mDataPoints) {
              for (int i = 0; i < 3; i++) {
                  float v = dp.getAxis(i);
                  if (v > POSITIVE_COUNTER_THRESHOLD && dir[i] < 1) {
                      pos[i]++;
                      dir[i] = 1;
                  } else if (v < NEGATIVE_COUNTER_THRESHOLD && dir[i] > -1) {
                      neg[i]++;
                      dir[i] = -1;
                  }
              }
          }
  
          for (int i = 0; i < 3; i++) {
              if (pos[i] >= MINIMUM_EACH_DIRECTION && neg[i] >= MINIMUM_EACH_DIRECTION) {
                  last_x = 0;
                  last_y = 0;
                  last_z = 0;
                  lastShake = SystemClock.elapsedRealtime();
                  mDataPoints.clear();
                  // Notify the listener.
                  mListener.onShakeDetected();
                  break;
              }
          }
      }
  
      @Override
      public void onAccuracyChanged(@NonNull Sensor sensor, int accuracy) { /* unused */ }
  
  }