use super::App;
use crate::Pt;
use crate::platform;
use crate::scenes::take_quit_request;
use android_activity::{AndroidApp, MainEvent, PollEvent};
use std::time::{Duration, Instant};
#[cfg(feature = "sensors")]
pub(crate) struct AndroidSensorState {
pub(crate) _manager: *mut ndk_sys::ASensorManager,
pub(crate) queue: *mut ndk_sys::ASensorEventQueue,
pub(crate) gyro: *const ndk_sys::ASensor,
pub(crate) accel: *const ndk_sys::ASensor,
pub(crate) mag: *const ndk_sys::ASensor,
pub(crate) rot: *const ndk_sys::ASensor,
pub(crate) step_counter: *const ndk_sys::ASensor,
pub(crate) step_detector: *const ndk_sys::ASensor,
pub(crate) event_buffer: std::sync::Arc<std::sync::Mutex<Vec<ndk_sys::ASensorEvent>>>,
pub(crate) last_local_day: u64,
pub(crate) today_step_total: u64,
pub(crate) yesterday_step_total: u64,
pub(crate) last_hardware_count: Option<u64>,
pub(crate) internal_data_path: std::path::PathBuf,
}
#[cfg(feature = "sensors")]
unsafe extern "C" fn sensor_callback(_fd: i32, _events: i32, data: *mut std::ffi::c_void) -> i32 {
let state = unsafe { &*(data as *const AndroidSensorState) };
let mut buffer = state.event_buffer.lock().unwrap();
unsafe {
let mut event = std::mem::zeroed::<ndk_sys::ASensorEvent>();
while ndk_sys::ASensorEventQueue_getEvents(state.queue, &mut event, 1) > 0 {
buffer.push(event);
}
}
1 }
pub(crate) struct PlatformData {
pub(crate) native_window: Option<ndk::native_window::NativeWindow>,
pub(crate) floating_surface: Option<wgpu::Surface<'static>>,
pub(crate) redraw_requested: bool,
#[cfg(feature = "sensors")]
pub(crate) sensor_state: Option<AndroidSensorState>,
pub(crate) internal_data_path: Option<std::path::PathBuf>,
}
impl PlatformData {
pub(crate) fn new() -> Self {
Self {
native_window: None,
floating_surface: None,
redraw_requested: false,
#[cfg(feature = "sensors")]
sensor_state: None,
internal_data_path: None,
}
}
}
impl App {
fn apply_pending_window_requests(&mut self) {
let _ = self.ctx.take_window_title_request();
let _ = self.ctx.take_cursor_visible_request();
let _ = self.ctx.take_fullscreen_request();
}
fn request_redraw(&mut self) {
self.platform.redraw_requested = true;
}
#[cfg(feature = "sensors")]
fn current_local_epoch_day() -> u64 {
crate::android::current_local_epoch_day().unwrap_or_else(|| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() / 86400)
.unwrap_or(0)
})
}
#[cfg(feature = "sensors")]
fn persist_step_state(state: &AndroidSensorState) {
let steps_file = state.internal_data_path.join("steps.txt");
let content = format!(
"v2 {} {} {} {}",
state.last_local_day,
state.today_step_total,
state.yesterday_step_total,
state.last_hardware_count.unwrap_or(0)
);
let _ = std::fs::write(steps_file, content);
}
#[cfg(feature = "sensors")]
fn roll_step_day_if_needed(state: &mut AndroidSensorState, current_day: u64) -> bool {
if state.last_local_day == 0 {
state.last_local_day = current_day;
return true;
}
if current_day <= state.last_local_day {
return false;
}
let day_gap = current_day - state.last_local_day;
if day_gap == 1 {
state.yesterday_step_total = state.today_step_total;
} else {
state.yesterday_step_total = 0;
}
state.today_step_total = 0;
state.last_local_day = current_day;
true
}
fn setup_native_window_surface(&mut self, window: &ndk::native_window::NativeWindow) {
let size = (window.width() as u32, window.height() as u32);
if size.0 == 0 || size.1 == 0 {
crate::android::logcat_warn(
"setup_native_window_surface skipped because window size is zero",
);
return;
}
crate::android::logcat_info(&format!(
"setup_native_window_surface size={}x{}",
size.0, size.1
));
eprintln!(
"[spot][android] Setting up surface for window: {}x{}",
size.0, size.1
);
unsafe {
ndk_sys::ANativeWindow_setBuffersGeometry(window.ptr().as_ptr() as *mut _, 0, 0, 1);
}
let surface = unsafe {
self.instance
.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: rwh_06::RawDisplayHandle::Android(
rwh_06::AndroidDisplayHandle::new(),
),
raw_window_handle: rwh_06::RawWindowHandle::AndroidNdk({
let handle = rwh_06::AndroidNdkWindowHandle::new(
std::ptr::NonNull::new(window.ptr().as_mut() as *mut _ as *mut _)
.unwrap(),
);
handle
}),
})
.expect("failed to create surface")
};
let surface =
unsafe { std::mem::transmute::<wgpu::Surface<'_>, wgpu::Surface<'static>>(surface) };
self.surface = Some(surface);
if let Some(surface) = self.surface.as_ref() {
if let Some(g) = self.ctx.runtime.graphics.as_mut() {
self.ctx.registry.gpu_generation += 1;
g.resize(surface, size.0, size.1);
eprintln!("[spot][android] Reusing existing graphics device for new surface.");
crate::android::logcat_info(
"reused existing graphics device for resumed/new surface",
);
self.init_state = platform::GraphicsInitState::Ready(Box::new(None));
self.ctx.set_window_logical_size(
Pt::from_physical_px(size.0 as f64, self.scale_factor),
Pt::from_physical_px(size.1 as f64, self.scale_factor),
);
return;
}
}
self.ctx.registry.gpu_generation += 1;
self.init_state = platform::GraphicsInitState::NotStarted;
platform::begin_graphics_init(
&mut self.init_state,
&self.instance,
self.surface.as_ref().unwrap(),
size.0,
size.1,
self.window_config.transparent,
);
self.ctx.set_window_logical_size(
Pt::from_physical_px(size.0 as f64, self.scale_factor),
Pt::from_physical_px(size.1 as f64, self.scale_factor),
);
eprintln!("[spot][android] Graphics initialization started for new surface.");
crate::android::logcat_info("graphics initialization started for native surface");
self.request_redraw();
}
pub(crate) fn run(&mut self, app: AndroidApp) {
crate::android::init(app.clone());
self.scale_factor = app.config().density().unwrap_or(160) as f64 / 160.0;
self.ctx.set_scale_factor(self.scale_factor);
self.platform.internal_data_path = app.internal_data_path();
eprintln!(
"[spot][android] entering run loop. scale_factor: {}",
self.scale_factor
);
crate::android::logcat_info(&format!(
"entering run loop scale_factor={}",
self.scale_factor
));
self.timing.reset();
self.request_redraw();
let mut frame_count = 0u64;
let mut first_draw_logged = false;
let mut first_frame_presented_logged = false;
let mut graphics_finalized_logged = false;
let mut should_exit_run_loop = false;
loop {
self.apply_pending_window_requests();
let has_drawable_scene = self.scene.has_active_scene()
&& self.ctx.runtime.graphics.is_some()
&& (self.platform.floating_surface.is_some() || self.surface.is_some());
let poll_timeout = if has_drawable_scene && self.platform.redraw_requested {
Duration::ZERO
} else {
self.timing
.next_deadline()
.saturating_duration_since(Instant::now())
};
if let Some(surface_obj) = crate::android::take_floating_surface() {
let jvm = unsafe { jni::JavaVM::from_raw(app.vm_as_ptr() as *mut _) }.unwrap();
let env = jvm.attach_current_thread().unwrap();
let surface_ptr = unsafe {
ndk_sys::ANativeWindow_fromSurface(
env.get_native_interface(),
surface_obj.as_obj().as_raw(),
)
};
if !surface_ptr.is_null() {
let native_window = unsafe {
ndk::native_window::NativeWindow::from_ptr(
std::ptr::NonNull::new(surface_ptr).unwrap(),
)
};
let size = (native_window.width() as u32, native_window.height() as u32);
unsafe {
ndk_sys::ANativeWindow_setBuffersGeometry(surface_ptr, 0, 0, 1);
}
match unsafe {
self.instance
.create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: rwh_06::RawDisplayHandle::Android(
rwh_06::AndroidDisplayHandle::new(),
),
raw_window_handle: rwh_06::RawWindowHandle::AndroidNdk({
rwh_06::AndroidNdkWindowHandle::new(
std::ptr::NonNull::new(surface_ptr as *mut _).unwrap(),
)
}),
})
} {
Ok(s) => {
eprintln!("[spot][android][floating] surface created successfully");
crate::android::logcat_info("floating surface created successfully");
let surface = unsafe {
std::mem::transmute::<wgpu::Surface<'_>, wgpu::Surface<'static>>(s)
};
self.platform.floating_surface = Some(surface);
if let Some(surface) = self.platform.floating_surface.as_ref() {
if let Some(g) = self.ctx.runtime.graphics.as_mut() {
g.resize(surface, size.0, size.1);
}
}
self.ctx.set_window_logical_size(
Pt::from_physical_px(size.0 as f64, self.scale_factor),
Pt::from_physical_px(size.1 as f64, self.scale_factor),
);
self.request_redraw();
}
Err(e) => {
crate::android::logcat_warn(&format!(
"floating surface creation failed: {:?}",
e
));
eprintln!("[spot][android][floating] surface creation failed: {:?}", e)
}
}
}
}
if self.surface.is_none() {
if let Some(window) = app.native_window() {
let size = (window.width() as u32, window.height() as u32);
if size.0 > 0 && size.1 > 0 {
eprintln!(
"[spot][android] Recovering missing surface from current native window: {}x{}",
size.0, size.1
);
crate::android::logcat_info(&format!(
"recovering missing surface from current native window {}x{}",
size.0, size.1
));
self.platform.native_window = Some(window.clone());
self.setup_native_window_surface(&window);
}
}
}
app.poll_events(Some(poll_timeout), |poll_event| {
match poll_event {
PollEvent::Main(MainEvent::InitWindow { .. }) => {
self.platform.native_window = app.native_window();
if let Some(window) = self.platform.native_window.clone() {
let size = (window.width(), window.height());
eprintln!("[spot][android] InitWindow: {}x{}", size.0, size.1);
crate::android::logcat_info(&format!(
"MainEvent::InitWindow size={}x{}",
size.0, size.1
));
self.setup_native_window_surface(&window);
} else {
crate::android::logcat_warn(
"MainEvent::InitWindow received but native_window() returned None",
);
}
}
PollEvent::Main(MainEvent::TerminateWindow { .. }) => {
eprintln!("[spot][android] TerminateWindow");
crate::android::logcat_warn("MainEvent::TerminateWindow");
self.surface.take();
self.platform.native_window.take();
}
PollEvent::Main(MainEvent::WindowResized { .. }) => {
if let (Some(surface), Some(window)) = (self.surface.as_ref(), self.platform.native_window.as_ref()) {
let size = (window.width() as u32, window.height() as u32);
eprintln!("[spot][android] WindowResized: {}x{}", size.0, size.1);
crate::android::logcat_info(&format!(
"MainEvent::WindowResized size={}x{}",
size.0, size.1
));
if size.0 > 0 && size.1 > 0 {
if let Some(g) = self.ctx.runtime.graphics.as_mut() {
g.resize(surface, size.0, size.1);
}
self.ctx.set_window_logical_size(
Pt::from_physical_px(size.0 as f64, self.scale_factor),
Pt::from_physical_px(size.1 as f64, self.scale_factor),
);
self.request_redraw();
}
}
}
PollEvent::Main(MainEvent::Resume { .. }) => {
eprintln!("[spot][android] Resume");
crate::android::logcat_info("MainEvent::Resume");
self.platform.floating_surface = None;
if let Some(window) = self.platform.native_window.clone() {
unsafe {
ndk_sys::ANativeWindow_setBuffersGeometry(window.ptr().as_ptr() as *mut _, 0, 0, 1);
}
eprintln!("[spot][android] Re-creating surface on resume to ensure stability");
crate::android::logcat_info(
"recreating native surface on resume to ensure stability",
);
self.setup_native_window_surface(&window);
self.timing.reset();
} else {
eprintln!("[spot][android] Resume: No native window available. Waiting for InitWindow.");
crate::android::logcat_warn(
"MainEvent::Resume without native window; waiting for InitWindow",
);
}
if crate::android::floating_window_enabled() {
if let Some(service_class) =
crate::android::floating_window_service_class()
{
crate::android::stop_service(service_class);
}
}
if self.ctx.runtime.audio.is_none() {
match crate::audio::AudioSystem::new() {
Ok(audio) => self.ctx.runtime.audio = Some(audio),
Err(e) => eprintln!("[spot][android][audio] initialization failed: {:?}", e),
}
}
if let Some(spot) = self.scene.spot_mut() {
spot.resumed(&mut self.ctx);
}
self.request_redraw();
#[cfg(feature = "sensors")]
self.init_sensors();
}
PollEvent::Main(MainEvent::Pause) => {
eprintln!("[spot][android] Pause");
crate::android::logcat_warn("MainEvent::Pause");
if crate::android::floating_window_enabled() {
if self.ctx.runtime.graphics.is_some() {
if let Some(factory) = crate::android::get_floating_scene_factory()
{
self.scene.remove_current(&mut self.ctx);
self.scene.set_active_scene(factory(&mut self.ctx));
self.scene.mark_floating();
}
}
if let Some(service_class) =
crate::android::floating_window_service_class()
{
crate::android::start_service(service_class);
}
}
if let Some(spot) = self.scene.spot_mut() {
spot.suspended(&mut self.ctx);
}
#[cfg(feature = "sensors")]
self.disable_high_frequency_sensors();
}
PollEvent::Main(MainEvent::ConfigChanged { .. }) => {
self.scale_factor = app.config().density().unwrap_or(160) as f64 / 160.0;
self.ctx.set_scale_factor(self.scale_factor);
eprintln!("[spot][android] ConfigChanged scale_factor: {}", self.scale_factor);
crate::android::logcat_info(&format!(
"MainEvent::ConfigChanged scale_factor={}",
self.scale_factor
));
self.request_redraw();
}
PollEvent::Main(MainEvent::Destroy) => {
eprintln!("[spot][android] Destroy");
crate::android::logcat_warn("MainEvent::Destroy");
should_exit_run_loop = true;
}
PollEvent::Main(MainEvent::InputAvailable) => {
if let Ok(mut iter) = app.input_events_iter() {
loop {
let read = iter.next(|event| {
match event {
android_activity::input::InputEvent::MotionEvent(motion_event) => {
let action = motion_event.action();
let (pointer_index, phase) = match action {
android_activity::input::MotionAction::Down => (0, crate::TouchPhase::Started),
android_activity::input::MotionAction::PointerDown => (motion_event.pointer_index(), crate::TouchPhase::Started),
android_activity::input::MotionAction::Up => (0, crate::TouchPhase::Ended),
android_activity::input::MotionAction::PointerUp => (motion_event.pointer_index(), crate::TouchPhase::Ended),
android_activity::input::MotionAction::Move => (motion_event.pointer_index(), crate::TouchPhase::Moved),
android_activity::input::MotionAction::Cancel => (0, crate::TouchPhase::Cancelled),
_ => return android_activity::InputStatus::Unhandled,
};
let pointer = motion_event.pointer_at_index(pointer_index);
let id = pointer.pointer_id() as u64;
let x = Pt::from_physical_px(pointer.x() as f64, self.scale_factor);
let y = Pt::from_physical_px(pointer.y() as f64, self.scale_factor);
self.ctx.input_mut().handle_touch_raw(id, (x, y), phase);
android_activity::InputStatus::Handled
}
_ => android_activity::InputStatus::Unhandled,
}
});
if !read { break; }
}
}
}
_ => {}
}
});
if should_exit_run_loop {
crate::android::logcat_warn("breaking Android run loop after MainEvent::Destroy");
break;
}
if self.surface.is_some() {
let graphics_opt = platform::finalize_graphics(&mut self.init_state);
let graphics_finalized = graphics_opt.is_some();
let was_floating_scene = self.scene.is_floating_scene();
if let Some(g) = graphics_opt {
self.ctx.runtime.graphics = Some(g);
}
if graphics_finalized && !graphics_finalized_logged {
crate::android::logcat_info("graphics initialization finalized successfully");
graphics_finalized_logged = true;
}
if graphics_finalized || was_floating_scene || self.scene.needs_initial_scene() {
if self.ctx.runtime.graphics.is_some() {
self.scene.restore_root_scene(&mut self.ctx);
if graphics_finalized {
eprintln!("[spot][android] Scene recreated for new graphics device.");
} else if was_floating_scene {
eprintln!("[spot][android] Main scene restored from floating state.");
} else {
eprintln!("[spot][android] Scene initialized.");
}
self.scene.clear_floating();
self.timing.reset();
self.request_redraw();
}
}
}
#[cfg(feature = "sensors")]
if self.platform.sensor_state.is_some() {
let current_day = Self::current_local_epoch_day();
if let Some(state) = self.platform.sensor_state.as_mut() {
let rolled_day = Self::roll_step_day_if_needed(state, current_day);
if rolled_day {
Self::persist_step_state(state);
}
self.ctx
.input_mut()
.handle_step_counter(state.today_step_total as f32);
self.ctx
.input_mut()
.handle_yesterday_step_counter(state.yesterday_step_total as f32);
}
let events = {
let state = self.platform.sensor_state.as_ref().unwrap();
let mut buffer = state.event_buffer.lock().unwrap();
std::mem::take(&mut *buffer)
};
unsafe {
for event in events {
match event.type_ {
1 => {
let x = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.x;
let y = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.y;
let z = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.z;
self.ctx.input_mut().handle_accelerometer(x, y, z);
}
2 => {
let x = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.x;
let y = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.y;
let z = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.z;
self.ctx.input_mut().handle_magnetometer(x, y, z);
}
4 => {
let x = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.x;
let y = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.y;
let z = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.z;
self.ctx.input_mut().handle_gyroscope(x, y, z);
}
11 => {
let x = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.x;
let y = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.y;
let z = event
.__bindgen_anon_1
.__bindgen_anon_1
.vector
.__bindgen_anon_1
.__bindgen_anon_1
.z;
let w = event.__bindgen_anon_1.__bindgen_anon_1.data[3];
self.ctx.input_mut().handle_rotation(x, y, z, w);
}
x if x == ndk_sys::ASENSOR_TYPE_STEP_DETECTOR as i32 => {
self.ctx.input_mut().handle_step_detector();
}
x if x == ndk_sys::ASENSOR_TYPE_STEP_COUNTER as i32 => {
let count = event.__bindgen_anon_1.u64_.step_counter;
eprintln!("[spot][android] Step counter event: count={}", count);
let state = self.platform.sensor_state.as_mut().unwrap();
let current_day = Self::current_local_epoch_day();
let rolled_day = Self::roll_step_day_if_needed(state, current_day);
let delta = match state.last_hardware_count {
Some(previous) if count >= previous => count - previous,
Some(previous) => {
eprintln!(
"[spot][android] Step counter reset detected: previous={} new={}",
previous, count
);
0
}
None => {
eprintln!(
"[spot][android] Establishing initial step counter at {}",
count
);
0
}
};
if delta > 0 {
state.today_step_total =
state.today_step_total.saturating_add(delta);
}
state.last_hardware_count = Some(count);
if delta > 0 || rolled_day {
eprintln!(
"[spot][android] Updated daily steps: current_day={} today={} yesterday={} delta={} last_count={}",
current_day,
state.today_step_total,
state.yesterday_step_total,
delta,
count
);
}
Self::persist_step_state(state);
eprintln!(
"[spot][android] Computed steps_today={} yesterday_steps={} using count={}",
state.today_step_total, state.yesterday_step_total, count,
);
self.ctx
.input_mut()
.handle_step_counter(state.today_step_total as f32);
self.ctx.input_mut().handle_yesterday_step_counter(
state.yesterday_step_total as f32,
);
}
_ => {}
}
}
}
}
let updates = self.timing.run_updates(4, |dt| {
self.ctx.set_delta_time(dt);
if let Some(spot) = self.scene.spot_mut() {
spot.update(&mut self.ctx, dt);
}
self.ctx.input_mut().end_frame();
});
if updates > 0 {
self.request_redraw();
}
if self.scene.has_active_scene() && self.platform.redraw_requested {
if !first_draw_logged {
crate::android::logcat_info("starting first draw attempt");
first_draw_logged = true;
}
self.platform.redraw_requested = false;
self.ctx.begin_frame();
if let Some(spot) = self.scene.spot_mut() {
spot.draw(&mut self.ctx);
}
if self.scene.apply_pending_switch(&mut self.ctx) {
if let Some(spot) = self.scene.spot_mut() {
self.ctx.begin_frame();
spot.draw(&mut self.ctx);
}
}
let mut graphics = self.ctx.runtime.graphics.take();
let draw_result = if let Some(surface) = self.platform.floating_surface.as_ref() {
graphics
.as_mut()
.map(|g| g.draw_context(surface, &mut self.ctx))
} else if let Some(surface) = self.surface.as_ref() {
graphics
.as_mut()
.map(|g| g.draw_context(surface, &mut self.ctx))
} else {
None
};
self.ctx.runtime.graphics = graphics;
if let Some(Err(e)) = draw_result {
crate::android::logcat_warn(&format!("draw_context failed with {:?}", e));
match e {
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated => {
eprintln!(
"[spot][android] Surface error: {:?}. Attempting recovery by re-creating surface.",
e
);
if let Some(window) = self.platform.native_window.clone() {
self.setup_native_window_surface(&window);
}
self.request_redraw();
}
wgpu::SurfaceError::Timeout => {
eprintln!(
"[spot][android] Surface acquisition timeout. Frame skipped."
);
self.request_redraw();
}
wgpu::SurfaceError::OutOfMemory => {
eprintln!("[spot][android] Out of memory error. Surface dropped.");
self.surface.take();
}
_ => {
eprintln!("[spot][android] Surface draw error: {:?}", e);
self.request_redraw();
}
}
} else {
if !first_frame_presented_logged {
crate::android::logcat_info("first frame submitted successfully");
first_frame_presented_logged = true;
}
self.request_redraw();
}
if let Some(g) = self.ctx.runtime.graphics.as_mut() {
g.poll_device(true);
}
frame_count += 1;
if frame_count % 300 == 0 {
eprintln!("[spot][android] Loop alive. Frame: {}", frame_count);
}
}
if take_quit_request() {
crate::android::logcat_warn(
"take_quit_request returned true; exiting Android run loop",
);
break;
}
}
}
#[cfg(feature = "sensors")]
pub(crate) fn init_sensors(&mut self) {
unsafe {
if self.platform.sensor_state.is_none() {
let manager = ndk_sys::ASensorManager_getInstance();
if manager.is_null() {
return;
}
let accel = ndk_sys::ASensorManager_getDefaultSensor(manager, 1);
let mag = ndk_sys::ASensorManager_getDefaultSensor(manager, 2);
let gyro = ndk_sys::ASensorManager_getDefaultSensor(manager, 4);
let rot = ndk_sys::ASensorManager_getDefaultSensor(manager, 11);
let step_detector = ndk_sys::ASensorManager_getDefaultSensor(
manager,
ndk_sys::ASENSOR_TYPE_STEP_DETECTOR as i32,
);
let step_counter = ndk_sys::ASensorManager_getDefaultSensor(
manager,
ndk_sys::ASENSOR_TYPE_STEP_COUNTER as i32,
);
if step_detector.is_null() {
eprintln!("[spot][android] Step detector sensor unavailable on this device.");
}
if step_counter.is_null() {
eprintln!("[spot][android] Step counter sensor unavailable on this device.");
}
let data_path = self
.platform
.internal_data_path
.clone()
.unwrap_or_else(|| std::path::PathBuf::from("/sdcard"));
let steps_file = data_path.join("steps.txt");
let mut last_local_day = Self::current_local_epoch_day();
let mut today_step_total = 0u64;
let mut yesterday_step_total = 0u64;
let mut last_hardware_count = None;
if let Ok(content) = std::fs::read_to_string(&steps_file) {
let parts: Vec<&str> = content.split_whitespace().collect();
if parts.first() == Some(&"v2") && parts.len() >= 5 {
last_local_day = parts[1].parse().unwrap_or(last_local_day);
today_step_total = parts[2].parse().unwrap_or(0);
yesterday_step_total = parts[3].parse().unwrap_or(0);
last_hardware_count = parts[4].parse().ok();
} else if parts.len() >= 3 {
last_local_day = parts[1].parse().unwrap_or(last_local_day);
last_hardware_count = None;
}
}
eprintln!(
"[spot][android] Restored step state: today={} yesterday={} last_day={} last_count={} file={}",
today_step_total,
yesterday_step_total,
last_local_day,
last_hardware_count
.map(|v: u64| v.to_string())
.unwrap_or_else(|| "none".to_string()),
steps_file.display()
);
let sensor_state = AndroidSensorState {
_manager: manager,
queue: std::ptr::null_mut(),
gyro,
accel,
mag,
rot,
step_counter,
step_detector,
event_buffer: std::sync::Arc::new(std::sync::Mutex::new(Vec::with_capacity(
32,
))),
last_local_day,
today_step_total,
yesterday_step_total,
last_hardware_count,
internal_data_path: data_path,
};
let looper = ndk_sys::ALooper_forThread();
if looper.is_null() {
return;
}
self.platform.sensor_state = Some(sensor_state);
let state_ref = self.platform.sensor_state.as_mut().unwrap();
let queue = ndk_sys::ASensorManager_createEventQueue(
manager,
looper,
ndk_sys::ALOOPER_POLL_CALLBACK as i32,
Some(sensor_callback),
state_ref as *mut _ as *mut std::ffi::c_void,
);
if queue.is_null() {
eprintln!("[spot][android] Failed to create sensor event queue");
self.platform.sensor_state = None;
return;
}
state_ref.queue = queue;
eprintln!("[spot][android] Sensor event queue created successfully");
}
if let Some(state) = self.platform.sensor_state.as_ref() {
if !state.accel.is_null() {
let rc = ndk_sys::ASensorEventQueue_enableSensor(state.queue, state.accel);
eprintln!("[spot][android] Enable accelerometer rc={}", rc);
let rc =
ndk_sys::ASensorEventQueue_setEventRate(state.queue, state.accel, 20_000);
eprintln!("[spot][android] Accelerometer event rate rc={}", rc);
}
if !state.mag.is_null() {
let rc = ndk_sys::ASensorEventQueue_enableSensor(state.queue, state.mag);
eprintln!("[spot][android] Enable magnetometer rc={}", rc);
let rc =
ndk_sys::ASensorEventQueue_setEventRate(state.queue, state.mag, 20_000);
eprintln!("[spot][android] Magnetometer event rate rc={}", rc);
}
if !state.gyro.is_null() {
let rc = ndk_sys::ASensorEventQueue_enableSensor(state.queue, state.gyro);
eprintln!("[spot][android] Enable gyroscope rc={}", rc);
let rc =
ndk_sys::ASensorEventQueue_setEventRate(state.queue, state.gyro, 20_000);
eprintln!("[spot][android] Gyroscope event rate rc={}", rc);
}
if !state.rot.is_null() {
let rc = ndk_sys::ASensorEventQueue_enableSensor(state.queue, state.rot);
eprintln!("[spot][android] Enable rotation vector rc={}", rc);
let rc =
ndk_sys::ASensorEventQueue_setEventRate(state.queue, state.rot, 20_000);
eprintln!("[spot][android] Rotation vector event rate rc={}", rc);
}
if !state.step_counter.is_null() {
let rc =
ndk_sys::ASensorEventQueue_enableSensor(state.queue, state.step_counter);
eprintln!("[spot][android] Enable step counter rc={}", rc);
}
if !state.step_detector.is_null() {
let rc =
ndk_sys::ASensorEventQueue_enableSensor(state.queue, state.step_detector);
eprintln!("[spot][android] Enable step detector rc={}", rc);
}
}
}
}
#[cfg(feature = "sensors")]
pub(crate) fn disable_high_frequency_sensors(&mut self) {
unsafe {
if let Some(state) = self.platform.sensor_state.as_ref() {
if !state.accel.is_null() {
ndk_sys::ASensorEventQueue_disableSensor(state.queue, state.accel);
}
if !state.mag.is_null() {
ndk_sys::ASensorEventQueue_disableSensor(state.queue, state.mag);
}
if !state.gyro.is_null() {
ndk_sys::ASensorEventQueue_disableSensor(state.queue, state.gyro);
}
if !state.rot.is_null() {
ndk_sys::ASensorEventQueue_disableSensor(state.queue, state.rot);
}
Self::persist_step_state(state);
}
}
}
}