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 Android version.
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 Android version.
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 Android version.
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 Android version.
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 Android version.
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    /// If you need write with such files, use [`AndroidFs::open_writable_stream`].
198    ///
199    /// # Args
200    /// - ***uri*** :  
201    /// Target file URI.  
202    /// This need to be **writable**.
203    /// 
204    /// # Support
205    /// All Android version.
206    pub fn open_file_writable(
207        &self, 
208        uri: &FileUri, 
209    ) -> crate::Result<std::fs::File> {
210
211        on_android!(#[allow(deprecated)] {
212            // Android 9 以下の場合、w は既存コンテンツを切り捨てる
213            if self.api_level()? <= api_level::ANDROID_9 {
214                self.open_file(uri, FileAccessMode::Write)
215            }
216            // Android 10 以上の場合、w は既存コンテンツの切り捨てを保証しない。
217            // そのため切り捨ててファイルを開くには wt を用いる必要があるが、
218            // wt は全ての file provider が対応しているとは限らないため、
219            // フォールバックを用いてなるべく多くの状況に対応する。
220            // https://issuetracker.google.com/issues/180526528?pli=1
221            else {
222                let (file, mode) = self.open_file_with_fallback(uri, [
223                    FileAccessMode::WriteTruncate, 
224                    FileAccessMode::ReadWriteTruncate,
225                    FileAccessMode::Write
226                ])?;
227
228                if mode == FileAccessMode::Write {
229                    // file provider が既存コンテンツを切り捨てず、
230                    // かつ書き込むデータ量が元のそれより少ない場合、ファイルが壊れる可能性がある。
231                    // これを避けるため強制的にデータを切り捨てる。
232                    // file provider の実装によっては set_len は失敗することがある。
233                    file.set_len(0)?;
234                }
235
236                Ok(file)
237            }
238        })
239    }
240
241    /// Open the file in the specified mode.  
242    /// 
243    /// # Note
244    /// If the target is a file on cloud storage or otherwise not physically present on the device,
245    /// the system creates a temporary file, downloads the data into it,
246    /// and then opens it. As a result, this processing may take longer than with regular local files.
247    /// 
248    /// When writing to a file with this function,
249    /// pay attention to the following points:
250    /// 
251    /// 1. **Files that do not exist on the device**:  
252    /// Writing to files that are not physically present on the device may not correctly
253    /// reflect changes. If you need to write to such files, use [`AndroidFs::open_writable_stream`].
254    ///
255    /// 2. **File mode restrictions**:  
256    /// Files provided by third-party apps may not support writable modes other than
257    /// [`FileAccessMode::Write`]. However, [`FileAccessMode::Write`] does not guarantee
258    /// that existing contents will always be truncated.  
259    /// As a result, if the new contents are shorter than the original, the file may
260    /// become corrupted. To avoid this, consider using
261    /// [`AndroidFs::open_file_writable`] or [`AndroidFs::open_writable_stream`], which
262    /// ensure that existing contents are truncated and also automatically apply the
263    /// maximum possible fallbacks.  
264    /// <https://issuetracker.google.com/issues/180526528>
265    /// 
266    /// # Args
267    /// - ***uri*** :  
268    /// Target file URI.  
269    /// This must have corresponding permissions (read, write, or both) for the specified ***mode***.
270    /// 
271    /// - ***mode*** :  
272    /// Indicates how the file is opened and the permissions granted.  
273    /// Note that files hosted by third-party apps may not support following:
274    ///     - [`FileAccessMode::ReadWrite`]
275    ///     - [`FileAccessMode::ReadWriteTruncate`]  
276    ///     - [`FileAccessMode::WriteAppend`]  
277    ///     - [`FileAccessMode::WriteTruncate`]
278    ///
279    /// # Support
280    /// All Android version.
281    pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
282        on_android!({
283            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
284            impl_de!(struct Res { fd: std::os::fd::RawFd });
285    
286            let mode = mode.to_mode();
287
288            self.api
289                .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
290                .map(|v| {
291                    use std::os::fd::FromRawFd;
292                    unsafe { std::fs::File::from_raw_fd(v.fd) }
293                })
294                .map_err(Into::into)
295        })
296    }
297 
298    /// For detailed documentation and notes, see [`AndroidFs::open_file`].  
299    ///
300    /// The modes specified in ***candidate_modes*** are tried in order.  
301    /// If the file can be opened, this returns the file along with the mode used.  
302    /// If all attempts fail, an error is returned.  
303    pub fn open_file_with_fallback(
304        &self, 
305        uri: &FileUri, 
306        candidate_modes: impl IntoIterator<Item = FileAccessMode>
307    ) -> crate::Result<(std::fs::File, FileAccessMode)> {
308
309        on_android!({
310            impl_se!(struct Req<'a> { uri: &'a FileUri, modes: Vec<&'a str> });
311            impl_de!(struct Res { fd: std::os::fd::RawFd, mode: String });
312    
313            let modes = candidate_modes.into_iter().map(|m| m.to_mode()).collect::<Vec<_>>();
314
315            if modes.is_empty() {
316                return Err(Error::with("candidate_modes must not be empty"));
317            }
318
319            self.api
320                .run_mobile_plugin::<Res>("getFileDescriptorWithFallback", Req { uri, modes })
321                .map_err(Into::into)
322                .and_then(|v| FileAccessMode::from_mode(&v.mode).map(|m| (v.fd, m)))
323                .map(|(fd, mode)| {
324                    let file = {
325                        use std::os::fd::FromRawFd;
326                        unsafe { std::fs::File::from_raw_fd(fd) }
327                    };
328                    (file, mode)
329                })
330        })
331    }
332
333    /// Opens a stream for writing to the specified file.  
334    /// This truncates the existing contents.  
335    /// 
336    /// # Usage
337    /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.  
338    /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.  
339    ///
340    /// After writing, call [`WritableStream::reflect`].  
341    /// 
342    /// # Note
343    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
344    /// If it is `false`, this behaves like [`AndroidFs::open_file_writable`].  
345    /// If it is `true`, this behaves like [`AndroidFs::open_writable_stream_via_kotlin`].  
346    /// 
347    /// # Args
348    /// - ***uri*** :  
349    /// Target file URI.  
350    /// This need to be **writable**.
351    /// 
352    /// # Support
353    /// All Android version.
354    pub fn open_writable_stream(
355        &self,
356        uri: &FileUri
357    ) -> Result<WritableStream<R>> {
358
359        on_android!({
360            let need_write_via_kotlin = self.need_write_via_kotlin(uri)?;
361            WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
362        })
363    }
364
365    /// Opens a writable stream to the specified file.  
366    /// This truncates the existing contents.  
367    /// 
368    /// This function always writes content via the Kotlin API, 
369    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
370    /// But this takes several times longer compared to [`AndroidFs::open_writable_stream`].
371    ///
372    /// [`AndroidFs::open_writable_stream`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].  
373    /// For performance reasons, it is strongly recommended to use [`AndroidFs::open_writable_stream`] 
374    /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.  
375    /// 
376    /// # Usage
377    /// [`WritableStream`] implements [`std::io::Write`], so it can be used for writing.  
378    /// As with [`std::fs::File`], wrap it with [`std::io::BufWriter`] if buffering is needed.  
379    ///
380    /// After writing, call [`WritableStream::reflect`].
381    /// 
382    /// # Args
383    /// - ***uri*** :  
384    /// Target file URI.  
385    /// This need to be **writable**.
386    /// 
387    /// # Support
388    /// All Android version.
389    pub fn open_writable_stream_via_kotlin(
390        &self,
391        uri: &FileUri
392    ) -> Result<WritableStream<R>> {
393
394        on_android!({
395            let need_write_via_kotlin = true;
396            WritableStream::new(self.app.clone(), uri.clone(), need_write_via_kotlin)
397        })
398    }
399
400    /// Reads the entire contents of a file into a bytes vector.  
401    /// 
402    /// # Args
403    /// - ***uri*** :  
404    /// Target file URI.    
405    /// Must be **readable**.
406    /// 
407    /// # Support
408    /// All Android version.
409    pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
410        on_android!({
411            let mut file = self.open_file_readable(uri)?;
412            let mut buf = file.metadata().ok()
413                .map(|m| m.len() as usize)
414                .map(Vec::with_capacity)
415                .unwrap_or_else(Vec::new);
416
417            file.read_to_end(&mut buf)?;
418            Ok(buf)
419        })
420    }
421
422    /// Reads the entire contents of a file into a string.  
423    /// 
424    /// # Args
425    /// - ***uri*** :  
426    /// Target file URI.  
427    /// Must be **readable**.
428    /// 
429    /// # Support
430    /// All Android version.
431    pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
432        on_android!({
433            let mut file = self.open_file_readable(uri)?;
434            let mut buf = file.metadata().ok()
435                .map(|m| m.len() as usize)
436                .map(String::with_capacity)
437                .unwrap_or_else(String::new);
438    
439            file.read_to_string(&mut buf)?;
440            Ok(buf)
441        })
442    }
443
444    /// Writes a slice as the entire contents of a file.  
445    /// This function will entirely replace its contents if it does exist.    
446    /// 
447    /// # Note
448    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
449    /// If it is `false`, this uses [`std::fs::File::write_all`].  
450    /// If it is `true`, this uses [`AndroidFs::write_via_kotlin`].  
451    /// 
452    /// # Args
453    /// - ***uri*** :  
454    /// Target file URI.  
455    /// Must be **writable**.
456    /// 
457    /// # Support
458    /// All Android version.
459    pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
460        on_android!({
461            let mut stream = self.open_writable_stream(uri)?;
462            stream.write_all(contents.as_ref())?;
463            stream.reflect()?;
464            Ok(())
465        })
466    }
467
468    /// Writes a slice as the entire contents of a file.  
469    /// This function will entirely replace its contents if it does exist.    
470    /// 
471    /// This function always writes content via the Kotlin API, 
472    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
473    /// But this takes several times longer compared to [`AndroidFs::write`].
474    ///
475    /// [`AndroidFs::write`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].  
476    /// For performance reasons, it is strongly recommended to use [`AndroidFs::write`] 
477    /// if you can tolerate that [`AndroidFs::need_write_via_kotlin`] may not cover all cases.  
478    /// 
479    /// # Support
480    /// All Android version.
481    pub fn write_via_kotlin(
482        &self, 
483        uri: &FileUri,
484        contents: impl AsRef<[u8]>
485    ) -> crate::Result<()> {
486
487        on_android!({
488            let mut stream = self.open_writable_stream_via_kotlin(uri)?;
489            stream.write_all(contents.as_ref())?;
490            stream.reflect()?;
491            Ok(())
492        })
493    }
494
495    /// Copies the contents of the source file to the destination.  
496    /// If the destination already has contents, they are truncated before writing the source contents.  
497    /// 
498    /// # Note
499    /// The behavior depends on [`AndroidFs::need_write_via_kotlin`].  
500    /// If it is `false`, this uses [`std::io::copy`] with [`std::fs::File`].  
501    /// If it is `true`, this uses [`AndroidFs::copy_via_kotlin`].  
502    /// 
503    /// # Args
504    /// - ***src*** :  
505    /// The URI of source file.   
506    /// Must be **readable**.
507    /// 
508    /// - ***dest*** :  
509    /// The URI of destination file.  
510    /// Must be **writable**.
511    /// 
512    /// # Support
513    /// All Android version.
514    pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
515        on_android!({
516            // NOTE:
517            // std::io::copy は std::fs::File 同士のコピーの場合、最適化が働く可能性がある。
518            // そのため self.open_writable_stream は用いない。
519
520            if self.need_write_via_kotlin(dest)? {
521                self.copy_via_kotlin(src, dest, None)?;
522            }
523            else {
524                let src = &mut self.open_file_readable(src)?;
525                let dest = &mut self.open_file_writable(dest)?;
526                std::io::copy(src, dest)?;
527            }
528            Ok(())
529        })
530    }
531
532    /// Copies the contents of src file to dest.  
533    /// If dest already has contents, it is truncated before write src contents.  
534    /// 
535    /// This function always writes content via the Kotlin API, 
536    /// ensuring that writes to files not physically present on the device, such as cloud storage files, are properly applied.
537    /// 
538    /// [`AndroidFs::copy`] automatically falls back to this function depending on [`AndroidFs::need_write_via_kotlin`].   
539    /// 
540    /// # Args
541    /// - ***src*** :  
542    /// The URI of source file.   
543    /// Must be **readable**.
544    /// 
545    /// - ***dest*** :  
546    /// The URI of destination file.  
547    /// Must be **writable**.
548    /// 
549    /// - ***buffer_size***:  
550    /// The size of the buffer, in bytes, to use during the copy process on Kotlin.  
551    /// 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. 
552    /// At least, when I checked, it was 8 KB.  
553    /// If zero, this causes error.
554    /// 
555    /// # Support
556    /// All Android version.
557    pub fn copy_via_kotlin(
558        &self, 
559        src: &FileUri, 
560        dest: &FileUri,
561        buffer_size: Option<u32>,
562    ) -> crate::Result<()> {
563
564        on_android!({
565            impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri, buffer_size: Option<u32> });
566            impl_de!(struct Res;);
567
568            if buffer_size.is_some_and(|s| s <= 0) {
569                return Err(Error { msg: "buffer_size must be non zero".into() })
570            }
571
572            self.api
573                .run_mobile_plugin::<Res>("copyFile", Req { src, dest, buffer_size })
574                .map(|_| ())
575                .map_err(Into::into)
576        })
577    }
578
579    /// Determines whether the file must be written via the Kotlin API rather than through a file descriptor.
580    /// 
581    /// **This may not cover all cases.**
582    /// 
583    /// # Why
584    /// For files that do not physically exist on the device, such as those stored in cloud storage,  
585    /// writing data through [`std::fs::File`] may not be properly reflected.  
586    /// In such cases, the write operation must be performed via the Kotlin API.   
587    /// <https://community.latenode.com/t/csv-export-to-google-drive-results-in-empty-file-but-local-storage-works-fine>   
588    ///
589    /// # Support
590    /// All Android version.
591    pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
592        on_android!({
593            // Note:
594            // Android 15 の Dropbox (ver. 440.2.4) では fd 経由で書き込んで反映された
595
596            const URI_PREFIXES: &'static [&'static str] = &[
597                "content://com.google.android.apps.docs", // Google drive
598            ];
599
600            Ok(URI_PREFIXES.iter().any(|prefix| uri.uri.starts_with(prefix)))
601        })
602    }
603
604    /// Renames a file or directory to a new name, and return new URI.  
605    /// Even if the names conflict, the existing file will not be overwritten.  
606    /// 
607    /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
608    /// In other words, this function returns a new URI without any permissions.
609    /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
610    /// In this, this function returns the same URI as original URI.
611    ///
612    /// # Args
613    /// - ***uri*** :  
614    /// URI of target entry.  
615    /// 
616    /// - ***new_name*** :  
617    /// New name of target entry. 
618    /// This include extension if use.  
619    /// The behaviour in the same name already exists depends on the file provider.  
620    /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.  
621    /// In the case of files hosted by other applications, errors may occur.  
622    /// But at least, the existing file will not be overwritten.  
623    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
624    /// 
625    /// # Support
626    /// All Android version.
627    pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
628        on_android!({
629            impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
630
631            let new_name = new_name.as_ref();
632
633            self.api
634                .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
635                .map_err(Into::into)
636        })
637    }
638
639    /// Remove the file.
640    /// 
641    /// # Args
642    /// - ***uri*** :  
643    /// Target file URI.  
644    /// Must be **writable**, at least. But even if it is, 
645    /// removing may not be possible in some cases. 
646    /// For details, refer to the documentation of the function that provided the URI.  
647    /// If not file, an error will occur.
648    /// 
649    /// # Support
650    /// All Android version.
651    pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
652        on_android!({
653            impl_se!(struct Req<'a> { uri: &'a FileUri });
654            impl_de!(struct Res;);
655    
656            self.api
657                .run_mobile_plugin::<Res>("deleteFile", Req { uri })
658                .map(|_| ())
659                .map_err(Into::into)
660        })
661    }
662
663    /// Remove the **empty** directory.
664    /// 
665    /// # Args
666    /// - ***uri*** :  
667    /// Target directory URI.  
668    /// Must be **writable**.  
669    /// If not empty directory, an error will occur.
670    /// 
671    /// # Support
672    /// All Android version.
673    pub fn remove_dir(&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>("deleteEmptyDir", Req { uri })
680                .map(|_| ())
681                .map_err(Into::into)
682        })
683    }
684
685    /// Removes a directory and all its contents. Use carefully!
686    /// 
687    /// # Args
688    /// - ***uri*** :  
689    /// Target directory URI.  
690    /// Must be **writable**.  
691    /// If not directory, an error will occur.
692    /// 
693    /// # Support
694    /// All Android version.
695    pub fn remove_dir_all(&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>("deleteDirAll", Req { uri })
702                .map(|_| ())
703                .map_err(Into::into)
704        })
705    }
706
707    /// Build a URI of an **existing** file located at the relative path from the specified directory.   
708    /// Error occurs, if the file does not exist.  
709    /// 
710    /// The permissions and validity period of the returned URI depend on the origin directory 
711    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
712    /// 
713    /// # Note
714    /// For [`AndroidFs::create_new_file`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
715    /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
716    /// 
717    /// # Support
718    /// All Android version.
719    pub fn try_resolve_file_uri(
720        &self, 
721        dir: &FileUri, 
722        relative_path: impl AsRef<std::path::Path>
723    ) -> crate::Result<FileUri> {
724
725        on_android!({
726            #[allow(deprecated)]
727            let uri = self.resolve_uri(dir, relative_path)?;         
728
729            if !self.get_type(&uri)?.is_file() {
730                return Err(crate::Error::with(format!("This is not a file: {uri:?}")))
731            }
732            Ok(uri)
733        })
734    }
735
736    /// Build a URI of an **existing** directory located at the relative path from the specified directory.   
737    /// Error occurs, if the directory does not exist.  
738    /// 
739    /// The permissions and validity period of the returned URI depend on the origin directory 
740    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
741    /// 
742    /// # Note
743    /// For [`AndroidFs::create_new_file`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
744    /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
745    /// 
746    /// # Support
747    /// All Android version.
748    pub fn try_resolve_dir_uri(
749        &self,
750        dir: &FileUri, 
751        relative_path: impl AsRef<std::path::Path>
752    ) -> crate::Result<FileUri> {
753
754        on_android!({
755            #[allow(deprecated)]
756            let uri = self.resolve_uri(dir, relative_path)?;
757            
758            if !self.get_type(&uri)?.is_dir() {
759                return Err(crate::Error::with(format!("This is not a directory: {uri:?}")))
760            }
761            Ok(uri)
762        })
763    }
764
765    /// Build a URI of an entry located at the relative path from the specified directory.   
766    /// 
767    /// This function does **not** create any entries; it only constructs the URI.
768    /// 
769    /// This function does not perform checks on the arguments or the returned URI.  
770    /// Even if the dir argument refers to a file, no error occurs (and no panic either).
771    /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.  
772    /// 
773    /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead. 
774    /// Or use this with [`AndroidFs::get_mime_type`].
775    /// 
776    /// The permissions and validity period of the returned URI depend on the origin directory 
777    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
778    /// 
779    /// # Note
780    /// For [`PublicStorage::create_new_file`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
781    /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
782    /// 
783    /// # Performance
784    /// This operation is relatively fast 
785    /// because it does not call Kotlin API and only involves operating strings on Rust side.
786    /// 
787    /// # Support
788    /// All Android version.
789    #[deprecated = "Use AndroidFs::try_resolve_file_uri or AndroidFs::try_resolve_dir_uri instead"]
790    pub fn resolve_uri(
791        &self, 
792        dir: &FileUri, 
793        relative_path: impl AsRef<std::path::Path>
794    ) -> crate::Result<FileUri> {
795
796        on_android!({
797            let base_dir = &dir.uri;
798            let relative_path = validate_relative_path(relative_path.as_ref())?;
799            let relative_path = relative_path.to_string_lossy();
800
801            if relative_path.is_empty() {
802                return Ok(dir.clone())
803            }
804
805            Ok(FileUri {
806                document_top_tree_uri: dir.document_top_tree_uri.clone(),
807                uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
808            })
809        })
810    }
811
812    /// See [`AndroidFs::get_thumbnail_to`] for descriptions.  
813    /// 
814    /// If thumbnail does not wrote to dest, return false.
815    pub fn get_thumbnail_to(
816        &self, 
817        src: &FileUri,
818        dest: &FileUri,
819        preferred_size: Size,
820        format: ImageFormat,
821    ) -> crate::Result<bool> {
822
823        on_android!({
824            impl_se!(struct Req<'a> {
825                src: &'a FileUri, 
826                dest: &'a FileUri,
827                format: &'a str,
828                quality: u8,
829                width: u32,
830                height: u32,
831            });
832            impl_de!(struct Res { value: bool });
833
834            let (quality, format) = match format {
835                ImageFormat::Png => (1.0, "Png"),
836                ImageFormat::Jpeg => (0.75, "Jpeg"),
837                ImageFormat::Webp => (0.7, "Webp"),
838                ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
839                ImageFormat::WebpWith { quality } => (quality, "Webp"),
840            };
841            let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
842            let Size { width, height } = preferred_size;
843        
844            self.api
845                .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
846                .map(|v| v.value)
847                .map_err(Into::into)
848        })
849    }
850
851    /// Query the provider to get a file thumbnail.  
852    /// If thumbnail does not exist it, return None.
853    /// 
854    /// Note this does not cache. Please do it in your part if need.  
855    /// 
856    /// # Args
857    /// - ***uri*** :  
858    /// Targe file uri.  
859    /// Thumbnail availablty depends on the file provider.  
860    /// In general, images and videos are available.  
861    /// For files in [`PrivateStorage`], 
862    /// the file type must match the filename extension.  
863    /// 
864    /// - ***preferred_size*** :  
865    /// Optimal thumbnail size desired.  
866    /// This may return a thumbnail of a different size, 
867    /// but never more than double the requested size. 
868    /// In any case, the aspect ratio is maintained.
869    /// 
870    /// - ***format*** :  
871    /// Thumbnail image format.  
872    /// [`ImageFormat::Jpeg`] is recommended. 
873    /// If you need transparency, use others.
874    /// 
875    /// # Support
876    /// All Android version.
877    pub fn get_thumbnail(
878        &self,
879        uri: &FileUri,
880        preferred_size: Size,
881        format: ImageFormat,
882    ) -> crate::Result<Option<Vec<u8>>> {
883
884        on_android!({
885            let (tmp_file, tmp_file_path) = self.private_storage().create_new_tmp_file()?;
886            std::mem::drop(tmp_file);
887
888            let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
889                .and_then(|ok| {
890                    if ok {
891                        std::fs::read(&tmp_file_path)
892                            .map(Some)
893                            .map_err(Into::into)
894                    }
895                    else {
896                        Ok(None)
897                    }
898                });
899
900            let _ = std::fs::remove_file(&tmp_file_path);
901
902            result
903        })
904    }
905
906    /// Creates a new empty file in the specified location and returns a URI.   
907    /// 
908    /// The permissions and validity period of the returned URIs depend on the origin directory 
909    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
910    /// 
911    /// # Args  
912    /// - ***dir*** :  
913    /// The URI of the base directory.  
914    /// Must be **read-write**.
915    ///  
916    /// - ***relative_path*** :  
917    /// The file path relative to the base directory.  
918    /// Any missing subdirectories in the specified path will be created automatically.  
919    /// If a file with the same name already exists, 
920    /// the system append a sequential number to ensure uniqueness.  
921    /// If no extension is present, 
922    /// the system may infer one from ***mime_type*** and may append it to the file name. 
923    /// But this append-extension operation depends on the model and version.  
924    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
925    ///  
926    /// - ***mime_type*** :  
927    /// The MIME type of the file to be created.  
928    /// If this is None, MIME type is inferred from the extension of ***relative_path***
929    /// and if that fails, `application/octet-stream` is used.  
930    ///  
931    /// # Support
932    /// All Android version.
933    pub fn create_new_file(
934        &self,
935        dir: &FileUri, 
936        relative_path: impl AsRef<std::path::Path>, 
937        mime_type: Option<&str>
938    ) -> crate::Result<FileUri> {
939
940        on_android!({
941            impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
942        
943            let relative_path = validate_relative_path(relative_path.as_ref())?;
944            let relative_path = relative_path.to_string_lossy();
945                
946            self.api
947                .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path: relative_path.as_ref() })
948                .map_err(Into::into)
949        })
950    }
951
952    /// Recursively create a directory and all of its parent components if they are missing,
953    /// then return the URI.  
954    /// If it already exists, do nothing and just return the direcotry uri.
955    /// 
956    /// [`AndroidFs::create_new_file`] does this automatically, so there is no need to use it together.
957    /// 
958    /// # Args  
959    /// - ***dir*** :  
960    /// The URI of the base directory.  
961    /// Must be **read-write**.
962    ///  
963    /// - ***relative_path*** :  
964    /// The directory path relative to the base directory.    
965    /// The system may sanitize these strings as needed, so those strings may not be used as it is.
966    ///  
967    /// # Support
968    /// All Android version.
969    pub fn create_dir_all(
970        &self,
971        dir: &FileUri, 
972        relative_path: impl AsRef<std::path::Path>, 
973    ) -> Result<FileUri> {
974
975        on_android!({
976            impl_se!(struct Req<'a> { dir: &'a FileUri,relative_path: &'a str });
977        
978            let relative_path = validate_relative_path(relative_path.as_ref())?;
979            let relative_path = relative_path.to_string_lossy();
980                
981            self.api
982                .run_mobile_plugin::<FileUri>("createDirAll", Req { dir, relative_path: relative_path.as_ref() })
983                .map_err(Into::into)
984        })
985    }
986
987    /// Returns the child files and directories of the specified directory.  
988    /// The order of the entries is not guaranteed.  
989    /// 
990    /// The permissions and validity period of the returned URIs depend on the origin directory 
991    /// (e.g., the top directory selected by [`FilePicker::pick_dir`])  
992    /// 
993    /// # Note
994    /// The returned type is an iterator, but the file system call is not executed lazily.  
995    /// Instead, all data is retrieved at once.  
996    /// For directories containing thousands or even tens of thousands of entries,  
997    /// this function may take several seconds to complete.  
998    /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
999    /// 
1000    /// # Args
1001    /// - ***uri*** :  
1002    /// Target directory URI.  
1003    /// Must be **readable**.
1004    /// 
1005    /// # Support
1006    /// All Android version.
1007    pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
1008        on_android!(std::iter::Empty::<_>, {
1009            impl_se!(struct Req<'a> { uri: &'a FileUri });
1010            impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
1011            impl_de!(struct Res { entries: Vec<Obj> });
1012
1013            use std::time::{UNIX_EPOCH, Duration};
1014    
1015            self.api
1016                .run_mobile_plugin::<Res>("readDir", Req { uri })
1017                .map(|v| v.entries.into_iter())
1018                .map(|v| v.map(|v| match v.mime_type {
1019                    Some(mime_type) => Entry::File {
1020                        name: v.name,
1021                        last_modified: UNIX_EPOCH + Duration::from_millis(v.last_modified as u64),
1022                        len: v.byte_size as u64,
1023                        mime_type,
1024                        uri: v.uri,
1025                    },
1026                    None => Entry::Dir {
1027                        name: v.name,
1028                        last_modified: UNIX_EPOCH + Duration::from_millis(v.last_modified as u64),
1029                        uri: v.uri,
1030                    }
1031                }))
1032                .map_err(Into::into)
1033        })
1034    }
1035
1036    /// Take persistent permission to access the file, directory and its descendants.  
1037    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
1038    /// 
1039    /// This works by just calling, without displaying any confirmation to the user.
1040    /// 
1041    /// 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)  
1042    /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
1043    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1044    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
1045    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1046    /// 
1047    /// # Args
1048    /// - **uri** :  
1049    /// URI of the target file or directory.   
1050    /// This must be a URI taken from following :  
1051    ///     - [`FilePicker::pick_files`]  
1052    ///     - [`FilePicker::pick_file`]  
1053    ///     - [`FilePicker::pick_visual_medias`]  
1054    ///     - [`FilePicker::pick_visual_media`]  
1055    ///     - [`FilePicker::pick_dir`]  
1056    ///     - [`FilePicker::save_file`]  
1057    ///     - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :  
1058    ///     If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function. 
1059    ///     Because the permissions and validity period of the descendant entry URIs depend on the origin directory.   
1060    /// 
1061    /// # Support
1062    /// All Android version. 
1063    pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1064        on_android!({
1065            impl_se!(struct Req<'a> { uri: &'a FileUri });
1066            impl_de!(struct Res;);
1067
1068            self.api
1069                .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1070                .map(|_| ())
1071                .map_err(Into::into)
1072        })
1073    }
1074
1075    /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].  
1076    /// Returns false if there are only non-persistent permissions or no permissions.
1077    /// 
1078    /// # Args
1079    /// - **uri** :  
1080    /// URI of the target file or directory.  
1081    /// This must be a URI taken from following :  
1082    ///     - [`FilePicker::pick_files`]  
1083    ///     - [`FilePicker::pick_file`]  
1084    ///     - [`FilePicker::pick_visual_medias`]  
1085    ///     - [`FilePicker::pick_visual_media`]  
1086    ///     - [`FilePicker::pick_dir`]  
1087    ///     - [`FilePicker::save_file`]  
1088    ///     - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :  
1089    ///     If use URI from thoese fucntions, the permissions of the origin directory URI is checked, not a entry iteself by this function. 
1090    ///     Because the permissions and validity period of the descendant entry URIs depend on the origin directory.   
1091    /// 
1092    /// - **mode** :  
1093    /// The mode of permission you want to check.  
1094    /// 
1095    /// # Support
1096    /// All Android version.
1097    pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1098        on_android!({
1099            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1100            impl_de!(struct Res { value: bool });
1101
1102            self.api
1103                .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1104                .map(|v| v.value)
1105                .map_err(Into::into)
1106        })
1107    }
1108
1109    /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.   
1110    /// 
1111    /// # Support
1112    /// All Android version.
1113    pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1114        on_android!(std::iter::Empty::<_>, {
1115            impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1116            impl_de!(struct Res { items: Vec<Obj> });
1117    
1118            self.api
1119                .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1120                .map(|v| v.items.into_iter())
1121                .map(|v| v.map(|v| {
1122                    let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1123                    match v.d {
1124                        true => PersistedUriPermission::Dir { uri, can_read, can_write },
1125                        false => PersistedUriPermission::File { uri, can_read, can_write }
1126                    }
1127                }))
1128                .map_err(Into::into)
1129        })
1130    }
1131
1132    /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
1133    /// 
1134    /// # Args
1135    /// - ***uri*** :  
1136    /// URI of the target file or directory.  
1137    ///
1138    /// # Support
1139    /// All Android version.
1140    pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1141        on_android!({
1142            impl_se!(struct Req<'a> { uri: &'a FileUri });
1143            impl_de!(struct Res;);
1144
1145            self.api
1146                .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1147                .map(|_| ())
1148                .map_err(Into::into)
1149        })
1150    }
1151
1152    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
1153    /// 
1154    /// # Support
1155    /// All Android version.
1156    pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1157        on_android!({
1158            impl_de!(struct Res);
1159
1160            self.api
1161                .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1162                .map(|_| ())
1163                .map_err(Into::into)
1164        })
1165    }
1166
1167    /// Verify whether this plugin is available.  
1168    /// 
1169    /// On Android, this returns true.  
1170    /// On other platforms, this returns false.  
1171    pub fn is_available(&self) -> bool {
1172        cfg!(target_os = "android")
1173    }
1174
1175    /// Get the api level of this Android device.
1176    /// 
1177    /// The correspondence table between API levels and Android versions can be found following.  
1178    /// <https://developer.android.com/guide/topics/manifest/uses-sdk-element#api-level-table>
1179    /// 
1180    /// If you want the constant value of the API level from an Android version, there is the [`api_level`] module.
1181    /// 
1182    /// # Table
1183    /// | Android version  | API Level |
1184    /// |------------------|-----------|
1185    /// | 16.0             | 36        |
1186    /// | 15.0             | 35        |
1187    /// | 14.0             | 34        |
1188    /// | 13.0             | 33        |
1189    /// | 12L              | 32        |
1190    /// | 12.0             | 31        |
1191    /// | 11.0             | 30        |
1192    /// | 10.0             | 29        |
1193    /// | 9.0              | 28        |
1194    /// | 8.1              | 27        |
1195    /// | 8.0              | 26        |
1196    /// | 7.1 - 7.1.2      | 25        |
1197    /// | 7.0              | 24        |
1198    /// 
1199    /// Tauri does not support Android versions below 7.
1200    pub fn api_level(&self) -> Result<i32> {
1201        on_android!({
1202            impl_de!(struct Res { value: i32 });
1203        
1204            static API_LEVEL: std::sync::OnceLock<i32> = std::sync::OnceLock::new();
1205
1206            if let Some(api_level) = API_LEVEL.get() {
1207                return Ok(*api_level)
1208            }
1209
1210            let api_level = self.api
1211                .run_mobile_plugin::<Res>("getApiLevel", "")?
1212                .value;
1213
1214            let _ = API_LEVEL.set(api_level);
1215
1216            Ok(api_level)
1217        })
1218    }
1219}