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) { } }