tauri_plugin_android_fs/api/
file_picker.rs

1use sync_async::sync_async;
2use crate::*;
3use super::*;
4
5
6/// API of file/dir picker.
7/// 
8/// # Examples
9/// ```no_run
10/// fn example(app: &tauri::AppHandle) {
11///     use tauri_plugin_android_fs::AndroidFsExt as _;
12/// 
13///     let api = app.android_fs();
14///     let file_picker = api.file_picker();
15/// }
16/// ```
17#[sync_async]
18pub struct FilePicker<'a, R: tauri::Runtime> {
19    #[cfg(target_os = "android")]
20    pub(crate) handle: &'a tauri::plugin::PluginHandle<R>,
21
22    #[cfg(not(target_os = "android"))]
23    #[allow(unused)]
24    pub(crate) handle: &'a std::marker::PhantomData<fn() -> R>,
25}
26
27#[cfg(target_os = "android")]
28#[sync_async(
29    use(if_sync) impls::SyncImpls as Impls;
30    use(if_async) impls::AsyncImpls as Impls;
31)]
32impl<'a, R: tauri::Runtime> FilePicker<'a, R> {
33    
34    #[always_sync]
35    fn impls(&self) -> Impls<'_, R> {
36        Impls { handle: &self.handle }
37    }
38}
39
40#[sync_async(
41    use(if_async) api_async::{AndroidFs, FileOpener, PrivateStorage, PublicStorage};
42    use(if_sync) api_sync::{AndroidFs, FileOpener, PrivateStorage, PublicStorage};
43)]
44impl<'a, R: tauri::Runtime> FilePicker<'a, R> {
45
46    /// Opens a system file picker and returns a **read-write** URIs.  
47    /// If no file is selected or the user cancels, an empty vec is returned.  
48    /// 
49    /// By default, returned URI is valid until the app or device is terminated. 
50    /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
51    /// 
52    /// This provides a standardized file explorer-style interface, 
53    /// and also allows file selection from part of third-party apps or cloud storage.
54    ///
55    /// Removing the returned files is also supported in most cases, 
56    /// but note that files provided by third-party apps may not be removable.  
57    ///  
58    /// # Args  
59    /// - ***initial_location*** :  
60    /// Indicate the initial location of dialog.  
61    /// This URI works even without any permissions.  
62    /// There is no need to use this if there is no special reason.  
63    /// System will do its best to launch the dialog in the specified entry 
64    /// if it's a directory, or the directory that contains the specified file if not.  
65    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
66    /// This must be a URI taken from following or it's derivative :   
67    ///     - [`PublicStorage::resolve_initial_location`]
68    ///     - [`AndroidFs::resolve_root_initial_location`]
69    ///     - [`FilePicker::pick_files`]
70    ///     - [`FilePicker::pick_file`]
71    ///     - [`FilePicker::pick_dir`]
72    ///     - [`FilePicker::save_file`]
73    /// 
74    /// - ***mime_types*** :  
75    /// The MIME types of the file to be selected.  
76    /// However, there is no guarantee that the returned file will match the specified types.  
77    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
78    ///  
79    /// # Support
80    /// All Android version.
81    /// 
82    /// # References
83    /// - <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
84    #[maybe_async]
85    pub fn pick_files(
86        &self,
87        initial_location: Option<&FileUri>,
88        mime_types: &[&str],
89    ) -> Result<Vec<FileUri>> {
90
91        #[cfg(not(target_os = "android"))] {
92            Err(Error::NOT_ANDROID)
93        }
94        #[cfg(target_os = "android")] {
95            self.impls().show_pick_file_dialog(initial_location, mime_types, true).await
96        }
97    }
98
99    /// Opens a system file picker and returns a **read-write** URI.  
100    /// If no file is selected or the user cancels, None is returned.  
101    /// 
102    /// By default, returned URI is valid until the app or device is terminated. 
103    /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
104    /// 
105    /// This provides a standardized file explorer-style interface, 
106    /// and also allows file selection from part of third-party apps or cloud storage.
107    ///
108    /// Removing the returned files is also supported in most cases, 
109    /// but note that files provided by third-party apps may not be removable.  
110    ///  
111    /// # Args  
112    /// - ***initial_location*** :  
113    /// Indicate the initial location of dialog.  
114    /// This URI works even without any permissions.  
115    /// There is no need to use this if there is no special reason.  
116    /// System will do its best to launch the dialog in the specified entry 
117    /// if it's a directory, or the directory that contains the specified file if not.  
118    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.  
119    /// This must be a URI taken from following or it's derivative :   
120    ///     - [`PublicStorage::resolve_initial_location`]
121    ///     - [`AndroidFs::resolve_root_initial_location`]
122    ///     - [`FilePicker::pick_files`]
123    ///     - [`FilePicker::pick_file`]
124    ///     - [`FilePicker::pick_dir`]
125    ///     - [`FilePicker::save_file`]
126    /// 
127    /// - ***mime_types*** :  
128    /// The MIME types of the file to be selected.  
129    /// However, there is no guarantee that the returned file will match the specified types.  
130    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
131    ///  
132    /// # Support
133    /// All Android version.
134    /// 
135    /// # References
136    /// - <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
137    #[maybe_async]
138    pub fn pick_file(
139        &self,
140        initial_location: Option<&FileUri>,
141        mime_types: &[&str],
142    ) -> Result<Option<FileUri>> {
143
144        #[cfg(not(target_os = "android"))] {
145            Err(Error::NOT_ANDROID)
146        }
147        #[cfg(target_os = "android")] {
148            self.impls().show_pick_file_dialog(initial_location, mime_types, false)
149                .await
150                .map(|mut i| i.pop())
151        }
152    }
153
154    /// Opens a media picker and returns a **readonly** URIs.  
155    /// If no file is selected or the user cancels, an empty vec is returned.  
156    ///  
157    /// By default, returned URI is valid until the app or device is terminated. 
158    /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
159    ///  
160    /// This media picker provides a gallery, 
161    /// sorted by date from newest to oldest. 
162    /// 
163    /// # Args  
164    /// - ***target*** :  
165    /// The media type of the file to be selected.  
166    /// Images or videos, or both.  
167    ///  
168    /// # Note
169    /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].  
170    /// Instead, it will be assigned a sequential number, such as `1000091523.png`. 
171    /// And this is marked intended behavior, not a bug.
172    /// - <https://issuetracker.google.com/issues/268079113>  
173    ///  
174    /// # Support
175    /// This feature is available on devices that meet the following criteria:  
176    /// - Running Android 11 (API level 30) or higher  
177    /// - Receive changes to Modular System Components through Google System Updates  
178    ///  
179    /// Availability on a given device can be verified by calling [`FilePicker::is_visual_media_picker_available`].  
180    /// If not supported, this function behaves the same as [`FilePicker::pick_files`].  
181    /// 
182    /// # References
183    /// - <https://developer.android.com/training/data-storage/shared/photopicker>
184    #[maybe_async]
185    pub fn pick_visual_medias(
186        &self,
187        target: VisualMediaTarget<'_>,
188    ) -> Result<Vec<FileUri>> {
189
190        #[cfg(not(target_os = "android"))] {
191            Err(Error::NOT_ANDROID)
192        }
193        #[cfg(target_os = "android")] {
194            self.impls().show_pick_visual_media_dialog(target, true).await
195        }
196    }
197
198    /// Opens a media picker and returns a **readonly** URI.  
199    /// If no file is selected or the user cancels, None is returned.  
200    ///  
201    /// By default, returned URI is valid until the app or device is terminated. 
202    /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
203    ///  
204    /// This media picker provides a gallery, 
205    /// sorted by date from newest to oldest. 
206    /// 
207    /// # Args  
208    /// - ***target*** :  
209    /// The media type of the file to be selected.  
210    /// Images or videos, or both.  
211    ///  
212    /// # Note
213    /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].  
214    /// Instead, it will be assigned a sequential number, such as `1000091523.png`. 
215    /// And this is marked intended behavior, not a bug.
216    /// - <https://issuetracker.google.com/issues/268079113>  
217    ///  
218    /// # Support
219    /// This feature is available on devices that meet the following criteria:  
220    /// - Running Android 11 (API level 30) or higher  
221    /// - Receive changes to Modular System Components through Google System Updates  
222    ///  
223    /// Availability on a given device can be verified by calling [`FilePicker::is_visual_media_picker_available`].  
224    /// If not supported, this function behaves the same as [`FilePicker::pick_file`].  
225    /// 
226    /// # References
227    /// - <https://developer.android.com/training/data-storage/shared/photopicker>
228    #[maybe_async]
229    pub fn pick_visual_media(
230        &self,
231        target: VisualMediaTarget<'_>,
232    ) -> Result<Option<FileUri>> {
233
234        #[cfg(not(target_os = "android"))] {
235            Err(Error::NOT_ANDROID)
236        }
237        #[cfg(target_os = "android")] {
238            self.impls().show_pick_visual_media_dialog(target, false)
239                .await
240                .map(|mut i| i.pop())
241        }
242    }
243
244    /// Opens a file picker and returns a **readonly** URIs.  
245    /// If no file is selected or the user cancels, an empty vec is returned.  
246    ///  
247    /// Returned URI is valid until the app or device is terminated. Can not persist it.
248    /// 
249    /// This works differently depending on the model and version.  
250    /// Recent devices often have the similar behaviour as [`FilePicker::pick_visual_medias`] or [`FilePicker::pick_files`].  
251    /// In older versions, third-party apps often handle request instead.
252    /// 
253    /// # Args  
254    /// - ***mime_types*** :  
255    /// The MIME types of the file to be selected.  
256    /// However, there is no guarantee that the returned file will match the specified types.  
257    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
258    ///  
259    /// # Support
260    /// All Android version.
261    /// 
262    /// # References
263    /// - <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
264    #[maybe_async]
265    pub fn pick_contents(
266        &self,
267        mime_types: &[&str],
268    ) -> Result<Vec<FileUri>> {
269
270        #[cfg(not(target_os = "android"))] {
271            Err(Error::NOT_ANDROID)
272        }
273        #[cfg(target_os = "android")] {
274            self.impls().show_pick_content_dialog(mime_types, true).await
275        }
276    }
277
278    /// Opens a file picker and returns a **readonly** URI.  
279    /// If no file is selected or the user cancels, None is returned.  
280    ///  
281    /// Returned URI is valid until the app or device is terminated. Can not persist it.
282    /// 
283    /// This works differently depending on the model and version.  
284    /// Recent devices often have the similar behaviour as [`FilePicker::pick_visual_media`] or [`FilePicker::pick_file`].  
285    /// In older versions, third-party apps often handle request instead.
286    /// 
287    /// # Args  
288    /// - ***mime_types*** :  
289    /// The MIME types of the file to be selected.  
290    /// However, there is no guarantee that the returned file will match the specified types.  
291    /// If left empty, all file types will be available (equivalent to `["*/*"]`).  
292    ///  
293    /// # Support
294    /// All Android version.
295    /// 
296    /// # References
297    /// - <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
298    #[maybe_async]
299    pub fn pick_content(
300        &self,
301        mime_types: &[&str],
302    ) -> Result<Option<FileUri>> {
303
304        #[cfg(not(target_os = "android"))] {
305            Err(Error::NOT_ANDROID)
306        }
307        #[cfg(target_os = "android")] {
308            self.impls().show_pick_content_dialog(mime_types, false)
309                .await
310                .map(|mut i| i.pop())
311        }
312    }
313
314    /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one, 
315    /// and returns a **read-write** directory URI. 
316    /// App can fully manage entries within the returned directory.  
317    /// If no directory is selected or the user cancels, `None` is returned. 
318    /// 
319    /// By default, returned URI is valid until the app or device is terminated. 
320    /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
321    /// 
322    /// This provides a standardized file explorer-style interface,
323    /// and also allows directory selection from part of third-party apps or cloud storage.
324    /// 
325    /// # Args  
326    /// - ***initial_location*** :  
327    /// Indicate the initial location of dialog.    
328    /// This URI works even without any permissions.  
329    /// There is no need to use this if there is no special reason.  
330    /// System will do its best to launch the dialog in the specified entry 
331    /// if it's a directory, or the directory that contains the specified file if not.  
332    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.   
333    /// This must be a URI taken from following or it's derivative :   
334    ///     - [`PublicStorage::resolve_initial_location`]
335    ///     - [`AndroidFs::resolve_root_initial_location`]
336    ///     - [`FilePicker::pick_files`]
337    ///     - [`FilePicker::pick_file`]
338    ///     - [`FilePicker::pick_dir`]
339    ///     - [`FilePicker::save_file`]
340    /// 
341    /// # Support
342    /// All Android version.
343    /// 
344    /// # References
345    /// - <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
346    #[maybe_async]
347    pub fn pick_dir(
348        &self,
349        initial_location: Option<&FileUri>,
350    ) -> Result<Option<FileUri>> {
351
352        #[cfg(not(target_os = "android"))] {
353            Err(Error::NOT_ANDROID)
354        }
355        #[cfg(target_os = "android")] {
356            self.impls().show_pick_dir_dialog(initial_location).await
357        }
358    }
359
360    /// Opens a system file saver and returns a **writeonly** URI.  
361    /// The returned file may be a newly created file with no content,
362    /// or it may be an existing file with the requested MIME type.  
363    /// If the user cancels, `None` is returned. 
364    /// 
365    /// By default, returned URI is valid until the app or device is terminated. 
366    /// If you want to persist it across app or device restarts, use [`AndroidFs::take_persistable_uri_permission`].
367    /// 
368    /// This provides a standardized file explorer-style interface, 
369    /// and also allows file selection from part of third-party apps or cloud storage.
370    /// 
371    /// Removing and reading the returned files is also supported in most cases, 
372    /// but note that files provided by third-party apps may not.  
373    ///  
374    /// # Args  
375    /// - ***initial_location*** :  
376    /// Indicate the initial location of dialog.    
377    /// This URI works even without any permissions.  
378    /// There is no need to use this if there is no special reason.  
379    /// System will do its best to launch the dialog in the specified entry 
380    /// if it's a directory, or the directory that contains the specified file if not.  
381    /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.   
382    /// This must be a URI taken from following or it's derivative :   
383    ///     - [`PublicStorage::resolve_initial_location`]
384    ///     - [`AndroidFs::resolve_root_initial_location`]
385    ///     - [`FilePicker::pick_files`]
386    ///     - [`FilePicker::pick_file`]
387    ///     - [`FilePicker::pick_dir`]
388    ///     - [`FilePicker::save_file`]
389    /// 
390    /// - ***initial_file_name*** :  
391    /// An initial file name.  
392    /// The user may change this value before creating the file.  
393    /// If no extension is present, 
394    /// the system may infer one from ***mime_type*** and may append it to the file name. 
395    /// But this append-extension operation depends on the model and version.
396    /// 
397    /// - ***mime_type*** :  
398    /// The MIME type of the file to be saved.  
399    /// If this is None, MIME type is inferred from the extension of ***initial_file_name*** (not file name by user input)
400    /// and if that fails, `application/octet-stream` is used.  
401    ///  
402    /// # Support
403    /// All Android version.
404    /// 
405    /// # References
406    /// - <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
407    #[maybe_async]
408    pub fn save_file(
409        &self,
410        initial_location: Option<&FileUri>,
411        initial_file_name: impl AsRef<str>,
412        mime_type: Option<&str>,
413    ) -> Result<Option<FileUri>> {
414        
415        #[cfg(not(target_os = "android"))] {
416            Err(Error::NOT_ANDROID)
417        }
418        #[cfg(target_os = "android")] {
419           self.impls().show_save_file_dialog(initial_location, initial_file_name, mime_type).await 
420        }
421    }
422
423    /// Verify whether [`FilePicker::pick_visual_medias`] is available on a given device.
424    /// 
425    /// # Support
426    /// All Android version.
427    #[maybe_async]
428    pub fn is_visual_media_picker_available(&self) -> Result<bool> {
429        #[cfg(not(target_os = "android"))] {
430            Err(Error::NOT_ANDROID)
431        }
432        #[cfg(target_os = "android")] {
433            self.impls().is_visual_media_picker_available().await
434        }
435    }
436}