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