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