content: /*
   * 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.
   */
  package com.achep.acdisplay.services.activemode.detectors;
  
  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 android.util.Log;
  
  import com.achep.base.Build;
  import com.achep.base.tests.Check;
  
  import java.util.ArrayList;
  import java.util.List;
  
  public class IdleExitDetector implements SensorEventListener {
  
      @SuppressWarnings("PointlessBooleanExpression")
      private static final boolean DEBUG_ALGORITHM = true && Build.DEBUG;
      private static final String TAG = "ShakeDetector";
  
      /**
       * 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 final List<DataPoint> mDataPoints = new ArrayList<>();
      private final Listener mListener;
      private SensorManager mSensorManager;
  
      private long lastShake = 0;
  
      private float last_x = 0, last_y = 0, last_z = 0;
      private float ave_x = 0, ave_y = 0, ave_z = 0;
  
      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 IdleExitDetector(@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);
          final long now = SystemClock.elapsedRealtime();
          final long deltaTime = now - KEEP_DATA_POINTS_FOR;
          // 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);
  
              // Remove outdated data points.
              while (mDataPoints.size() > 0 && mDataPoints.get(0).time < deltaTime) {
                  mDataPoints.remove(0);
              }
  
              // Calculate average threshold.
              final int length = mDataPoints.size();
              if (length > 10) {
                  for (DataPoint i : mDataPoints) {
                      ave_x += Math.abs(i.x);
                      ave_y += Math.abs(i.y);
                      ave_z += Math.abs(i.z);
                  }
                  ave_x /= length;
                  ave_y /= length;
                  ave_z /= length;
  
  //                if (DEBUG_ALGORITHM) {
  //                    Log.d(TAG, "ave_x=" + ave_x
  //                            + " ave_y=" + ave_y
  //                            + " ave_z=" + ave_z
  //                            + " length=" + length);
  //                }
  
                  final float ave = (ave_x + ave_y + ave_z) / 3f;
                  final float aveDp = (Math.abs(dp.x) + Math.abs(dp.x) + Math.abs(dp.x)) / 3f;
                  final float ratio = aveDp / ave;
  
                  if (DEBUG_ALGORITHM) {
                      Log.d(TAG, "ave=" + ave
                              + " ave_dp=" + aveDp
                              + " ave_ratio=" + ratio
                              + " delta=" + (aveDp - ave));
                  }
  
                  if (Math.abs(ratio) > 6 && Math.abs(aveDp - ave) > 0.5f && ave <= 0.3f) {
                      mListener.onShakeDetected();
  
                      Log.e(TAG, "ave=" + ave
                              + " ave_dp=" + aveDp
                              + " ave_ratio=" + ratio
                              + " delta=" + (aveDp - ave));
                  }
              }
          }
  
          last_x = x;
          last_y = y;
          last_z = z;
      }
  
      @Override
      public void onAccuracyChanged(@NonNull Sensor sensor, int accuracy) { /* unused */ }
  
  }