tauri_plugin_android_fs/api/
android_fs.rs

1use std::io::{Read as _, Write as _};
2use crate::*;
3
4
5/// ***Root API***  
6/// 
7/// # Examples
8/// ```
9/// fn example(app: &tauri::AppHandle) {
10///     use tauri_plugin_android_fs::AndroidFsExt as _;
11/// 
12///     let api = app.android_fs();
13/// }
14/// ```
15pub struct AndroidFs<R: tauri::Runtime> {
16    #[cfg(target_os = "android")]
17    pub(crate) app: tauri::AppHandle<R>, 
18
19    #[cfg(target_os = "android")]
20    pub(crate) api: tauri::plugin::PluginHandle<R>, 
21
22    #[cfg(target_os = "android")]
23    pub(crate) intent_lock: std::sync::Mutex<()>,
24
25    #[cfg(not(target_os = "android"))]
26    _marker: std::marker::PhantomData<fn() -> R>
27}
28
29impl<R: tauri::Runtime> AndroidFs<R> {
30
31    pub(crate) fn new<C: serde::de::DeserializeOwned>(
32        app: tauri::AppHandle<R>,
33        api: tauri::plugin::PluginApi<R, C>,
34    ) -> crate::Result<Self> {
35
36        #[cfg(target_os = "android")] {
37            Ok(Self {
38                api: api.register_android_plugin("com.plugin.android_fs", "AndroidFsPlugin")?, 
39                app,
40                intent_lock: std::sync::Mutex::new(())
41            })
42        }
43        
44        #[cfg(not(target_os = "android"))] {
45            Ok(Self { _marker: Default::default() })
46        }
47    }
48}
49
50impl<R: tauri::Runtime> AndroidFs<R> {
51
52    /// Verify whether this plugin is available.  
53    /// 
54    /// On Android, this returns true.  
55    /// On other platforms, this returns false.  
56    pub fn is_available(&self) -> bool {
57        cfg!(target_os = "android")
58    }
59
60    /// Get the file or directory name.  
61    /// 
62    /// # Args
63    /// - ***uri*** :  
64    /// Target URI.  
65    /// This needs to be **readable**.
66    /// 
67    /// # Support
68    /// All.
69    pub fn get_name(&self, uri: &FileUri) -> crate::Result<String> {
70        on_android!({
71            impl_se!(struct Req<'a> { uri: &'a FileUri });
72            impl_de!(struct Res { name: String });
73
74            self.api
75                .run_mobile_plugin::<Res>("getName", Req { uri })
76                .map(|v| v.name)
77                .map_err(Into::into)
78        })
79    }
80
81    /// Query the provider to get mime type.  
82    /// If the directory, this returns `None`.  
83    /// If the file, this returns no `None`.  
84    /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.  
85    ///
86    /// In the case of files in [`PrivateStorage`], this is determined from the extension.
87    /// 
88    /// # Args
89    /// - ***uri*** :  
90    /// Target URI.  
91    /// This needs to be **readable**.
92    /// 
93    /// # Support
94    /// All.
95    pub fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>> {
96        on_android!({
97            impl_se!(struct Req<'a> { uri: &'a FileUri });
98            impl_de!(struct Res { value: Option<String> });
99
100            self.api
101                .run_mobile_plugin::<Res>("getMimeType", Req { uri })
102                .map(|v| v.value)
103                .map_err(Into::into)
104        })
105    }
106
107    /// Queries the file system to get information about a file, directory.
108    /// 
109    /// # Args
110    /// - ***uri*** :  
111    /// Target URI.  
112    /// This needs to be **readable**.
113    /// 
114    /// # Note
115    /// This uses [`AndroidFs::open_file`] internally.
116    /// 
117    /// # Support
118    /// All.
119    pub fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
120        on_android!({
121            let file = self.open_file(uri, FileAccessMode::Read)?;
122            Ok(file.metadata()?)
123        })
124    }
125
126    /// Open a file in the specified mode.
127    /// 
128    /// # Args
129    /// - ***uri*** :  
130    /// Target file URI.  
131    /// This must have corresponding permissions (read, write, or both) for the specified **mode**.
132    /// 
133    /// - ***mode*** :  
134    /// Indicates how the file is opened and the permissions granted.  
135    /// Note that files hosted by third-party apps may not support following:
136    ///     - [`FileAccessMode::ReadWrite`]
137    ///     - [`FileAccessMode::ReadWriteTruncate`]  
138    ///     - [`FileAccessMode::WriteAppend`]  
139    /// (ex: Files on GoogleDrive)  
140    ///
141    /// # Note
142    /// This method uses a FileDescriptor internally. 
143    /// However, if the target file does not physically exist on the device, such as cloud-based files, 
144    /// the write operation using a FileDescriptor may not be reflected properly.
145    /// In such cases, consider using [AndroidFs::write_via_kotlin], 
146    /// which writes using a standard method, 
147    /// or [AndroidFs::write], which automatically falls back to that approach when necessary.
148    /// If you specifically need to write using stream not entire contents, see [AndroidFs::write_via_kotlin_in] or [AndroidFs::copy_via_kotlin] with temporary file.  
149    /// 
150    /// It seems that the issue does not occur on all cloud storage platforms. At least, files on Google Drive have issues, 
151    /// but files on Dropbox can be written to correctly using a FileDescriptor.
152    /// If you encounter issues with cloud storage other than Google Drive, please let me know on [Github](https://github.com/aiueo13/tauri-plugin-android-fs/issues/new). 
153    /// This information will be used in [AndroidFs::need_write_via_kotlin] used by `AndroidFs::write`.  
154    /// 
155    /// There are no problems with file reading.
156    /// 
157    /// # Support
158    /// All.
159    pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
160        on_android!({
161            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
162            impl_de!(struct Res { fd: std::os::fd::RawFd });
163    
164            let mode = match mode {
165                FileAccessMode::Read => "r",
166                FileAccessMode::Write => "w",
167                FileAccessMode::WriteTruncate => "wt",
168                FileAccessMode::WriteAppend => "wa",
169                FileAccessMode::ReadWriteTruncate => "rwt",
170                FileAccessMode::ReadWrite => "rw",
171            };
172
173            self.api
174                .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
175                .map(|v| {
176                    use std::os::fd::FromRawFd;
177                    unsafe { std::fs::File::from_raw_fd(v.fd) }
178                })
179                .map_err(Into::into)
180        })
181    }
182
183    /// Reads the entire contents of a file into a bytes vector.  
184    /// 
185    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
186    /// 
187    /// # Args
188    /// - ***uri*** :  
189    /// Target file URI.    
190    /// This needs to be **readable**.
191    /// 
192    /// # Support
193    /// All.
194    pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
195        on_android!({
196            let mut file = self.open_file(uri, FileAccessMode::Read)?;
197            let mut buf = file.metadata().ok()
198                .map(|m| m.len() as usize)
199                .map(Vec::with_capacity)
200                .unwrap_or_else(Vec::new);
201
202            file.read_to_end(&mut buf)?;
203            Ok(buf)
204        })
205    }
206
207    /// Reads the entire contents of a file into a string.  
208    /// 
209    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
210    /// 
211    /// # Args
212    /// - ***uri*** :  
213    /// Target file URI.  
214    /// This needs to be **readable**.
215    /// 
216    /// # Support
217    /// All.
218    pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
219        on_android!({
220            let mut file = self.open_file(uri, FileAccessMode::Read)?;
221            let mut buf = file.metadata().ok()
222                .map(|m| m.len() as usize)
223                .map(String::with_capacity)
224                .unwrap_or_else(String::new);
225    
226            file.read_to_string(&mut buf)?;
227            Ok(buf)
228        })
229    }
230
231    /// Writes a slice as the entire contents of a file.  
232    /// This function will entirely replace its contents if it does exist.    
233    /// 
234    /// If you want to operate the file, use [`AndroidFs::open_file`] instead.  
235    /// 
236    /// # Args
237    /// - ***uri*** :  
238    /// Target file URI.  
239    /// This needs to be **writable**.
240    /// 
241    /// # Support
242    /// All.
243    pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
244        on_android!({
245            if self.need_write_via_kotlin(uri)? {
246                self.write_via_kotlin(uri, contents)?;
247            }
248            else {
249                let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
250                file.write_all(contents.as_ref())?;
251            }
252            Ok(())
253        })
254    }
255
256    /// Writes a slice as the entire contents of a file.  
257    /// This function will entirely replace its contents if it does exist.    
258    /// 
259    /// Differences from `std::fs::File::write_all` is the process is done on Kotlin side.  
260    /// See [`AndroidFs::open_file`] for why this function exists.
261    /// 
262    /// If [`AndroidFs::write`] is used, it automatically fall back to this by [`AndroidFs::need_write_via_kotlin`], 
263    /// so there should be few opportunities to use this.
264    /// 
265    /// If you want to write using `std::fs::File`, not entire contents, use [`AndroidFs::write_via_kotlin_in`].
266    /// 
267    /// # Inner process
268    /// The contents is written to a temporary file by Rust side 
269    /// and then copied to the specified file on Kotlin side by [`AndroidFs::copy_via_kotlin`].  
270    /// 
271    /// # Support
272    /// All.
273    pub fn write_via_kotlin(
274        &self, 
275        uri: &FileUri,
276        contents: impl AsRef<[u8]>
277    ) -> crate::Result<()> {
278
279        on_android!({
280            self.write_via_kotlin_in(uri, |file| file.write_all(contents.as_ref()))
281        })
282    }
283
284    /// See [`AndroidFs::write_via_kotlin`] for information.  
285    /// Use this if you want to write using `std::fs::File`, not entire contents.
286    /// 
287    /// If you want to retain the file outside the closure, 
288    /// you can perform the same operation using [`AndroidFs::copy_via_kotlin`] and [`PrivateStorage`]. 
289    /// For details, please refer to the internal implementation of this function.
290    /// 
291    /// # Args
292    /// - ***uri*** :  
293    /// Target file URI to write.
294    /// 
295    /// - **contetns_writer** :  
296    /// A closure that accepts a mutable reference to a `std::fs::File`
297    /// and performs the actual write operations. Note that this represents a temporary file.
298    pub fn write_via_kotlin_in<T>(
299        &self, 
300        uri: &FileUri,
301        contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
302    ) -> crate::Result<T> {
303
304        on_android!({
305            let tmp_file_path = {
306                use std::sync::atomic::{AtomicUsize, Ordering};
307
308                static COUNTER: AtomicUsize = AtomicUsize::new(0);
309                let id = COUNTER.fetch_add(1, Ordering::Relaxed);
310
311                self.private_storage().resolve_path_with(
312                    PrivateDir::Cache,
313                    format!("{TMP_DIR_RELATIVE_PATH}/write_via_kotlin_in {id}")
314                )?
315            };
316
317            if let Some(parent) = tmp_file_path.parent() {
318                let _ = std::fs::create_dir_all(parent);
319            }
320
321            let result = {
322                let ref mut file = std::fs::File::create(&tmp_file_path)?;
323                contents_writer(file)
324            };
325
326            let result = result
327                .map_err(crate::Error::from)
328                .and_then(|t| self.copy_via_kotlin(&(&tmp_file_path).into(), uri).map(|_| t));
329
330            let _ = std::fs::remove_file(&tmp_file_path);
331
332            result
333        })
334    }
335
336    /// Determines if the file needs to be written via Kotlin side instead of Rust side.  
337    /// Currently, this returns true only if the file is on GoogleDrive.  
338    /// 
339    /// # Support
340    /// All.
341    pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
342        on_android!({
343            Ok(uri.uri.starts_with("content://com.google.android.apps.docs.storage"))
344        })
345    }
346
347    /// Copies the contents of src file to dest.  
348    /// If dest already has contents, it is truncated before write src contents.  
349    /// 
350    /// This copy process is done on Kotlin side, not on Rust.  
351    /// Large files in GB units are also supported.  
352    /// Note that [`AndroidFs::copy`] and [`std::io::copy`] are faster.  
353    ///  
354    /// See [`AndroidFs::write_via_kotlin`] for why this function exists.
355    /// 
356    /// # Args
357    /// - ***src*** :  
358    /// The URI of source file.   
359    /// This needs to be **readable**.
360    /// 
361    /// - ***dest*** :  
362    /// The URI of destination file.  
363    /// This needs to be **writable**.
364    /// 
365    /// # Support
366    /// All.
367    pub fn copy_via_kotlin(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
368        on_android!({
369            impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri });
370            impl_de!(struct Res;);
371
372            self.api
373                .run_mobile_plugin::<Res>("copyFile", Req { src, dest })
374                .map(|_| ())
375                .map_err(Into::into)
376        })
377    }
378
379    /// Copies the contents of src file to dest.  
380    /// If dest already has contents, it is truncated before write src contents.  
381    /// 
382    /// # Args
383    /// - ***src*** :  
384    /// The URI of source file.   
385    /// This needs to be **readable**.
386    /// 
387    /// - ***dest*** :  
388    /// The URI of destination file.  
389    /// This needs to be **writable**.
390    /// 
391    /// # Support
392    /// All.
393    pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
394        on_android!({
395            let src = &mut self.open_file(src, FileAccessMode::Read)?;
396            let dest = &mut self.open_file(dest, FileAccessMode::WriteTruncate)?;
397            std::io::copy(src, dest)?;
398            Ok(())
399        })
400    }
401
402    /// Renames a file or directory to a new name, and return new URI.  
403    /// Even if the names conflict, the existing file will not be overwritten.  
404    /// 
405    /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
406    /// In other words, this function returns a new URI without any permissions.
407    /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
408    /// In this, this function returns the same URI as original URI.
409    ///
410    /// # Args
411    /// - ***uri*** :  
412    /// URI of target entry.  
413    /// 
414    /// - ***new_name*** :  
415    /// New name of target entry. 
416    /// This include extension if use.  
417    /// The behaviour in the same name already exists depends on the file provider.  
418    /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.  
419    /// In the case of files hosted by other applications, errors may occur.  
420    /// But at least, the existing file will not be overwritten.  
421    /// 
422    /// # Support
423    /// All.
424    pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
425        on_android!({
426            impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
427
428            let new_name = new_name.as_ref();
429
430            self.api
431                .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
432                .map_err(Into::into)
433        })
434    }
435
436    /// Remove the file.
437    /// 
438    /// # Args
439    /// - ***uri*** :  
440    /// Target file URI.  
441    /// This needs to be **writable**, at least. But even if it is, 
442    /// removing may not be possible in some cases. 
443    /// For details, refer to the documentation of the function that provided the URI.  
444    /// If not file, an error will occur.
445    /// 
446    /// # Support
447    /// All.
448    pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
449        on_android!({
450            impl_se!(struct Req<'a> { uri: &'a FileUri });
451            impl_de!(struct Res;);
452    
453            self.api
454                .run_mobile_plugin::<Res>("deleteFile", Req { uri })
455                .map(|_| ())
456                .map_err(Into::into)
457        })
458    }
459
460    /// Remove the **empty** directory.
461    /// 
462    /// # Args
463    /// - ***uri*** :  
464    /// Target directory URI.  
465    /// This needs to be **writable**.  
466    /// If not empty directory, an error will occur.
467    /// 
468    /// # Support
469    /// All.
470    pub fn remove_dir(&self, uri: &FileUri) -> crate::Result<()> {
471        on_android!({
472            impl_se!(struct Req<'a> { uri: &'a FileUri });
473            impl_de!(struct Res;);
474        
475            self.api
476                .run_mobile_plugin::<Res>("deleteEmptyDir", Req { uri })
477                .map(|_| ())
478                .map_err(Into::into)
479        })
480    }
481
482    /// Removes a directory and all its contents. Use carefully!
483    /// 
484    /// # Args
485    /// - ***uri*** :  
486    /// Target directory URI.  
487    /// This needs to be **writable**.  
488    /// If not directory, an error will occur.
489    /// 
490    /// # Support
491    /// All.
492    pub fn remove_dir_all(&self, uri: &FileUri) -> crate::Result<()> {
493        on_android!({
494            impl_se!(struct Req<'a> { uri: &'a FileUri });
495            impl_de!(struct Res;);
496        
497            self.api
498                .run_mobile_plugin::<Res>("deleteDirAll", Req { uri })
499                .map(|_| ())
500                .map_err(Into::into)
501        })
502    }
503
504    /// Build a URI of an **existing** file located at the relative path from the specified directory.   
505    /// Error occurs, if the file does not exist.  
506    /// 
507    /// The permissions and validity period of the returned URI depend on the origin directory 
508    /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`]) 
509    /// 
510    /// # Support
511    /// All.
512    pub fn try_resolve_file_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
513        on_android!({
514            let uri = self.resolve_uri(dir, relative_path)?;            
515            if self.get_mime_type(&uri)?.is_none() {
516                return Err(crate::Error { msg: format!("This is a directory, not a file: {uri:?}").into() })
517            }
518            Ok(uri)
519        })
520    }
521
522    /// Build a URI of an **existing** directory located at the relative path from the specified directory.   
523    /// Error occurs, if the directory does not exist.  
524    /// 
525    /// The permissions and validity period of the returned URI depend on the origin directory 
526    /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`]) 
527    /// 
528    /// # Support
529    /// All.
530    pub fn try_resolve_dir_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
531        on_android!({
532            let uri = self.resolve_uri(dir, relative_path)?;
533            if self.get_mime_type(&uri)?.is_some() {
534                return Err(crate::Error { msg: format!("This is a file, not a directory: {uri:?}").into() })
535            }
536            Ok(uri)
537        })
538    }
539
540    /// Build a URI of an entry located at the relative path from the specified directory.   
541    /// 
542    /// This function does not perform checks on the arguments or the returned URI.  
543    /// Even if the dir argument refers to a file, no error occurs (and no panic either).
544    /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.  
545    /// 
546    /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead. 
547    /// Or use this with [`AndroidFs::get_mime_type`].
548    /// 
549    /// The permissions and validity period of the returned URI depend on the origin directory 
550    /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`]) 
551    /// 
552    /// # Performance
553    /// This operation is relatively fast 
554    /// because it does not call Kotlin API and only involves operating strings on Rust side.
555    /// 
556    /// # Support
557    /// All.
558    pub fn resolve_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
559        on_android!({
560            let base_dir = &dir.uri;
561            let relative_path = relative_path.as_ref().trim_matches('/');
562
563            if relative_path.is_empty() {
564                return Ok(dir.clone())
565            }
566
567            Ok(FileUri {
568                document_top_tree_uri: dir.document_top_tree_uri.clone(),
569                uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
570            })
571        })
572    }
573
574    /// See [`AndroidFs::get_thumbnail_to`] for descriptions.  
575    /// 
576    /// If thumbnail does not wrote to dest, return false.
577    pub fn get_thumbnail_to(
578        &self, 
579        src: &FileUri,
580        dest: &FileUri,
581        preferred_size: Size,
582        format: ImageFormat,
583    ) -> crate::Result<bool> {
584
585        on_android!({
586            impl_se!(struct Req<'a> {
587                src: &'a FileUri, 
588                dest: &'a FileUri,
589                format: &'a str,
590                quality: u8,
591                width: u32,
592                height: u32,
593            });
594            impl_de!(struct Res { value: bool });
595
596            let (quality, format) = match format {
597                ImageFormat::Png => (1.0, "Png"),
598                ImageFormat::Jpeg => (0.75, "Jpeg"),
599                ImageFormat::Webp => (0.7, "Webp"),
600                ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
601                ImageFormat::WebpWith { quality } => (quality, "Webp"),
602            };
603            let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
604            let Size { width, height } = preferred_size;
605        
606            self.api
607                .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
608                .map(|v| v.value)
609                .map_err(Into::into)
610        })
611    }
612
613    /// Query the provider to get a file thumbnail.  
614    /// If thumbnail does not exist it, return None.
615    /// 
616    /// Note this does not cache. Please do it in your part if need.  
617    /// 
618    /// # Args
619    /// - ***uri*** :  
620    /// Targe file uri.  
621    /// Thumbnail availablty depends on the file provider.  
622    /// In general, images and videos are available.  
623    /// For files in [`PrivateStorage`], 
624    /// the file type must match the filename extension.  
625    /// 
626    /// - ***preferred_size*** :  
627    /// Optimal thumbnail size desired.  
628    /// This may return a thumbnail of a different size, 
629    /// but never more than double the requested size. 
630    /// In any case, the aspect ratio is maintained.
631    /// 
632    /// - ***format*** :  
633    /// Thumbnail image format.  
634    /// [`ImageFormat::Jpeg`] is recommended. 
635    /// If you need transparency, use others.
636    /// 
637    /// # Support
638    /// All.
639    pub fn get_thumbnail(
640        &self,
641        uri: &FileUri,
642        preferred_size: Size,
643        format: ImageFormat,
644    ) -> crate::Result<Option<Vec<u8>>> {
645
646        on_android!({
647            let tmp_file_path = {
648                use std::sync::atomic::{AtomicUsize, Ordering};
649
650                static COUNTER: AtomicUsize = AtomicUsize::new(0);
651                let id = COUNTER.fetch_add(1, Ordering::Relaxed);
652
653                self.private_storage().resolve_path_with(
654                    PrivateDir::Cache,
655                    format!("{TMP_DIR_RELATIVE_PATH}/get_thumbnail {id}")
656                )?
657            };
658
659            if let Some(parent) = tmp_file_path.parent() {
660                let _ = std::fs::create_dir_all(parent);
661            }
662
663            std::fs::File::create(&tmp_file_path)?;
664
665            let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
666                .and_then(|ok| {
667                    if (ok) {
668                        std::fs::read(&tmp_file_path)
669                            .map(Some)
670                            .map_err(Into::into)
671                    }
672                    else {
673                        Ok(None)
674                    }
675                });
676
677            let _ = std::fs::remove_file(&tmp_file_path);
678
679            result
680        })
681    }
682
683    /// Creates a new empty file in the specified location and returns a URI.  
684    /// 
685    /// The permissions and validity period of the returned URIs depend on the origin directory 
686    /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`]) 
687    ///  
688    /// # Args  
689    /// - ***dir*** :  
690    /// The URI of the base directory.  
691    /// This needs to be **read-write**.
692    ///  
693    /// - ***relative_path*** :  
694    /// The file path relative to the base directory.  
695    /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.  
696    /// Any missing subdirectories in the specified path will be created automatically.  
697    ///  
698    /// - ***mime_type*** :  
699    /// The MIME type of the file to be created.  
700    /// If this is None, MIME type is inferred from the extension of ***relative_path***
701    /// and if that fails, `application/octet-stream` is used.  
702    ///  
703    /// # Support
704    /// All.
705    pub fn create_file(
706        &self,
707        dir: &FileUri, 
708        relative_path: impl AsRef<str>, 
709        mime_type: Option<&str>
710    ) -> crate::Result<FileUri> {
711
712        on_android!({
713            impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
714        
715            let relative_path = relative_path.as_ref();
716
717            self.api
718                .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
719                .map_err(Into::into)
720        })
721    }
722
723    /// Recursively create a directory and all of its parent components if they are missing,
724    /// then return the URI.
725    /// 
726    /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
727    /// 
728    /// # Args  
729    /// - ***dir*** :  
730    /// The URI of the base directory.  
731    /// This needs to be **read-write**.
732    ///  
733    /// - ***relative_path*** :  
734    /// The directory path relative to the base directory.    
735    ///  
736    /// # Support
737    /// All.
738    pub fn create_dir_all(
739        &self,
740        dir: &FileUri, 
741        relative_path: impl AsRef<str>, 
742    ) -> Result<FileUri> {
743
744        on_android!({
745            let relative_path = relative_path.as_ref().trim_matches('/');
746            if relative_path.is_empty() {
747                return Ok(dir.clone())
748            }
749
750            // TODO:
751            // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
752            let tmp_file_uri = self.create_file(
753                dir, 
754                format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"), 
755                Some("application/octet-stream")
756            )?;
757            let _ = self.remove_file(&tmp_file_uri);
758            let uri = self.resolve_uri(dir, relative_path)?;
759
760            Ok(uri)
761        })
762    }
763
764    /// Returns the child files and directories of the specified directory.  
765    /// The order of the entries is not guaranteed.  
766    /// 
767    /// The permissions and validity period of the returned URIs depend on the origin directory 
768    /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])  
769    /// 
770    /// # Args
771    /// - ***uri*** :  
772    /// Target directory URI.  
773    /// This needs to be **readable**.
774    ///  
775    /// # Note  
776    /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily. 
777    /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.  
778    /// 
779    /// # Support
780    /// All.
781    pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
782        on_android!(std::iter::Empty::<_>, {
783            impl_se!(struct Req<'a> { uri: &'a FileUri });
784            impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
785            impl_de!(struct Res { entries: Vec<Obj> });
786    
787            self.api
788                .run_mobile_plugin::<Res>("readDir", Req { uri })
789                .map(|v| v.entries.into_iter())
790                .map(|v| v.map(|v| match v.mime_type {
791                    Some(mime_type) => Entry::File {
792                        name: v.name,
793                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
794                        len: v.byte_size as u64,
795                        mime_type,
796                        uri: v.uri,
797                    },
798                    None => Entry::Dir {
799                        name: v.name,
800                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
801                        uri: v.uri,
802                    }
803                }))
804                .map_err(Into::into)
805        })
806    }
807
808    /// Opens a system file picker and returns a **read-write** URIs.  
809    /// If no file is selected or the user cancels, an empty vec is returned.  
810    /// 
811    /// By default, returned URI is valid until the app is terminated. 
812    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
813    /// 
814    /// This provides a standardized file explorer-style interface, 
815    /// and also allows file selection from part of third-party apps or cloud storage.
816    ///
817    /// Removing the returned files is also supported in most cases, 
818    /// but note that files provided by third-party apps may not be removable.  
819    ///  
820    /// # Args  
821    /// - ***initial_location*** :  
822    /// Indicate the initial location of dialog.  
823    /// There is no need to use this if there is no special reason.  
824    /// System will do its best to launch the dialog in the specified entry 
825    /// if it's a directory, or the directory that contains the specified file if not.  
826    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
827    /// This must be a URI taken from following :   
828    ///     - [`AndroidFs::resolve_initial_location`]
829    ///     - [`AndroidFs::show_open_file_dialog`]
830    ///     - [`AndroidFs::show_save_file_dialog`]
831    ///     - [`AndroidFs::show_manage_dir_dialog`]
832    ///     - [`AndroidFs::resolve_uri`]
833    ///     - [`AndroidFs::try_resolve_file_uri`]
834    ///     - [`AndroidFs::try_resolve_dir_uri`]
835    ///     - [`AndroidFs::read_dir`]
836    ///     - [`AndroidFs::create_file`]
837    /// 
838    /// - ***mime_types*** :  
839    /// The MIME types of the file to be selected.  
840    /// However, there is no guarantee that the returned file will match the specified types.  
841    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
842    ///  
843    /// - ***multiple*** :  
844    /// Indicates whether multiple file selection is allowed.  
845    /// 
846    /// # Support
847    /// All.
848    /// 
849    /// # References
850    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
851    pub fn show_open_file_dialog(
852        &self,
853        initial_location: Option<&FileUri>,
854        mime_types: &[&str],
855        multiple: bool,
856    ) -> crate::Result<Vec<FileUri>> {
857
858        on_android!({
859            impl_se!(struct Req<'a> { 
860                mime_types: &'a [&'a str],
861                multiple: bool,
862                initial_location: Option<&'a FileUri>
863            });
864            impl_de!(struct Res { uris: Vec<FileUri> });
865    
866            let _guard = self.intent_lock.lock();
867            self.api
868                .run_mobile_plugin::<Res>("showOpenFileDialog", Req { mime_types, multiple, initial_location })
869                .map(|v| v.uris)
870                .map_err(Into::into)
871        })
872    }
873
874    /// Opens a file picker and returns a **readonly** URIs.  
875    /// If no file is selected or the user cancels, an empty vec is returned.  
876    ///  
877    /// Returned URI is valid until the app is terminated. Can not persist it.
878    /// 
879    /// This works differently depending on the model and version.  
880    /// But recent devices often have the similar behaviour as [`AndroidFs::show_open_visual_media_dialog`] or [`AndroidFs::show_open_file_dialog`].  
881    /// Use this, if you want your app to simply read/import data.
882    /// 
883    /// # Args  
884    /// - ***mime_types*** :  
885    /// The MIME types of the file to be selected.  
886    /// However, there is no guarantee that the returned file will match the specified types.  
887    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
888    ///  
889    /// - ***multiple*** :  
890    /// Indicates whether multiple file selection is allowed.  
891    /// 
892    /// # Support
893    /// All.
894    /// 
895    /// # References
896    /// <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
897    pub fn show_open_content_dialog(
898        &self,
899        mime_types: &[&str],
900        multiple: bool
901    ) -> crate::Result<Vec<FileUri>> {
902
903        on_android!({
904            impl_se!(struct Req<'a> { mime_types: &'a [&'a str], multiple: bool });
905            impl_de!(struct Res { uris: Vec<FileUri> });
906
907            let _guard = self.intent_lock.lock();
908            self.api
909                .run_mobile_plugin::<Res>("showOpenContentDialog", Req { mime_types, multiple })
910                .map(|v| v.uris)
911                .map_err(Into::into)
912        })
913    }
914
915    /// Opens a media picker and returns a **readonly** URIs.  
916    /// If no file is selected or the user cancels, an empty vec is returned.  
917    ///  
918    /// By default, returned URI is valid until the app is terminated. 
919    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
920    ///  
921    /// This media picker provides a browsable interface that presents the user with their media library, 
922    /// sorted by date from newest to oldest. 
923    /// 
924    /// # Args  
925    /// - ***target*** :  
926    /// The media type of the file to be selected.  
927    /// Images or videos, or both.  
928    ///  
929    /// - ***multiple*** :  
930    /// Indicates whether multiple file selection is allowed.  
931    ///  
932    /// # Note
933    /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].  
934    /// Instead, it will be assigned a sequential number, such as `1000091523.png`. 
935    /// And this is marked intended behavior, not a bug.
936    /// - <https://issuetracker.google.com/issues/268079113>  
937    ///  
938    /// # Support
939    /// This feature is available on devices that meet the following criteria:  
940    /// - Running Android 11 (API level 30) or higher  
941    /// - Receive changes to Modular System Components through Google System Updates  
942    ///  
943    /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].  
944    /// If not supported, this function behaves the same as [`AndroidFs::show_open_file_dialog`].  
945    /// 
946    /// # References
947    /// <https://developer.android.com/training/data-storage/shared/photopicker>
948    pub fn show_open_visual_media_dialog(
949        &self,
950        target: VisualMediaTarget,
951        multiple: bool,
952    ) -> crate::Result<Vec<FileUri>> {
953
954        on_android!({
955            impl_se!(struct Req { multiple: bool, target: VisualMediaTarget });
956            impl_de!(struct Res { uris: Vec<FileUri> });
957    
958            let _guard = self.intent_lock.lock();
959            self.api
960                .run_mobile_plugin::<Res>("showOpenVisualMediaDialog", Req { multiple, target })
961                .map(|v| v.uris)
962                .map_err(Into::into)
963        })
964    }
965
966    /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one, 
967    /// and returns a **read-write** directory URI. 
968    /// App can fully manage entries within the returned directory.  
969    /// If no directory is selected or the user cancels, `None` is returned. 
970    /// 
971    /// By default, returned URI is valid until the app is terminated. 
972    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
973    /// 
974    /// This provides a standardized file explorer-style interface.
975    /// 
976    /// # Args  
977    /// - ***initial_location*** :  
978    /// Indicate the initial location of dialog.    
979    /// There is no need to use this if there is no special reason.  
980    /// System will do its best to launch the dialog in the specified entry 
981    /// if it's a directory, or the directory that contains the specified file if not.  
982    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.   
983    /// This must be a URI taken from following :   
984    ///     - [`AndroidFs::resolve_initial_location`]
985    ///     - [`AndroidFs::show_open_file_dialog`]
986    ///     - [`AndroidFs::show_save_file_dialog`]
987    ///     - [`AndroidFs::show_manage_dir_dialog`]
988    ///     - [`AndroidFs::resolve_uri`]
989    ///     - [`AndroidFs::try_resolve_file_uri`]
990    ///     - [`AndroidFs::try_resolve_dir_uri`]
991    ///     - [`AndroidFs::read_dir`]
992    ///     - [`AndroidFs::create_file`]
993    /// 
994    /// # Support
995    /// All.
996    /// 
997    /// # References
998    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
999    pub fn show_manage_dir_dialog(
1000        &self,
1001        initial_location: Option<&FileUri>,
1002    ) -> crate::Result<Option<FileUri>> {
1003
1004        on_android!({
1005            impl_se!(struct Req<'a> { initial_location: Option<&'a FileUri> });
1006            impl_de!(struct Res { uri: Option<FileUri> });
1007
1008            let _guard = self.intent_lock.lock();
1009            self.api
1010                .run_mobile_plugin::<Res>("showManageDirDialog", Req { initial_location })
1011                .map(|v| v.uri)
1012                .map_err(Into::into)
1013        })
1014    }
1015
1016    /// Please use [`AndroidFs::show_manage_dir_dialog`] instead.
1017    #[deprecated = "Confusing name. Please use show_manage_dir_dialog instead."]
1018    #[warn(deprecated)]
1019    pub fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
1020        on_android!({
1021            self.show_manage_dir_dialog(None)
1022        })
1023    }
1024
1025    /// Opens a dialog to save a file and returns a **writeonly** URI.  
1026    /// The returned file may be a newly created file with no content,
1027    /// or it may be an existing file with the requested MIME type.  
1028    /// If the user cancels, `None` is returned. 
1029    /// 
1030    /// By default, returned URI is valid until the app is terminated. 
1031    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
1032    /// 
1033    /// This provides a standardized file explorer-style interface, 
1034    /// and also allows file selection from part of third-party apps or cloud storage.
1035    /// 
1036    /// Removing and reading the returned files is also supported in most cases, 
1037    /// but note that files provided by third-party apps may not.  
1038    ///  
1039    /// # Args  
1040    /// - ***initial_location*** :  
1041    /// Indicate the initial location of dialog.  
1042    /// There is no need to use this if there is no special reason.  
1043    /// System will do its best to launch the dialog in the specified entry 
1044    /// if it's a directory, or the directory that contains the specified file if not.  
1045    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
1046    /// This must be a URI taken from following :   
1047    ///     - [`AndroidFs::resolve_initial_location`]
1048    ///     - [`AndroidFs::show_open_file_dialog`]
1049    ///     - [`AndroidFs::show_save_file_dialog`]
1050    ///     - [`AndroidFs::show_manage_dir_dialog`]
1051    ///     - [`AndroidFs::resolve_uri`]
1052    ///     - [`AndroidFs::try_resolve_file_uri`]
1053    ///     - [`AndroidFs::try_resolve_dir_uri`]
1054    ///     - [`AndroidFs::read_dir`]
1055    ///     - [`AndroidFs::create_file`]
1056    /// 
1057    /// - ***initial_file_name*** :  
1058    /// An initial file name, but the user may change this value before creating the file.  
1059    /// 
1060    /// - ***mime_type*** :  
1061    /// The MIME type of the file to be saved.  
1062    /// If this is None, MIME type is inferred from the extension of ***initial_file_name*** (not file name by user input)
1063    /// and if that fails, `application/octet-stream` is used.  
1064    ///  
1065    /// # Support
1066    /// All.
1067    /// 
1068    /// # References
1069    /// <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
1070    pub fn show_save_file_dialog(
1071        &self,
1072        initial_location: Option<&FileUri>,
1073        initial_file_name: impl AsRef<str>,
1074        mime_type: Option<&str>,
1075    ) -> crate::Result<Option<FileUri>> {
1076
1077        on_android!({
1078            impl_se!(struct Req<'a> {
1079                initial_file_name: &'a str, 
1080                mime_type: Option<&'a str>, 
1081                initial_location: Option<&'a FileUri> 
1082            });
1083            impl_de!(struct Res { uri: Option<FileUri> });
1084    
1085            let initial_file_name = initial_file_name.as_ref();
1086        
1087            let _guard = self.intent_lock.lock();
1088            self.api
1089                .run_mobile_plugin::<Res>("showSaveFileDialog", Req { initial_file_name, mime_type, initial_location })
1090                .map(|v| v.uri)
1091                .map_err(Into::into)
1092        })
1093    }
1094
1095    /// Create an **restricted** URI for the specified directory.  
1096    /// This should only be used as `initial_location` in the dialog. 
1097    /// It must not be used for any other purpose.  
1098    /// 
1099    /// This is useful when selecting (creating) new files and folders, 
1100    /// but when selecting existing entries, `initial_location` is often better with None.
1101    /// 
1102    /// Note this is an informal method and is not guaranteed to work reliably.
1103    /// But this URI does not cause the dialog to error.  
1104    /// So please use this with the mindset that it's better than doing nothing.  
1105    /// 
1106    /// # Examples
1107    /// ```
1108    /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
1109    ///
1110    /// fn sample(app: tauri::AppHandle) {
1111    ///     let api = app.android_fs();
1112    ///
1113    ///     // Get URI of the top directory
1114    ///     let initial_location = api.resolve_initial_location(
1115    ///         InitialLocation::TopPublicDir,
1116    ///         false,
1117    ///     ).expect("Should be on Android");
1118    ///
1119    ///     // Get URI of ~/Pictures/
1120    ///     let initial_location = api.resolve_initial_location(
1121    ///         PublicImageDir::Pictures,
1122    ///         false
1123    ///     ).expect("Should be on Android");
1124    ///
1125    ///     // Get URI of ~/Documents/sub_dir1/sub_dir2/
1126    ///     let initial_location = api.resolve_initial_location(
1127    ///         InitialLocation::DirInPublicDir {
1128    ///             base_dir: PublicGeneralPurposeDir::Documents.into(),
1129    ///             relative_path: "sub_dir1/sub_dir2"
1130    ///         },
1131    ///         true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
1132    ///     ).expect("Should be on Android");
1133    ///
1134    ///     // Open dialog with initial_location
1135    ///     let _ = api.show_save_file_dialog(Some(&initial_location), "", None);
1136    ///     let _ = api.show_open_file_dialog(Some(&initial_location), &[], true);
1137    ///     let _ = api.show_manage_dir_dialog(Some(&initial_location));
1138    /// }
1139    /// ```
1140    /// 
1141    /// # Support
1142    /// All.
1143    pub fn resolve_initial_location<'a>(
1144        &self,
1145        dir: impl Into<InitialLocation<'a>>,
1146        create_dirs: bool
1147    ) -> crate::Result<FileUri> {
1148
1149        on_android!({
1150            const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary%3A";
1151
1152            let uri = match dir.into() {
1153                InitialLocation::TopPublicDir => TOP_DIR.into(),
1154                InitialLocation::PublicDir(dir) => format!("{TOP_DIR}{dir}"),
1155                InitialLocation::DirInPublicDir { base_dir, relative_path } => {
1156                    let relative_path = relative_path.trim_matches('/');
1157
1158                    if relative_path.is_empty() {
1159                        format!("{TOP_DIR}{base_dir}")
1160                    }
1161                    else {
1162                        if create_dirs {
1163                            let _ = self.public_storage()
1164                                .create_file(base_dir, format!("{relative_path}/tmp"), Some("application/octet-stream"))
1165                                .and_then(|u| self.remove_file(&u));
1166                        }
1167            
1168                        let sub_dirs = encode_document_id(relative_path);
1169                        format!("{TOP_DIR}{base_dir}%2F{sub_dirs}")
1170                    }
1171                }
1172            };
1173
1174            Ok(FileUri { uri, document_top_tree_uri: None })
1175        })
1176    }
1177
1178    /// Opens a dialog for sharing file to other apps.  
1179    /// 
1180    /// An error will occur if there is no app that can handle the request. 
1181    /// Please use [`AndroidFs::can_share_file`] to confirm.
1182    /// 
1183    /// # Args
1184    /// - **uri** :  
1185    /// Target file uri to share.  
1186    /// This needs to be **readable**.  
1187    /// Files in [`PrivateStorage`] ***cannot*** be used.
1188    /// 
1189    /// # Support
1190    /// All.
1191    pub fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1192        on_android!({
1193            impl_se!(struct Req<'a> { uri: &'a FileUri });
1194            impl_de!(struct Res;);
1195
1196            self.api
1197                .run_mobile_plugin::<Res>("shareFile", Req { uri })
1198                .map(|_| ())
1199                .map_err(Into::into)
1200        })
1201    }
1202
1203    /// Opens a dialog for viewing file on other apps.  
1204    /// This performs the general "open file" action.
1205    /// 
1206    /// An error will occur if there is no app that can handle the request. 
1207    /// Please use [`AndroidFs::can_view_file`] to confirm.
1208    /// 
1209    /// # Args
1210    /// - **uri** :  
1211    /// Target file uri to view.  
1212    /// This needs to be **readable**.  
1213    /// Files in [`PrivateStorage`] ***cannot*** be used.
1214    /// 
1215    /// # Support
1216    /// All.
1217    pub fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1218        on_android!({
1219            impl_se!(struct Req<'a> { uri: &'a FileUri });
1220            impl_de!(struct Res;);
1221    
1222            self.api
1223                .run_mobile_plugin::<Res>("viewFile", Req { uri })
1224                .map(|_| ())
1225                .map_err(Into::into)
1226        })
1227    }
1228
1229    /// Determines whether the specified file can be used with [`AndroidFs::show_share_file_dialog`].
1230    /// # Args
1231    /// - **uri** :  
1232    /// Target file uri.  
1233    /// This needs to be **readable**.
1234    /// 
1235    /// # Support
1236    /// All.
1237    pub fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool> {
1238        on_android!({
1239            impl_se!(struct Req<'a> { uri: &'a FileUri });
1240            impl_de!(struct Res { value: bool });
1241
1242            self.api
1243                .run_mobile_plugin::<Res>("canShareFile", Req { uri })
1244                .map(|v| v.value)
1245                .map_err(Into::into)
1246        })
1247    }
1248
1249    /// Determines whether the specified file can be used with [`AndroidFs::show_view_file_dialog`].
1250    /// 
1251    /// # Args
1252    /// - **uri** :  
1253    /// Target file uri.  
1254    /// This needs to be **readable**.
1255    /// 
1256    /// # Support
1257    /// All.
1258    pub fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool> {
1259        on_android!({
1260            impl_se!(struct Req<'a> { uri: &'a FileUri });
1261            impl_de!(struct Res { value: bool });
1262
1263            self.api
1264                .run_mobile_plugin::<Res>("canViewFile", Req { uri })
1265                .map(|v| v.value)
1266                .map_err(Into::into)
1267        })
1268    }
1269
1270    /// Take persistent permission to access the file, directory and its descendants.  
1271    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
1272    /// 
1273    /// This works by just calling, without displaying any confirmation to the user.
1274    /// 
1275    /// 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)  
1276    /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
1277    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1278    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
1279    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1280    /// 
1281    /// # Args
1282    /// - **uri** :  
1283    /// URI of the target file or directory. This must be a URI taken from following :  
1284    ///     - [`AndroidFs::show_open_file_dialog`]
1285    ///     - [`AndroidFs::show_open_visual_media_dialog`]
1286    ///     - [`AndroidFs::show_save_file_dialog`]
1287    ///     - [`AndroidFs::show_manage_dir_dialog`]  
1288    ///     - [`AndroidFs::read_dir`] :  
1289    ///         If this, the permissions of the origin directory URI is persisted, not a entry iteself. 
1290    ///         Because the permissions and validity period of the entry URIs depend on the origin directory.
1291    /// 
1292    /// # Support
1293    /// All. 
1294    pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1295        on_android!({
1296            impl_se!(struct Req<'a> { uri: &'a FileUri });
1297            impl_de!(struct Res;);
1298
1299            self.api
1300                .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1301                .map(|_| ())
1302                .map_err(Into::into)
1303        })
1304    }
1305
1306    /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].  
1307    /// Returns false if there are only non-persistent permissions or no permissions.
1308    /// 
1309    /// # Args
1310    /// - **uri** :  
1311    /// URI of the target file or directory.  
1312    /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself. 
1313    /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1314    ///
1315    /// - **mode** :  
1316    /// The mode of permission you want to check.  
1317    /// 
1318    /// # Support
1319    /// All.
1320    pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1321        on_android!({
1322            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1323            impl_de!(struct Res { value: bool });
1324
1325            self.api
1326                .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1327                .map(|v| v.value)
1328                .map_err(Into::into)
1329        })
1330    }
1331
1332    /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.   
1333    /// 
1334    /// # Support
1335    /// All.
1336    pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1337        on_android!(std::iter::Empty::<_>, {
1338            impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1339            impl_de!(struct Res { items: Vec<Obj> });
1340    
1341            self.api
1342                .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1343                .map(|v| v.items.into_iter())
1344                .map(|v| v.map(|v| {
1345                    let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1346                    match v.d {
1347                        true => PersistedUriPermission::Dir { uri, can_read, can_write },
1348                        false => PersistedUriPermission::File { uri, can_read, can_write }
1349                    }
1350                }))
1351                .map_err(Into::into)
1352        })
1353    }
1354
1355    /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
1356    /// 
1357    /// # Args
1358    /// - ***uri*** :  
1359    /// URI of the target file or directory.  
1360    ///
1361    /// # Support
1362    /// All.
1363    pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1364        on_android!({
1365            impl_se!(struct Req<'a> { uri: &'a FileUri });
1366            impl_de!(struct Res;);
1367
1368            self.api
1369                .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1370                .map(|_| ())
1371                .map_err(Into::into)
1372        })
1373    }
1374
1375    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
1376    /// 
1377    /// # Support
1378    /// All.
1379    pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1380        on_android!({
1381            impl_de!(struct Res);
1382
1383            self.api
1384                .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1385                .map(|_| ())
1386                .map_err(Into::into)
1387        })
1388    }
1389
1390    /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
1391    /// 
1392    /// # Support
1393    /// All.
1394    pub fn is_visual_media_dialog_available(&self) -> crate::Result<bool> {
1395        on_android!({
1396            impl_de!(struct Res { value: bool });
1397
1398            self.api
1399                .run_mobile_plugin::<Res>("isVisualMediaDialogAvailable", "")
1400                .map(|v| v.value)
1401                .map_err(Into::into)
1402        })
1403    }
1404
1405    /// File storage intended for the app's use only.
1406    pub fn private_storage(&self) -> PrivateStorage<'_, R> {
1407        PrivateStorage(self)
1408    }
1409
1410    /// File storage that is available to other applications and users.
1411    pub fn public_storage(&self) -> PublicStorage<'_, R> {
1412        PublicStorage(self)
1413    }
1414}