tauri_plugin_android_fs/api/android_fs.rs
1use sync_async::sync_async;
2use crate::*;
3use super::*;
4
5
6/// ***Root API***
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/// }
15/// ```
16
17#[sync_async]
18pub struct AndroidFs<R: tauri::Runtime> {
19 #[cfg(target_os = "android")]
20 pub(crate) handle: tauri::plugin::PluginHandle<R>,
21
22 #[cfg(not(target_os = "android"))]
23 #[allow(unused)]
24 pub(crate) handle: 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<R: tauri::Runtime> AndroidFs<R> {
33
34 #[always_sync]
35 pub(crate) fn impls(&self) -> Impls<'_, R> {
36 Impls { handle: &self.handle }
37 }
38}
39
40#[sync_async(
41 use(if_async) api_async::{FileOpener, FilePicker, AppStorage, PrivateStorage, PublicStorage};
42 use(if_sync) api_sync::{FileOpener, FilePicker, AppStorage, PrivateStorage, PublicStorage};
43)]
44impl<R: tauri::Runtime> AndroidFs<R> {
45
46 /// API of file storage that is available to other applications and users.
47 #[always_sync]
48 pub fn public_storage(&self) -> PublicStorage<'_, R> {
49 PublicStorage { handle: &self.handle }
50 }
51
52 /// API of file storage intended for the app's use only.
53 #[always_sync]
54 pub fn private_storage(&self) -> PrivateStorage<'_, R> {
55 PrivateStorage { handle: &self.handle }
56 }
57
58 /// API of file storage intended for the app's use.
59 #[always_sync]
60 pub fn app_storage(&self) -> AppStorage<'_, R> {
61 AppStorage { handle: &self.handle }
62 }
63
64 /// API of file/dir picker.
65 #[always_sync]
66 pub fn file_picker(&self) -> FilePicker<'_, R> {
67 FilePicker { handle: &self.handle }
68 }
69
70 /// API of opening file/dir with other apps.
71 #[always_sync]
72 pub fn file_opener(&self) -> FileOpener<'_, R> {
73 FileOpener { handle: &self.handle }
74 }
75
76 /// Get the file or directory name.
77 ///
78 /// # Args
79 /// - ***uri*** :
80 /// Target URI.
81 /// Must be **readable**.
82 ///
83 /// # Support
84 /// All Android version.
85 #[maybe_async]
86 pub fn get_name(&self, uri: &FileUri) -> Result<String> {
87 #[cfg(not(target_os = "android"))] {
88 Err(Error::NOT_ANDROID)
89 }
90 #[cfg(target_os = "android")] {
91 self.impls().get_entry_name(uri).await
92 }
93 }
94
95 /// Queries the provider to get the MIME type.
96 ///
97 /// For file URIs via [`FileUri::from_path`], the MIME type is determined from the file extension.
98 /// In most other cases, it uses the MIME type that was associated with the file when it was created.
99 /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.
100 ///
101 /// If the target is a directory, an error will occur.
102 /// To check whether the target is a file or a directory, use [`AndroidFs::get_type`].
103 ///
104 /// # Args
105 /// - ***uri*** :
106 /// Target file URI.
107 /// Must be **readable**.
108 ///
109 /// # Support
110 /// All Android version.
111 #[maybe_async]
112 pub fn get_mime_type(&self, uri: &FileUri) -> Result<String> {
113 #[cfg(not(target_os = "android"))] {
114 Err(Error::NOT_ANDROID)
115 }
116 #[cfg(target_os = "android")] {
117 self.impls().get_file_mime_type(uri).await
118 }
119 }
120
121 /// Gets the entry type.
122 ///
123 /// If the target is a directory, returns [`EntryType::Dir`].
124 ///
125 /// If the target is a file, returns [`EntryType::File { mime_type }`](EntryType::File).
126 /// For file URIs via [`FileUri::from_path`], the MIME type is determined from the file extension.
127 /// In most other cases, it uses the MIME type that was associated with the file when it was created.
128 /// If the MIME type is unknown or unset, it falls back to `"application/octet-stream"`.
129 ///
130 /// # Args
131 /// - ***uri*** :
132 /// Target URI.
133 /// Must be **readable**.
134 ///
135 /// # Support
136 /// All Android version.
137 #[maybe_async]
138 pub fn get_type(&self, uri: &FileUri) -> Result<EntryType> {
139 #[cfg(not(target_os = "android"))] {
140 Err(Error::NOT_ANDROID)
141 }
142 #[cfg(target_os = "android")] {
143 self.impls().get_entry_type(uri).await
144 }
145 }
146
147 /// Gets the entry information.
148 ///
149 /// # Args
150 /// - ***uri*** :
151 /// Target URI.
152 /// Must be **readable**.
153 ///
154 /// # Support
155 /// All Android version.
156 #[maybe_async]
157 pub fn get_info(&self, uri: &FileUri) -> Result<Entry> {
158 #[cfg(not(target_os = "android"))] {
159 Err(Error::NOT_ANDROID)
160 }
161 #[cfg(target_os = "android")] {
162 self.impls().get_entry_info(uri).await
163 }
164 }
165
166 /// Gets the file length in bytes.
167 ///
168 /// # Args
169 /// - ***uri*** :
170 /// Target URI.
171 /// Must be **readable**.
172 ///
173 /// # Support
174 /// All Android version.
175 #[maybe_async]
176 pub fn get_len(&self, uri: &FileUri) -> Result<u64> {
177 #[cfg(not(target_os = "android"))] {
178 Err(Error::NOT_ANDROID)
179 }
180 #[cfg(target_os = "android")] {
181 self.impls().get_file_len(uri).await
182 }
183 }
184
185 /// Queries the file system to get information about a file, directory.
186 ///
187 /// # Args
188 /// - ***uri*** :
189 /// Target URI.
190 /// Must be **readable**.
191 ///
192 /// # Note
193 /// This uses [`AndroidFs::open_file`] internally.
194 ///
195 /// # Support
196 /// All Android version.
197 #[maybe_async]
198 pub fn get_metadata(&self, uri: &FileUri) -> Result<std::fs::Metadata> {
199 #[cfg(not(target_os = "android"))] {
200 Err(Error::NOT_ANDROID)
201 }
202 #[cfg(target_os = "android")] {
203 self.impls().get_entry_metadata(uri).await
204 }
205 }
206
207 /// Open the file in **readable** mode.
208 ///
209 /// # Note
210 /// If the target is a file on cloud storage or otherwise not physically present on the device,
211 /// the file provider may downloads the entire contents, and then opens it.
212 /// As a result, this processing may take longer than with regular local files.
213 /// And files might be a pair of pipe or socket for streaming data.
214 ///
215 /// # Args
216 /// - ***uri*** :
217 /// Target file URI.
218 /// This need to be **readable**.
219 ///
220 /// # Support
221 /// All Android version.
222 #[maybe_async]
223 pub fn open_file_readable(&self, uri: &FileUri) -> Result<std::fs::File> {
224 #[cfg(not(target_os = "android"))] {
225 Err(Error::NOT_ANDROID)
226 }
227 #[cfg(target_os = "android")] {
228 self.impls().open_file_readable(uri).await
229 }
230 }
231
232 /// Open the file in **writable** mode.
233 /// This truncates the existing contents.
234 ///
235 /// # Args
236 /// - ***uri*** :
237 /// Target file URI.
238 /// This need to be **writable**.
239 ///
240 /// # Support
241 /// All Android version.
242 #[maybe_async]
243 pub fn open_file_writable(
244 &self,
245 uri: &FileUri,
246 ) -> Result<std::fs::File> {
247
248 #[cfg(not(target_os = "android"))] {
249 Err(Error::NOT_ANDROID)
250 }
251 #[cfg(target_os = "android")] {
252 self.impls().open_file_writable(uri).await
253 }
254 }
255
256 /// Open the file in the specified mode.
257 ///
258 /// # Note
259 /// 1. **Delay**:
260 /// If the target is a file on cloud storage or otherwise not physically present on the device,
261 /// the file provider may downloads the entire contents, and then opens it.
262 /// As a result, this processing may take longer than with regular local files.
263 /// And files might be a pair of pipe or socket for streaming data.
264 ///
265 /// 2. **File mode restrictions**:
266 /// Files provided by third-party apps may not support modes other than
267 /// [`FileAccessMode::Write`] or [`FileAccessMode::Read`].
268 /// However, [`FileAccessMode::Write`] does not guarantee
269 /// that existing contents will always be truncated.
270 /// As a result, if the new contents are shorter than the original, the file may
271 /// become corrupted. To avoid this, consider using
272 /// [`AndroidFs::open_file_writable`], which
273 /// ensure that existing contents are truncated and also automatically apply the
274 /// maximum possible fallbacks.
275 /// - <https://issuetracker.google.com/issues/180526528>
276 ///
277 /// # Args
278 /// - ***uri*** :
279 /// Target file URI.
280 /// This must have corresponding permissions (read, write, or both) for the specified ***mode***.
281 ///
282 /// - ***mode*** :
283 /// Indicates how the file is opened and the permissions granted.
284 ///
285 /// # Support
286 /// All Android version.
287 #[maybe_async]
288 pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> Result<std::fs::File> {
289 #[cfg(not(target_os = "android"))] {
290 Err(Error::NOT_ANDROID)
291 }
292 #[cfg(target_os = "android")] {
293 self.impls().open_file(uri, mode).await
294 }
295 }
296
297 /// For detailed documentation and notes, see [`AndroidFs::open_file`].
298 ///
299 /// The modes specified in ***candidate_modes*** are tried in order.
300 /// If the file can be opened, this returns the file along with the mode used.
301 /// If all attempts fail, an error is returned.
302 #[maybe_async]
303 pub fn open_file_with_fallback(
304 &self,
305 uri: &FileUri,
306 candidate_modes: impl IntoIterator<Item = FileAccessMode>
307 ) -> Result<(std::fs::File, FileAccessMode)> {
308
309 #[cfg(not(target_os = "android"))] {
310 Err(Error::NOT_ANDROID)
311 }
312 #[cfg(target_os = "android")] {
313 self.impls().open_file_with_fallback(uri, candidate_modes).await
314 }
315 }
316
317 /// Reads the entire contents of a file into a bytes vector.
318 ///
319 /// # Args
320 /// - ***uri*** :
321 /// Target file URI.
322 /// Must be **readable**.
323 ///
324 /// # Support
325 /// All Android version.
326 #[maybe_async]
327 pub fn read(&self, uri: &FileUri) -> Result<Vec<u8>> {
328 #[cfg(not(target_os = "android"))] {
329 Err(Error::NOT_ANDROID)
330 }
331 #[cfg(target_os = "android")] {
332 self.impls().read_file(uri).await
333 }
334 }
335
336 /// Reads the entire contents of a file into a string.
337 ///
338 /// # Args
339 /// - ***uri*** :
340 /// Target file URI.
341 /// Must be **readable**.
342 ///
343 /// # Support
344 /// All Android version.
345 #[maybe_async]
346 pub fn read_to_string(&self, uri: &FileUri) -> Result<String> {
347 #[cfg(not(target_os = "android"))] {
348 Err(Error::NOT_ANDROID)
349 }
350 #[cfg(target_os = "android")] {
351 self.impls().read_file_to_string(uri).await
352 }
353 }
354
355 /// Writes a slice as the entire contents of a file.
356 /// This function will entirely replace its contents if it does exist.
357 ///
358 /// # Args
359 /// - ***uri*** :
360 /// Target file URI.
361 /// Must be **writable**.
362 ///
363 /// # Support
364 /// All Android version.
365 #[maybe_async]
366 pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> Result<()> {
367 #[cfg(not(target_os = "android"))] {
368 Err(Error::NOT_ANDROID)
369 }
370 #[cfg(target_os = "android")] {
371 self.impls().write_file(uri, contents).await
372 }
373 }
374
375 /// Copies the contents of the source file to the destination.
376 /// If the destination already has contents, they are truncated before writing the source contents.
377 ///
378 /// # Args
379 /// - ***src*** :
380 /// The URI of source file.
381 /// Must be **readable**.
382 ///
383 /// - ***dest*** :
384 /// The URI of destination file.
385 /// Must be **writable**.
386 ///
387 /// # Support
388 /// All Android version.
389 #[maybe_async]
390 pub fn copy(&self, src: &FileUri, dest: &FileUri) -> Result<()> {
391 #[cfg(not(target_os = "android"))] {
392 Err(Error::NOT_ANDROID)
393 }
394 #[cfg(target_os = "android")] {
395 self.impls().copy_file(src, dest).await
396 }
397 }
398
399 /// Renames a file or directory to a new name, and return new URI.
400 /// Even if the names conflict, the existing file will not be overwritten.
401 ///
402 /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
403 /// In other words, this function returns a new URI without any permissions.
404 /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
405 /// In this, this function returns the same URI as original URI.
406 ///
407 /// # Args
408 /// - ***uri*** :
409 /// URI of target entry.
410 ///
411 /// - ***new_name*** :
412 /// New name of target entry.
413 /// This include extension if use.
414 /// The behaviour in the same name already exists depends on the file provider.
415 /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.
416 /// In the case of files hosted by other applications, errors may occur.
417 /// But at least, the existing file will not be overwritten.
418 /// The system may sanitize these strings as needed, so those strings may not be used as it is.
419 ///
420 /// # Support
421 /// All Android version.
422 #[maybe_async]
423 pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> Result<FileUri> {
424 #[cfg(not(target_os = "android"))] {
425 Err(Error::NOT_ANDROID)
426 }
427 #[cfg(target_os = "android")] {
428 self.impls().rename_entry(uri, new_name).await
429 }
430 }
431
432 /// Remove the file.
433 ///
434 /// # Args
435 /// - ***uri*** :
436 /// Target file URI.
437 /// Must be **read-writable**.
438 /// If not file, an error will occur.
439 ///
440 /// # Support
441 /// All Android version.
442 #[maybe_async]
443 pub fn remove_file(&self, uri: &FileUri) -> Result<()> {
444 #[cfg(not(target_os = "android"))] {
445 Err(Error::NOT_ANDROID)
446 }
447 #[cfg(target_os = "android")] {
448 self.impls().remove_file(uri).await
449 }
450 }
451
452 /// Remove the **empty** directory.
453 ///
454 /// # Args
455 /// - ***uri*** :
456 /// Target directory URI.
457 /// Must be **read-writable**.
458 /// If not empty directory, an error will occur.
459 ///
460 /// # Support
461 /// All Android version.
462 #[maybe_async]
463 pub fn remove_dir(&self, uri: &FileUri) -> Result<()> {
464 #[cfg(not(target_os = "android"))] {
465 Err(Error::NOT_ANDROID)
466 }
467 #[cfg(target_os = "android")] {
468 self.impls().remove_dir_if_empty(uri).await
469 }
470 }
471
472 /// Removes a directory and all its contents. Use carefully!
473 ///
474 /// # Args
475 /// - ***uri*** :
476 /// Target directory URI.
477 /// Must be **read-writable**.
478 /// If not directory, an error will occur.
479 ///
480 /// # Support
481 /// All Android version.
482 #[maybe_async]
483 pub fn remove_dir_all(&self, uri: &FileUri) -> Result<()> {
484 #[cfg(not(target_os = "android"))] {
485 Err(Error::NOT_ANDROID)
486 }
487 #[cfg(target_os = "android")] {
488 self.impls().remove_dir_all(uri).await
489 }
490 }
491
492 /// Build a URI of an **existing** file located at the relative path from the specified directory.
493 /// Error occurs, if the file does not exist.
494 ///
495 /// The permissions and validity period of the returned URI depend on the origin directory
496 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
497 ///
498 /// # Note
499 /// For [`AndroidFs::create_new_file`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
500 /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
501 /// So consider using [`AndroidFs::create_new_file_and_return_relative_path`].
502 ///
503 /// # Args
504 /// - ***uri*** :
505 /// Base directory URI.
506 /// Must be **readable**.
507 ///
508 /// - ***relative_path*** :
509 /// Relative path from base directory.
510 ///
511 /// # Support
512 /// All Android version.
513 #[maybe_async]
514 pub fn resolve_file_uri(
515 &self,
516 dir: &FileUri,
517 relative_path: impl AsRef<std::path::Path>
518 ) -> Result<FileUri> {
519
520 #[cfg(not(target_os = "android"))] {
521 Err(Error::NOT_ANDROID)
522 }
523 #[cfg(target_os = "android")] {
524 self.impls().resolve_file_uri(dir, relative_path, false).await
525 }
526 }
527
528 /// Build a URI of an **existing** directory located at the relative path from the specified directory.
529 /// Error occurs, if the directory does not exist.
530 ///
531 /// The permissions and validity period of the returned URI depend on the origin directory
532 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
533 ///
534 /// # Note
535 /// For [`AndroidFs::create_dir_all`] and etc, the system may sanitize path strings as needed, so those strings may not be used as it is.
536 /// However, this function does not perform any sanitization, so the same ***relative_path*** may still fail.
537 /// So consider using [`AndroidFs::create_dir_all_and_return_relative_path`].
538 ///
539 /// # Args
540 /// - ***uri*** :
541 /// Base directory URI.
542 /// Must be **readable**.
543 ///
544 /// - ***relative_path*** :
545 /// Relative path from base directory.
546 ///
547 /// # Support
548 /// All Android version.
549 #[maybe_async]
550 pub fn resolve_dir_uri(
551 &self,
552 dir: &FileUri,
553 relative_path: impl AsRef<std::path::Path>
554 ) -> Result<FileUri> {
555
556 #[cfg(not(target_os = "android"))] {
557 Err(Error::NOT_ANDROID)
558 }
559 #[cfg(target_os = "android")] {
560 self.impls().resolve_dir_uri(dir, relative_path, false).await
561 }
562 }
563
564 /// See [`AndroidFs::get_thumbnail`] for descriptions.
565 ///
566 /// If thumbnail does not wrote to dest, return false.
567 #[maybe_async]
568 pub fn get_thumbnail_to(
569 &self,
570 src: &FileUri,
571 dest: &FileUri,
572 preferred_size: Size,
573 format: ImageFormat,
574 ) -> Result<bool> {
575
576 #[cfg(not(target_os = "android"))] {
577 Err(Error::NOT_ANDROID)
578 }
579 #[cfg(target_os = "android")] {
580 self.impls().get_file_thumbnail_to_file(src, dest, preferred_size, format).await
581 }
582 }
583
584 /// Get a file thumbnail.
585 /// If thumbnail does not exist it, return None.
586 ///
587 /// Note this does not cache. Please do it in your part if need.
588 ///
589 /// # Args
590 /// - ***uri*** :
591 /// Targe file uri.
592 /// Thumbnail availablty depends on the file provider.
593 /// In general, images and videos are available.
594 /// For file URIs via [`FileUri::from_path`],
595 /// the file type must match the filename extension.
596 /// In this case, the type is determined by the extension and generate thumbnails.
597 /// Otherwise, thumbnails are provided through MediaStore, file provider, and etc.
598 ///
599 /// - ***preferred_size*** :
600 /// Optimal thumbnail size desired.
601 /// This may return a thumbnail of a different size,
602 /// but never more than about double the requested size.
603 /// In any case, the aspect ratio is maintained.
604 ///
605 /// - ***format*** :
606 /// Thumbnail image format.
607 /// If you’re not sure which one to use, [`ImageFormat::Jpeg`] is recommended.
608 /// If you need transparency, use others.
609 ///
610 /// # Support
611 /// All Android version.
612 #[maybe_async]
613 pub fn get_thumbnail(
614 &self,
615 uri: &FileUri,
616 preferred_size: Size,
617 format: ImageFormat,
618 ) -> Result<Option<Vec<u8>>> {
619
620 #[cfg(not(target_os = "android"))] {
621 Err(Error::NOT_ANDROID)
622 }
623 #[cfg(target_os = "android")] {
624 self.impls().get_file_thumbnail(uri, preferred_size, format).await
625 }
626 }
627
628 /// Get a file thumbnail that encoded to base64 string.
629 /// If thumbnail does not exist it, return None.
630 ///
631 /// Note this does not cache. Please do it in your part if need.
632 ///
633 /// # Inner
634 /// This uses Kotlin's [`android.util.Base64.encodeToString(.., android.util.Base64.NO_WRAP)`](https://developer.android.com/reference/android/util/Base64#encodeToString(byte[],%20int)) internally.
635 /// It is the same as [`base64::engine::general_purpose::STANDARD`](https://docs.rs/base64/0.22.1/base64/engine/general_purpose/constant.STANDARD.html) in `base64` crate.
636 ///
637 /// # Args
638 /// - ***uri*** :
639 /// Targe file uri.
640 /// Thumbnail availablty depends on the file provider.
641 /// In general, images and videos are available.
642 /// For file URIs via [`FileUri::from_path`],
643 /// the file type must match the filename extension.
644 /// In this case, the type is determined by the extension and generate thumbnails.
645 /// Otherwise, thumbnails are provided through MediaStore, file provider, and etc.
646 ///
647 /// - ***preferred_size*** :
648 /// Optimal thumbnail size desired.
649 /// This may return a thumbnail of a different size,
650 /// but never more than about double the requested size.
651 /// In any case, the aspect ratio is maintained.
652 ///
653 /// - ***format*** :
654 /// Thumbnail image format.
655 /// If you’re not sure which one to use, [`ImageFormat::Jpeg`] is recommended.
656 /// If you need transparency, use others.
657 ///
658 /// # Support
659 /// All Android version.
660 #[maybe_async]
661 pub fn get_thumbnail_base64(
662 &self,
663 uri: &FileUri,
664 preferred_size: Size,
665 format: ImageFormat,
666 ) -> Result<Option<String>> {
667
668 #[cfg(not(target_os = "android"))] {
669 Err(Error::NOT_ANDROID)
670 }
671 #[cfg(target_os = "android")] {
672 self.impls().get_file_thumbnail_base64(uri, preferred_size, format).await
673 }
674 }
675
676 /// Creates a new empty file in the specified location and returns a URI.
677 ///
678 /// The permissions and validity period of the returned URIs depend on the origin directory
679 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
680 ///
681 /// # Args
682 /// - ***dir*** :
683 /// The URI of the base directory.
684 /// Must be **read-write**.
685 ///
686 /// - ***relative_path*** :
687 /// The file path relative to the base directory.
688 /// Any missing parent directories will be created automatically.
689 /// If a file with the same name already exists, a sequential number may be appended to ensure uniqueness.
690 /// If the file has no extension, one may be inferred from ***mime_type*** and appended to the file name.
691 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
692 /// Note those operation may vary depending on the file provider.
693 ///
694 /// - ***mime_type*** :
695 /// The MIME type of the file to be created.
696 /// If this is None, MIME type is inferred from the extension of ***relative_path***
697 /// and if that fails, `application/octet-stream` is used.
698 ///
699 /// # Support
700 /// All Android version.
701 #[maybe_async]
702 pub fn create_new_file(
703 &self,
704 dir: &FileUri,
705 relative_path: impl AsRef<std::path::Path>,
706 mime_type: Option<&str>
707 ) -> Result<FileUri> {
708
709 #[cfg(not(target_os = "android"))] {
710 Err(Error::NOT_ANDROID)
711 }
712 #[cfg(target_os = "android")] {
713 self.impls().create_new_file(dir, relative_path, mime_type).await
714 }
715 }
716
717 /// Creates a new empty file in the specified location and returns a URI and relative path.
718 ///
719 /// The returned relative path may be sanitized and have a suffix appended to the file name,
720 /// so it may differ from the input relative path.
721 /// And it is a logical path within the file provider and
722 /// available for [`AndroidFs::resolve_file_uri`].
723 ///
724 /// The permissions and validity period of the returned URIs depend on the origin directory
725 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
726 ///
727 /// # Args
728 /// - ***dir*** :
729 /// The URI of the base directory.
730 /// Must be **read-write**.
731 ///
732 /// - ***relative_path*** :
733 /// The file path relative to the base directory.
734 /// Any missing parent directories will be created automatically.
735 /// If a file with the same name already exists, a sequential number may be appended to ensure uniqueness.
736 /// If the file has no extension, one may be inferred from ***mime_type*** and appended to the file name.
737 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
738 /// Note those operation may vary depending on the file provider.
739 ///
740 /// - ***mime_type*** :
741 /// The MIME type of the file to be created.
742 /// If this is None, MIME type is inferred from the extension of ***relative_path***
743 /// and if that fails, `application/octet-stream` is used.
744 ///
745 /// # Support
746 /// All Android version.
747 #[maybe_async]
748 pub fn create_new_file_and_return_relative_path(
749 &self,
750 dir: &FileUri,
751 relative_path: impl AsRef<std::path::Path>,
752 mime_type: Option<&str>
753 ) -> Result<(FileUri, std::path::PathBuf)> {
754
755 #[cfg(not(target_os = "android"))] {
756 Err(Error::NOT_ANDROID)
757 }
758 #[cfg(target_os = "android")] {
759 self.impls().create_new_file_and_retrun_relative_path(dir, relative_path, mime_type).await
760 }
761 }
762
763 /// Creates a directory and it's parents at the specified location if they are missing,
764 /// then return the URI.
765 /// If it already exists, do nothing and just return the direcotry uri.
766 ///
767 /// [`AndroidFs::create_new_file`] does this automatically, so there is no need to use it together.
768 ///
769 /// # Args
770 /// - ***dir*** :
771 /// The URI of the base directory.
772 /// Must be **read-write**.
773 ///
774 /// - ***relative_path*** :
775 /// The directory path relative to the base directory.
776 /// Any missing parent directories will be created automatically.
777 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
778 /// Note this sanitization may vary depending on the file provider.
779 ///
780 /// # Support
781 /// All Android version.
782 #[maybe_async]
783 pub fn create_dir_all(
784 &self,
785 dir: &FileUri,
786 relative_path: impl AsRef<std::path::Path>,
787 ) -> Result<FileUri> {
788
789 #[cfg(not(target_os = "android"))] {
790 Err(Error::NOT_ANDROID)
791 }
792 #[cfg(target_os = "android")] {
793 self.impls().create_dir_all(dir, relative_path).await
794 }
795 }
796
797 /// Recursively create a directory and all of its parent components if they are missing,
798 /// then return the URI and relative path.
799 ///
800 /// The returned relative path may be sanitized,
801 /// so it may differ from the input relative path.
802 /// And it is a logical path within the file provider and
803 /// available for [`AndroidFs::resolve_dir_uri`].
804 ///
805 /// [`AndroidFs::create_new_file`] does this automatically, so there is no need to use it together.
806 ///
807 /// # Args
808 /// - ***dir*** :
809 /// The URI of the base directory.
810 /// Must be **read-write**.
811 ///
812 /// - ***relative_path*** :
813 /// The directory path relative to the base directory.
814 /// Any missing parent directories will be created automatically.
815 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
816 /// Note this sanitization may vary depending on the file provider.
817 ///
818 /// # Support
819 /// All Android version.
820 #[maybe_async]
821 pub fn create_dir_all_and_return_relative_path(
822 &self,
823 dir: &FileUri,
824 relative_path: impl AsRef<std::path::Path>,
825 ) -> Result<(FileUri, std::path::PathBuf)> {
826
827 #[cfg(not(target_os = "android"))] {
828 Err(Error::NOT_ANDROID)
829 }
830 #[cfg(target_os = "android")] {
831 self.impls().create_dir_all_and_return_relative_path(dir, relative_path).await
832 }
833 }
834
835 /// Returns the child files and directories of the specified directory.
836 /// The order of the entries is not guaranteed.
837 ///
838 /// The permissions and validity period of the returned URIs depend on the origin directory
839 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
840 ///
841 /// This retrieves all metadata including `uri`, `name`, `last_modified`, `len`, and `mime_type`.
842 /// If only specific information is needed,
843 /// using [`AndroidFs::read_dir_with_options`] will improve performance.
844 ///
845 /// # Note
846 /// The returned type is an iterator, but the file system call is not executed lazily.
847 /// Instead, all data is retrieved at once.
848 /// For directories containing thousands or even tens of thousands of entries,
849 /// this function may take several seconds to complete.
850 /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
851 ///
852 /// # Args
853 /// - ***uri*** :
854 /// Target directory URI.
855 /// Must be **readable**.
856 ///
857 /// # Support
858 /// All Android version.
859 #[maybe_async]
860 pub fn read_dir(&self, uri: &FileUri) -> Result<impl Iterator<Item = Entry>> {
861 let entries = self.read_dir_with_options(uri, EntryOptions::ALL).await?
862 .map(Entry::try_from)
863 .filter_map(Result::ok);
864
865 Ok(entries)
866 }
867
868 /// Returns the child files and directories of the specified directory.
869 /// The order of the entries is not guaranteed.
870 ///
871 /// The permissions and validity period of the returned URIs depend on the origin directory
872 /// (e.g., the top directory selected by [`FilePicker::pick_dir`])
873 ///
874 /// # Note
875 /// The returned type is an iterator, but the file system call is not executed lazily.
876 /// Instead, all data is retrieved at once.
877 /// For directories containing thousands or even tens of thousands of entries,
878 /// this function may take several seconds to complete.
879 /// The returned iterator itself is low-cost, as it only performs lightweight data formatting.
880 ///
881 /// # Args
882 /// - ***uri*** :
883 /// Target directory URI.
884 /// Must be **readable**.
885 ///
886 /// # Support
887 /// All Android version.
888 #[maybe_async]
889 pub fn read_dir_with_options(
890 &self,
891 uri: &FileUri,
892 options: EntryOptions
893 ) -> Result<impl Iterator<Item = OptionalEntry>> {
894
895 #[cfg(not(target_os = "android"))] {
896 Err::<std::iter::Empty<_>, _>(Error::NOT_ANDROID)
897 }
898 #[cfg(target_os = "android")] {
899 self.impls().read_dir_with_options(uri, options).await
900 }
901 }
902
903 /// Take persistent permission to access the file, directory and its descendants.
904 /// This is a prolongation of an already acquired permission, not the acquisition of a new one.
905 ///
906 /// This works by just calling, without displaying any confirmation to the user.
907 ///
908 /// Note that [there is a limit to the total number of URI that can be made persistent by this function.](https://stackoverflow.com/questions/71099575/should-i-release-persistableuripermission-when-a-new-storage-location-is-chosen/71100621#71100621)
909 /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].
910 /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
911 /// So check by [`AndroidFs::check_persisted_uri_permission`].
912 /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
913 ///
914 /// # Args
915 /// - **uri** :
916 /// URI of the target file or directory.
917 /// This must be a URI taken from following :
918 /// - [`FilePicker::pick_files`]
919 /// - [`FilePicker::pick_file`]
920 /// - [`FilePicker::pick_visual_medias`]
921 /// - [`FilePicker::pick_visual_media`]
922 /// - [`FilePicker::pick_dir`]
923 /// - [`FilePicker::save_file`]
924 /// - [`AndroidFs::resolve_file_uri`], [`AndroidFs::resolve_dir_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :
925 /// If use URI from thoese fucntions, the permissions of the origin directory URI is persisted, not a entry iteself by this function.
926 /// Because the permissions and validity period of the descendant entry URIs depend on the origin directory.
927 ///
928 /// # Support
929 /// All Android version.
930 #[maybe_async]
931 pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> Result<()> {
932 #[cfg(not(target_os = "android"))] {
933 Err(Error::NOT_ANDROID)
934 }
935 #[cfg(target_os = "android")] {
936 self.impls().take_persistable_uri_permission(uri).await
937 }
938 }
939
940 /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
941 /// Returns false if there are only non-persistent permissions or no permissions.
942 ///
943 /// # Args
944 /// - **uri** :
945 /// URI of the target file or directory.
946 /// This must be a URI taken from following :
947 /// - [`FilePicker::pick_files`]
948 /// - [`FilePicker::pick_file`]
949 /// - [`FilePicker::pick_visual_medias`]
950 /// - [`FilePicker::pick_visual_media`]
951 /// - [`FilePicker::pick_dir`]
952 /// - [`FilePicker::save_file`]
953 /// - [`AndroidFs::resolve_file_uri`], [`AndroidFs::resolve_dir_uri`], [`AndroidFs::read_dir`], [`AndroidFs::create_new_file`], [`AndroidFs::create_dir_all`] :
954 /// If use URI from thoese fucntions, the permissions of the origin directory URI is checked, not a entry iteself by this function.
955 /// Because the permissions and validity period of the descendant entry URIs depend on the origin directory.
956 ///
957 /// - **permission** :
958 /// The permission you want to check.
959 ///
960 /// # Support
961 /// All Android version.
962 #[maybe_async]
963 pub fn check_persisted_uri_permission(
964 &self,
965 uri: &FileUri,
966 permission: UriPermission
967 ) -> Result<bool> {
968
969 #[cfg(not(target_os = "android"))] {
970 Err(Error::NOT_ANDROID)
971 }
972 #[cfg(target_os = "android")] {
973 self.impls().check_persisted_uri_permission(uri, permission).await
974 }
975 }
976
977 /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.
978 ///
979 /// # Support
980 /// All Android version.
981 #[maybe_async]
982 pub fn get_all_persisted_uri_permissions(&self) -> Result<impl Iterator<Item = PersistedUriPermissionState>> {
983 #[cfg(not(target_os = "android"))] {
984 Err::<std::iter::Empty<_>, _>(Error::NOT_ANDROID)
985 }
986 #[cfg(target_os = "android")] {
987 self.impls().get_all_persisted_uri_permissions().await
988 }
989 }
990
991 /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
992 ///
993 /// # Args
994 /// - ***uri*** :
995 /// URI of the target file or directory.
996 ///
997 /// # Support
998 /// All Android version.
999 #[maybe_async]
1000 pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> Result<()> {
1001 #[cfg(not(target_os = "android"))] {
1002 Err(Error::NOT_ANDROID)
1003 }
1004 #[cfg(target_os = "android")] {
1005 self.impls().release_persisted_uri_permission(uri).await
1006 }
1007 }
1008
1009 /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].
1010 ///
1011 /// # Support
1012 /// All Android version.
1013 #[maybe_async]
1014 pub fn release_all_persisted_uri_permissions(&self) -> Result<()> {
1015 #[cfg(not(target_os = "android"))] {
1016 Err(Error::NOT_ANDROID)
1017 }
1018 #[cfg(target_os = "android")] {
1019 self.impls().release_all_persisted_uri_permissions().await
1020 }
1021 }
1022
1023 /// See [`AppStorage::get_volumes`] or [`PublicStorage::get_volumes`] for details.
1024 ///
1025 /// The difference is that this does not perform any filtering.
1026 /// You can it by [`StorageVolume { is_available_for_app_storage, is_available_for_public_storage, .. } `](StorageVolume).
1027 #[maybe_async]
1028 pub fn get_volumes(&self) -> Result<Vec<StorageVolume>> {
1029 #[cfg(not(target_os = "android"))] {
1030 Err(Error::NOT_ANDROID)
1031 }
1032 #[cfg(target_os = "android")] {
1033 self.impls().get_available_storage_volumes().await
1034 }
1035 }
1036
1037 /// See [`AppStorage::get_primary_volume`] or [`PublicStorage::get_primary_volume`] for details.
1038 ///
1039 /// The difference is that this does not perform any filtering.
1040 /// You can it by [`StorageVolume { is_available_for_app_storage, is_available_for_public_storage, .. } `](StorageVolume).
1041 #[maybe_async]
1042 pub fn get_primary_volume(&self) -> Result<Option<StorageVolume>> {
1043 #[cfg(not(target_os = "android"))] {
1044 Err(Error::NOT_ANDROID)
1045 }
1046 #[cfg(target_os = "android")] {
1047 self.impls().get_primary_storage_volume_if_available().await
1048 }
1049 }
1050
1051 /// Builds the storage volume root URI.
1052 ///
1053 /// This should only be used as `initial_location` in the file picker, such as [`FilePicker::pick_files`].
1054 /// It must not be used for any other purpose.
1055 ///
1056 /// This is useful when selecting save location,
1057 /// but when selecting existing entries, `initial_location` is often better with None.
1058 ///
1059 /// # Args
1060 /// - ***volume_id*** :
1061 /// ID of the storage volume, such as internal storage, SD card, etc.
1062 /// If `None` is provided, [`the primary storage volume`](AndroidFs::get_primary_volume) will be used.
1063 ///
1064 /// # Support
1065 /// All Android version.
1066 #[maybe_async]
1067 pub fn resolve_root_initial_location(&self, volume_id: Option<&StorageVolumeId>) -> Result<FileUri> {
1068 #[cfg(not(target_os = "android"))] {
1069 Err(Error::NOT_ANDROID)
1070 }
1071 #[cfg(target_os = "android")] {
1072 self.impls().resolve_root_initial_location(volume_id).await
1073 }
1074 }
1075
1076 /// Verify whether this plugin is available.
1077 ///
1078 /// On Android, this returns true.
1079 /// On other platforms, this returns false.
1080 #[always_sync]
1081 pub fn is_available(&self) -> bool {
1082 cfg!(target_os = "android")
1083 }
1084
1085 /// Get the api level of this Android device.
1086 ///
1087 /// The correspondence table between API levels and Android versions can be found following.
1088 /// <https://developer.android.com/guide/topics/manifest/uses-sdk-element#api-level-table>
1089 ///
1090 /// If you want the constant value of the API level from an Android version, there is the [`api_level`] module.
1091 ///
1092 /// # Table
1093 /// | Android version | API Level |
1094 /// |------------------|-----------|
1095 /// | 16.0 | 36 |
1096 /// | 15.0 | 35 |
1097 /// | 14.0 | 34 |
1098 /// | 13.0 | 33 |
1099 /// | 12L | 32 |
1100 /// | 12.0 | 31 |
1101 /// | 11.0 | 30 |
1102 /// | 10.0 | 29 |
1103 /// | 9.0 | 28 |
1104 /// | 8.1 | 27 |
1105 /// | 8.0 | 26 |
1106 /// | 7.1 - 7.1.2 | 25 |
1107 /// | 7.0 | 24 |
1108 ///
1109 /// Tauri does not support Android versions below 7.
1110 #[always_sync]
1111 pub fn api_level(&self) -> Result<i32> {
1112 #[cfg(not(target_os = "android"))] {
1113 Err(Error::NOT_ANDROID)
1114 }
1115 #[cfg(target_os = "android")] {
1116 self.impls().api_level()
1117 }
1118 }
1119
1120 /// See [`AndroidFs::resolve_file_uri`] for details.
1121 ///
1122 /// The difference is that this may skip checking whether the target exists and is a file.
1123 /// As a result, in many cases it avoids the delay (from a few to several tens of milliseconds) caused by calling a Kotlin-side function.
1124 ///
1125 /// Note that, depending on the situation,
1126 /// the Kotlin-side function may be called or a check may be performed,
1127 /// which could result in an error or a delay.
1128 #[maybe_async]
1129 pub fn _resolve_file_uri(
1130 &self,
1131 dir: &FileUri,
1132 relative_path: impl AsRef<std::path::Path>
1133 ) -> Result<FileUri> {
1134
1135 #[cfg(not(target_os = "android"))] {
1136 Err(Error::NOT_ANDROID)
1137 }
1138 #[cfg(target_os = "android")] {
1139 self.impls().resolve_file_uri(dir, relative_path, true).await
1140 }
1141 }
1142
1143 /// See [`AndroidFs::resolve_dir_uri`] for details.
1144 ///
1145 /// The difference is that this may skip checking whether the target exists and is a directory.
1146 /// As a result, in many cases it avoids the delay (from a few to several tens of milliseconds) caused by calling a Kotlin-side function.
1147 ///
1148 /// Note that, depending on the situation,
1149 /// the Kotlin-side function may be called or a check may be performed,
1150 /// which could result in an error or a delay.
1151 #[maybe_async]
1152 pub fn _resolve_dir_uri(
1153 &self,
1154 dir: &FileUri,
1155 relative_path: impl AsRef<std::path::Path>
1156 ) -> Result<FileUri> {
1157
1158 #[cfg(not(target_os = "android"))] {
1159 Err(Error::NOT_ANDROID)
1160 }
1161 #[cfg(target_os = "android")] {
1162 self.impls().resolve_dir_uri(dir, relative_path, true).await
1163 }
1164 }
1165}