tauri_plugin_android_fs/api/
file_picker.rs

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