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