1#![cfg(target_os = "android")]
2
3use std::path::PathBuf;
4
5pub use android_activity as activity;
6pub type App = android_activity::AndroidApp;
7
8static APP: std::sync::OnceLock<App> = std::sync::OnceLock::new();
9
10pub fn init(app: App) {
11 APP.set(app).unwrap();
12}
13
14pub fn app() -> &'static App {
15 APP.get().expect("Android app was not set")
16}
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
19pub enum FileMode {
20 FileSystem,
21 Assets,
22}
23
24static FILE_MODE: std::sync::OnceLock<std::sync::Mutex<FileMode>> = std::sync::OnceLock::new();
25
26fn file_mode_mutex() -> &'static std::sync::Mutex<FileMode> {
27 FILE_MODE.get_or_init(|| std::sync::Mutex::new(FileMode::Assets))
28}
29
30pub fn set_file_mode(file_mode: FileMode) {
31 *file_mode_mutex().lock().unwrap() = file_mode;
32}
33
34pub fn file_mode() -> FileMode {
35 *file_mode_mutex().lock().unwrap()
36}
37
38mod make_it_work {
39 use jni::{JNIEnv, JavaVM};
40 use std::ffi::c_void;
41
42 #[no_mangle]
43 pub extern "C" fn JNI_OnLoad(
44 vm: jni::JavaVM,
45 res: *mut std::os::raw::c_void,
46 ) -> jni::sys::jint {
47 std::thread::sleep(std::time::Duration::from_secs(3));
49 let env = vm.get_env().unwrap();
50 let vm = vm.get_java_vm_pointer() as *mut c_void;
51 unsafe {
52 ndk_context::initialize_android_context(vm, res);
53 }
54 jni::JNIVersion::V6.into()
55 }
56
57 #[no_mangle]
58 pub unsafe extern "C" fn __cxa_pure_virtual() {
59 loop {}
60 }
61}
62
63pub fn copy_assets_to_filesystem(
64 asset_dirs: impl IntoIterator<Item = impl AsRef<std::path::Path>>,
65 destination: impl AsRef<std::path::Path>,
66) {
67 copy(asset_dirs, destination).unwrap();
68
69 use std::{
70 error::Error,
71 ffi::{CStr, CString},
72 fs, io,
73 path::{Path, PathBuf},
74 };
75
76 use jni::{
77 objects::{JObject, JObjectArray, JValueGen},
78 JNIEnv, JavaVM,
79 };
80 use ndk::asset::Asset;
81
82 pub type CopyResult<T> = Result<T, Box<dyn Error>>;
83
84 pub fn copy(
85 asset_dirs: impl IntoIterator<Item = impl AsRef<Path>>,
86 destination: impl AsRef<Path>,
87 ) -> CopyResult<()> {
88 let ctx = ndk_context::android_context();
90 let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()) }?;
91 let mut env = vm.attach_current_thread()?;
92
93 let asset_manager = env
95 .call_method(
96 unsafe { JObject::from_raw(ctx.context().cast()) },
97 "getAssets",
98 "()Landroid/content/res/AssetManager;",
99 &[],
100 )?
101 .l()?;
102
103 for asset_dir in asset_dirs {
105 copy_recursively(
106 &mut *env,
107 &asset_manager,
108 asset_dir.as_ref().to_path_buf(),
109 destination.as_ref().join(asset_dir),
110 )?;
111 }
112
113 Ok(())
114 }
115
116 fn copy_recursively(
117 env: &mut JNIEnv,
118 asset_manager: &JObject,
119 asset_dir: PathBuf,
120 destination_dir: PathBuf,
121 ) -> CopyResult<()> {
122 for asset_filename in list(env, &asset_manager, &asset_dir)? {
123 let asset_path = asset_dir.join(&asset_filename);
124 if let Some(asset) = open_asset(&CString::new(asset_path.to_string_lossy().as_ref())?) {
125 copy_asset(asset, asset_filename, &destination_dir)?;
126 } else {
127 copy_recursively(
128 env,
129 &asset_manager,
130 asset_path,
131 destination_dir.join(asset_filename),
132 )?;
133 }
134 }
135 Ok(())
136 }
137
138 fn list(
139 env: &mut JNIEnv,
140 asset_manager: &JObject,
141 asset_dir: &Path,
142 ) -> CopyResult<Vec<String>> {
143 let asset_array: JObjectArray = env
144 .call_method(
145 asset_manager,
146 "list",
147 "(Ljava/lang/String;)[Ljava/lang/String;",
148 &[JValueGen::Object(
149 &env.new_string(asset_dir.to_string_lossy())?.into(),
150 )],
151 )?
152 .l()?
153 .into();
154
155 let mut assets = Vec::new();
156 for index in 0..env.get_array_length(&asset_array)? {
157 let elem = env.get_object_array_element(&asset_array, index)?.into();
158 let asset: String = env.get_string(&elem)?.into();
159 assets.push(asset);
160 }
161
162 Ok(assets)
163 }
164
165 fn open_asset(asset_path: &CStr) -> Option<Asset> {
166 app().asset_manager().open(asset_path)
167 }
168
169 fn copy_asset(
170 mut asset: Asset,
171 filename: impl AsRef<Path>,
172 destination_dir: impl AsRef<Path>,
173 ) -> CopyResult<()> {
174 fs::create_dir_all(destination_dir.as_ref())?;
175 let mut file = fs::File::options()
176 .create(true)
177 .write(true)
178 .open(destination_dir.as_ref().join(filename))?;
179
180 io::copy(&mut asset, &mut file)?;
181 Ok(())
182 }
183}