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