tauri_plugin_android_fs/api/
file_picker.rs

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