app/src/main/java/net/astropenguin/naroureader/TiltSensor.java
raw · 3531 bytes
package net.astropenguin.naroureader;
import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.view.Surface;
import android.view.WindowManager;
import org.mozilla.gecko.util.GeckoBundle;
/** On-demand, screen-relative accelerometer samples. Never runs in the background. */
final class TiltSensor implements SensorEventListener {
private final Activity activity;
private final SensorManager manager;
private final Sensor sensor;
private final Handler handler = new Handler(Looper.getMainLooper());
private final Runnable timeout = this::stop;
private boolean listening;
private long sampleTime;
private float x;
private int rotation = -1;
TiltSensor(Activity activity) {
this.activity = activity;
manager = (SensorManager) activity.getSystemService(Activity.SENSOR_SERVICE);
sensor = manager == null ? null : manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
GeckoBundle sample(boolean enabled, boolean moving) {
if (!enabled) stop();
else if (sensor != null) {
if (!listening) {
sampleTime = 0;
listening = manager.registerListener(this, sensor, 50_000);
}
handler.removeCallbacks(timeout);
handler.postDelayed(timeout, 1000); // Also stops if a document disappears or JS stalls.
}
boolean fresh = listening && sampleTime != 0 && SystemClock.elapsedRealtime() - sampleTime < 500;
if (fresh && moving) activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
else activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
// GeckoView requires GeckoBundle for outgoing structured replies.
GeckoBundle result = new GeckoBundle(4);
result.putBoolean("available", sensor != null);
result.putBoolean("active", enabled && fresh);
result.putDouble("x", x);
result.putInt("rotation", rotation);
return result;
}
void stop() {
if (listening) manager.unregisterListener(this);
listening = false;
sampleTime = 0;
handler.removeCallbacks(timeout);
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@SuppressWarnings("deprecation")
@Override public void onSensorChanged(SensorEvent event) {
int nextRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
float raw;
switch (nextRotation) {
case Surface.ROTATION_90: raw = -event.values[1]; break;
case Surface.ROTATION_180: raw = -event.values[0]; break;
case Surface.ROTATION_270: raw = event.values[1]; break;
default: raw = event.values[0];
}
raw = Math.max(-1f, Math.min(1f, raw / SensorManager.GRAVITY_EARTH));
long now = SystemClock.elapsedRealtime();
// Small low-pass filter rejects hand tremor, reset on resume or rotation.
float weight = (float) (1 - Math.exp(-(now - sampleTime) / 100.0));
x = sampleTime == 0 || rotation != nextRotation ? raw : x + weight * (raw - x);
rotation = nextRotation;
sampleTime = now;
}
@Override public void onAccuracyChanged(Sensor sensor, int accuracy) { }
}