tauri_plugin_android_fs/
lib.rs

1//! Overview and usage is [here](https://crates.io/crates/tauri-plugin-android-fs)
2
3mod models;
4mod impls;
5mod error;
6
7use std::io::{Read as _, Write as _};
8
9pub use models::*;
10pub use error::{Error, Result};
11pub use impls::{AndroidFsExt, init};
12
13/// API
14pub trait AndroidFs<R: tauri::Runtime> {
15
16    /// Verify whether this plugin is available.  
17    /// 
18    /// On Android, this returns true.  
19    /// On other platforms, this returns false.  
20    fn is_available(&self) -> bool {
21        #[cfg(not(target_os = "android"))] {
22            false
23        }
24        #[cfg(target_os = "android")] {
25            true
26        }
27    }
28
29    /// Get the file or directory name.  
30    /// 
31    /// # Args
32    /// - ***uri*** :  
33    /// Target URI.  
34    /// This needs to be **readable**.
35    /// 
36    /// # Support
37    /// All Android version.
38    fn get_name(&self, uri: &FileUri) -> crate::Result<String>;
39
40    /// Query the provider to get mime type.  
41    /// If the directory, this returns `None`.  
42    /// If the file, this returns no `None`.  
43    /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.  
44    ///
45    /// # Args
46    /// - ***uri*** :  
47    /// Target URI.  
48    /// This needs to be **readable**.
49    /// 
50    /// # Support
51    /// All Android version.
52    fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>>;
53
54    /// Queries the file system to get information about a file, directory.
55    /// 
56    /// # Args
57    /// - ***uri*** :  
58    /// Target URI.  
59    /// This needs to be **readable**.
60    /// 
61    /// # Note
62    /// This uses [`AndroidFs::open_file`] internally.
63    /// 
64    /// # Support
65    /// All Android version.
66    fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
67        let file = self.open_file(uri, FileAccessMode::Read)?;
68        Ok(file.metadata()?)
69    }
70
71    /// Open a file in the specified mode.
72    /// 
73    /// # Args
74    /// - ***uri*** :  
75    /// Target file URI.  
76    /// This must have corresponding permissions (read, write, or both) for the specified **mode**.
77    /// 
78    /// - ***mode*** :  
79    /// Indicates how the file is opened and the permissions granted.  
80    /// Note that files provided by third-party apps may not support [`FileAccessMode::WriteAppend`]. (ex: Files on GoogleDrive)  
81    ///
82    /// # Note
83    /// This method uses a FileDescriptor internally. 
84    /// However, if the target file does not physically exist on the device, such as cloud-based files, 
85    /// the write operation using a FileDescriptor may not be reflected properly.
86    /// In such cases, consider using [AndroidFs::write_via_kotlin], 
87    /// which writes using a standard method, 
88    /// or [AndroidFs::write], which automatically falls back to that approach when necessary.
89    /// If you specifically need to write using std::fs::File not entire contents, see [AndroidFs::write_via_kotlin_in] or [AndroidFs::copy_via_kotlin].  
90    /// 
91    /// It seems that the issue does not occur on all cloud storage platforms. At least, files on Google Drive have issues, 
92    /// but files on Dropbox can be written to correctly using a FileDescriptor.
93    /// 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). 
94    /// This information will be used in [AndroidFs::need_write_via_kotlin] used by `AndroidFs::write`.  
95    /// 
96    /// There are no problems with file reading.
97    /// 
98    /// # Support
99    /// All Android version.
100    fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File>;
101
102    /// Reads the entire contents of a file into a bytes vector.  
103    /// 
104    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
105    /// 
106    /// # Args
107    /// - ***uri*** :  
108    /// Target file URI.    
109    /// This needs to be **readable**.
110    /// 
111    /// # Support
112    /// All Android version.
113    fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
114        let mut file = self.open_file(uri, FileAccessMode::Read)?;
115        let mut buf = file.metadata().ok()
116            .map(|m| m.len() as usize)
117            .map(Vec::with_capacity)
118            .unwrap_or_else(Vec::new);
119
120        file.read_to_end(&mut buf)?;
121        Ok(buf)
122    }
123
124    /// Reads the entire contents of a file into a string.  
125    /// 
126    /// If you need to operate the file, use [`AndroidFs::open_file`] instead.  
127    /// 
128    /// # Args
129    /// - ***uri*** :  
130    /// Target file URI.  
131    /// This needs to be **readable**.
132    /// 
133    /// # Support
134    /// All Android version.
135    fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
136        let mut file = self.open_file(uri, FileAccessMode::Read)?;
137        let mut buf = file.metadata().ok()
138            .map(|m| m.len() as usize)
139            .map(String::with_capacity)
140            .unwrap_or_else(String::new);
141    
142        file.read_to_string(&mut buf)?;
143        Ok(buf)
144    }
145
146    /// Writes a slice as the entire contents of a file.  
147    /// This function will entirely replace its contents if it does exist.    
148    /// 
149    /// If you want to operate the file, use [`AndroidFs::open_file`] instead.  
150    /// 
151    /// # Args
152    /// - ***uri*** :  
153    /// Target file URI.  
154    /// This needs to be **writable**.
155    /// 
156    /// # Support
157    /// All Android version.
158    fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
159        if self.need_write_via_kotlin(uri)? {
160            self.write_via_kotlin(uri, contents)?;
161            return Ok(())
162        }
163
164        let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
165        file.write_all(contents.as_ref())?;
166        Ok(())
167    }
168
169    /// Writes a slice as the entire contents of a file.  
170    /// This function will entirely replace its contents if it does exist.    
171    /// 
172    /// Differences from `std::fs::File::write_all` is the process is done on Kotlin side.  
173    /// See [`AndroidFs::open_file`] for why this function exists.
174    /// 
175    /// If [`AndroidFs::write`] is used, it automatically fall back to this by [`AndroidFs::need_write_via_kotlin`], 
176    /// so there should be few opportunities to use this.
177    /// 
178    /// If you want to write using `std::fs::File`, not entire contents, use [`AndroidFs::write_via_kotlin_in`].
179    /// 
180    /// # Inner process
181    /// The contents is written to a temporary file by Rust side 
182    /// and then copied to the specified file on Kotlin side by [`AndroidFs::copy_via_kotlin`].  
183    /// 
184    /// # Support
185    /// All Android version.
186    fn write_via_kotlin(
187        &self, 
188        uri: &FileUri,
189        contents: impl AsRef<[u8]>
190    ) -> crate::Result<()> {
191
192        self.write_via_kotlin_in(uri, |file| file.write_all(contents.as_ref()))
193    }
194
195    /// See [`AndroidFs::write_via_kotlin`] for information.  
196    /// Use this if you want to write using `std::fs::File`, not entire contents.
197    /// 
198    /// If you want to retain the file outside the closure, 
199    /// you can perform the same operation using [`AndroidFs::copy_via_kotlin`] and [`PrivateStorage`]. 
200    /// For details, please refer to the internal implementation of this function.
201    /// 
202    /// # Args
203    /// - ***uri*** :  
204    /// Target file URI to write.
205    /// 
206    /// - **contetns_writer** :  
207    /// A closure that accepts a mutable reference to a `std::fs::File`
208    /// and performs the actual write operations. Note that this represents a temporary file.
209    fn write_via_kotlin_in<T>(
210        &self, 
211        uri: &FileUri,
212        contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
213    ) -> crate::Result<T> {
214
215        static TMP_FILE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
216
217        // 一時ファイルの排他アクセスを保証
218        let _guard = TMP_FILE_LOCK.lock();
219
220        // 一時ファイルのパスを取得
221        let tmp_file_path = self.app_handle()
222            .android_fs()
223            .private_storage()
224            .resolve_path_with(PrivateDir::Cache, "tauri-plugin-android-fs-tempfile-write-via-kotlin")?;
225
226        // 一時ファイルに内容を書き込む
227        // エラー処理は一時ファイルを削除するまで保留
228        let result = {
229            let ref mut file = std::fs::File::create(&tmp_file_path)?;
230            contents_writer(file)
231        };
232
233        // 上記処理が成功していれば、一時ファイルの内容をkotlin側で指定されたファイルにコピーする
234        // エラー処理は一時ファイルを削除するまで保留
235        let result = result
236            .map_err(crate::Error::from)
237            .and_then(|t| self.copy_via_kotlin(&(&tmp_file_path).into(), uri).map(|_| t));
238
239        // 一時ファイルを削除
240        let _ = std::fs::remove_file(&tmp_file_path);
241
242        result
243    }
244
245    /// Determines if the file needs to be written via Kotlin side instead of Rust side.  
246    /// Currently, this returns true only if the file is on GoogleDrive.  
247    /// 
248    /// # Support
249    /// All Android version.
250    fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
251        Ok(uri.uri.starts_with("content://com.google.android.apps.docs.storage"))
252    }
253
254    /// Copies the contents of src file to dest. 
255    /// 
256    /// This copy process is done on Kotlin side, not on Rust.  
257    /// Large files in GB units are also supported.  
258    ///  
259    /// See [`AndroidFs::write_via_kotlin`] for why this function exists.
260    /// 
261    /// # Args
262    /// - ***src*** :  
263    /// The URI of source file.   
264    /// This needs to be **readable**.
265    /// 
266    /// - ***dest*** :  
267    /// The URI of destination file.  
268    /// This needs to be **writable**.
269    /// 
270    /// # Support
271    /// All Android version.
272    fn copy_via_kotlin(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()>;
273
274    /// Remove the file.
275    /// 
276    /// # Args
277    /// - ***uri*** :  
278    /// Target file URI.  
279    /// This needs to be **writable**, at least. But even if it is, 
280    /// removing may not be possible in some cases. 
281    /// For details, refer to the documentation of the function that provided the URI.
282    /// 
283    /// # Support
284    /// All Android version.
285    fn remove_file(&self, uri: &FileUri) -> crate::Result<()>;
286
287    /// Remove the **empty** directory.
288    /// 
289    /// # Args
290    /// - ***uri*** :  
291    /// Target directory URI.  
292    /// This needs to be **writable**.
293    /// 
294    /// # Support
295    /// All Android version.
296    fn remove_dir(&self, uri: &FileUri) -> crate::Result<()>;
297
298    /// Creates a new empty file in the specified location and returns a URI.  
299    /// 
300    /// The permissions and validity period of the returned URIs depend on the origin directory 
301    /// (e.g., the top directory selected by [`AndroidFs::show_open_dir_dialog`]) 
302    ///  
303    /// # Args  
304    /// - ***dir*** :  
305    /// The URI of the base directory.  
306    /// This needs to be **read-write**.
307    ///  
308    /// - ***relative_path*** :  
309    /// The file path relative to the base directory.  
310    /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.  
311    /// Any missing subdirectories in the specified path will be created automatically.  
312    ///  
313    /// - ***mime_type*** :  
314    /// The MIME type of the file to be created.  
315    /// If this is None, MIME type is inferred from the extension 
316    /// and if that fails, `application/octet-stream` is used.  
317    ///  
318    /// # Support
319    /// All Android version.
320    fn create_file(
321        &self,
322        dir: &FileUri, 
323        relative_path: impl AsRef<str>, 
324        mime_type: Option<&str>
325    ) -> crate::Result<FileUri>;
326
327    /// Returns the child files and directories of the specified directory.  
328    /// The order of the entries is not guaranteed.  
329    /// 
330    /// The permissions and validity period of the returned URIs depend on the origin directory 
331    /// (e.g., the top directory selected by [`AndroidFs::show_open_dir_dialog`])  
332    /// 
333    /// # Args
334    /// - **uri** :  
335    /// Target directory URI.  
336    /// This needs to be **readable**.
337    ///  
338    /// # Note  
339    /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily. 
340    /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.  
341    /// 
342    /// # Support
343    /// All Android version.
344    fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>>;
345
346    /// Opens a system file picker and returns a **read-write** URIs.  
347    /// If no file is selected or the user cancels, an empty vec is returned.  
348    /// 
349    /// This provides a relatively consistent interface regardless of version or device, 
350    /// and also allows file selection from third-party apps or cloud storage.
351    /// 
352    /// By default, returned URI is valid until the app is terminated. 
353    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
354    ///
355    /// Removing the returned files is also supported in most cases, 
356    /// but note that files provided by third-party apps may not be removable.  
357    ///  
358    /// Just to read images and videos, consider using [`AndroidFs::show_open_visual_media_dialog`] instead. 
359    ///  
360    /// # Args  
361    /// - ***initial_location*** :  
362    /// Indicate the initial location of dialog.  
363    /// System will do its best to launch the dialog in the specified entry 
364    /// if it's a directory, or the directory that contains the specified file if not.  
365    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
366    /// 
367    /// - ***mime_types*** :  
368    /// The MIME types of the file to be selected.  
369    /// However, there is no guarantee that the returned file will match the specified types.  
370    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
371    ///  
372    /// - ***multiple*** :  
373    /// Indicates whether multiple file selection is allowed.  
374    /// 
375    /// # Issue
376    /// This dialog has known issues. See the following for details and workarounds
377    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
378    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
379    /// 
380    /// # Support
381    /// All Android version.
382    /// 
383    /// # References
384    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
385    fn show_open_file_dialog(
386        &self,
387        initial_location: Option<&FileUri>,
388        mime_types: &[&str],
389        multiple: bool,
390    ) -> crate::Result<Vec<FileUri>>;
391
392    /// Opens a media picker and returns a **readonly** URIs.  
393    /// If no file is selected or the user cancels, an empty vec is returned.  
394    ///  
395    /// This media picker provides a browsable interface that presents the user with their media library, 
396    /// sorted by date from newest to oldest. 
397    /// 
398    /// By default, returned URI is valid until the app is terminated. 
399    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
400    ///  
401    /// # Args  
402    /// - ***target*** :  
403    /// The media type of the file to be selected.  
404    /// Images or videos, or both.  
405    ///  
406    /// - ***multiple*** :  
407    /// Indicates whether multiple file selection is allowed.  
408    ///  
409    /// # Issue
410    /// This dialog has known issues. See the following for details and workarounds
411    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
412    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
413    ///  
414    /// # Note
415    /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].  
416    /// Instead, it will be assigned a sequential number, such as `1000091523.png`. 
417    /// And this is marked intended behavior, not a bug.
418    /// - <https://issuetracker.google.com/issues/268079113>  
419    ///  
420    /// # Support
421    /// This feature is available on devices that meet the following criteria:  
422    /// - Running Android 11 (API level 30) or higher  
423    /// - Receive changes to Modular System Components through Google System Updates  
424    ///  
425    /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].  
426    /// If not supported, this function behaves the same as [`AndroidFs::show_open_file_dialog`].  
427    /// 
428    /// # References
429    /// <https://developer.android.com/training/data-storage/shared/photopicker>
430    fn show_open_visual_media_dialog(
431        &self,
432        target: VisualMediaTarget,
433        multiple: bool,
434    ) -> crate::Result<Vec<FileUri>>;
435
436    /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one, 
437    /// and returns a **read-write** directory URI. 
438    /// App can fully manage entries within the returned directory.  
439    /// If no directory is selected or the user cancels, `None` is returned. 
440    /// 
441    /// By default, returned URI is valid until the app is terminated. 
442    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
443    /// 
444    /// # Args  
445    /// - ***initial_location*** :  
446    /// Indicate the initial location of dialog.  
447    /// System will do its best to launch the dialog in the specified entry 
448    /// if it's a directory, or the directory that contains the specified file if not.  
449    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
450    /// 
451    /// # Issue
452    /// This dialog has known issues. See the following for details and workarounds
453    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
454    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
455    ///  
456    /// # Support
457    /// All Android version.
458    /// 
459    /// # References
460    /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
461    fn show_manage_dir_dialog(
462        &self,
463        initial_location: Option<&FileUri>,
464    ) -> crate::Result<Option<FileUri>>;
465
466    /// Please use [`AndroidFs::show_manage_dir_dialog`] instead.
467    #[deprecated = "Confusing name. Please use show_manage_dir_dialog instead."]
468    #[warn(deprecated)]
469    fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
470        self.show_manage_dir_dialog(None)
471    }
472
473    /// Opens a dialog to save a file and returns a **writeonly** URI.  
474    /// The returned file may be a newly created file with no content,
475    /// or it may be an existing file with the requested MIME type.  
476    /// If the user cancels, `None` is returned. 
477    /// 
478    /// By default, returned URI is valid until the app is terminated. 
479    /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
480    /// 
481    /// Removing and reading the returned files is also supported in most cases, 
482    /// but note that files provided by third-party apps may not.  
483    ///  
484    /// # Args  
485    /// - ***initial_location*** :  
486    /// Indicate the initial location of dialog.  
487    /// System will do its best to launch the dialog in the specified entry 
488    /// if it's a directory, or the directory that contains the specified file if not.  
489    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
490    /// 
491    /// - ***initial_file_name*** :  
492    /// An initial file name, but the user may change this value before creating the file.  
493    /// 
494    /// - ***mime_type*** :  
495    /// The MIME type of the file to be saved.  
496    /// If this is None, MIME type is inferred from the extension 
497    /// and if that fails, `application/octet-stream` is used.  
498    ///  
499    /// # Issue
500    /// This dialog has known issues. See the following for details and workarounds
501    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>  
502    /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>  
503    /// 
504    /// # Support
505    /// All Android version.
506    /// 
507    /// # References
508    /// <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
509    fn show_save_file_dialog(
510        &self,
511        initial_location: Option<&FileUri>,
512        initial_file_name: impl AsRef<str>,
513        mime_type: Option<&str>,
514    ) -> crate::Result<Option<FileUri>>;
515
516    /// Opens a dialog for sharing file to other apps.  
517    /// 
518    /// An error will occur if there is no app that can handle the request. 
519    /// Please use [`AndroidFs::can_share_file`] to confirm.
520    /// 
521    /// # Args
522    /// - **uri** :  
523    /// Target file uri to share.  
524    /// This needs to be **readable**.  
525    /// This given from [`PrivateStorage`] or [`AndroidFs::show_open_visual_media_dialog`] ***cannot*** be used.
526    /// 
527    /// # Support
528    /// All Android version.
529    fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()>;
530
531    /// Opens a dialog for viewing file on other apps.  
532    /// This performs the general "open file" action.
533    /// 
534    /// An error will occur if there is no app that can handle the request. 
535    /// Please use [`AndroidFs::can_view_file`] to confirm.
536    /// 
537    /// # Args
538    /// - **uri** :  
539    /// Target file uri to view.  
540    /// This needs to be **readable**.  
541    /// This given from [`PrivateStorage`] or [`AndroidFs::show_open_visual_media_dialog`] ***cannot*** be used.
542    /// 
543    /// # Support
544    /// All Android version.
545    fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()>;
546
547    /// Determines whether the specified file can be used with [`AndroidFs::show_share_file_dialog`].
548    /// # Args
549    /// - **uri** :  
550    /// Target file uri.  
551    /// This needs to be **readable**.
552    /// 
553    /// # Support
554    /// All Android version.
555    fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool>;
556
557    /// Determines whether the specified file can be used with [`AndroidFs::show_view_file_dialog`].
558    /// 
559    /// # Args
560    /// - **uri** :  
561    /// Target file uri.  
562    /// This needs to be **readable**.
563    /// 
564    /// # Support
565    /// All Android version.
566    fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool>;
567
568    /// Take persistent permission to access the file, directory and its descendants.  
569    /// This is a prolongation of an already acquired permission, not the acquisition of a new one.  
570    /// 
571    /// This works by just calling, without displaying any confirmation to the user.
572    /// 
573    /// 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)  
574    /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].  
575    /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
576    /// So check by [`AndroidFs::check_persisted_uri_permission`].  
577    /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
578    /// 
579    /// # Args
580    /// - **uri** :  
581    /// URI of the target file or directory. This must be a URI taken from following :  
582    ///     - [`AndroidFs::show_open_file_dialog`]
583    ///     - [`AndroidFs::show_open_visual_media_dialog`]
584    ///     - [`AndroidFs::show_save_file_dialog`]
585    ///     - [`AndroidFs::show_manage_dir_dialog`]  
586    ///     - [`AndroidFs::read_dir`] :  
587    ///         If this, the permissions of the origin directory URI is persisted, not a entry iteself. 
588    ///         Because the permissions and validity period of the entry URIs depend on the origin directory.
589    /// 
590    /// # Support
591    /// All Android version. 
592    fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()>;
593
594    /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
595    /// Returns false if there are only non-persistent permissions or no permissions.
596    /// 
597    /// # Args
598    /// - **uri** :  
599    /// URI of the target file or directory.  
600    ///
601    /// - **mode** :  
602    /// The mode of permission you want to check.  
603    /// 
604    /// # Support
605    /// All Android version.
606    fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool>;
607
608    /// Return list of all URI permission grants that have been persisted by [`AndroidFs::take_persistable_uri_permission`].   
609    /// 
610    /// # Support
611    /// All Android version.
612    fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>>;
613
614    /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].   
615    /// 
616    /// # Args
617    /// - ***uri*** :  
618    /// URI of the target file or directory.  
619    ///
620    /// # Support
621    /// All Android version.
622    fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()>;
623
624    /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].  
625    /// 
626    /// # Support
627    /// All Android version.
628    fn release_all_persisted_uri_permissions(&self) -> crate::Result<()>;
629
630    /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
631    /// 
632    /// # Support
633    /// All Android version.
634    fn is_visual_media_dialog_available(&self) -> crate::Result<bool>;
635
636    /// File storage intended for the app’s use only.
637    fn private_storage(&self) -> &impl PrivateStorage<R>;
638
639    /// File storage that is available to other applications and users.
640    fn public_storage(&self) -> &impl PublicStorage<R>;
641
642    fn app_handle(&self) -> &tauri::AppHandle<R>;
643}
644
645/// File storage that is available to other applications and users.
646pub trait PublicStorage<R: tauri::Runtime> {
647
648    /// See [`PublicStorage::create_file_in_public_dir`] for description.  
649    /// 
650    /// This is the same as following: 
651    /// ``````ignore
652    /// create_file_in_public_dir(
653    ///     dir,
654    ///     format!("{app_name}/{relative_path}"),
655    ///     mime_type
656    /// );
657    /// ``````
658    fn create_file_in_public_app_dir(
659        &self,
660        dir: impl Into<PublicDir>,
661        relative_path: impl AsRef<str>, 
662        mime_type: Option<&str>
663    ) -> crate::Result<FileUri> {
664
665        let config = self.app_handle().config();
666        let app_name = config.product_name.as_deref().unwrap_or("");
667        let app_name = match app_name.is_empty() {
668            true => &config.identifier,
669            false => app_name
670        };
671        let app_name = app_name.replace('/', " ");
672        let relative_path = relative_path.as_ref().trim_start_matches('/');
673        let relative_path_with_subdir = format!("{app_name}/{relative_path}");
674
675        self.create_file_in_public_dir(dir, relative_path_with_subdir, mime_type)
676    }
677
678    /// Creates a new empty file in the specified public directory and returns a **persistent read-write** URI.  
679    ///  
680    /// The created file has following features :   
681    /// - Will be registered with the corresponding MediaStore as needed.  
682    /// - Always supports remove.
683    /// - Not removed when the app is uninstalled.
684    /// 
685    /// # Args
686    /// - ***dir*** :  
687    /// The base directory.  
688    ///  
689    /// - ***relative_path_with_subdir*** :  
690    /// The file path relative to the base directory.  
691    /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.  
692    /// Any missing subdirectories in the specified path will be created automatically.  
693    /// Please specify a subdirectory in this, 
694    /// such as `MyApp/file.txt` or `MyApp/2025-2-11/file.txt`. Do not use `file.txt`.  
695    /// As shown above, it is customary to specify the app name at the beginning of the subdirectory, 
696    /// and in this case, using [`PublicStorage::create_file_in_public_app_dir`] is recommended.
697    ///  
698    /// - ***mime_type*** :  
699    /// The MIME type of the file to be created.  
700    /// If this is None, MIME type is inferred from the extension 
701    /// and if that fails, `application/octet-stream` is used.  
702    /// When using [`PublicImageDir`], please use only image MIME types; using other types may cause errors.
703    /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
704    /// Only [`PublicGeneralPurposeDir`] supports all MIME types. 
705    /// 
706    /// # Support
707    /// Android 10 (API level 29) or higher.  
708    /// Lower are need runtime request of `WRITE_EXTERNAL_STORAGE`. (This option will be made available in the future)
709    ///
710    /// [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
711    /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].  
712    /// [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
713    /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].  
714    /// Others are available in all Android versions.
715    fn create_file_in_public_dir(
716        &self,
717        dir: impl Into<PublicDir>,
718        relative_path_with_subdir: impl AsRef<str>, 
719        mime_type: Option<&str>
720    ) -> crate::Result<FileUri>;
721
722    /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
723    /// 
724    /// # Support
725    /// All Android version.
726    fn is_audiobooks_dir_available(&self) -> crate::Result<bool>;
727
728    /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
729    /// 
730    /// # Support
731    /// All Android version.
732    fn is_recordings_dir_available(&self) -> crate::Result<bool>;
733
734    fn app_handle(&self) -> &tauri::AppHandle<R>;
735}
736
737/// File storage intended for the app’s use only.  
738pub trait PrivateStorage<R: tauri::Runtime> {
739
740    /// Get the absolute path of the specified directory.  
741    /// App can fully manage entries within this directory without any permission via std::fs.   
742    ///
743    /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s initialising request.  
744    /// When using [`PrivateDir::Cache`], the system will automatically delete files in this directory as disk space is needed elsewhere on the device.   
745    /// 
746    /// The returned path may change over time if the calling app is moved to an adopted storage device, 
747    /// so only relative paths should be persisted.   
748    /// 
749    /// # Examples
750    /// ```no_run
751    /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, PrivateDir, PrivateStorage};
752    /// 
753    /// fn example(app: tauri::AppHandle) {
754    ///     let api = app.android_fs().private_storage();
755    /// 
756    ///     let dir_path = api.resolve_path(PrivateDir::Data).unwrap();
757    ///     let file_path = dir_path.join("2025-2-12/data.txt");
758    ///     
759    ///     // Write file
760    ///     std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
761    ///     std::fs::write(&file_path, "aaaa").unwrap();
762    /// 
763    ///     // Read file
764    ///     let _ = std::fs::read_to_string(&file_path).unwrap();
765    /// 
766    ///     // Remove file
767    ///     std::fs::remove_file(&file_path).unwrap();
768    /// }
769    /// ```
770    /// 
771    /// # Support
772    /// All Android version.
773    fn resolve_path(&self, dir: PrivateDir) -> crate::Result<std::path::PathBuf>;
774
775    /// Get the absolute path of the specified relative path and base directory.  
776    /// App can fully manage entries of this path without any permission via std::fs.   
777    ///
778    /// See [`PrivateStorage::resolve_path`] for details.  
779    /// 
780    /// # Support
781    /// All Android version.
782    fn resolve_path_with(
783        &self,
784        dir: PrivateDir,
785        relative_path: impl AsRef<str>
786    ) -> crate::Result<std::path::PathBuf> {
787
788        let relative_path = relative_path.as_ref().trim_start_matches('/');
789        let path = self.resolve_path(dir)?.join(relative_path);
790        Ok(path)
791    }
792
793    fn resolve_uri(&self, dir: PrivateDir) -> crate::Result<FileUri> {
794        self.resolve_path(dir).map(Into::into)
795    }
796
797    fn resolve_uri_with(&self, dir: PrivateDir, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
798        self.resolve_path_with(dir, relative_path).map(Into::into)
799    }
800
801    /// Writes a slice as the entire contents of a file.  
802    /// 
803    /// This function will create a file if it does not exist, and will entirely replace its contents if it does.  
804    /// Recursively create parent directories if they are missing.  
805    /// 
806    /// This internally uses [`PrivateStorage::resolve_path`] , [`std::fs::create_dir_all`], and [`std::fs::write`].  
807    /// See [`PrivateStorage::resolve_path`] for details.  
808    /// 
809    /// # Support
810    /// All Android version.
811    fn write(
812        &self, 
813        base_dir: PrivateDir, 
814        relative_path: impl AsRef<str>, 
815        contents: impl AsRef<[u8]>
816    ) -> crate::Result<()> {
817
818        let path = self.resolve_path_with(base_dir, relative_path)?;
819
820        if let Some(parent_dir) = path.parent() {
821            std::fs::create_dir_all(parent_dir)?;
822        }
823
824        std::fs::write(path, contents)?;
825
826        Ok(())
827    }
828
829    /// Open a file in read-only mode.  
830    /// 
831    /// If you only need to read the entire file contents, consider using [`PrivateStorage::read`]  or [`PrivateStorage::read_to_string`] instead.  
832    /// 
833    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::open`].  
834    /// See [`PrivateStorage::resolve_path`] for details.  
835    /// 
836    /// # Support
837    /// All Android version.
838    fn open_file(
839        &self,
840        base_dir: PrivateDir, 
841        relative_path: impl AsRef<str>, 
842    ) -> crate::Result<std::fs::File> {
843
844        let path = self.resolve_path_with(base_dir, relative_path)?;
845        Ok(std::fs::File::open(path)?)
846    }
847
848    /// Opens a file in write-only mode.  
849    /// This function will create a file if it does not exist, and will truncate it if it does.
850    /// 
851    /// If you only need to write the contents, consider using [`PrivateStorage::write`]  instead.  
852    /// 
853    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create`].  
854    /// See [`PrivateStorage::resolve_path`] for details.  
855    /// 
856    /// # Support
857    /// All Android version.
858    fn create_file(
859        &self,
860        base_dir: PrivateDir, 
861        relative_path: impl AsRef<str>, 
862    ) -> crate::Result<std::fs::File> {
863
864        let path = self.resolve_path_with(base_dir, relative_path)?;
865        Ok(std::fs::File::create(path)?)
866    }
867
868    /// Creates a new file in read-write mode; error if the file exists. 
869    /// 
870    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create_new`].  
871    /// See [`PrivateStorage::resolve_path`] for details.  
872    /// 
873    /// # Support
874    /// All Android version.
875    fn create_new_file(
876        &self,
877        base_dir: PrivateDir, 
878        relative_path: impl AsRef<str>, 
879    ) -> crate::Result<std::fs::File> {
880
881        let path = self.resolve_path_with(base_dir, relative_path)?;
882        Ok(std::fs::File::create_new(path)?)
883    }
884
885    /// Reads the entire contents of a file into a bytes vector.  
886    /// 
887    /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.  
888    /// 
889    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read`].  
890    /// See [`PrivateStorage::resolve_path`] for details.  
891    /// 
892    /// # Support
893    /// All Android version.
894    fn read(
895        &self,
896        base_dir: PrivateDir, 
897        relative_path: impl AsRef<str>, 
898    ) -> crate::Result<Vec<u8>> {
899
900        let path = self.resolve_path_with(base_dir, relative_path)?;
901        Ok(std::fs::read(path)?)
902    }
903
904    /// Reads the entire contents of a file into a string.  
905    /// 
906    /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.  
907    /// 
908    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_to_string`].  
909    /// See [`PrivateStorage::resolve_path`] for details.  
910    /// 
911    /// # Support
912    /// All Android version.
913    fn read_to_string(
914        &self,
915        base_dir: PrivateDir,
916        relative_path: impl AsRef<str>, 
917    ) -> crate::Result<String> {
918
919        let path = self.resolve_path_with(base_dir, relative_path)?;
920        Ok(std::fs::read_to_string(path)?)
921    }
922
923    /// Returns an iterator over the entries within a directory.
924    /// 
925    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_dir`].  
926    /// See [`PrivateStorage::resolve_path`] for details.  
927    /// 
928    /// # Support
929    /// All Android version.
930    fn read_dir(
931        &self,
932        base_dir: PrivateDir,
933        relative_path: Option<&str>,
934    ) -> crate::Result<std::fs::ReadDir> {
935
936        let path = match relative_path {
937            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
938            None => self.resolve_path(base_dir)?,
939        };
940
941        Ok(std::fs::read_dir(path)?)
942    }
943
944    /// Removes a file from the filesystem.  
945    /// 
946    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_file`].  
947    /// See [`PrivateStorage::resolve_path`] for details.  
948    /// 
949    /// # Support
950    /// All Android version.
951    fn remove_file(
952        &self,
953        base_dir: PrivateDir,
954        relative_path: impl AsRef<str>,
955    ) -> crate::Result<()> {
956
957        let path = self.resolve_path_with(base_dir, relative_path)?;
958        Ok(std::fs::remove_file(path)?)
959    }
960
961    /// Removes an empty directory.  
962    /// If you want to remove a directory that is not empty, as well as all of its contents recursively, consider using [`PrivateStorage::remove_dir_all`] instead.  
963    /// 
964    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir`].  
965    /// See [`PrivateStorage::resolve_path`] for details.  
966    /// 
967    /// # Support
968    /// All Android version.
969    fn remove_dir(
970        &self,
971        base_dir: PrivateDir,
972        relative_path: Option<&str>,
973    ) -> crate::Result<()> {
974
975        let path = match relative_path {
976            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
977            None => self.resolve_path(base_dir)?,
978        };
979
980        std::fs::remove_dir(path)?;
981        Ok(())
982    }
983
984    /// Removes a directory at this path, after removing all its contents. Use carefully!  
985    /// 
986    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir_all`].  
987    /// See [`PrivateStorage::resolve_path`] for details.  
988    /// 
989    /// # Support
990    /// All Android version.
991    fn remove_dir_all(
992        &self,
993        base_dir: PrivateDir,
994        relative_path: Option<&str>,
995    ) -> crate::Result<()> {
996
997        let path = match relative_path {
998            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
999            None => self.resolve_path(base_dir)?,
1000        };
1001
1002        std::fs::remove_dir_all(path)?;
1003        Ok(())
1004    }
1005
1006    /// Returns Ok(true) if the path points at an existing entity.  
1007    /// 
1008    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::exists`].  
1009    /// See [`PrivateStorage::resolve_path`] for details.  
1010    /// 
1011    /// # Support
1012    /// All Android version.
1013    fn exists(
1014        &self,
1015        base_dir: PrivateDir,
1016        relative_path: impl AsRef<str>
1017    ) -> crate::Result<bool> {
1018
1019        let path = self.resolve_path_with(base_dir, relative_path)?;
1020        Ok(std::fs::exists(path)?)
1021    }
1022
1023    /// Queries the file system to get information about a file, directory.  
1024    /// 
1025    /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::metadata`].  
1026    /// See [`PrivateStorage::resolve_path`] for details.  
1027    /// 
1028    /// # Support
1029    /// All Android version.
1030    fn metadata(
1031        &self,
1032        base_dir: PrivateDir,
1033        relative_path: Option<&str>,
1034    ) -> crate::Result<std::fs::Metadata> {
1035
1036        let path = match relative_path {
1037            Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
1038            None => self.resolve_path(base_dir)?,
1039        };
1040
1041        Ok(std::fs::metadata(path)?)
1042    }
1043
1044    fn app_handle(&self) -> &tauri::AppHandle<R>;
1045}