tauri_plugin_android_fs/api/
android_fs.rs

1#[allow(unused)]
2use std::io::{Read as _, Write as _};
3use crate::*;
4
5
6/// ***Root API***  
7/// 
8/// # Examples
9/// ```
10/// fn example(app: &tauri::AppHandle) {
11///     use tauri_plugin_android_fs::AndroidFsExt as _;
12/// 
13///     let api = app.android_fs();
14/// }
15/// ```
16pub struct AndroidFs<R: tauri::Runtime> {
17    #[cfg(target_os = "android")]
18    pub(crate) app: tauri::AppHandle<R>, 
19
20    #[cfg(target_os = "android")]
21    pub(crate) api: tauri::plugin::PluginHandle<R>, 
22
23    #[cfg(target_os = "android")]
24    pub(crate) intent_lock: std::sync::Mutex<()>,
25
26    #[cfg(not(target_os = "android"))]
27    _marker: std::marker::PhantomData<fn() -> R>
28}
29
30impl<R: tauri::Runtime> AndroidFs<R> {
31
32    pub(crate) fn new<C: serde::de::DeserializeOwned>(
33        app: tauri::AppHandle<R>,
34        api: tauri::plugin::PluginApi<R, C>,
35    ) -> crate::Result<Self> {
36
37        #[cfg(target_os = "android")] {
38            Ok(Self {
39                api: api.register_android_plugin("com.plugin.android_fs", "AndroidFsPlugin")?, 
40                app,
41                intent_lock: std::sync::Mutex::new(())
42            })
43        }
44        
45        #[cfg(not(target_os = "android"))] {
46            Ok(Self { _marker: Default::default() })
47        }
48    }
49}
50
51impl<R: tauri::Runtime> AndroidFs<R> {
52
53    /// API of file storage intended for the app's use only.
54    pub fn private_storage(&self) -> PrivateStorage<'_, R> {
55        PrivateStorage(self)
56    }
57
58    /// API of file storage that is available to other applications and users.
59    pub fn public_storage(&self) -> PublicStorage<'_, R> {
60        PublicStorage(self)
61    }
62
63    /// API of file/dir picker.
64    pub fn file_picker(&self) -> FilePicker<'_, R> {
65        FilePicker(self)
66    }
67
68    /// API of sharing files with other apps.
69    pub fn file_sender(&self) -> FileSender<'_, R> {
70        FileSender(self)
71    }
72
73    /// Get the file or directory name.  
74    /// 
75    /// # Args
76    /// - ***uri*** :  
77    /// Target URI.  
78    /// Must be **readable**.
79    /// 
80    /// # Support
81    /// All.
82    pub fn get_name(&self, uri: &FileUri) -> crate::Result<String> {
83        on_android!({
84            impl_se!(struct Req<'a> { uri: &'a FileUri });
85            impl_de!(struct Res { name: String });
86
87            self.api
88                .run_mobile_plugin::<Res>("getName", Req { uri })
89                .map(|v| v.name)
90                .map_err(Into::into)
91        })
92    }
93
94    /// Queries the provider to get the MIME type.
95    ///
96    /// For files in [`PrivateStorage`], the MIME type is determined from the file extension.  
97    /// In most other cases, it uses the MIME type that was associated with the file when it was created.  
98    /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.  
99    /// 
100    /// If the target is a directory, an error will occur.  
101    /// To check whether the target is a file or a directory, use [`AndroidFs::get_type`].  
102    /// 
103    /// # Args
104    /// - ***uri*** :  
105    /// Target file URI.  
106    /// Must be **readable**.
107    /// 
108    /// # Support
109    /// All.
110    pub fn get_mime_type(&self, uri: &FileUri) -> crate::Result<String> {
111        on_android!({
112            let mime_type = self.get_type(uri)?
113                .into_mime_type()
114                .ok_or_else(|| Error { msg: "This is not file".into() })?;
115
116            Ok(mime_type)
117        })
118    }
119
120    /// Gets the entry type.
121    ///
122    /// If the target is a directory, returns [`EntryType::Dir`].
123    ///
124    /// If the target is a file, returns [`EntryType::File { mime_type }`](EntryType::File).  
125    /// For files in [`PrivateStorage`], the MIME type is determined from the file extension.  
126    /// In most other cases, it uses the MIME type that was associated with the file when it was created.  
127    /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.  
128    /// 
129    /// # Args
130    /// - ***uri*** :  
131    /// Target URI.  
132    /// Must be **readable**.
133    /// 
134    /// # Support
135    /// All.
136    pub fn get_type(&self, uri: &FileUri) -> crate::Result<EntryType> {
137        on_android!({
138            impl_se!(struct Req<'a> { uri: &'a FileUri });
139            impl_de!(struct Res { value: Option<String> });
140
141            self.api
142                .run_mobile_plugin::<Res>("getMimeType", Req { uri })
143                .map(|v| match v.value {
144                    Some(mime_type) => EntryType::File { mime_type },
145                    None => EntryType::Dir,
146                })
147                .map_err(Into::into)
148        })
149    }
150
151    /// Queries the file system to get information about a file, directory.
152    /// 
153    /// # Args
154    /// - ***uri*** :  
155    /// Target URI.  
156    /// Must be **readable**.
157    /// 
158    /// # Note
159    /// This uses [`AndroidFs::open_file`] internally.
160    /// 
161    /// # Support
162    /// All.
163    pub fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
164        on_android!({
165            let file = self.open_file_readable(uri)?;
166            Ok(file.metadata()?)
167        })
168    }
169
170    /// Open the file in **readable** mode.  
171    /// 
172    /// # Note
173    /// If the target is a file on cloud storage or otherwise not physically present on the device,
174    /// the system creates a temporary file, downloads the data into it,
175    /// and then opens it. As a result, this processing may take longer than with regular local files.
176    /// 
177    /// # Args
178    /// - ***uri*** :  
179    /// Target file URI.  
180    /// This need to be **readable**.
181    /// 
182    /// # Support
183    /// All.
184    pub fn open_file_readable(&self, uri: &FileUri) -> Result<std::fs::File> {
185        self.open_file(uri, FileAccessMode::Read)
186    }
187
188    /// Open the file in **writable** mode.  
189    /// This truncates the existing contents.  
190    /// 
191    /// If you only need a [`std::io::Write`] instead of a [`std::fs::File`], or if applicable, 
192    /// please use [`AndroidFs::open_writable_stream`] instead.
193    /// 
194    /// # Note
195    /// If the target is a file on cloud storage or otherwise not actually present on the device, 
196    /// writing with such files may not correctly reflect the changes.  
197    /// In this case, this function may causes an error, or may not.  
198    /// If you need write with such files, use [`AndroidFs::open_writable_stream`].
199    ///
200    /// # Args
201    /// - ***uri*** :  
202    /// Target file URI.  
203    /// This need to be **writable**.
204    /// 
205    /// # Support
206    /// All.
207    pub fn open_file_writable(
208        &self, 
209        uri: &FileUri, 
210    ) -> crate::Result<std::fs::File> {
211
212        on_android!(#[allow(deprecated)] {
213            // サイレントエラーをなるべく避けるため強制的にエラーにする。
214            if self.need_write_via_kotlin(uri)? {
215                return Err(Error { msg: "This content does not support write via file descriptor. Please use AndroidFs::open_writable_stream instead.".into() })
216            }
217
218            // Android 9 以下の場合、w は既存コンテンツを切り捨てる
219            if self.api_level()? <= 28 {
220                self.open_file(uri, FileAccessMode::Write)
221            }
222            // Android 10 以上の場合、w は既存コンテンツの切り捨てを保証しない。
223            // そのため切り捨ててファイルを開くには wt を用いる必要があるが、
224            // wt は全ての file provider が対応しているとは限らないため、
225            // フォールバックを用いてなるべく多くの状況に対応する。
226            // https://issuetracker.google.com/issues/180526528?pli=1
227            else {
228                let (file, mode) = self.open_file_with_fallback(uri, [
229                    FileAccessMode::WriteTruncate, 
230                    FileAccessMode::ReadWriteTruncate,
231                    FileAccessMode::Write
232                ])?;
233
234                if mode == FileAccessMode::Write {
235                    // file provider が既存コンテンツを切り捨てず、
236                    // かつ書き込むデータ量が元のそれより少ない場合、ファイルが壊れる可能性がある。
237                    // これを避けるため強制的にデータを切り捨てる。
238                    // file provider の実装によっては set_len は失敗することがある。
239                    file.set_len(0)?;
240                }
241
242                Ok(file)
243            }
244        })
245    }
246
247    /// Open the file in the specified mode.  
248    /// 
249    /// # Note
250    /// If the target is a file on cloud storage or otherwise not physically present on the device,
251    /// the system creates a temporary file, downloads the data into it,
252    /// and then opens it. As a result, this processing may take longer than with regular local files.
253    /// 
254    /// When writing to a file with this function,
255    /// pay attention to the following points:
256    /// 
257    /// 1. **Files that do not exist on the device**:  
258    /// Writing to files that are not physically present on the device may not correctly
259    /// reflect changes. If you need to write to such files, use [`AndroidFs::open_writable_stream`].
260    ///
261    /// 2. **File mode restrictions**:  
262    /// Files provided by third-party apps may not support writable modes other than
263    /// [`FileAccessMode::Write`]. However, [`FileAccessMode::Write`] does not guarantee
264    /// that existing contents will always be truncated.  
265    /// As a result, if the new contents are shorter than the original, the file may
266    /// become corrupted. To avoid this, consider using
267    /// [`AndroidFs::open_file_writable`] or [`AndroidFs::open_writable_stream`], which
268    /// ensure that existing contents are truncated and also automatically apply the
269    /// maximum possible fallbacks.  
270    /// <https://issuetracker.google.com/issues/180526528>
271    /// 
272    /// # Args
273    /// - ***uri*** :  
274    /// Target file URI.  
275    /// This must have corresponding permissions (read, write, or both) for the specified ***mode***.
276    /// 
277    /// - ***mode*** :  
278    /// Indicates how the file is opened and the permissions granted.  
279    /// Note that files hosted by third-party apps may not support following:
280    ///     - [`FileAccessMode::ReadWrite`]
281    ///     - [`FileAccessMode::ReadWriteTruncate`]  
282    ///     - [`FileAccessMode::WriteAppend`]  
283    ///     - [`FileAccessMode::WriteTruncate`]
284    ///
285    /// # Support
286    /// All.
287    pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
288        on_android!({
289            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
290            impl_de!(struct Res { fd: std::os::fd::RawFd });
291    
292            let mode = mode.to_mode();
293
294            self.api
295                .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
296                .map(|v| {
297                    use std::os::fd::FromRawFd;
298                    unsafe { std::fs::File::from_raw_fd(v.fd) }
299                })
300                .map_err(Into::into)
301        })
302    }
303 
304    /// For detailed documentation and notes, see [`AndroidFs::open_file`].  
305    ///
306    /// The modes specified in ***candidate_modes*** are tried in order.  
307    /// If the file can be opened, this returns the file along with the mode used.  
308    /// If all attempts fail, an error is returned.  
309    pub fn open_file_with_fallback(
310        &self, 
311        uri: &FileUri, 
312        candidate_modes: impl IntoIterator<Item = FileAccessMode>
313    ) -> crate::Result<(std::fs::File, FileAccessMode)> {
314
315        on_android!({
316            impl_se!(struct Req<'a> { uri: &'a FileUri, modes: Vec<&'a str> });
317            impl_de!(struct Res { fd: std::os::fd::RawFd, mode: String });
318    
319            let modes = candidate_modes.into_iter().map(|m| m.to_mode()).collect::<Vec<_>>();
320
321            if modes.is_empty() {
322                return Err(Error { msg: "candidate_modes should not be empty".into() });
323            }
324
325            self.api
326                .run_mobile_plugin::<Res>("getFileDescriptorWithFallback", Req { uri, modes })
327                .map_err(Into::into)
328                .and_then(|v| FileAccessMode::from_mode(&v.mode).map(|m| (v.fd, m)))
329                .map(|(fd, mode)| {
330                    let file = {
331                        use std::os::fd::FromRawFd;
332                        unsafe { std::fs::File::from_raw_fd(fd) }
333                    };
334                    (file, mode)
335                })
336        })
337    }
338
339    /// Opens a stream for writing to the specified file.  
340    /// This truncates the existing contents.  
341    /// 
342    /// # Usage
343    /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.  
344    /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.  
345    ///
346    /// After writing, call [`WritableStream::reflect`].  
347    /// 
348    /// # Note
349    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
350    /// If it is `false`, this behaves like [`AndroidFs::open_file_writable`].  
351    /// If it is `true`, this behaves like [`AndroidFs::open_writable_stream_via_kotlin`].  
352    /// 
353    /// # Args
354    /// - ***uri*** :  
355    /// Target file URI.  
356    /// This need to be **writable**.
357    /// 
358    /// # Support
359    /// All.
360    pub fn open_writable_stream(
361        &self,
362        uri: &FileUri
363    ) -> Result<WritableStream<R>> {
364
365        on_android!({
366            let need_write_via_kotlin = self.need_write_via_kotlin(uri)?;
367            WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
368        })
369    }
370
371    /// Opens a writable stream to the specified file.  
372    /// This truncates the existing contents.  
373    /// 
374    /// This function always writes content via the Kotlin API, 
375    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
376    /// But this takes several times longer compared to [`AndroidFs::open_writable_stream`].
377    ///
378    /// [`AndroidFs::open_writable_stream`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].  
379    /// For performance reasons, it is strongly recommended to use [`AndroidFs::open_writable_stream`] 
380    /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.  
381    /// 
382    /// # Usage
383    /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.  
384    /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.  
385    ///
386    /// After writing, call [`WritableStream::reflect`].
387    /// 
388    /// # Args
389    /// - ***uri*** :  
390    /// Target file URI.  
391    /// This need to be **writable**.
392    /// 
393    /// # Support
394    /// All.
395    pub fn open_writable_stream_via_kotlin(
396        &self,
397        uri: &FileUri
398    ) -> Result<WritableStream<R>> {
399
400        on_android!({
401            let need_write_via_kotlin = true;
402            WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
403        })
404    }
405
406    /// Reads the entire contents of a file into a bytes vector.  
407    /// 
408    /// # Args
409    /// - ***uri*** :  
410    /// Target file URI.    
411    /// Must be **readable**.
412    /// 
413    /// # Support
414    /// All.
415    pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
416        on_android!({
417            let mut file = self.open_file_readable(uri)?;
418            let mut buf = file.metadata().ok()
419                .map(|m| m.len() as usize)
420                .map(Vec::with_capacity)
421                .unwrap_or_else(Vec::new);
422
423            file.read_to_end(&mut buf)?;
424            Ok(buf)
425        })
426    }
427
428    /// Reads the entire contents of a file into a string.  
429    /// 
430    /// # Args
431    /// - ***uri*** :  
432    /// Target file URI.  
433    /// Must be **readable**.
434    /// 
435    /// # Support
436    /// All.
437    pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
438        on_android!({
439            let mut file = self.open_file_readable(uri)?;
440            let mut buf = file.metadata().ok()
441                .map(|m| m.len() as usize)
442                .map(String::with_capacity)
443                .unwrap_or_else(String::new);
444    
445            file.read_to_string(&mut buf)?;
446            Ok(buf)
447        })
448    }
449
450    /// Writes a slice as the entire contents of a file.  
451    /// This function will entirely replace its contents if it does exist.    
452    /// 
453    /// # Note
454    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
455    /// If it is `false`, this uses [`std::fs::File::write_all`].  
456    /// If it is `true`, this uses [`AndroidFs::write_via_kotlin`].  
457    /// 
458    /// # Args
459    /// - ***uri*** :  
460    /// Target file URI.  
461    /// Must be **writable**.
462    /// 
463    /// # Support
464    /// All.
465    pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
466        on_android!({
467            let mut stream = self.open_writable_stream(uri)?;
468
469            match stream.write_all(contents.as_ref()) {
470                Ok(_) => stream.reflect(),
471                Err(e) => {
472                    // これは drop 時にも必要に応じて行われるのでなくてもいい。
473                    // drop 時に任せるよりこれを呼び出した方がコストが低いので呼び出す。
474                    let _ = stream.close_without_reflect();
475                    Err(e.into())
476                },
477            }
478        })
479    }
480
481    /// Writes a slice as the entire contents of a file.  
482    /// This function will entirely replace its contents if it does exist.    
483    /// 
484    /// This function always writes content via the Kotlin API, 
485    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
486    /// But this takes several times longer compared to [`AndroidFs::write`].
487    ///
488    /// [`AndroidFs::write`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].  
489    /// For performance reasons, it is strongly recommended to use [`AndroidFs::write`] 
490    /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.  
491    /// 
492    /// # Support
493    /// All.
494    pub fn write_via_kotlin(
495        &self, 
496        uri: &FileUri,
497        contents: impl AsRef<[u8]>
498    ) -> crate::Result<()> {
499
500        on_android!({
501            let mut stream = self.open_writable_stream_via_kotlin(uri)?;
502            
503            match stream.write_all(contents.as_ref()) {
504                Ok(_) => stream.reflect(),
505                Err(e) => {
506                    // これは drop 時にも必要に応じて行われるのでなくてもいい。
507                    // drop 時に任せるよりこれを呼び出した方がコストが低いので呼び出す。
508                    let _ = stream.close_without_reflect();
509                    Err(e.into())
510                },
511            }
512        })
513    }
514
515    /// Copies the contents of the source file to the destination.  
516    /// If the destination already has contents, they are truncated before writing the source contents.  
517    /// 
518    /// # Note
519    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
520    /// If it is `false`, this uses [`std::io::copy`] with [`std::fs::File`].  
521    /// If it is `true`, this uses [`AndroidFs::copy_via_kotlin`].  
522    /// 
523    /// # Args
524    /// - ***src*** :  
525    /// The URI of source file.   
526    /// Must be **readable**.
527    /// 
528    /// - ***dest*** :  
529    /// The URI of destination file.  
530    /// Must be **writable**.
531    /// 
532    /// # Support
533    /// All.
534    pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
535        on_android!({
536            // NOTE:
537            // std::io::copy は std::fs::File 同士のコピーの場合、最適化が働く可能性がある。
538            // そのため self.open_writable_stream は用いない。
539
540            if self.need_write_via_kotlin(dest)? {
541                self.copy_via_kotlin(src, dest, None)?;
542            }
543            else {
544                let src = &mut self.open_file_readable(src)?;
545                let dest = &mut self.open_file_writable(dest)?;
546                std::io::copy(src, dest)?;
547            }
548            Ok(())
549        })
550    }
551
552    /// Copies the contents of src file to dest.  
553    /// If dest already has contents, it is truncated before write src contents.  
554    /// 
555    /// This function always writes content via the Kotlin API, 
556    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
557    /// 
558    /// [`AndroidFs::copy`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].  
559    /// For performance reasons, it is strongly recommended to use [`AndroidFs::copy`] 
560    /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.  
561    /// 
562    /// # Args
563    /// - ***src*** :  
564    /// The URI of source file.   
565    /// Must be **readable**.
566    /// 
567    /// - ***dest*** :  
568    /// The URI of destination file.  
569    /// Must be **writable**.
570    /// 
571    /// - ***buffer_size***:  
572    /// The size of the buffer, in bytes, to use during the copy process on Kotlin.  
573    /// If `None`, [`DEFAULT_BUFFER_SIZE`](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.io/-d-e-f-a-u-l-t_-b-u-f-f-e-r_-s-i-z-e.html) is used. 
574    /// At least, when I checked, it was 8 KB.  
575    /// If zero, this causes error.
576    /// 
577    /// # Support
578    /// All.
579    pub fn copy_via_kotlin(
580        &self, 
581        src: &FileUri, 
582        dest: &FileUri,
583        buffer_size: Option<u32>,
584    ) -> crate::Result<()> {
585
586        on_android!({
587            impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri, buffer_size: Option<u32> });
588            impl_de!(struct Res;);
589
590            if buffer_size.is_some_and(|s| s <= 0) {
591                return Err(Error { msg: "buffer_size must be non zero".into() })
592            }
593
594            self.api
595                .run_mobile_plugin::<Res>("copyFile", Req { src, dest, buffer_size })
596                .map(|_| ())
597                .map_err(Into::into)
598        })
599    }
600
601    /// Determines whether the file must be written via the Kotlin API rather than through a file descriptor.
602    /// 
603    /// **Even if this returns `false`, it does not guarantee that writing via the Kotlin API is never required.**
604    /// 
605    /// # Note
606    /// Currently, this function returns `true` only for files hosted by the following apps:  
607    ///  - Google Drive
608    /// 
609    /// # Why
610    /// For files that do not physically exist on the device, such as those stored in cloud storage,  
611    /// writing data through [`std::fs::File`] may not be properly reflected.  
612    /// In such cases, the write operation must be performed via the Kotlin API.   
613    /// <https://community.latenode.com/t/csv-export-to-google-drive-results-in-empty-file-but-local-storage-works-fine>   
614    ///
615    /// # Support
616    /// All.
617    pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
618        on_android!({
619            const URI_PREFIXES: &'static [&'static str] = &[
620                "content://com.google.android.apps.docs", // Google drive
621            ];
622
623            Ok(URI_PREFIXES.iter().any(|prefix| uri.uri.starts_with(prefix)))
624        })
625    }
626
627    /// Renames a file or directory to a new name, and return new URI.  
628    /// Even if the names conflict, the existing file will not be overwritten.  
629    /// 
630    /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
631    /// In other words, this function returns a new URI without any permissions.
632    /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
633    /// In this, this function returns the same URI as original URI.
634    ///
635    /// # Args
636    /// - ***uri*** :  
637    /// URI of target entry.  
638    /// 
639    /// - ***new_name*** :  
640    /// New name of target entry. 
641    /// This include extension if use.  
642    /// The behaviour in the same name already exists depends on the file provider.  
643    /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.  
644    /// In the case of files hosted by other applications, errors may occur.  
645    /// But at least, the existing file will not be overwritten.  
646    /// 
647    /// # Support
648    /// All.
649    pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
650        on_android!({
651            impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
652
653            let new_name = new_name.as_ref();
654
655            self.api
656                .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
657                .map_err(Into::into)
658        })
659    }
660
661    /// Remove the file.
662    /// 
663    /// # Args
664    /// - ***uri*** :  
665    /// Target file URI.  
666    /// Must be **writable**, at least. But even if it is, 
667    /// removing may not be possible in some cases. 
668    /// For details, refer to the documentation of the function that provided the URI.  
669    /// If not file, an error will occur.
670    /// 
671    /// # Support
672    /// All.
673    pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
674        on_android!({
675            impl_se!(struct Req<'a> { uri: &'a FileUri });
676            impl_de!(struct Res;);
677    
678            self.api
679                .run_mobile_plugin::<Res>("deleteFile", Req { uri })
680                .map(|_| ())
681                .map_err(Into::into)
682        })
683    }
684
685    /// Remove the **empty** directory.
686    /// 
687    /// # Args
688    /// - ***uri*** :  
689    /// Target directory URI.  
690    /// Must be **writable**.  
691    /// If not empty directory, an error will occur.
692    /// 
693    /// # Support
694    /// All.
695    pub fn remove_dir(&self, uri: &FileUri) -> crate::Result<()> {
696        on_android!({
697            impl_se!(struct Req<'a> { uri: &'a FileUri });
698            impl_de!(struct Res;);
699        
700            self.api
701                .run_mobile_plugin::<Res>("deleteEmptyDir", Req { uri })
702                .map(|_| ())
703                .map_err(Into::into)
704        })
705    }
706
707    /// Removes a directory and all its contents. Use carefully!
708    /// 
709    /// # Args
710    /// - ***uri*** :  
711    /// Target directory URI.  
712    /// Must be **writable**.  
713    /// If not directory, an error will occur.
714    /// 
715    /// # Support
716    /// All.
717    pub fn remove_dir_all(&self, uri: &FileUri) -> crate::Result<()> {
718        on_android!({
719            impl_se!(struct Req<'a> { uri: &'a FileUri });
720            impl_de!(struct Res;);
721        
722            self.api
723                .run_mobile_plugin::<Res>("deleteDirAll", Req { uri })
724                .map(|_| ())
725                .map_err(Into::into)
726        })
727    }
728
729    /// Build a URI of an **existing** file located at the relative path from the specified directory.   
730    /// Error occurs, if the file does not exist.  
731    /// 
732    /// The permissions and validity period of the returned URI depend on the origin directory 
733    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
734    /// 
735    /// # Support
736    /// All.
737    pub fn try_resolve_file_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
738        on_android!({
739            #[allow(deprecated)]
740            let uri = self.resolve_uri(dir, relative_path)?;         
741
742            if !self.get_type(&uri)?.is_file() {
743                return Err(crate::Error { msg: format!("This is not a file: {uri:?}").into() })
744            }
745            Ok(uri)
746        })
747    }
748
749    /// Build a URI of an **existing** directory located at the relative path from the specified directory.   
750    /// Error occurs, if the directory does not exist.  
751    /// 
752    /// The permissions and validity period of the returned URI depend on the origin directory 
753    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
754    /// 
755    /// # Support
756    /// All.
757    pub fn try_resolve_dir_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
758        on_android!({
759            #[allow(deprecated)]
760            let uri = self.resolve_uri(dir, relative_path)?;
761            
762            if !self.get_type(&uri)?.is_dir() {
763                return Err(crate::Error { msg: format!("This is not a directory: {uri:?}").into() })
764            }
765            Ok(uri)
766        })
767    }
768
769    /// Build a URI of an entry located at the relative path from the specified directory.   
770    /// 
771    /// This function does not perform checks on the arguments or the returned URI.  
772    /// Even if the dir argument refers to a file, no error occurs (and no panic either).
773    /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.  
774    /// 
775    /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead. 
776    /// Or use this with [`AndroidFs::get_mime_type`].
777    /// 
778    /// The permissions and validity period of the returned URI depend on the origin directory 
779    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
780    /// 
781    /// # Performance
782    /// This operation is relatively fast 
783    /// because it does not call Kotlin API and only involves operating strings on Rust side.
784    /// 
785    /// # Support
786    /// All.
787    #[deprecated = "Use AndroidFs::try_resolve_file_uri or AndroidFs::try_resolve_dir_uri instead"]
788    pub fn resolve_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
789        on_android!({
790            let base_dir = &dir.uri;
791            let relative_path = relative_path.as_ref().trim_matches('/');
792
793            if relative_path.is_empty() {
794                return Ok(dir.clone())
795            }
796
797            Ok(FileUri {
798                document_top_tree_uri: dir.document_top_tree_uri.clone(),
799                uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
800            })
801        })
802    }
803
804    /// See [`AndroidFs::get_thumbnail_to`] for descriptions.  
805    /// 
806    /// If thumbnail does not wrote to dest, return false.
807    pub fn get_thumbnail_to(
808        &self, 
809        src: &FileUri,
810        dest: &FileUri,
811        preferred_size: Size,
812        format: ImageFormat,
813    ) -> crate::Result<bool> {
814
815        on_android!({
816            impl_se!(struct Req<'a> {
817                src: &'a FileUri, 
818                dest: &'a FileUri,
819                format: &'a str,
820                quality: u8,
821                width: u32,
822                height: u32,
823            });
824            impl_de!(struct Res { value: bool });
825
826            let (quality, format) = match format {
827                ImageFormat::Png => (1.0, "Png"),
828                ImageFormat::Jpeg => (0.75, "Jpeg"),
829                ImageFormat::Webp => (0.7, "Webp"),
830                ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
831                ImageFormat::WebpWith { quality } => (quality, "Webp"),
832            };
833            let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
834            let Size { width, height } = preferred_size;
835        
836            self.api
837                .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
838                .map(|v| v.value)
839                .map_err(Into::into)
840        })
841    }
842
843    /// Query the provider to get a file thumbnail.  
844    /// If thumbnail does not exist it, return None.
845    /// 
846    /// Note this does not cache. Please do it in your part if need.  
847    /// 
848    /// # Args
849    /// - ***uri*** :  
850    /// Targe file uri.  
851    /// Thumbnail availablty depends on the file provider.  
852    /// In general, images and videos are available.  
853    /// For files in [`PrivateStorage`], 
854    /// the file type must match the filename extension.  
855    /// 
856    /// - ***preferred_size*** :  
857    /// Optimal thumbnail size desired.  
858    /// This may return a thumbnail of a different size, 
859    /// but never more than double the requested size. 
860    /// In any case, the aspect ratio is maintained.
861    /// 
862    /// - ***format*** :  
863    /// Thumbnail image format.  
864    /// [`ImageFormat::Jpeg`] is recommended. 
865    /// If you need transparency, use others.
866    /// 
867    /// # Support
868    /// All.
869    pub fn get_thumbnail(
870        &self,
871        uri: &FileUri,
872        preferred_size: Size,
873        format: ImageFormat,
874    ) -> crate::Result<Option<Vec<u8>>> {
875
876        on_android!({
877            let (tmp_file, tmp_file_path) = self.private_storage().create_new_tmp_file()?;
878            std::mem::drop(tmp_file);
879
880            let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
881                .and_then(|ok| {
882                    if ok {
883                        std::fs::read(&tmp_file_path)
884                            .map(Some)
885                            .map_err(Into::into)
886                    }
887                    else {
888                        Ok(None)
889                    }
890                });
891
892            let _ = std::fs::remove_file(&tmp_file_path);
893
894            result
895        })
896    }
897
898    /// Creates a new empty file in the specified location and returns a URI.   
899    /// 
900    /// The permissions and validity period of the returned URIs depend on the origin directory 
901    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
902    /// 
903    /// # Args  
904    /// - ***dir*** :  
905    /// The URI of the base directory.  
906    /// Must be **read-write**.
907    ///  
908    /// - ***relative_path*** :  
909    /// The file path relative to the base directory.  
910    /// Any missing subdirectories in the specified path will be created automatically.  
911    /// If a file with the same name already exists, 
912    /// the system append a sequential number to ensure uniqueness.  
913    /// If no extension is present, 
914    /// the system may infer one from ***mime_type*** and may append it to the file name. 
915    /// But this append-extension operation depends on the model and version.  
916    ///  
917    /// - ***mime_type*** :  
918    /// The MIME type of the file to be created.  
919    /// If this is None, MIME type is inferred from the extension of ***relative_path***
920    /// and if that fails, `application/octet-stream` is used.  
921    ///  
922    /// # Support
923    /// All.
924    pub fn create_file(
925        &self,
926        dir: &FileUri, 
927        relative_path: impl AsRef<str>, 
928        mime_type: Option<&str>
929    ) -> crate::Result<FileUri> {
930
931        on_android!({
932            impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
933        
934            let relative_path = relative_path.as_ref();
935
936            self.api
937                .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
938                .map_err(Into::into)
939        })
940    }
941
942    /// Recursively create a directory and all of its parent components if they are missing,
943    /// then return the URI.  
944    /// If it already exists, do nothing and just return the direcotry uri.
945    /// 
946    /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
947    /// 
948    /// # Args  
949    /// - ***dir*** :  
950    /// The URI of the base directory.  
951    /// Must be **read-write**.
952    ///  
953    /// - ***relative_path*** :  
954    /// The directory path relative to the base directory.    
955    ///  
956    /// # Support
957    /// All.
958    pub fn create_dir_all(
959        &self,
960        dir: &FileUri, 
961        relative_path: impl AsRef<str>, 
962    ) -> Result<FileUri> {
963
964        on_android!({
965            let relative_path = relative_path.as_ref().trim_matches('/');
966            if relative_path.is_empty() {
967                return Ok(dir.clone())
968            }
969
970            // TODO:
971            // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
972            let tmp_file_uri = self.create_file(
973                dir, 
974                format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"), 
975                Some("application/octet-stream")
976            )?;
977            let _ = self.remove_file(&tmp_file_uri);
978            let uri = self.resolve_uri(dir, relative_path)?;
979
980            Ok(uri)
981        })
982    }
983
984    /// Returns the child files and directories of the specified directory.  
985    /// The order of the entries is not guaranteed.  
986    /// 
987    /// The permissions and validity period of the returned URIs depend on the origin directory 
988    /// (e.g., the top directory selected by [`FilePicker::pick_dir`])  
989    /// 
990    /// # Note
991    /// The returned type is an iterator, but the file system call is not executed lazily.  
992    /// Instead, all data is retrieved at once.  
993    /// For directories containing thousands or even tens of thousands of entries,  
994    /// this function may take several seconds to complete.  
995    /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
996    /// 
997    /// # Args
998    /// - ***uri*** :  
999    /// Target directory URI.  
1000    /// Must be **readable**.
1001    /// 
1002    /// # Support
1003    /// All.
1004    pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
1005        on_android!(std::iter::Empty::<_>, {
1006            impl_se!(struct Req<'a> { uri: &'a FileUri });
1007            impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
1008            impl_de!(struct Res { entries: Vec<Obj> });
1009    
1010            // TODO:
1011            // データ転送のためのデータ形式 (ResとObj) を効率的なものにしたい
1012
1013            self.api
1014                .run_mobile_plugin::<Res>("readDir", Req { uri })
1015                .map(|v| v.entries.into_iter())
1016                .map(|v| v.map(|v| match v.mime_type {
1017                    Some(mime_type) => Entry::File {
1018                        name: v.name,
1019                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
1020                        len: v.byte_size as u64,
1021                        mime_type,
1022                        uri: v.uri,
1023                    },
1024                    None => Entry::Dir {
1025                        name: v.name,
1026                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
1027                        uri: v.uri,
1028                    }
1029                }))
1030                .map_err(Into::into)
1031        })
1032    }
1033
1034    /// Create an **restricted** URI for the specified directory.  
1035    /// This should only be used as `initial_location` in the file picker. 
1036    /// It must not be used for any other purpose.  
1037    /// 
1038    /// This is useful when selecting (creating) new files and folders, 
1039    /// but when selecting existing entries, `initial_location` is often better with None.
1040    /// 
1041    /// Note this is an informal method and is not guaranteed to work reliably.
1042    /// But this URI does not cause the dialog to error.  
1043    /// So please use this with the mindset that it's better than doing nothing.  
1044    /// 
1045    /// # Examples
1046    /// ```rust
1047    ///  use tauri_plugin_android_fs::{AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
1048    ///
1049    /// fn sample(app: tauri::AppHandle) {
1050    ///     let api = app.android_fs();
1051    ///
1052    ///     // Get URI of the top public directory in primary volume
1053    ///     let initial_location = api.resolve_initial_location(
1054    ///         InitialLocation::TopPublicDir,
1055    ///         false,
1056    ///     ).expect("Should be on Android");
1057    ///
1058    ///     // Get URI of ~/Pictures/
1059    ///     let initial_location = api.resolve_initial_location(
1060    ///         PublicImageDir::Pictures,
1061    ///         false
1062    ///     ).expect("Should be on Android");
1063    ///
1064    ///     // Get URI of ~/Documents/sub_dir1/sub_dir2/
1065    ///     let initial_location = api.resolve_initial_location(
1066    ///         InitialLocation::DirInPublicDir {
1067    ///             base_dir: PublicGeneralPurposeDir::Documents.into(),
1068    ///             relative_path: "sub_dir1/sub_dir2"
1069    ///         },
1070    ///         true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
1071    ///     ).expect("Should be on Android");
1072    ///
1073    ///     // Open dialog with initial_location
1074    ///     let _ = api.file_picker().save_file(Some(&initial_location), "", None);
1075    ///     let _ = api.file_picker().pick_file(Some(&initial_location), &[]);
1076    ///     let _ = api.file_picker().pick_dir(Some(&initial_location));
1077    /// }
1078    /// ```
1079    /// 
1080    /// # Support
1081    /// All.
1082    ///
1083    /// Note :  
1084    /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
1085    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
1086    /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
1087    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
1088    /// - Others dirs are available in all Android versions.
1089    pub fn resolve_initial_location<'a>(
1090        &self,
1091        dir: impl Into<InitialLocation<'a>>,
1092        create_dirs: bool
1093    ) -> crate::Result<FileUri> {
1094
1095        on_android!({
1096            const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary";
1097
1098            let uri = match dir.into() {
1099                InitialLocation::TopPublicDir => format!("{TOP_DIR}%3A"),
1100                InitialLocation::PublicDir(dir) => format!("{TOP_DIR}%3A{dir}"),
1101                InitialLocation::DirInPublicDir { base_dir, relative_path } => {
1102                    let relative_path = relative_path.trim_matches('/');
1103
1104                    if relative_path.is_empty() {
1105                        format!("{TOP_DIR}%3A{base_dir}")
1106                    }
1107                    else {
1108                        if create_dirs {
1109                            let _ = self.public_storage().create_dir_all(base_dir, relative_path);
1110                        }
1111                        let sub_dirs = encode_document_id(relative_path);
1112                        format!("{TOP_DIR}%3A{base_dir}%2F{sub_dirs}")
1113                    }
1114                },
1115                InitialLocation::DirInPublicAppDir { base_dir, relative_path } => {
1116                    let relative_path = &format!(
1117                        "{}/{}", 
1118                        self.public_storage().app_dir_name()?,
1119                        relative_path.trim_matches('/'),
1120                    );
1121                  
1122                    return self.resolve_initial_location(
1123                        InitialLocation::DirInPublicDir { base_dir, relative_path }, 
1124                        create_dirs
1125                    )
1126                }
1127            };
1128
1129            Ok(FileUri { uri, document_top_tree_uri: None })
1130        })
1131    }
1132
1133    /// Take persistent permission to access the file, directory and its descendants.  
1134    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
1135    /// 
1136    /// This works by just calling, without displaying any confirmation to the user.
1137    /// 
1138    /// Note that [there is a limit to the total number of URI that can be made persistent by this function.](https://stackoverflow.com/questions/71099575/should-i-release-persistableuripermission-when-a-new-storage-location-is-chosen/71100621#71100621)  
1139    /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
1140    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1141    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
1142    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1143    /// 
1144    /// # Args
1145    /// - **uri** :  
1146    /// URI of the target file or directory. This must be a URI taken from following :  
1147    ///     - [`FilePicker::pick_files`]  
1148    ///     - [`FilePicker::pick_file`]  
1149    ///     - [`FilePicker::pick_visual_medias`]  
1150    ///     - [`FilePicker::pick_visual_media`]  
1151    ///     - [`FilePicker::pick_dir`]  
1152    ///     - [`FilePicker::save_file`]  
1153    ///     - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_file`], [`AndroidFs::create_dir_all`] :  
1154    ///     If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function. 
1155    ///     Because the permissions and validity period of the descendant entry URIs depend on the origin directory.   
1156    /// 
1157    /// # Support
1158    /// All. 
1159    pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1160        on_android!({
1161            impl_se!(struct Req<'a> { uri: &'a FileUri });
1162            impl_de!(struct Res;);
1163
1164            self.api
1165                .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1166                .map(|_| ())
1167                .map_err(Into::into)
1168        })
1169    }
1170
1171    /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].  
1172    /// Returns false if there are only non-persistent permissions or no permissions.
1173    /// 
1174    /// # Args
1175    /// - **uri** :  
1176    /// URI of the target file or directory.  
1177    /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself. 
1178    /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1179    ///
1180    /// - **mode** :  
1181    /// The mode of permission you want to check.  
1182    /// 
1183    /// # Support
1184    /// All.
1185    pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1186        on_android!({
1187            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1188            impl_de!(struct Res { value: bool });
1189
1190            self.api
1191                .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1192                .map(|v| v.value)
1193                .map_err(Into::into)
1194        })
1195    }
1196
1197    /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.   
1198    /// 
1199    /// # Support
1200    /// All.
1201    pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1202        on_android!(std::iter::Empty::<_>, {
1203            impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1204            impl_de!(struct Res { items: Vec<Obj> });
1205    
1206            self.api
1207                .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1208                .map(|v| v.items.into_iter())
1209                .map(|v| v.map(|v| {
1210                    let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1211                    match v.d {
1212                        true => PersistedUriPermission::Dir { uri, can_read, can_write },
1213                        false => PersistedUriPermission::File { uri, can_read, can_write }
1214                    }
1215                }))
1216                .map_err(Into::into)
1217        })
1218    }
1219
1220    /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
1221    /// 
1222    /// # Args
1223    /// - ***uri*** :  
1224    /// URI of the target file or directory.  
1225    ///
1226    /// # Support
1227    /// All.
1228    pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1229        on_android!({
1230            impl_se!(struct Req<'a> { uri: &'a FileUri });
1231            impl_de!(struct Res;);
1232
1233            self.api
1234                .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1235                .map(|_| ())
1236                .map_err(Into::into)
1237        })
1238    }
1239
1240    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
1241    /// 
1242    /// # Support
1243    /// All.
1244    pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1245        on_android!({
1246            impl_de!(struct Res);
1247
1248            self.api
1249                .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1250                .map(|_| ())
1251                .map_err(Into::into)
1252        })
1253    }
1254
1255    /// Verify whether this plugin is available.  
1256    /// 
1257    /// On Android, this returns true.  
1258    /// On other platforms, this returns false.  
1259    pub fn is_available(&self) -> bool {
1260        cfg!(target_os = "android")
1261    }
1262
1263    /// Get the api level of this Android device.
1264    /// 
1265    /// The correspondence table between API levels and Android versions can be found following.  
1266    /// <https://developer.android.com/guide/topics/manifest/uses-sdk-element#api-level-table>
1267    /// 
1268    /// | Android version  | API Level |
1269    /// |------------------|-----------|
1270    /// | 16.0             | 36        |
1271    /// | 15.0             | 35        |
1272    /// | 14.0             | 34        |
1273    /// | 13.0             | 33        |
1274    /// | 12L              | 32        |
1275    /// | 12.0             | 31        |
1276    /// | 11.0             | 30        |
1277    /// | 10.0             | 29        |
1278    /// | 9.0              | 28        |
1279    /// | 8.1              | 27        |
1280    /// | 8.0              | 26        |
1281    /// | 7.1 - 7.1.2      | 25        |
1282    /// | 7.0              | 24        |
1283    /// 
1284    /// Tauri does not support Android versions below 7.
1285    pub fn api_level(&self) -> Result<usize> {
1286        on_android!({
1287            impl_de!(struct Res { value: usize });
1288        
1289            static API_LEVEL: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1290
1291            if let Some(api_level) = API_LEVEL.get() {
1292                return Ok(*api_level)
1293            }
1294
1295            let api_level = self.api
1296                .run_mobile_plugin::<Res>("getApiLevel", "")?
1297                .value;
1298
1299            let _ = API_LEVEL.set(api_level);
1300
1301            Ok(api_level)
1302        })
1303    }
1304
1305
1306
1307    
1308    /// See [`AndroidFs::write_via_kotlin`] for information.  
1309    /// Use this if you want to write using `std::fs::File`, not entire contents.
1310    /// 
1311    /// # Args
1312    /// - ***uri*** :  
1313    /// Target file URI to write.
1314    /// 
1315    /// - **contetns_writer** :  
1316    /// A closure that accepts a mutable reference to a `std::fs::File`
1317    /// and performs the actual write operations. Note that this represents a temporary file.
1318    #[deprecated = "Use AndroidFs::open_writable_stream_via_kotlin instead."]
1319    pub fn write_via_kotlin_in<T>(
1320        &self, 
1321        uri: &FileUri,
1322        contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
1323    ) -> crate::Result<T> {
1324
1325        on_android!({
1326            let (mut tmp_file, tmp_file_path) = self.private_storage().create_new_tmp_file()?;
1327
1328            let result = (|| -> Result<T> {
1329                let t = contents_writer(&mut tmp_file)?;
1330                // 反映されるまで待機する
1331                tmp_file.sync_data()?;
1332                // copy を行う前にファイルを閉じる
1333                std::mem::drop(tmp_file);
1334
1335                self.copy_via_kotlin(&(&tmp_file_path).into(), uri, None)?;
1336                Ok(t)
1337            })();
1338
1339            let _ = std::fs::remove_file(&tmp_file_path);
1340
1341            result
1342        })
1343    }
1344}