diff --git a/Cargo.toml b/Cargo.toml index f677d0e2..54ed5e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,9 @@ resolver = "2" members = [ "ndk", "ndk-sys", + "ndk-examples", ] + +[patch.crates-io] +ndk = { path = "ndk" } +ndk-sys = { path = "ndk-sys" } diff --git a/ndk-examples/Cargo.toml b/ndk-examples/Cargo.toml new file mode 100644 index 00000000..16a058db --- /dev/null +++ b/ndk-examples/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ndk-examples" +version = "0.0.0" +publish = false +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1" +log = "0.4" +ndk = { path = "../ndk", features = ["media", "api-level-35"] } # in sync +ndk-sys = { path = "../ndk-sys" } +rustix = { version = "0.38", default-features = false, features = ["std", "pipe", "stdio"] } +# android-activity = { path = "../../android-activity/android-activity", features = ["native-activity"] } +# android-activity = { version = "0.5", features = ["native-activity"] } +# winit = { version = "0.29", features = ["android-native-activity"] } diff --git a/ndk-examples/manifest.yaml b/ndk-examples/manifest.yaml new file mode 100644 index 00000000..ef3dae15 --- /dev/null +++ b/ndk-examples/manifest.yaml @@ -0,0 +1,11 @@ +android: + manifest: + uses_permission: + - name: android.permission.INTERNET + sdk: + # Need at least API level 28 for MediaDataSource. Otherwise: + # 4907 4963 W Parcel : Expecting binder but got null! + # 4907 4963 E NuMediaExtractor: initMediaExtractor: failed to create MediaExtractor + # 4907 4963 E le.ndk_examples: sf error code: -1010 + min_sdk_version: 33 + target_sdk_version: 33 diff --git a/ndk-examples/src/lib.rs b/ndk-examples/src/lib.rs new file mode 100644 index 00000000..518745b1 --- /dev/null +++ b/ndk-examples/src/lib.rs @@ -0,0 +1,173 @@ +use std::{ + ffi::{CStr, CString}, + fs::File, + io::{BufRead as _, BufReader, Read}, + os::fd::AsFd, + ptr::{addr_of, NonNull}, + sync::{Arc, Mutex}, + time::Duration, +}; + +use anyhow::Result; +use log::{error, info, Level}; +use ndk::{ + asset::AssetManager, + data_space::DataSpace, + hardware_buffer::HardwareBufferUsage, + looper::{FdEvent, ThreadLooper}, + media::{ + image_reader::{Image, ImageFormat, ImageReader}, + media_format::MediaFormat, + }, + native_activity::NativeActivity, + native_window::NativeWindow, + thermal::ThermalManager, + // surface_control::{SurfaceControl, SurfaceTransaction}, +}; + +pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) { + let prio = match level { + Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR, + Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN, + Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO, + Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG, + Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE, + }; + unsafe { + ndk_sys::__android_log_write(prio.0 as i32, tag.as_ptr(), msg.as_ptr()); + } +} + +pub(crate) fn forward_stdio_to_logcat() -> std::thread::JoinHandle> { + // XXX: make this stdout/stderr redirection an optional / opt-in feature?... + + let file = { + let logpipe = rustix::pipe::pipe().unwrap(); + rustix::stdio::dup2_stdout(&logpipe.1).unwrap(); + rustix::stdio::dup2_stderr(&logpipe.1).unwrap(); + + File::from(logpipe.0) + }; + + std::thread::Builder::new() + .name("stdio-to-logcat".to_string()) + .spawn(move || -> std::io::Result<()> { + let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap(); + let mut reader = BufReader::new(file); + let mut buffer = String::new(); + loop { + buffer.clear(); + let len = match reader.read_line(&mut buffer) { + Ok(len) => len, + Err(e) => { + error!("Logcat forwarder failed to read stdin/stderr: {e:?}"); + break Err(e); + } + }; + if len == 0 { + break Ok(()); + } else if let Ok(msg) = CString::new(buffer.clone()) { + android_log(Level::Info, tag, &msg); + } + } + }) + .expect("Failed to start stdout/stderr to logcat forwarder thread") +} + +#[no_mangle] +unsafe extern "C" fn ANativeActivity_onCreate(activity: *mut ndk_sys::ANativeActivity) { + forward_stdio_to_logcat(); + + let activity = &mut *activity; + + let mut thread = std::thread::spawn(move || { + let looper = ThreadLooper::prepare(); + let (read, write) = rustix::pipe::pipe().unwrap(); + + println!("New thread ID {:?}", std::thread::current().id()); + let tm = Arc::new(ThermalManager::new().unwrap()); + dbg!(tm.current_thermal_status()); + // dbg!(tm + // .thermal_headroom_thresholds() + // .unwrap() + // .unwrap() + // .collect::>()); + let token = Arc::new(Mutex::new(None)); + let t2 = token.clone(); + let tm2 = tm.clone(); + *t2.lock().unwrap() = Some( + tm.register_thermal_status_listener(Box::new(move |s| { + println!( + "Thermal callback fired on ID {:?}", + std::thread::current().id() + ); + dbg!(s); + println!("BEFORE UNREGISTERED"); + tm2.unregister_thermal_status_listener(dbg!(token.lock().unwrap().take().unwrap())) + .unwrap(); + println!("UNREGISTERED"); + })) + .unwrap(), + ); + // std::thread::sleep(Duration::from_millis(2000)); + // tm.unregister_thermal_status_listener(x).unwrap(); + // std::thread::sleep(Duration::from_millis(200)); + looper + .as_foreign() + .add_fd(read.as_fd(), 10, FdEvent::all(), std::ptr::null_mut()) + .unwrap(); + + // rustix::io::write(&write, b"hello\0").unwrap(); + + loop { + println!("Starting looper"); + match looper + .poll_once() + // .poll_once_timeout(Duration::from_millis(200)) + .unwrap() + { + ndk::looper::Poll::Wake => todo!(), + ndk::looper::Poll::Callback => { + info!("Callback"); + } + ndk::looper::Poll::Timeout => { + rustix::io::write(&write, b"Timeout\0").unwrap(); + } + ndk::looper::Poll::Event { + ident, + fd, + events, + data, + } => { + dbg!(fd, events); + let mut data = vec![0; 1024]; + let len = dbg!(rustix::io::read(fd, &mut data)).unwrap(); + dbg!(&data[..len]); + + rustix::io::write(&write, b"Hello from event to CB\0").unwrap(); + + looper + .as_foreign() + .add_fd_with_callback(read.as_fd(), FdEvent::all(), |fd, event| { + dbg!(fd, events); + let mut data = vec![0; 1024]; + let len = dbg!(rustix::io::read(fd, &mut data)).unwrap(); + dbg!(&data[..len]); + // rustix::io::write(&write, b"Hello from CB to whomever\0").unwrap(); + true + // false + }) + .unwrap(); + dbg!(&write); + } + } + } + // looper + // .as_foreign() + // .add_fd(read.as_fd(), 10, FdEvent::all(), std::ptr::null_mut()); + }); + + activity.instance = <*mut _>::cast(&mut thread); + std::mem::forget(thread); + // main(app).expect("Main failed") +} diff --git a/ndk/Cargo.toml b/ndk/Cargo.toml index 4f29c897..7d159b49 100644 --- a/ndk/Cargo.toml +++ b/ndk/Cargo.toml @@ -15,7 +15,7 @@ categories = ["os", "os::android-apis", "api-bindings"] [features] default = ["rwh_06"] -all = ["audio", "bitmap", "media", "nativewindow", "sync", "api-level-34", "rwh_04", "rwh_05", "rwh_06"] +all = ["audio", "bitmap", "media", "nativewindow", "sync", "api-level-35", "rwh_04", "rwh_05", "rwh_06"] audio = ["ffi/audio", "api-level-26"] bitmap = ["ffi/bitmap"]