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    /// API of file storage intended for the app's use only.
61    pub fn private_storage(&self) -> PrivateStorage<'_, R> {
62        PrivateStorage(self)
63    }
64
65    /// API of file storage that is available to other applications and users.
66    pub fn public_storage(&self) -> PublicStorage<'_, R> {
67        PublicStorage(self)
68    }
69
70    /// API of file/dir picker.
71    pub fn file_picker(&self) -> FilePicker<'_, R> {
72        FilePicker(self)
73    }
74
75    /// API of sharing files with other apps.
76    pub fn file_sender(&self) -> FileSender<'_, R> {
77        FileSender(self)
78    }
79
80    /// Get the file or directory name.  
81    /// 
82    /// # Args
83    /// - ***uri*** :  
84    /// Target URI.  
85    /// This needs to be **readable**.
86    /// 
87    /// # Support
88    /// All.
89    pub fn get_name(&self, uri: &FileUri) -> crate::Result<String> {
90        on_android!({
91            impl_se!(struct Req<'a> { uri: &'a FileUri });
92            impl_de!(struct Res { name: String });
93
94            self.api
95                .run_mobile_plugin::<Res>("getName", Req { uri })
96                .map(|v| v.name)
97                .map_err(Into::into)
98        })
99    }
100
101    /// Query the provider to get mime type.  
102    /// If the directory, this returns `None`.  
103    /// If the file, this returns no `None`.  
104    /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.  
105    ///
106    /// In the case of files in [`PrivateStorage`], this is determined from the extension.
107    /// 
108    /// # Args
109    /// - ***uri*** :  
110    /// Target URI.  
111    /// This needs to be **readable**.
112    /// 
113    /// # Support
114    /// All.
115    pub fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>> {
116        on_android!({
117            impl_se!(struct Req<'a> { uri: &'a FileUri });
118            impl_de!(struct Res { value: Option<String> });
119
120            self.api
121                .run_mobile_plugin::<Res>("getMimeType", Req { uri })
122                .map(|v| v.value)
123                .map_err(Into::into)
124        })
125    }
126
127    /// Queries the file system to get information about a file, directory.
128    /// 
129    /// # Args
130    /// - ***uri*** :  
131    /// Target URI.  
132    /// This needs to be **readable**.
133    /// 
134    /// # Note
135    /// This uses [`AndroidFs::open_file`] internally.
136    /// 
137    /// # Support
138    /// All.
139    pub fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
140        on_android!({
141            let file = self.open_file(uri, FileAccessMode::Read)?;
142            Ok(file.metadata()?)
143        })
144    }
145
146    /// Open a file in the specified mode.
147    /// 
148    /// # Args
149    /// - ***uri*** :  
150    /// Target file URI.  
151    /// This must have corresponding permissions (read, write, or both) for the specified **mode**.
152    /// 
153    /// - ***mode*** :  
154    /// Indicates how the file is opened and the permissions granted.  
155    /// Note that files hosted by third-party apps may not support following:
156    ///     - [`FileAccessMode::ReadWrite`]
157    ///     - [`FileAccessMode::ReadWriteTruncate`]  
158    ///     - [`FileAccessMode::WriteAppend`]  
159    /// (ex: Files on GoogleDrive)  
160    ///
161    /// # Note
162    /// This method uses a FileDescriptor internally. 
163    /// However, if the target file does not physically exist on the device, such as cloud-based files, 
164    /// the write operation using a FileDescriptor may not be reflected properly.
165    /// In such cases, consider using [AndroidFs::write_via_kotlin], 
166    /// which writes using a standard method, 
167    /// or [AndroidFs::write], which automatically falls back to that approach when necessary.
168    /// 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.  
169    /// 
170    /// It seems that the issue does not occur on all cloud storage platforms. At least, files on Google Drive have issues, 
171    /// but files on Dropbox can be written to correctly using a FileDescriptor.
172    /// 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). 
173    /// This information will be used in [AndroidFs::need_write_via_kotlin] used by `AndroidFs::write`.  
174    /// 
175    /// There are no problems with file reading.
176    /// 
177    /// # Support
178    /// All.
179    pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
180        on_android!({
181            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
182            impl_de!(struct Res { fd: std::os::fd::RawFd });
183    
184            let mode = match mode {
185                FileAccessMode::Read => "r",
186                FileAccessMode::Write => "w",
187                FileAccessMode::WriteTruncate => "wt",
188                FileAccessMode::WriteAppend => "wa",
189                FileAccessMode::ReadWriteTruncate => "rwt",
190                FileAccessMode::ReadWrite => "rw",
191            };
192
193            self.api
194                .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
195                .map(|v| {
196                    use std::os::fd::FromRawFd;
197                    unsafe { std::fs::File::from_raw_fd(v.fd) }
198                })
199                .map_err(Into::into)
200        })
201    }
202
203    /// Reads the entire contents of a file into a bytes vector.  
204    /// 
205    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
206    /// 
207    /// # Args
208    /// - ***uri*** :  
209    /// Target file URI.    
210    /// This needs to be **readable**.
211    /// 
212    /// # Support
213    /// All.
214    pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
215        on_android!({
216            let mut file = self.open_file(uri, FileAccessMode::Read)?;
217            let mut buf = file.metadata().ok()
218                .map(|m| m.len() as usize)
219                .map(Vec::with_capacity)
220                .unwrap_or_else(Vec::new);
221
222            file.read_to_end(&mut buf)?;
223            Ok(buf)
224        })
225    }
226
227    /// Reads the entire contents of a file into a string.  
228    /// 
229    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
230    /// 
231    /// # Args
232    /// - ***uri*** :  
233    /// Target file URI.  
234    /// This needs to be **readable**.
235    /// 
236    /// # Support
237    /// All.
238    pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
239        on_android!({
240            let mut file = self.open_file(uri, FileAccessMode::Read)?;
241            let mut buf = file.metadata().ok()
242                .map(|m| m.len() as usize)
243                .map(String::with_capacity)
244                .unwrap_or_else(String::new);
245    
246            file.read_to_string(&mut buf)?;
247            Ok(buf)
248        })
249    }
250
251    /// Writes a slice as the entire contents of a file.  
252    /// This function will entirely replace its contents if it does exist.    
253    /// 
254    /// If you want to operate the file, use [`AndroidFs::open_file`] instead.  
255    /// 
256    /// # Args
257    /// - ***uri*** :  
258    /// Target file URI.  
259    /// This needs to be **writable**.
260    /// 
261    /// # Support
262    /// All.
263    pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
264        on_android!({
265            if self.need_write_via_kotlin(uri)? {
266                self.write_via_kotlin(uri, contents)?;
267            }
268            else {
269                let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
270                file.write_all(contents.as_ref())?;
271            }
272            Ok(())
273        })
274    }
275
276    /// Writes a slice as the entire contents of a file.  
277    /// This function will entirely replace its contents if it does exist.    
278    /// 
279    /// Differences from `std::fs::File::write_all` is the process is done on Kotlin side.  
280    /// See [`AndroidFs::open_file`] for why this function exists.
281    /// 
282    /// If [`AndroidFs::write`] is used, it automatically fall back to this by [`AndroidFs::need_write_via_kotlin`], 
283    /// so there should be few opportunities to use this.
284    /// 
285    /// If you want to write using `std::fs::File`, not entire contents, use [`AndroidFs::write_via_kotlin_in`].
286    /// 
287    /// # Inner process
288    /// The contents is written to a temporary file by Rust side 
289    /// and then copied to the specified file on Kotlin side by [`AndroidFs::copy_via_kotlin`].  
290    /// 
291    /// # Support
292    /// All.
293    pub fn write_via_kotlin(
294        &self, 
295        uri: &FileUri,
296        contents: impl AsRef<[u8]>
297    ) -> crate::Result<()> {
298
299        on_android!({
300            self.write_via_kotlin_in(uri, |file| file.write_all(contents.as_ref()))
301        })
302    }
303
304    /// See [`AndroidFs::write_via_kotlin`] for information.  
305    /// Use this if you want to write using `std::fs::File`, not entire contents.
306    /// 
307    /// If you want to retain the file outside the closure, 
308    /// you can perform the same operation using [`AndroidFs::copy_via_kotlin`] and [`PrivateStorage`]. 
309    /// For details, please refer to the internal implementation of this function.
310    /// 
311    /// # Args
312    /// - ***uri*** :  
313    /// Target file URI to write.
314    /// 
315    /// - **contetns_writer** :  
316    /// A closure that accepts a mutable reference to a `std::fs::File`
317    /// and performs the actual write operations. Note that this represents a temporary file.
318    pub fn write_via_kotlin_in<T>(
319        &self, 
320        uri: &FileUri,
321        contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
322    ) -> crate::Result<T> {
323
324        on_android!({
325            let tmp_file_path = {
326                use std::sync::atomic::{AtomicUsize, Ordering};
327
328                static COUNTER: AtomicUsize = AtomicUsize::new(0);
329                let id = COUNTER.fetch_add(1, Ordering::Relaxed);
330
331                self.private_storage().resolve_path_with(
332                    PrivateDir::Cache,
333                    format!("{TMP_DIR_RELATIVE_PATH}/write_via_kotlin_in {id}")
334                )?
335            };
336
337            if let Some(parent) = tmp_file_path.parent() {
338                let _ = std::fs::create_dir_all(parent);
339            }
340
341            let result = {
342                let ref mut file = std::fs::File::create(&tmp_file_path)?;
343                contents_writer(file)
344            };
345
346            let result = result
347                .map_err(crate::Error::from)
348                .and_then(|t| self.copy_via_kotlin(&(&tmp_file_path).into(), uri).map(|_| t));
349
350            let _ = std::fs::remove_file(&tmp_file_path);
351
352            result
353        })
354    }
355
356    /// Determines if the file needs to be written via Kotlin side instead of Rust side.  
357    /// Currently, this returns true only if the file is on GoogleDrive.  
358    /// 
359    /// # Support
360    /// All.
361    pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
362        on_android!({
363            Ok(uri.uri.starts_with("content://com.google.android.apps.docs.storage"))
364        })
365    }
366
367    /// Copies the contents of src file to dest.  
368    /// If dest already has contents, it is truncated before write src contents.  
369    /// 
370    /// This copy process is done on Kotlin side, not on Rust.  
371    /// Large files in GB units are also supported.  
372    /// Note that [`AndroidFs::copy`] and [`std::io::copy`] are faster.  
373    ///  
374    /// See [`AndroidFs::write_via_kotlin`] for why this function exists.
375    /// 
376    /// # Args
377    /// - ***src*** :  
378    /// The URI of source file.   
379    /// This needs to be **readable**.
380    /// 
381    /// - ***dest*** :  
382    /// The URI of destination file.  
383    /// This needs to be **writable**.
384    /// 
385    /// # Support
386    /// All.
387    pub fn copy_via_kotlin(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
388        on_android!({
389            impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri });
390            impl_de!(struct Res;);
391
392            self.api
393                .run_mobile_plugin::<Res>("copyFile", Req { src, dest })
394                .map(|_| ())
395                .map_err(Into::into)
396        })
397    }
398
399    /// Copies the contents of src file to dest.  
400    /// If dest already has contents, it is truncated before write src contents.  
401    /// 
402    /// # Args
403    /// - ***src*** :  
404    /// The URI of source file.   
405    /// This needs to be **readable**.
406    /// 
407    /// - ***dest*** :  
408    /// The URI of destination file.  
409    /// This needs to be **writable**.
410    /// 
411    /// # Support
412    /// All.
413    pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
414        on_android!({
415            let src = &mut self.open_file(src, FileAccessMode::Read)?;
416            let dest = &mut self.open_file(dest, FileAccessMode::WriteTruncate)?;
417            std::io::copy(src, dest)?;
418            Ok(())
419        })
420    }
421
422    /// Renames a file or directory to a new name, and return new URI.  
423    /// Even if the names conflict, the existing file will not be overwritten.  
424    /// 
425    /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
426    /// In other words, this function returns a new URI without any permissions.
427    /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
428    /// In this, this function returns the same URI as original URI.
429    ///
430    /// # Args
431    /// - ***uri*** :  
432    /// URI of target entry.  
433    /// 
434    /// - ***new_name*** :  
435    /// New name of target entry. 
436    /// This include extension if use.  
437    /// The behaviour in the same name already exists depends on the file provider.  
438    /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.  
439    /// In the case of files hosted by other applications, errors may occur.  
440    /// But at least, the existing file will not be overwritten.  
441    /// 
442    /// # Support
443    /// All.
444    pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
445        on_android!({
446            impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
447
448            let new_name = new_name.as_ref();
449
450            self.api
451                .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
452                .map_err(Into::into)
453        })
454    }
455
456    /// Remove the file.
457    /// 
458    /// # Args
459    /// - ***uri*** :  
460    /// Target file URI.  
461    /// This needs to be **writable**, at least. But even if it is, 
462    /// removing may not be possible in some cases. 
463    /// For details, refer to the documentation of the function that provided the URI.  
464    /// If not file, an error will occur.
465    /// 
466    /// # Support
467    /// All.
468    pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
469        on_android!({
470            impl_se!(struct Req<'a> { uri: &'a FileUri });
471            impl_de!(struct Res;);
472    
473            self.api
474                .run_mobile_plugin::<Res>("deleteFile", Req { uri })
475                .map(|_| ())
476                .map_err(Into::into)
477        })
478    }
479
480    /// Remove the **empty** directory.
481    /// 
482    /// # Args
483    /// - ***uri*** :  
484    /// Target directory URI.  
485    /// This needs to be **writable**.  
486    /// If not empty directory, an error will occur.
487    /// 
488    /// # Support
489    /// All.
490    pub fn remove_dir(&self, uri: &FileUri) -> crate::Result<()> {
491        on_android!({
492            impl_se!(struct Req<'a> { uri: &'a FileUri });
493            impl_de!(struct Res;);
494        
495            self.api
496                .run_mobile_plugin::<Res>("deleteEmptyDir", Req { uri })
497                .map(|_| ())
498                .map_err(Into::into)
499        })
500    }
501
502    /// Removes a directory and all its contents. Use carefully!
503    /// 
504    /// # Args
505    /// - ***uri*** :  
506    /// Target directory URI.  
507    /// This needs to be **writable**.  
508    /// If not directory, an error will occur.
509    /// 
510    /// # Support
511    /// All.
512    pub fn remove_dir_all(&self, uri: &FileUri) -> crate::Result<()> {
513        on_android!({
514            impl_se!(struct Req<'a> { uri: &'a FileUri });
515            impl_de!(struct Res;);
516        
517            self.api
518                .run_mobile_plugin::<Res>("deleteDirAll", Req { uri })
519                .map(|_| ())
520                .map_err(Into::into)
521        })
522    }
523
524    /// Build a URI of an **existing** file located at the relative path from the specified directory.   
525    /// Error occurs, if the file does not exist.  
526    /// 
527    /// The permissions and validity period of the returned URI depend on the origin directory 
528    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
529    /// 
530    /// # Support
531    /// All.
532    pub fn try_resolve_file_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
533        on_android!({
534            let uri = self.resolve_uri(dir, relative_path)?;            
535            if self.get_mime_type(&uri)?.is_none() {
536                return Err(crate::Error { msg: format!("This is a directory, not a file: {uri:?}").into() })
537            }
538            Ok(uri)
539        })
540    }
541
542    /// Build a URI of an **existing** directory located at the relative path from the specified directory.   
543    /// Error occurs, if the directory does not exist.  
544    /// 
545    /// The permissions and validity period of the returned URI depend on the origin directory 
546    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
547    /// 
548    /// # Support
549    /// All.
550    pub fn try_resolve_dir_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
551        on_android!({
552            let uri = self.resolve_uri(dir, relative_path)?;
553            if self.get_mime_type(&uri)?.is_some() {
554                return Err(crate::Error { msg: format!("This is a file, not a directory: {uri:?}").into() })
555            }
556            Ok(uri)
557        })
558    }
559
560    /// Build a URI of an entry located at the relative path from the specified directory.   
561    /// 
562    /// This function does not perform checks on the arguments or the returned URI.  
563    /// Even if the dir argument refers to a file, no error occurs (and no panic either).
564    /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.  
565    /// 
566    /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead. 
567    /// Or use this with [`AndroidFs::get_mime_type`].
568    /// 
569    /// The permissions and validity period of the returned URI depend on the origin directory 
570    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
571    /// 
572    /// # Performance
573    /// This operation is relatively fast 
574    /// because it does not call Kotlin API and only involves operating strings on Rust side.
575    /// 
576    /// # Support
577    /// All.
578    pub fn resolve_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
579        on_android!({
580            let base_dir = &dir.uri;
581            let relative_path = relative_path.as_ref().trim_matches('/');
582
583            if relative_path.is_empty() {
584                return Ok(dir.clone())
585            }
586
587            Ok(FileUri {
588                document_top_tree_uri: dir.document_top_tree_uri.clone(),
589                uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
590            })
591        })
592    }
593
594    /// See [`AndroidFs::get_thumbnail_to`] for descriptions.  
595    /// 
596    /// If thumbnail does not wrote to dest, return false.
597    pub fn get_thumbnail_to(
598        &self, 
599        src: &FileUri,
600        dest: &FileUri,
601        preferred_size: Size,
602        format: ImageFormat,
603    ) -> crate::Result<bool> {
604
605        on_android!({
606            impl_se!(struct Req<'a> {
607                src: &'a FileUri, 
608                dest: &'a FileUri,
609                format: &'a str,
610                quality: u8,
611                width: u32,
612                height: u32,
613            });
614            impl_de!(struct Res { value: bool });
615
616            let (quality, format) = match format {
617                ImageFormat::Png => (1.0, "Png"),
618                ImageFormat::Jpeg => (0.75, "Jpeg"),
619                ImageFormat::Webp => (0.7, "Webp"),
620                ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
621                ImageFormat::WebpWith { quality } => (quality, "Webp"),
622            };
623            let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
624            let Size { width, height } = preferred_size;
625        
626            self.api
627                .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
628                .map(|v| v.value)
629                .map_err(Into::into)
630        })
631    }
632
633    /// Query the provider to get a file thumbnail.  
634    /// If thumbnail does not exist it, return None.
635    /// 
636    /// Note this does not cache. Please do it in your part if need.  
637    /// 
638    /// # Args
639    /// - ***uri*** :  
640    /// Targe file uri.  
641    /// Thumbnail availablty depends on the file provider.  
642    /// In general, images and videos are available.  
643    /// For files in [`PrivateStorage`], 
644    /// the file type must match the filename extension.  
645    /// 
646    /// - ***preferred_size*** :  
647    /// Optimal thumbnail size desired.  
648    /// This may return a thumbnail of a different size, 
649    /// but never more than double the requested size. 
650    /// In any case, the aspect ratio is maintained.
651    /// 
652    /// - ***format*** :  
653    /// Thumbnail image format.  
654    /// [`ImageFormat::Jpeg`] is recommended. 
655    /// If you need transparency, use others.
656    /// 
657    /// # Support
658    /// All.
659    pub fn get_thumbnail(
660        &self,
661        uri: &FileUri,
662        preferred_size: Size,
663        format: ImageFormat,
664    ) -> crate::Result<Option<Vec<u8>>> {
665
666        on_android!({
667            let tmp_file_path = {
668                use std::sync::atomic::{AtomicUsize, Ordering};
669
670                static COUNTER: AtomicUsize = AtomicUsize::new(0);
671                let id = COUNTER.fetch_add(1, Ordering::Relaxed);
672
673                self.private_storage().resolve_path_with(
674                    PrivateDir::Cache,
675                    format!("{TMP_DIR_RELATIVE_PATH}/get_thumbnail {id}")
676                )?
677            };
678
679            if let Some(parent) = tmp_file_path.parent() {
680                let _ = std::fs::create_dir_all(parent);
681            }
682
683            std::fs::File::create(&tmp_file_path)?;
684
685            let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
686                .and_then(|ok| {
687                    if (ok) {
688                        std::fs::read(&tmp_file_path)
689                            .map(Some)
690                            .map_err(Into::into)
691                    }
692                    else {
693                        Ok(None)
694                    }
695                });
696
697            let _ = std::fs::remove_file(&tmp_file_path);
698
699            result
700        })
701    }
702
703    /// Creates a new empty file in the specified location and returns a URI.   
704    /// 
705    /// The permissions and validity period of the returned URIs depend on the origin directory 
706    /// (e.g., the top directory selected by [`FilePicker::pick_dir`]) 
707    ///  
708    /// Please note that this has a different meaning from `std::fs::create` that open the file in write mod.
709    /// If you need it, use [`AndroidFs::open_file`] with [`FileAccessMode::WriteTruncate`].
710    /// 
711    /// # Args  
712    /// - ***dir*** :  
713    /// The URI of the base directory.  
714    /// This needs to be **read-write**.
715    ///  
716    /// - ***relative_path*** :  
717    /// The file path relative to the base directory.  
718    /// Any missing subdirectories in the specified path will be created automatically.  
719    /// If a file with the same name already exists, 
720    /// the system append a sequential number to ensure uniqueness.  
721    /// If no extension is present, 
722    /// the system may infer one from ***mime_type*** and may append it to the file name. 
723    /// But this append-extension operation depends on the model and version.  
724    ///  
725    /// - ***mime_type*** :  
726    /// The MIME type of the file to be created.  
727    /// If this is None, MIME type is inferred from the extension of ***relative_path***
728    /// and if that fails, `application/octet-stream` is used.  
729    ///  
730    /// # Support
731    /// All.
732    pub fn create_file(
733        &self,
734        dir: &FileUri, 
735        relative_path: impl AsRef<str>, 
736        mime_type: Option<&str>
737    ) -> crate::Result<FileUri> {
738
739        on_android!({
740            impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
741        
742            let relative_path = relative_path.as_ref();
743
744            self.api
745                .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
746                .map_err(Into::into)
747        })
748    }
749
750    /// Recursively create a directory and all of its parent components if they are missing,
751    /// then return the URI.  
752    /// If it already exists, do nothing and just return the direcotry uri.
753    /// 
754    /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
755    /// 
756    /// # Args  
757    /// - ***dir*** :  
758    /// The URI of the base directory.  
759    /// This needs to be **read-write**.
760    ///  
761    /// - ***relative_path*** :  
762    /// The directory path relative to the base directory.    
763    ///  
764    /// # Support
765    /// All.
766    pub fn create_dir_all(
767        &self,
768        dir: &FileUri, 
769        relative_path: impl AsRef<str>, 
770    ) -> Result<FileUri> {
771
772        on_android!({
773            let relative_path = relative_path.as_ref().trim_matches('/');
774            if relative_path.is_empty() {
775                return Ok(dir.clone())
776            }
777
778            // TODO:
779            // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
780            let tmp_file_uri = self.create_file(
781                dir, 
782                format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"), 
783                Some("application/octet-stream")
784            )?;
785            let _ = self.remove_file(&tmp_file_uri);
786            let uri = self.resolve_uri(dir, relative_path)?;
787
788            Ok(uri)
789        })
790    }
791
792    /// Returns the child files and directories of the specified directory.  
793    /// The order of the entries is not guaranteed.  
794    /// 
795    /// The permissions and validity period of the returned URIs depend on the origin directory 
796    /// (e.g., the top directory selected by [`FilePicker::pick_dir`])  
797    /// 
798    /// # Args
799    /// - ***uri*** :  
800    /// Target directory URI.  
801    /// This needs to be **readable**.
802    ///  
803    /// # Note  
804    /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily. 
805    /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.  
806    /// 
807    /// # Support
808    /// All.
809    pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
810        on_android!(std::iter::Empty::<_>, {
811            impl_se!(struct Req<'a> { uri: &'a FileUri });
812            impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
813            impl_de!(struct Res { entries: Vec<Obj> });
814    
815            self.api
816                .run_mobile_plugin::<Res>("readDir", Req { uri })
817                .map(|v| v.entries.into_iter())
818                .map(|v| v.map(|v| match v.mime_type {
819                    Some(mime_type) => Entry::File {
820                        name: v.name,
821                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
822                        len: v.byte_size as u64,
823                        mime_type,
824                        uri: v.uri,
825                    },
826                    None => Entry::Dir {
827                        name: v.name,
828                        last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
829                        uri: v.uri,
830                    }
831                }))
832                .map_err(Into::into)
833        })
834    }
835
836    /// Use [`FilePicker::pick_files`] instead.
837    #[deprecated = "Use FilePicker::pick_files instead"]
838    pub fn show_open_file_dialog(
839        &self,
840        initial_location: Option<&FileUri>,
841        mime_types: &[&str],
842        multiple: bool,
843    ) -> crate::Result<Vec<FileUri>> {
844
845        self.file_picker().pick_files(initial_location, mime_types, multiple)
846    }
847
848    /// Use [`FilePicker::pick_contents`] instead.
849    #[deprecated = "Use FilePicker::pick_contents instead"]
850    pub fn show_open_content_dialog(
851        &self,
852        mime_types: &[&str],
853        multiple: bool
854    ) -> crate::Result<Vec<FileUri>> {
855
856        self.file_picker().pick_contents(mime_types, multiple)
857    }
858
859    /// Use [`FilePicker::pick_visual_medias`] instead.
860    #[deprecated = "Use FilePicker::pick_visual_medias instead"]
861    pub fn show_open_visual_media_dialog(
862        &self,
863        target: VisualMediaTarget,
864        multiple: bool,
865    ) -> crate::Result<Vec<FileUri>> {
866
867        self.file_picker().pick_visual_medias(target, multiple)
868    }
869
870    /// Use [`FilePicker::pick_dir`] instead.
871    #[deprecated = "Use FilePicker::pick_dir instead"]
872    pub fn show_manage_dir_dialog(
873        &self,
874        initial_location: Option<&FileUri>,
875    ) -> crate::Result<Option<FileUri>> {
876
877        self.file_picker().pick_dir(initial_location)
878    }
879
880    /// Use [`FilePicker::pick_dir`] instead.
881    #[deprecated = "Use FilePicker::pick_dir instead."]
882    pub fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
883        self.file_picker().pick_dir(None)
884    }
885
886
887    /// Use [`FilePicker::save_file`] instead.
888    #[deprecated = "Use FilePicker::save_file instead."]
889    pub fn show_save_file_dialog(
890        &self,
891        initial_location: Option<&FileUri>,
892        initial_file_name: impl AsRef<str>,
893        mime_type: Option<&str>,
894    ) -> crate::Result<Option<FileUri>> {
895        
896        self.file_picker().save_file(initial_location, initial_file_name, mime_type)
897    }
898
899    /// Create an **restricted** URI for the specified directory.  
900    /// This should only be used as `initial_location` in the file picker. 
901    /// It must not be used for any other purpose.  
902    /// 
903    /// This is useful when selecting (creating) new files and folders, 
904    /// but when selecting existing entries, `initial_location` is often better with None.
905    /// 
906    /// Note this is an informal method and is not guaranteed to work reliably.
907    /// But this URI does not cause the dialog to error.  
908    /// So please use this with the mindset that it's better than doing nothing.  
909    /// 
910    /// # Examples
911    /// ```rust
912    ///  use tauri_plugin_android_fs::{AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
913    ///
914    /// fn sample(app: tauri::AppHandle) {
915    ///     let api = app.android_fs();
916    ///
917    ///     // Get URI of the top public directory in primary volume
918    ///     let initial_location = api.resolve_initial_location(
919    ///         InitialLocation::TopPublicDir,
920    ///         false,
921    ///     ).expect("Should be on Android");
922    ///
923    ///     // Get URI of ~/Pictures/
924    ///     let initial_location = api.resolve_initial_location(
925    ///         PublicImageDir::Pictures,
926    ///         false
927    ///     ).expect("Should be on Android");
928    ///
929    ///     // Get URI of ~/Documents/sub_dir1/sub_dir2/
930    ///     let initial_location = api.resolve_initial_location(
931    ///         InitialLocation::DirInPublicDir {
932    ///             base_dir: PublicGeneralPurposeDir::Documents.into(),
933    ///             relative_path: "sub_dir1/sub_dir2"
934    ///         },
935    ///         true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
936    ///     ).expect("Should be on Android");
937    ///
938    ///     // Open dialog with initial_location
939    ///     let _ = api.file_picker().save_file(Some(&initial_location), "", None);
940    ///     let _ = api.file_picker().pick_file(Some(&initial_location), &[]);
941    ///     let _ = api.file_picker().pick_dir(Some(&initial_location));
942    /// }
943    /// ```
944    /// 
945    /// # Support
946    /// All.
947    pub fn resolve_initial_location<'a>(
948        &self,
949        dir: impl Into<InitialLocation<'a>>,
950        create_dirs: bool
951    ) -> crate::Result<FileUri> {
952
953        on_android!({
954            const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary";
955
956            let uri = match dir.into() {
957                InitialLocation::TopPublicDir => format!("{TOP_DIR}%3A"),
958                InitialLocation::PublicDir(dir) => format!("{TOP_DIR}%3A{dir}"),
959                InitialLocation::DirInPublicDir { base_dir, relative_path } => {
960                    let relative_path = relative_path.trim_matches('/');
961
962                    if relative_path.is_empty() {
963                        format!("{TOP_DIR}%3A{base_dir}")
964                    }
965                    else {
966                        if create_dirs {
967                            let _ = self.public_storage().create_dir_all(base_dir, relative_path);
968                        }
969                        let sub_dirs = encode_document_id(relative_path);
970                        format!("{TOP_DIR}%3A{base_dir}%2F{sub_dirs}")
971                    }
972                },
973                InitialLocation::DirInPublicAppDir { base_dir, relative_path } => {
974                    let relative_path = &format!(
975                        "{}/{}", 
976                        self.public_storage().app_dir_name()?,
977                        relative_path.trim_matches('/'),
978                    );
979                  
980                    return self.resolve_initial_location(
981                        InitialLocation::DirInPublicDir { base_dir, relative_path }, 
982                        create_dirs
983                    )
984                }
985            };
986
987            Ok(FileUri { uri, document_top_tree_uri: None })
988        })
989    }
990
991    /// Use [`FileSender::share_file`] instead
992    #[deprecated = "Use FileSender::share_file instead."]
993    pub fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
994        self.file_sender().share_file(uri)
995    }
996    
997    /// Use [`FileSender::open_file`] instead
998    #[deprecated = "Use FileSender::open_file instead."]
999    pub fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1000        self.file_sender().open_file(uri)
1001    }
1002
1003    /// Use [`FileSender::can_share_file`] instead
1004    #[deprecated = "Use FileSender::can_share_file instead"]
1005    pub fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool> {
1006        #[allow(deprecated)]
1007        self.file_sender().can_share_file(uri)
1008    }
1009
1010    /// Use [`FileSender::can_open_file`] instead
1011    #[deprecated = "Use FileSender::can_open_file instead"]
1012    pub fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool> {
1013        #[allow(deprecated)]
1014        self.file_sender().can_open_file(uri)
1015    }
1016
1017    /// Take persistent permission to access the file, directory and its descendants.  
1018    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
1019    /// 
1020    /// This works by just calling, without displaying any confirmation to the user.
1021    /// 
1022    /// 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)  
1023    /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
1024    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1025    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
1026    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1027    /// 
1028    /// # Args
1029    /// - **uri** :  
1030    /// URI of the target file or directory. This must be a URI taken from following :  
1031    ///     - [`FilePicker::pick_files`]  
1032    ///     - [`FilePicker::pick_file`]  
1033    ///     - [`FilePicker::pick_visual_medias`]  
1034    ///     - [`FilePicker::pick_visual_media`]  
1035    ///     - [`FilePicker::pick_dir`]  
1036    ///     - [`FilePicker::save_file`]  
1037    ///     - [`AndroidFs::try_resolve_file_uri`], [`AndroidFs::try_resolve_dir_uri`], [`AndroidFs::resolve_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_file`], [`AndroidFs::create_dir_all`] :  
1038    ///     If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function. 
1039    ///     Because the permissions and validity period of the descendant entry URIs depend on the origin directory.   
1040    /// 
1041    /// # Support
1042    /// All. 
1043    pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1044        on_android!({
1045            impl_se!(struct Req<'a> { uri: &'a FileUri });
1046            impl_de!(struct Res;);
1047
1048            self.api
1049                .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1050                .map(|_| ())
1051                .map_err(Into::into)
1052        })
1053    }
1054
1055    /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].  
1056    /// Returns false if there are only non-persistent permissions or no permissions.
1057    /// 
1058    /// # Args
1059    /// - **uri** :  
1060    /// URI of the target file or directory.  
1061    /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself. 
1062    /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1063    ///
1064    /// - **mode** :  
1065    /// The mode of permission you want to check.  
1066    /// 
1067    /// # Support
1068    /// All.
1069    pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1070        on_android!({
1071            impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1072            impl_de!(struct Res { value: bool });
1073
1074            self.api
1075                .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1076                .map(|v| v.value)
1077                .map_err(Into::into)
1078        })
1079    }
1080
1081    /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.   
1082    /// 
1083    /// # Support
1084    /// All.
1085    pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1086        on_android!(std::iter::Empty::<_>, {
1087            impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1088            impl_de!(struct Res { items: Vec<Obj> });
1089    
1090            self.api
1091                .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1092                .map(|v| v.items.into_iter())
1093                .map(|v| v.map(|v| {
1094                    let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1095                    match v.d {
1096                        true => PersistedUriPermission::Dir { uri, can_read, can_write },
1097                        false => PersistedUriPermission::File { uri, can_read, can_write }
1098                    }
1099                }))
1100                .map_err(Into::into)
1101        })
1102    }
1103
1104    /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
1105    /// 
1106    /// # Args
1107    /// - ***uri*** :  
1108    /// URI of the target file or directory.  
1109    ///
1110    /// # Support
1111    /// All.
1112    pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1113        on_android!({
1114            impl_se!(struct Req<'a> { uri: &'a FileUri });
1115            impl_de!(struct Res;);
1116
1117            self.api
1118                .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1119                .map(|_| ())
1120                .map_err(Into::into)
1121        })
1122    }
1123
1124    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
1125    /// 
1126    /// # Support
1127    /// All.
1128    pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1129        on_android!({
1130            impl_de!(struct Res);
1131
1132            self.api
1133                .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1134                .map(|_| ())
1135                .map_err(Into::into)
1136        })
1137    }
1138
1139    /// Use [`FilePicker::is_visual_media_picker_available`] instead.
1140    #[deprecated = "Use FilePicker::is_visual_media_picker_available instead"]
1141    pub fn is_visual_media_dialog_available(&self) -> crate::Result<bool> {
1142        self.file_picker().is_visual_media_picker_available()
1143    }
1144}