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