penguin/narou-android

Simple 縦書き reader for narou

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