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}