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}