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    /// Please note that this has a different meaning from `std::fs::create` that open the file in write mod.
689    /// If you need it, use [`AndroidFs::open_file`] with [`FileAccessMode::WriteTrucncate`].
690    /// 
691    /// # Args  
692    /// - ***dir*** :  
693    /// The URI of the base directory.  
694    /// This needs to be **read-write**.
695    ///  
696    /// - ***relative_path*** :  
697    /// The file path relative to the base directory.  
698    /// Any missing subdirectories in the specified path will be created automatically.  
699    /// If a file with the same name already exists, 
700    /// the system append a sequential number to ensure uniqueness.  
701    /// If no extension is present, 
702    /// the system may infer one from ***mime_type*** and may append it to the file name. 
703    /// But this append-extension operation depends on the model and version.  
704    ///  
705    /// - ***mime_type*** :  
706    /// The MIME type of the file to be created.  
707    /// If this is None, MIME type is inferred from the extension of ***relative_path***
708    /// and if that fails, `application/octet-stream` is used.  
709    ///  
710    /// # Support
711    /// All.
712    pub fn create_file(
713        &self,
714        dir: &FileUri, 
715        relative_path: impl AsRef<str>, 
716        mime_type: Option<&str>
717    ) -> crate::Result<FileUri> {
718
719        on_android!({
720            impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
721        
722            let relative_path = relative_path.as_ref();
723
724            self.api
725                .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
726                .map_err(Into::into)
727        })
728    }
729
730    /// Recursively create a directory and all of its parent components if they are missing,
731    /// then return the URI.  
732    /// If it already exists, do nothing and just return the direcotry uri.
733    /// 
734    /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
735    /// 
736    /// # Args  
737    /// - ***dir*** :  
738    /// The URI of the base directory.  
739    /// This needs to be **read-write**.
740    ///  
741    /// - ***relative_path*** :  
742    /// The directory path relative to the base directory.    
743    ///  
744    /// # Support
745    /// All.
746    pub fn create_dir_all(
747        &self,
748        dir: &FileUri, 
749        relative_path: impl AsRef<str>, 
750    ) -> Result<FileUri> {
751
752        on_android!({
753            let relative_path = relative_path.as_ref().trim_matches('/');
754            if relative_path.is_empty() {
755                return Ok(dir.clone())
756            }
757
758            // TODO:
759            // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
760            let tmp_file_uri = self.create_file(
761                dir, 
762                format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"), 
763                Some("application/octet-stream")
764            )?;
765            let _ = self.remove_file(&tmp_file_uri);
766            let uri = self.resolve_uri(dir, relative_path)?;
767
768            Ok(uri)
769        })
770    }
771
772    /// Returns the child files and directories of the specified directory.  
773    /// The order of the entries is not guaranteed.  
774    /// 
775    /// The permissions and validity period of the returned URIs depend on the origin directory 
776    /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])  
777    /// 
778    /// # Args
779    /// - ***uri*** :  
780    /// Target directory URI.  
781    /// This needs to be **readable**.
782    ///  
783    /// # Note  
784    /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily. 
785    /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.  
786    /// 
787    /// # Support
788    /// All.
789    pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
790        on_android!(std::iter::Empty::<_>, {
791            impl_se!(struct Req<'a> { uri: &'a FileUri });
792            impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
793            impl_de!(struct Res { entries: Vec<Obj> });
794    
795            self.api
796                .run_mobile_plugin::<Res>("readDir", Req { uri })
797                .map(|v| v.entries.into_iter())
798                .map(|v| v.map(|v| match v.mime_type {
799                    Some(mime_type) => Entry::File {
800                        name: v.name,
801                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
802                        len: v.byte_size as u64,
803                        mime_type,
804                        uri: v.uri,
805                    },
806                    None => Entry::Dir {
807                        name: v.name,
808                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
809                        uri: v.uri,
810                    }
811                }))
812                .map_err(Into::into)
813        })
814    }
815
816    /// Opens a system file picker and returns a **read-write** URIs.  
817    /// If no file is selected or the user cancels, an empty vec is returned.  
818    /// 
819    /// By default, returned URI is valid until the app is terminated. 
820    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
821    /// 
822    /// This provides a standardized file explorer-style interface, 
823    /// and also allows file selection from part of third-party apps or cloud storage.
824    ///
825    /// Removing the returned files is also supported in most cases, 
826    /// but note that files provided by third-party apps may not be removable.  
827    ///  
828    /// # Args  
829    /// - ***initial_location*** :  
830    /// Indicate the initial location of dialog.  
831    /// There is no need to use this if there is no special reason.  
832    /// System will do its best to launch the dialog in the specified entry 
833    /// if it's a directory, or the directory that contains the specified file if not.  
834    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
835    /// This must be a URI taken from following :   
836    ///     - [`AndroidFs::resolve_initial_location`]
837    ///     - [`AndroidFs::show_open_file_dialog`]
838    ///     - [`AndroidFs::show_save_file_dialog`]
839    ///     - [`AndroidFs::show_manage_dir_dialog`]
840    ///     - [`AndroidFs::resolve_uri`]
841    ///     - [`AndroidFs::try_resolve_file_uri`]
842    ///     - [`AndroidFs::try_resolve_dir_uri`]
843    ///     - [`AndroidFs::read_dir`]
844    ///     - [`AndroidFs::create_file`]
845    /// 
846    /// - ***mime_types*** :  
847    /// The MIME types of the file to be selected.  
848    /// However, there is no guarantee that the returned file will match the specified types.  
849    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
850    ///  
851    /// - ***multiple*** :  
852    /// Indicates whether multiple file selection is allowed.  
853    /// 
854    /// # Support
855    /// All.
856    /// 
857    /// # References
858    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
859    pub fn show_open_file_dialog(
860        &self,
861        initial_location: Option<&FileUri>,
862        mime_types: &[&str],
863        multiple: bool,
864    ) -> crate::Result<Vec<FileUri>> {
865
866        on_android!({
867            impl_se!(struct Req<'a> { 
868                mime_types: &'a [&'a str],
869                multiple: bool,
870                initial_location: Option<&'a FileUri>
871            });
872            impl_de!(struct Res { uris: Vec<FileUri> });
873    
874            let _guard = self.intent_lock.lock();
875            self.api
876                .run_mobile_plugin::<Res>("showOpenFileDialog", Req { mime_types, multiple, initial_location })
877                .map(|v| v.uris)
878                .map_err(Into::into)
879        })
880    }
881
882    /// Opens a file picker and returns a **readonly** URIs.  
883    /// If no file is selected or the user cancels, an empty vec is returned.  
884    ///  
885    /// Returned URI is valid until the app is terminated. Can not persist it.
886    /// 
887    /// This works differently depending on the model and version.  
888    /// But recent devices often have the similar behaviour as [`AndroidFs::show_open_visual_media_dialog`] or [`AndroidFs::show_open_file_dialog`].  
889    /// Use this, if you want your app to simply read/import data.
890    /// 
891    /// # Args  
892    /// - ***mime_types*** :  
893    /// The MIME types of the file to be selected.  
894    /// However, there is no guarantee that the returned file will match the specified types.  
895    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
896    ///  
897    /// - ***multiple*** :  
898    /// Indicates whether multiple file selection is allowed.  
899    /// 
900    /// # Support
901    /// All.
902    /// 
903    /// # References
904    /// <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
905    pub fn show_open_content_dialog(
906        &self,
907        mime_types: &[&str],
908        multiple: bool
909    ) -> crate::Result<Vec<FileUri>> {
910
911        on_android!({
912            impl_se!(struct Req<'a> { mime_types: &'a [&'a str], multiple: bool });
913            impl_de!(struct Res { uris: Vec<FileUri> });
914
915            let _guard = self.intent_lock.lock();
916            self.api
917                .run_mobile_plugin::<Res>("showOpenContentDialog", Req { mime_types, multiple })
918                .map(|v| v.uris)
919                .map_err(Into::into)
920        })
921    }
922
923    /// Opens a media picker and returns a **readonly** URIs.  
924    /// If no file is selected or the user cancels, an empty vec is returned.  
925    ///  
926    /// By default, returned URI is valid until the app is terminated. 
927    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
928    ///  
929    /// This media picker provides a browsable interface that presents the user with their media library, 
930    /// sorted by date from newest to oldest. 
931    /// 
932    /// # Args  
933    /// - ***target*** :  
934    /// The media type of the file to be selected.  
935    /// Images or videos, or both.  
936    ///  
937    /// - ***multiple*** :  
938    /// Indicates whether multiple file selection is allowed.  
939    ///  
940    /// # Note
941    /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].  
942    /// Instead, it will be assigned a sequential number, such as `1000091523.png`. 
943    /// And this is marked intended behavior, not a bug.
944    /// - <https://issuetracker.google.com/issues/268079113>  
945    ///  
946    /// # Support
947    /// This feature is available on devices that meet the following criteria:  
948    /// - Running Android 11 (API level 30) or higher  
949    /// - Receive changes to Modular System Components through Google System Updates  
950    ///  
951    /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].  
952    /// If not supported, this function behaves the same as [`AndroidFs::show_open_file_dialog`].  
953    /// 
954    /// # References
955    /// <https://developer.android.com/training/data-storage/shared/photopicker>
956    pub fn show_open_visual_media_dialog(
957        &self,
958        target: VisualMediaTarget,
959        multiple: bool,
960    ) -> crate::Result<Vec<FileUri>> {
961
962        on_android!({
963            impl_se!(struct Req { multiple: bool, target: VisualMediaTarget });
964            impl_de!(struct Res { uris: Vec<FileUri> });
965    
966            let _guard = self.intent_lock.lock();
967            self.api
968                .run_mobile_plugin::<Res>("showOpenVisualMediaDialog", Req { multiple, target })
969                .map(|v| v.uris)
970                .map_err(Into::into)
971        })
972    }
973
974    /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one, 
975    /// and returns a **read-write** directory URI. 
976    /// App can fully manage entries within the returned directory.  
977    /// If no directory is selected or the user cancels, `None` is returned. 
978    /// 
979    /// By default, returned URI is valid until the app is terminated. 
980    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
981    /// 
982    /// This provides a standardized file explorer-style interface,
983    /// and also allows file selection from part of third-party apps or cloud storage.
984    /// 
985    /// # Args  
986    /// - ***initial_location*** :  
987    /// Indicate the initial location of dialog.    
988    /// There is no need to use this if there is no special reason.  
989    /// System will do its best to launch the dialog in the specified entry 
990    /// if it's a directory, or the directory that contains the specified file if not.  
991    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.   
992    /// This must be a URI taken from following :   
993    ///     - [`AndroidFs::resolve_initial_location`]
994    ///     - [`AndroidFs::show_open_file_dialog`]
995    ///     - [`AndroidFs::show_save_file_dialog`]
996    ///     - [`AndroidFs::show_manage_dir_dialog`]
997    ///     - [`AndroidFs::resolve_uri`]
998    ///     - [`AndroidFs::try_resolve_file_uri`]
999    ///     - [`AndroidFs::try_resolve_dir_uri`]
1000    ///     - [`AndroidFs::read_dir`]
1001    ///     - [`AndroidFs::create_file`]
1002    /// 
1003    /// # Support
1004    /// All.
1005    /// 
1006    /// # References
1007    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
1008    pub fn show_manage_dir_dialog(
1009        &self,
1010        initial_location: Option<&FileUri>,
1011    ) -> crate::Result<Option<FileUri>> {
1012
1013        on_android!({
1014            impl_se!(struct Req<'a> { initial_location: Option<&'a FileUri> });
1015            impl_de!(struct Res { uri: Option<FileUri> });
1016
1017            let _guard = self.intent_lock.lock();
1018            self.api
1019                .run_mobile_plugin::<Res>("showManageDirDialog", Req { initial_location })
1020                .map(|v| v.uri)
1021                .map_err(Into::into)
1022        })
1023    }
1024
1025    /// Please use [`AndroidFs::show_manage_dir_dialog`] instead.
1026    #[deprecated = "Confusing name. Please use show_manage_dir_dialog instead."]
1027    #[warn(deprecated)]
1028    pub fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
1029        on_android!({
1030            self.show_manage_dir_dialog(None)
1031        })
1032    }
1033
1034    /// Opens a dialog to save a file and returns a **writeonly** URI.  
1035    /// The returned file may be a newly created file with no content,
1036    /// or it may be an existing file with the requested MIME type.  
1037    /// If the user cancels, `None` is returned. 
1038    /// 
1039    /// By default, returned URI is valid until the app is terminated. 
1040    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
1041    /// 
1042    /// This provides a standardized file explorer-style interface, 
1043    /// and also allows file selection from part of third-party apps or cloud storage.
1044    /// 
1045    /// Removing and reading the returned files is also supported in most cases, 
1046    /// but note that files provided by third-party apps may not.  
1047    ///  
1048    /// # Args  
1049    /// - ***initial_location*** :  
1050    /// Indicate the initial location of dialog.  
1051    /// There is no need to use this if there is no special reason.  
1052    /// System will do its best to launch the dialog in the specified entry 
1053    /// if it's a directory, or the directory that contains the specified file if not.  
1054    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
1055    /// This must be a URI taken from following :   
1056    ///     - [`AndroidFs::resolve_initial_location`]
1057    ///     - [`AndroidFs::show_open_file_dialog`]
1058    ///     - [`AndroidFs::show_save_file_dialog`]
1059    ///     - [`AndroidFs::show_manage_dir_dialog`]
1060    ///     - [`AndroidFs::resolve_uri`]
1061    ///     - [`AndroidFs::try_resolve_file_uri`]
1062    ///     - [`AndroidFs::try_resolve_dir_uri`]
1063    ///     - [`AndroidFs::read_dir`]
1064    ///     - [`AndroidFs::create_file`]
1065    /// 
1066    /// - ***initial_file_name*** :  
1067    /// An initial file name.  
1068    /// The user may change this value before creating the file.  
1069    /// If no extension is present, 
1070    /// the system may infer one from ***mime_type*** and may append it to the file name. 
1071    /// But this append-extension operation depends on the model and version.
1072    /// 
1073    /// - ***mime_type*** :  
1074    /// The MIME type of the file to be saved.  
1075    /// If this is None, MIME type is inferred from the extension of ***initial_file_name*** (not file name by user input)
1076    /// and if that fails, `application/octet-stream` is used.  
1077    ///  
1078    /// # Support
1079    /// All.
1080    /// 
1081    /// # References
1082    /// <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
1083    pub fn show_save_file_dialog(
1084        &self,
1085        initial_location: Option<&FileUri>,
1086        initial_file_name: impl AsRef<str>,
1087        mime_type: Option<&str>,
1088    ) -> crate::Result<Option<FileUri>> {
1089
1090        on_android!({
1091            impl_se!(struct Req<'a> {
1092                initial_file_name: &'a str, 
1093                mime_type: Option<&'a str>, 
1094                initial_location: Option<&'a FileUri> 
1095            });
1096            impl_de!(struct Res { uri: Option<FileUri> });
1097    
1098            let initial_file_name = initial_file_name.as_ref();
1099        
1100            let _guard = self.intent_lock.lock();
1101            self.api
1102                .run_mobile_plugin::<Res>("showSaveFileDialog", Req { initial_file_name, mime_type, initial_location })
1103                .map(|v| v.uri)
1104                .map_err(Into::into)
1105        })
1106    }
1107
1108    /// Create an **restricted** URI for the specified directory.  
1109    /// This should only be used as `initial_location` in the dialog. 
1110    /// It must not be used for any other purpose.  
1111    /// 
1112    /// This is useful when selecting (creating) new files and folders, 
1113    /// but when selecting existing entries, `initial_location` is often better with None.
1114    /// 
1115    /// Note this is an informal method and is not guaranteed to work reliably.
1116    /// But this URI does not cause the dialog to error.  
1117    /// So please use this with the mindset that it's better than doing nothing.  
1118    /// 
1119    /// # Examples
1120    /// ```
1121    /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
1122    ///
1123    /// fn sample(app: tauri::AppHandle) {
1124    ///     let api = app.android_fs();
1125    ///
1126    ///     // Get URI of the top public directory in primary volume
1127    ///     let initial_location = api.resolve_initial_location(
1128    ///         InitialLocation::TopPublicDir,
1129    ///         false,
1130    ///     ).expect("Should be on Android");
1131    ///
1132    ///     // Get URI of ~/Pictures/
1133    ///     let initial_location = api.resolve_initial_location(
1134    ///         PublicImageDir::Pictures,
1135    ///         false
1136    ///     ).expect("Should be on Android");
1137    ///
1138    ///     // Get URI of ~/Documents/sub_dir1/sub_dir2/
1139    ///     let initial_location = api.resolve_initial_location(
1140    ///         InitialLocation::DirInPublicDir {
1141    ///             base_dir: PublicGeneralPurposeDir::Documents.into(),
1142    ///             relative_path: "sub_dir1/sub_dir2"
1143    ///         },
1144    ///         true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
1145    ///     ).expect("Should be on Android");
1146    ///
1147    ///     // Open dialog with initial_location
1148    ///     let _ = api.show_save_file_dialog(Some(&initial_location), "", None);
1149    ///     let _ = api.show_open_file_dialog(Some(&initial_location), &[], true);
1150    ///     let _ = api.show_manage_dir_dialog(Some(&initial_location));
1151    /// }
1152    /// ```
1153    /// 
1154    /// # Support
1155    /// All.
1156    pub fn resolve_initial_location<'a>(
1157        &self,
1158        dir: impl Into<InitialLocation<'a>>,
1159        create_dirs: bool
1160    ) -> crate::Result<FileUri> {
1161
1162        on_android!({
1163            const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary";
1164
1165            let uri = match dir.into() {
1166                InitialLocation::TopPublicDir => format!("{TOP_DIR}%3A"),
1167                InitialLocation::PublicDir(dir) => format!("{TOP_DIR}%3A{dir}"),
1168                InitialLocation::DirInPublicDir { base_dir, relative_path } => {
1169                    let relative_path = relative_path.trim_matches('/');
1170
1171                    if relative_path.is_empty() {
1172                        format!("{TOP_DIR}%3A{base_dir}")
1173                    }
1174                    else {
1175                        if create_dirs {
1176                            let _ = self.public_storage().create_dir_all(base_dir, relative_path);
1177                        }
1178                        let sub_dirs = encode_document_id(relative_path);
1179                        format!("{TOP_DIR}%3A{base_dir}%2F{sub_dirs}")
1180                    }
1181                },
1182                InitialLocation::DirInPublicAppDir { base_dir, relative_path } => {
1183                    let relative_path = &format!(
1184                        "{}/{}", 
1185                        self.public_storage().app_dir_name()?,
1186                        relative_path.trim_matches('/'),
1187                    );
1188                  
1189                    return self.resolve_initial_location(
1190                        InitialLocation::DirInPublicDir { base_dir, relative_path }, 
1191                        create_dirs
1192                    )
1193                }
1194            };
1195
1196            Ok(FileUri { uri, document_top_tree_uri: None })
1197        })
1198    }
1199
1200    /// Opens a dialog for sharing file to other apps.  
1201    /// 
1202    /// An error will occur if there is no app that can handle the request. 
1203    /// Please use [`AndroidFs::can_share_file`] to confirm.
1204    /// 
1205    /// # Args
1206    /// - **uri** :  
1207    /// Target file uri to share.  
1208    /// This needs to be **readable**.  
1209    /// Files in [`PrivateStorage`] ***cannot*** be used.
1210    /// 
1211    /// # Support
1212    /// All.
1213    pub fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1214        on_android!({
1215            impl_se!(struct Req<'a> { uri: &'a FileUri });
1216            impl_de!(struct Res;);
1217
1218            self.api
1219                .run_mobile_plugin::<Res>("shareFile", Req { uri })
1220                .map(|_| ())
1221                .map_err(Into::into)
1222        })
1223    }
1224
1225    /// Opens a dialog for viewing file on other apps.  
1226    /// This performs the general "open file" action.
1227    /// 
1228    /// An error will occur if there is no app that can handle the request. 
1229    /// Please use [`AndroidFs::can_view_file`] to confirm.
1230    /// 
1231    /// # Args
1232    /// - **uri** :  
1233    /// Target file uri to view.  
1234    /// This needs to be **readable**.  
1235    /// Files in [`PrivateStorage`] ***cannot*** be used.
1236    /// 
1237    /// # Support
1238    /// All.
1239    pub fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1240        on_android!({
1241            impl_se!(struct Req<'a> { uri: &'a FileUri });
1242            impl_de!(struct Res;);
1243    
1244            self.api
1245                .run_mobile_plugin::<Res>("viewFile", Req { uri })
1246                .map(|_| ())
1247                .map_err(Into::into)
1248        })
1249    }
1250
1251    /// Determines whether the specified file can be used with [`AndroidFs::show_share_file_dialog`].
1252    /// # Args
1253    /// - **uri** :  
1254    /// Target file uri.  
1255    /// This needs to be **readable**.
1256    /// 
1257    /// # Support
1258    /// All.
1259    pub fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool> {
1260        on_android!({
1261            impl_se!(struct Req<'a> { uri: &'a FileUri });
1262            impl_de!(struct Res { value: bool });
1263
1264            self.api
1265                .run_mobile_plugin::<Res>("canShareFile", Req { uri })
1266                .map(|v| v.value)
1267                .map_err(Into::into)
1268        })
1269    }
1270
1271    /// Determines whether the specified file can be used with [`AndroidFs::show_view_file_dialog`].
1272    /// 
1273    /// # Args
1274    /// - **uri** :  
1275    /// Target file uri.  
1276    /// This needs to be **readable**.
1277    /// 
1278    /// # Support
1279    /// All.
1280    pub fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool> {
1281        on_android!({
1282            impl_se!(struct Req<'a> { uri: &'a FileUri });
1283            impl_de!(struct Res { value: bool });
1284
1285            self.api
1286                .run_mobile_plugin::<Res>("canViewFile", Req { uri })
1287                .map(|v| v.value)
1288                .map_err(Into::into)
1289        })
1290    }
1291
1292    /// Take persistent permission to access the file, directory and its descendants.  
1293    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
1294    /// 
1295    /// This works by just calling, without displaying any confirmation to the user.
1296    /// 
1297    /// 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)  
1298    /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
1299    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1300    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
1301    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1302    /// 
1303    /// # Args
1304    /// - **uri** :  
1305    /// URI of the target file or directory. This must be a URI taken from following :  
1306    ///     - [`AndroidFs::show_open_file_dialog`]
1307    ///     - [`AndroidFs::show_open_visual_media_dialog`]
1308    ///     - [`AndroidFs::show_save_file_dialog`]
1309    ///     - [`AndroidFs::show_manage_dir_dialog`]  
1310    ///     - [`AndroidFs::read_dir`] :  
1311    ///         If this, the permissions of the origin directory URI is persisted, not a entry iteself. 
1312    ///         Because the permissions and validity period of the entry URIs depend on the origin directory.
1313    /// 
1314    /// # Support
1315    /// All. 
1316    pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1317        on_android!({
1318            impl_se!(struct Req<'a> { uri: &'a FileUri });
1319            impl_de!(struct Res;);
1320
1321            self.api
1322                .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1323                .map(|_| ())
1324                .map_err(Into::into)
1325        })
1326    }
1327
1328    /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].  
1329    /// Returns false if there are only non-persistent permissions or no permissions.
1330    /// 
1331    /// # Args
1332    /// - **uri** :  
1333    /// URI of the target file or directory.  
1334    /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself. 
1335    /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1336    ///
1337    /// - **mode** :  
1338    /// The mode of permission you want to check.  
1339    /// 
1340    /// # Support
1341    /// All.
1342    pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1343        on_android!({
1344            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1345            impl_de!(struct Res { value: bool });
1346
1347            self.api
1348                .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1349                .map(|v| v.value)
1350                .map_err(Into::into)
1351        })
1352    }
1353
1354    /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.   
1355    /// 
1356    /// # Support
1357    /// All.
1358    pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1359        on_android!(std::iter::Empty::<_>, {
1360            impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1361            impl_de!(struct Res { items: Vec<Obj> });
1362    
1363            self.api
1364                .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1365                .map(|v| v.items.into_iter())
1366                .map(|v| v.map(|v| {
1367                    let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1368                    match v.d {
1369                        true => PersistedUriPermission::Dir { uri, can_read, can_write },
1370                        false => PersistedUriPermission::File { uri, can_read, can_write }
1371                    }
1372                }))
1373                .map_err(Into::into)
1374        })
1375    }
1376
1377    /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
1378    /// 
1379    /// # Args
1380    /// - ***uri*** :  
1381    /// URI of the target file or directory.  
1382    ///
1383    /// # Support
1384    /// All.
1385    pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1386        on_android!({
1387            impl_se!(struct Req<'a> { uri: &'a FileUri });
1388            impl_de!(struct Res;);
1389
1390            self.api
1391                .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1392                .map(|_| ())
1393                .map_err(Into::into)
1394        })
1395    }
1396
1397    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
1398    /// 
1399    /// # Support
1400    /// All.
1401    pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1402        on_android!({
1403            impl_de!(struct Res);
1404
1405            self.api
1406                .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1407                .map(|_| ())
1408                .map_err(Into::into)
1409        })
1410    }
1411
1412    /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
1413    /// 
1414    /// # Support
1415    /// All.
1416    pub fn is_visual_media_dialog_available(&self) -> crate::Result<bool> {
1417        on_android!({
1418            impl_de!(struct Res { value: bool });
1419
1420            self.api
1421                .run_mobile_plugin::<Res>("isVisualMediaDialogAvailable", "")
1422                .map(|v| v.value)
1423                .map_err(Into::into)
1424        })
1425    }
1426
1427    /// File storage intended for the app's use only.
1428    pub fn private_storage(&self) -> PrivateStorage<'_, R> {
1429        PrivateStorage(self)
1430    }
1431
1432    /// File storage that is available to other applications and users.
1433    pub fn public_storage(&self) -> PublicStorage<'_, R> {
1434        PublicStorage(self)
1435    }
1436}