tauri_plugin_android_fs/api/android_fs.rs
1use std::io::{Read as _, Write as _};
2use crate::*;
3
4
5/// ***Root API***
6///
7/// # Examples
8/// ```
9/// fn example(app: &tauri::AppHandle) {
10/// use tauri_plugin_android_fs::AndroidFsExt as _;
11///
12/// let api = app.android_fs();
13/// }
14/// ```
15pub struct AndroidFs<R: tauri::Runtime> {
16 #[cfg(target_os = "android")]
17 pub(crate) app: tauri::AppHandle<R>,
18
19 #[cfg(target_os = "android")]
20 pub(crate) api: tauri::plugin::PluginHandle<R>,
21
22 #[cfg(target_os = "android")]
23 pub(crate) intent_lock: std::sync::Mutex<()>,
24
25 #[cfg(not(target_os = "android"))]
26 _marker: std::marker::PhantomData<fn() -> R>
27}
28
29impl<R: tauri::Runtime> AndroidFs<R> {
30
31 pub(crate) fn new<C: serde::de::DeserializeOwned>(
32 app: tauri::AppHandle<R>,
33 api: tauri::plugin::PluginApi<R, C>,
34 ) -> crate::Result<Self> {
35
36 #[cfg(target_os = "android")] {
37 Ok(Self {
38 api: api.register_android_plugin("com.plugin.android_fs", "AndroidFsPlugin")?,
39 app,
40 intent_lock: std::sync::Mutex::new(())
41 })
42 }
43
44 #[cfg(not(target_os = "android"))] {
45 Ok(Self { _marker: Default::default() })
46 }
47 }
48}
49
50impl<R: tauri::Runtime> AndroidFs<R> {
51
52 /// Verify whether this plugin is available.
53 ///
54 /// On Android, this returns true.
55 /// On other platforms, this returns false.
56 pub fn is_available(&self) -> bool {
57 cfg!(target_os = "android")
58 }
59
60 /// Get the file or directory name.
61 ///
62 /// # Args
63 /// - ***uri*** :
64 /// Target URI.
65 /// This needs to be **readable**.
66 ///
67 /// # Support
68 /// All.
69 pub fn get_name(&self, uri: &FileUri) -> crate::Result<String> {
70 on_android!({
71 impl_se!(struct Req<'a> { uri: &'a FileUri });
72 impl_de!(struct Res { name: String });
73
74 self.api
75 .run_mobile_plugin::<Res>("getName", Req { uri })
76 .map(|v| v.name)
77 .map_err(Into::into)
78 })
79 }
80
81 /// Query the provider to get mime type.
82 /// If the directory, this returns `None`.
83 /// If the file, this returns no `None`.
84 /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.
85 ///
86 /// In the case of files in [`PrivateStorage`], this is determined from the extension.
87 ///
88 /// # Args
89 /// - ***uri*** :
90 /// Target URI.
91 /// This needs to be **readable**.
92 ///
93 /// # Support
94 /// All.
95 pub fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>> {
96 on_android!({
97 impl_se!(struct Req<'a> { uri: &'a FileUri });
98 impl_de!(struct Res { value: Option<String> });
99
100 self.api
101 .run_mobile_plugin::<Res>("getMimeType", Req { uri })
102 .map(|v| v.value)
103 .map_err(Into::into)
104 })
105 }
106
107 /// Queries the file system to get information about a file, directory.
108 ///
109 /// # Args
110 /// - ***uri*** :
111 /// Target URI.
112 /// This needs to be **readable**.
113 ///
114 /// # Note
115 /// This uses [`AndroidFs::open_file`] internally.
116 ///
117 /// # Support
118 /// All.
119 pub fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
120 on_android!({
121 let file = self.open_file(uri, FileAccessMode::Read)?;
122 Ok(file.metadata()?)
123 })
124 }
125
126 /// Open a file in the specified mode.
127 ///
128 /// # Args
129 /// - ***uri*** :
130 /// Target file URI.
131 /// This must have corresponding permissions (read, write, or both) for the specified **mode**.
132 ///
133 /// - ***mode*** :
134 /// Indicates how the file is opened and the permissions granted.
135 /// Note that files hosted by third-party apps may not support following:
136 /// - [`FileAccessMode::ReadWrite`]
137 /// - [`FileAccessMode::ReadWriteTruncate`]
138 /// - [`FileAccessMode::WriteAppend`]
139 /// (ex: Files on GoogleDrive)
140 ///
141 /// # Note
142 /// This method uses a FileDescriptor internally.
143 /// However, if the target file does not physically exist on the device, such as cloud-based files,
144 /// the write operation using a FileDescriptor may not be reflected properly.
145 /// In such cases, consider using [AndroidFs::write_via_kotlin],
146 /// which writes using a standard method,
147 /// or [AndroidFs::write], which automatically falls back to that approach when necessary.
148 /// If you specifically need to write using stream not entire contents, see [AndroidFs::write_via_kotlin_in] or [AndroidFs::copy_via_kotlin] with temporary file.
149 ///
150 /// It seems that the issue does not occur on all cloud storage platforms. At least, files on Google Drive have issues,
151 /// but files on Dropbox can be written to correctly using a FileDescriptor.
152 /// If you encounter issues with cloud storage other than Google Drive, please let me know on [Github](https://github.com/aiueo13/tauri-plugin-android-fs/issues/new).
153 /// This information will be used in [AndroidFs::need_write_via_kotlin] used by `AndroidFs::write`.
154 ///
155 /// There are no problems with file reading.
156 ///
157 /// # Support
158 /// All.
159 pub fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File> {
160 on_android!({
161 impl_se!(struct Req<'a> { uri: &'a FileUri, mode: &'a str });
162 impl_de!(struct Res { fd: std::os::fd::RawFd });
163
164 let mode = match mode {
165 FileAccessMode::Read => "r",
166 FileAccessMode::Write => "w",
167 FileAccessMode::WriteTruncate => "wt",
168 FileAccessMode::WriteAppend => "wa",
169 FileAccessMode::ReadWriteTruncate => "rwt",
170 FileAccessMode::ReadWrite => "rw",
171 };
172
173 self.api
174 .run_mobile_plugin::<Res>("getFileDescriptor", Req { uri, mode })
175 .map(|v| {
176 use std::os::fd::FromRawFd;
177 unsafe { std::fs::File::from_raw_fd(v.fd) }
178 })
179 .map_err(Into::into)
180 })
181 }
182
183 /// Reads the entire contents of a file into a bytes vector.
184 ///
185 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
186 ///
187 /// # Args
188 /// - ***uri*** :
189 /// Target file URI.
190 /// This needs to be **readable**.
191 ///
192 /// # Support
193 /// All.
194 pub fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
195 on_android!({
196 let mut file = self.open_file(uri, FileAccessMode::Read)?;
197 let mut buf = file.metadata().ok()
198 .map(|m| m.len() as usize)
199 .map(Vec::with_capacity)
200 .unwrap_or_else(Vec::new);
201
202 file.read_to_end(&mut buf)?;
203 Ok(buf)
204 })
205 }
206
207 /// Reads the entire contents of a file into a string.
208 ///
209 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
210 ///
211 /// # Args
212 /// - ***uri*** :
213 /// Target file URI.
214 /// This needs to be **readable**.
215 ///
216 /// # Support
217 /// All.
218 pub fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
219 on_android!({
220 let mut file = self.open_file(uri, FileAccessMode::Read)?;
221 let mut buf = file.metadata().ok()
222 .map(|m| m.len() as usize)
223 .map(String::with_capacity)
224 .unwrap_or_else(String::new);
225
226 file.read_to_string(&mut buf)?;
227 Ok(buf)
228 })
229 }
230
231 /// Writes a slice as the entire contents of a file.
232 /// This function will entirely replace its contents if it does exist.
233 ///
234 /// If you want to operate the file, use [`AndroidFs::open_file`] instead.
235 ///
236 /// # Args
237 /// - ***uri*** :
238 /// Target file URI.
239 /// This needs to be **writable**.
240 ///
241 /// # Support
242 /// All.
243 pub fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
244 on_android!({
245 if self.need_write_via_kotlin(uri)? {
246 self.write_via_kotlin(uri, contents)?;
247 }
248 else {
249 let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
250 file.write_all(contents.as_ref())?;
251 }
252 Ok(())
253 })
254 }
255
256 /// Writes a slice as the entire contents of a file.
257 /// This function will entirely replace its contents if it does exist.
258 ///
259 /// Differences from `std::fs::File::write_all` is the process is done on Kotlin side.
260 /// See [`AndroidFs::open_file`] for why this function exists.
261 ///
262 /// If [`AndroidFs::write`] is used, it automatically fall back to this by [`AndroidFs::need_write_via_kotlin`],
263 /// so there should be few opportunities to use this.
264 ///
265 /// If you want to write using `std::fs::File`, not entire contents, use [`AndroidFs::write_via_kotlin_in`].
266 ///
267 /// # Inner process
268 /// The contents is written to a temporary file by Rust side
269 /// and then copied to the specified file on Kotlin side by [`AndroidFs::copy_via_kotlin`].
270 ///
271 /// # Support
272 /// All.
273 pub fn write_via_kotlin(
274 &self,
275 uri: &FileUri,
276 contents: impl AsRef<[u8]>
277 ) -> crate::Result<()> {
278
279 on_android!({
280 self.write_via_kotlin_in(uri, |file| file.write_all(contents.as_ref()))
281 })
282 }
283
284 /// See [`AndroidFs::write_via_kotlin`] for information.
285 /// Use this if you want to write using `std::fs::File`, not entire contents.
286 ///
287 /// If you want to retain the file outside the closure,
288 /// you can perform the same operation using [`AndroidFs::copy_via_kotlin`] and [`PrivateStorage`].
289 /// For details, please refer to the internal implementation of this function.
290 ///
291 /// # Args
292 /// - ***uri*** :
293 /// Target file URI to write.
294 ///
295 /// - **contetns_writer** :
296 /// A closure that accepts a mutable reference to a `std::fs::File`
297 /// and performs the actual write operations. Note that this represents a temporary file.
298 pub fn write_via_kotlin_in<T>(
299 &self,
300 uri: &FileUri,
301 contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
302 ) -> crate::Result<T> {
303
304 on_android!({
305 let tmp_file_path = {
306 use std::sync::atomic::{AtomicUsize, Ordering};
307
308 static COUNTER: AtomicUsize = AtomicUsize::new(0);
309 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
310
311 self.private_storage().resolve_path_with(
312 PrivateDir::Cache,
313 format!("{TMP_DIR_RELATIVE_PATH}/write_via_kotlin_in {id}")
314 )?
315 };
316
317 if let Some(parent) = tmp_file_path.parent() {
318 let _ = std::fs::create_dir_all(parent);
319 }
320
321 let result = {
322 let ref mut file = std::fs::File::create(&tmp_file_path)?;
323 contents_writer(file)
324 };
325
326 let result = result
327 .map_err(crate::Error::from)
328 .and_then(|t| self.copy_via_kotlin(&(&tmp_file_path).into(), uri).map(|_| t));
329
330 let _ = std::fs::remove_file(&tmp_file_path);
331
332 result
333 })
334 }
335
336 /// Determines if the file needs to be written via Kotlin side instead of Rust side.
337 /// Currently, this returns true only if the file is on GoogleDrive.
338 ///
339 /// # Support
340 /// All.
341 pub fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
342 on_android!({
343 Ok(uri.uri.starts_with("content://com.google.android.apps.docs.storage"))
344 })
345 }
346
347 /// Copies the contents of src file to dest.
348 /// If dest already has contents, it is truncated before write src contents.
349 ///
350 /// This copy process is done on Kotlin side, not on Rust.
351 /// Large files in GB units are also supported.
352 /// Note that [`AndroidFs::copy`] and [`std::io::copy`] are faster.
353 ///
354 /// See [`AndroidFs::write_via_kotlin`] for why this function exists.
355 ///
356 /// # Args
357 /// - ***src*** :
358 /// The URI of source file.
359 /// This needs to be **readable**.
360 ///
361 /// - ***dest*** :
362 /// The URI of destination file.
363 /// This needs to be **writable**.
364 ///
365 /// # Support
366 /// All.
367 pub fn copy_via_kotlin(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
368 on_android!({
369 impl_se!(struct Req<'a> { src: &'a FileUri, dest: &'a FileUri });
370 impl_de!(struct Res;);
371
372 self.api
373 .run_mobile_plugin::<Res>("copyFile", Req { src, dest })
374 .map(|_| ())
375 .map_err(Into::into)
376 })
377 }
378
379 /// Copies the contents of src file to dest.
380 /// If dest already has contents, it is truncated before write src contents.
381 ///
382 /// # Args
383 /// - ***src*** :
384 /// The URI of source file.
385 /// This needs to be **readable**.
386 ///
387 /// - ***dest*** :
388 /// The URI of destination file.
389 /// This needs to be **writable**.
390 ///
391 /// # Support
392 /// All.
393 pub fn copy(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()> {
394 on_android!({
395 let src = &mut self.open_file(src, FileAccessMode::Read)?;
396 let dest = &mut self.open_file(dest, FileAccessMode::WriteTruncate)?;
397 std::io::copy(src, dest)?;
398 Ok(())
399 })
400 }
401
402 /// Renames a file or directory to a new name, and return new URI.
403 /// Even if the names conflict, the existing file will not be overwritten.
404 ///
405 /// Note that when files or folders (and their descendants) are renamed, their URIs will change, and any previously granted permissions will be lost.
406 /// In other words, this function returns a new URI without any permissions.
407 /// However, for files created in PublicStorage, the URI remains unchanged even after such operations, and all permissions are retained.
408 /// In this, this function returns the same URI as original URI.
409 ///
410 /// # Args
411 /// - ***uri*** :
412 /// URI of target entry.
413 ///
414 /// - ***new_name*** :
415 /// New name of target entry.
416 /// This include extension if use.
417 /// The behaviour in the same name already exists depends on the file provider.
418 /// In the case of e.g. [`PublicStorage`], the suffix (e.g. `(1)`) is added to this name.
419 /// In the case of files hosted by other applications, errors may occur.
420 /// But at least, the existing file will not be overwritten.
421 ///
422 /// # Support
423 /// All.
424 pub fn rename(&self, uri: &FileUri, new_name: impl AsRef<str>) -> crate::Result<FileUri> {
425 on_android!({
426 impl_se!(struct Req<'a> { uri: &'a FileUri, new_name: &'a str });
427
428 let new_name = new_name.as_ref();
429
430 self.api
431 .run_mobile_plugin::<FileUri>("rename", Req { uri, new_name })
432 .map_err(Into::into)
433 })
434 }
435
436 /// Remove the file.
437 ///
438 /// # Args
439 /// - ***uri*** :
440 /// Target file URI.
441 /// This needs to be **writable**, at least. But even if it is,
442 /// removing may not be possible in some cases.
443 /// For details, refer to the documentation of the function that provided the URI.
444 /// If not file, an error will occur.
445 ///
446 /// # Support
447 /// All.
448 pub fn remove_file(&self, uri: &FileUri) -> crate::Result<()> {
449 on_android!({
450 impl_se!(struct Req<'a> { uri: &'a FileUri });
451 impl_de!(struct Res;);
452
453 self.api
454 .run_mobile_plugin::<Res>("deleteFile", Req { uri })
455 .map(|_| ())
456 .map_err(Into::into)
457 })
458 }
459
460 /// Remove the **empty** directory.
461 ///
462 /// # Args
463 /// - ***uri*** :
464 /// Target directory URI.
465 /// This needs to be **writable**.
466 /// If not empty directory, an error will occur.
467 ///
468 /// # Support
469 /// All.
470 pub fn remove_dir(&self, uri: &FileUri) -> crate::Result<()> {
471 on_android!({
472 impl_se!(struct Req<'a> { uri: &'a FileUri });
473 impl_de!(struct Res;);
474
475 self.api
476 .run_mobile_plugin::<Res>("deleteEmptyDir", Req { uri })
477 .map(|_| ())
478 .map_err(Into::into)
479 })
480 }
481
482 /// Removes a directory and all its contents. Use carefully!
483 ///
484 /// # Args
485 /// - ***uri*** :
486 /// Target directory URI.
487 /// This needs to be **writable**.
488 /// If not directory, an error will occur.
489 ///
490 /// # Support
491 /// All.
492 pub fn remove_dir_all(&self, uri: &FileUri) -> crate::Result<()> {
493 on_android!({
494 impl_se!(struct Req<'a> { uri: &'a FileUri });
495 impl_de!(struct Res;);
496
497 self.api
498 .run_mobile_plugin::<Res>("deleteDirAll", Req { uri })
499 .map(|_| ())
500 .map_err(Into::into)
501 })
502 }
503
504 /// Build a URI of an **existing** file located at the relative path from the specified directory.
505 /// Error occurs, if the file does not exist.
506 ///
507 /// The permissions and validity period of the returned URI depend on the origin directory
508 /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])
509 ///
510 /// # Support
511 /// All.
512 pub fn try_resolve_file_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
513 on_android!({
514 let uri = self.resolve_uri(dir, relative_path)?;
515 if self.get_mime_type(&uri)?.is_none() {
516 return Err(crate::Error { msg: format!("This is a directory, not a file: {uri:?}").into() })
517 }
518 Ok(uri)
519 })
520 }
521
522 /// Build a URI of an **existing** directory located at the relative path from the specified directory.
523 /// Error occurs, if the directory does not exist.
524 ///
525 /// The permissions and validity period of the returned URI depend on the origin directory
526 /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])
527 ///
528 /// # Support
529 /// All.
530 pub fn try_resolve_dir_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
531 on_android!({
532 let uri = self.resolve_uri(dir, relative_path)?;
533 if self.get_mime_type(&uri)?.is_some() {
534 return Err(crate::Error { msg: format!("This is a file, not a directory: {uri:?}").into() })
535 }
536 Ok(uri)
537 })
538 }
539
540 /// Build a URI of an entry located at the relative path from the specified directory.
541 ///
542 /// This function does not perform checks on the arguments or the returned URI.
543 /// Even if the dir argument refers to a file, no error occurs (and no panic either).
544 /// Instead, it simply returns an invalid URI that will cause errors if used with other functions.
545 ///
546 /// If you need check, consider using [`AndroidFs::try_resolve_file_uri`] or [`AndroidFs::try_resolve_dir_uri`] instead.
547 /// Or use this with [`AndroidFs::get_mime_type`].
548 ///
549 /// The permissions and validity period of the returned URI depend on the origin directory
550 /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])
551 ///
552 /// # Performance
553 /// This operation is relatively fast
554 /// because it does not call Kotlin API and only involves operating strings on Rust side.
555 ///
556 /// # Support
557 /// All.
558 pub fn resolve_uri(&self, dir: &FileUri, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
559 on_android!({
560 let base_dir = &dir.uri;
561 let relative_path = relative_path.as_ref().trim_matches('/');
562
563 if relative_path.is_empty() {
564 return Ok(dir.clone())
565 }
566
567 Ok(FileUri {
568 document_top_tree_uri: dir.document_top_tree_uri.clone(),
569 uri: format!("{base_dir}%2F{}", encode_document_id(relative_path))
570 })
571 })
572 }
573
574 /// See [`AndroidFs::get_thumbnail_to`] for descriptions.
575 ///
576 /// If thumbnail does not wrote to dest, return false.
577 pub fn get_thumbnail_to(
578 &self,
579 src: &FileUri,
580 dest: &FileUri,
581 preferred_size: Size,
582 format: ImageFormat,
583 ) -> crate::Result<bool> {
584
585 on_android!({
586 impl_se!(struct Req<'a> {
587 src: &'a FileUri,
588 dest: &'a FileUri,
589 format: &'a str,
590 quality: u8,
591 width: u32,
592 height: u32,
593 });
594 impl_de!(struct Res { value: bool });
595
596 let (quality, format) = match format {
597 ImageFormat::Png => (1.0, "Png"),
598 ImageFormat::Jpeg => (0.75, "Jpeg"),
599 ImageFormat::Webp => (0.7, "Webp"),
600 ImageFormat::JpegWith { quality } => (quality, "Jpeg"),
601 ImageFormat::WebpWith { quality } => (quality, "Webp"),
602 };
603 let quality = (quality * 100.0).clamp(0.0, 100.0) as u8;
604 let Size { width, height } = preferred_size;
605
606 self.api
607 .run_mobile_plugin::<Res>("getThumbnail", Req { src, dest, format, quality, width, height })
608 .map(|v| v.value)
609 .map_err(Into::into)
610 })
611 }
612
613 /// Query the provider to get a file thumbnail.
614 /// If thumbnail does not exist it, return None.
615 ///
616 /// Note this does not cache. Please do it in your part if need.
617 ///
618 /// # Args
619 /// - ***uri*** :
620 /// Targe file uri.
621 /// Thumbnail availablty depends on the file provider.
622 /// In general, images and videos are available.
623 /// For files in [`PrivateStorage`],
624 /// the file type must match the filename extension.
625 ///
626 /// - ***preferred_size*** :
627 /// Optimal thumbnail size desired.
628 /// This may return a thumbnail of a different size,
629 /// but never more than double the requested size.
630 /// In any case, the aspect ratio is maintained.
631 ///
632 /// - ***format*** :
633 /// Thumbnail image format.
634 /// [`ImageFormat::Jpeg`] is recommended.
635 /// If you need transparency, use others.
636 ///
637 /// # Support
638 /// All.
639 pub fn get_thumbnail(
640 &self,
641 uri: &FileUri,
642 preferred_size: Size,
643 format: ImageFormat,
644 ) -> crate::Result<Option<Vec<u8>>> {
645
646 on_android!({
647 let tmp_file_path = {
648 use std::sync::atomic::{AtomicUsize, Ordering};
649
650 static COUNTER: AtomicUsize = AtomicUsize::new(0);
651 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
652
653 self.private_storage().resolve_path_with(
654 PrivateDir::Cache,
655 format!("{TMP_DIR_RELATIVE_PATH}/get_thumbnail {id}")
656 )?
657 };
658
659 if let Some(parent) = tmp_file_path.parent() {
660 let _ = std::fs::create_dir_all(parent);
661 }
662
663 std::fs::File::create(&tmp_file_path)?;
664
665 let result = self.get_thumbnail_to(uri, &(&tmp_file_path).into(), preferred_size, format)
666 .and_then(|ok| {
667 if (ok) {
668 std::fs::read(&tmp_file_path)
669 .map(Some)
670 .map_err(Into::into)
671 }
672 else {
673 Ok(None)
674 }
675 });
676
677 let _ = std::fs::remove_file(&tmp_file_path);
678
679 result
680 })
681 }
682
683 /// Creates a new empty file in the specified location and returns a URI.
684 ///
685 /// The permissions and validity period of the returned URIs depend on the origin directory
686 /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])
687 ///
688 /// # Args
689 /// - ***dir*** :
690 /// The URI of the base directory.
691 /// This needs to be **read-write**.
692 ///
693 /// - ***relative_path*** :
694 /// The file path relative to the base directory.
695 /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.
696 /// Any missing subdirectories in the specified path will be created automatically.
697 ///
698 /// - ***mime_type*** :
699 /// The MIME type of the file to be created.
700 /// If this is None, MIME type is inferred from the extension of ***relative_path***
701 /// and if that fails, `application/octet-stream` is used.
702 ///
703 /// # Support
704 /// All.
705 pub fn create_file(
706 &self,
707 dir: &FileUri,
708 relative_path: impl AsRef<str>,
709 mime_type: Option<&str>
710 ) -> crate::Result<FileUri> {
711
712 on_android!({
713 impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
714
715 let relative_path = relative_path.as_ref();
716
717 self.api
718 .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
719 .map_err(Into::into)
720 })
721 }
722
723 /// Recursively create a directory and all of its parent components if they are missing,
724 /// then return the URI.
725 ///
726 /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
727 ///
728 /// # Args
729 /// - ***dir*** :
730 /// The URI of the base directory.
731 /// This needs to be **read-write**.
732 ///
733 /// - ***relative_path*** :
734 /// The directory path relative to the base directory.
735 ///
736 /// # Support
737 /// All.
738 pub fn create_dir_all(
739 &self,
740 dir: &FileUri,
741 relative_path: impl AsRef<str>,
742 ) -> Result<FileUri> {
743
744 on_android!({
745 let relative_path = relative_path.as_ref().trim_matches('/');
746 if relative_path.is_empty() {
747 return Ok(dir.clone())
748 }
749
750 // TODO:
751 // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
752 let tmp_file_uri = self.create_file(
753 dir,
754 format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"),
755 Some("application/octet-stream")
756 )?;
757 let _ = self.remove_file(&tmp_file_uri);
758 let uri = self.resolve_uri(dir, relative_path)?;
759
760 Ok(uri)
761 })
762 }
763
764 /// Returns the child files and directories of the specified directory.
765 /// The order of the entries is not guaranteed.
766 ///
767 /// The permissions and validity period of the returned URIs depend on the origin directory
768 /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])
769 ///
770 /// # Args
771 /// - ***uri*** :
772 /// Target directory URI.
773 /// This needs to be **readable**.
774 ///
775 /// # Note
776 /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily.
777 /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.
778 ///
779 /// # Support
780 /// All.
781 pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
782 on_android!(std::iter::Empty::<_>, {
783 impl_se!(struct Req<'a> { uri: &'a FileUri });
784 impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
785 impl_de!(struct Res { entries: Vec<Obj> });
786
787 self.api
788 .run_mobile_plugin::<Res>("readDir", Req { uri })
789 .map(|v| v.entries.into_iter())
790 .map(|v| v.map(|v| match v.mime_type {
791 Some(mime_type) => Entry::File {
792 name: v.name,
793 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
794 len: v.byte_size as u64,
795 mime_type,
796 uri: v.uri,
797 },
798 None => Entry::Dir {
799 name: v.name,
800 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
801 uri: v.uri,
802 }
803 }))
804 .map_err(Into::into)
805 })
806 }
807
808 /// Opens a system file picker and returns a **read-write** URIs.
809 /// If no file is selected or the user cancels, an empty vec is returned.
810 ///
811 /// By default, returned URI is valid until the app is terminated.
812 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
813 ///
814 /// This provides a standardized file explorer-style interface,
815 /// and also allows file selection from part of third-party apps or cloud storage.
816 ///
817 /// Removing the returned files is also supported in most cases,
818 /// but note that files provided by third-party apps may not be removable.
819 ///
820 /// # Args
821 /// - ***initial_location*** :
822 /// Indicate the initial location of dialog.
823 /// There is no need to use this if there is no special reason.
824 /// System will do its best to launch the dialog in the specified entry
825 /// if it's a directory, or the directory that contains the specified file if not.
826 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
827 /// This must be a URI taken from following :
828 /// - [`AndroidFs::resolve_initial_location`]
829 /// - [`AndroidFs::show_open_file_dialog`]
830 /// - [`AndroidFs::show_save_file_dialog`]
831 /// - [`AndroidFs::show_manage_dir_dialog`]
832 /// - [`AndroidFs::resolve_uri`]
833 /// - [`AndroidFs::try_resolve_file_uri`]
834 /// - [`AndroidFs::try_resolve_dir_uri`]
835 /// - [`AndroidFs::read_dir`]
836 /// - [`AndroidFs::create_file`]
837 ///
838 /// - ***mime_types*** :
839 /// The MIME types of the file to be selected.
840 /// However, there is no guarantee that the returned file will match the specified types.
841 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
842 ///
843 /// - ***multiple*** :
844 /// Indicates whether multiple file selection is allowed.
845 ///
846 /// # Support
847 /// All.
848 ///
849 /// # References
850 /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
851 pub fn show_open_file_dialog(
852 &self,
853 initial_location: Option<&FileUri>,
854 mime_types: &[&str],
855 multiple: bool,
856 ) -> crate::Result<Vec<FileUri>> {
857
858 on_android!({
859 impl_se!(struct Req<'a> {
860 mime_types: &'a [&'a str],
861 multiple: bool,
862 initial_location: Option<&'a FileUri>
863 });
864 impl_de!(struct Res { uris: Vec<FileUri> });
865
866 let _guard = self.intent_lock.lock();
867 self.api
868 .run_mobile_plugin::<Res>("showOpenFileDialog", Req { mime_types, multiple, initial_location })
869 .map(|v| v.uris)
870 .map_err(Into::into)
871 })
872 }
873
874 /// Opens a file picker and returns a **readonly** URIs.
875 /// If no file is selected or the user cancels, an empty vec is returned.
876 ///
877 /// Returned URI is valid until the app is terminated. Can not persist it.
878 ///
879 /// This works differently depending on the model and version.
880 /// But recent devices often have the similar behaviour as [`AndroidFs::show_open_visual_media_dialog`] or [`AndroidFs::show_open_file_dialog`].
881 /// Use this, if you want your app to simply read/import data.
882 ///
883 /// # Args
884 /// - ***mime_types*** :
885 /// The MIME types of the file to be selected.
886 /// However, there is no guarantee that the returned file will match the specified types.
887 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
888 ///
889 /// - ***multiple*** :
890 /// Indicates whether multiple file selection is allowed.
891 ///
892 /// # Support
893 /// All.
894 ///
895 /// # References
896 /// <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
897 pub fn show_open_content_dialog(
898 &self,
899 mime_types: &[&str],
900 multiple: bool
901 ) -> crate::Result<Vec<FileUri>> {
902
903 on_android!({
904 impl_se!(struct Req<'a> { mime_types: &'a [&'a str], multiple: bool });
905 impl_de!(struct Res { uris: Vec<FileUri> });
906
907 let _guard = self.intent_lock.lock();
908 self.api
909 .run_mobile_plugin::<Res>("showOpenContentDialog", Req { mime_types, multiple })
910 .map(|v| v.uris)
911 .map_err(Into::into)
912 })
913 }
914
915 /// Opens a media picker and returns a **readonly** URIs.
916 /// If no file is selected or the user cancels, an empty vec is returned.
917 ///
918 /// By default, returned URI is valid until the app is terminated.
919 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
920 ///
921 /// This media picker provides a browsable interface that presents the user with their media library,
922 /// sorted by date from newest to oldest.
923 ///
924 /// # Args
925 /// - ***target*** :
926 /// The media type of the file to be selected.
927 /// Images or videos, or both.
928 ///
929 /// - ***multiple*** :
930 /// Indicates whether multiple file selection is allowed.
931 ///
932 /// # Note
933 /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].
934 /// Instead, it will be assigned a sequential number, such as `1000091523.png`.
935 /// And this is marked intended behavior, not a bug.
936 /// - <https://issuetracker.google.com/issues/268079113>
937 ///
938 /// # Support
939 /// This feature is available on devices that meet the following criteria:
940 /// - Running Android 11 (API level 30) or higher
941 /// - Receive changes to Modular System Components through Google System Updates
942 ///
943 /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].
944 /// If not supported, this function behaves the same as [`AndroidFs::show_open_file_dialog`].
945 ///
946 /// # References
947 /// <https://developer.android.com/training/data-storage/shared/photopicker>
948 pub fn show_open_visual_media_dialog(
949 &self,
950 target: VisualMediaTarget,
951 multiple: bool,
952 ) -> crate::Result<Vec<FileUri>> {
953
954 on_android!({
955 impl_se!(struct Req { multiple: bool, target: VisualMediaTarget });
956 impl_de!(struct Res { uris: Vec<FileUri> });
957
958 let _guard = self.intent_lock.lock();
959 self.api
960 .run_mobile_plugin::<Res>("showOpenVisualMediaDialog", Req { multiple, target })
961 .map(|v| v.uris)
962 .map_err(Into::into)
963 })
964 }
965
966 /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one,
967 /// and returns a **read-write** directory URI.
968 /// App can fully manage entries within the returned directory.
969 /// If no directory is selected or the user cancels, `None` is returned.
970 ///
971 /// By default, returned URI is valid until the app is terminated.
972 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
973 ///
974 /// This provides a standardized file explorer-style interface.
975 ///
976 /// # Args
977 /// - ***initial_location*** :
978 /// Indicate the initial location of dialog.
979 /// There is no need to use this if there is no special reason.
980 /// System will do its best to launch the dialog in the specified entry
981 /// if it's a directory, or the directory that contains the specified file if not.
982 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
983 /// This must be a URI taken from following :
984 /// - [`AndroidFs::resolve_initial_location`]
985 /// - [`AndroidFs::show_open_file_dialog`]
986 /// - [`AndroidFs::show_save_file_dialog`]
987 /// - [`AndroidFs::show_manage_dir_dialog`]
988 /// - [`AndroidFs::resolve_uri`]
989 /// - [`AndroidFs::try_resolve_file_uri`]
990 /// - [`AndroidFs::try_resolve_dir_uri`]
991 /// - [`AndroidFs::read_dir`]
992 /// - [`AndroidFs::create_file`]
993 ///
994 /// # Support
995 /// All.
996 ///
997 /// # References
998 /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
999 pub fn show_manage_dir_dialog(
1000 &self,
1001 initial_location: Option<&FileUri>,
1002 ) -> crate::Result<Option<FileUri>> {
1003
1004 on_android!({
1005 impl_se!(struct Req<'a> { initial_location: Option<&'a FileUri> });
1006 impl_de!(struct Res { uri: Option<FileUri> });
1007
1008 let _guard = self.intent_lock.lock();
1009 self.api
1010 .run_mobile_plugin::<Res>("showManageDirDialog", Req { initial_location })
1011 .map(|v| v.uri)
1012 .map_err(Into::into)
1013 })
1014 }
1015
1016 /// Please use [`AndroidFs::show_manage_dir_dialog`] instead.
1017 #[deprecated = "Confusing name. Please use show_manage_dir_dialog instead."]
1018 #[warn(deprecated)]
1019 pub fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
1020 on_android!({
1021 self.show_manage_dir_dialog(None)
1022 })
1023 }
1024
1025 /// Opens a dialog to save a file and returns a **writeonly** URI.
1026 /// The returned file may be a newly created file with no content,
1027 /// or it may be an existing file with the requested MIME type.
1028 /// If the user cancels, `None` is returned.
1029 ///
1030 /// By default, returned URI is valid until the app is terminated.
1031 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
1032 ///
1033 /// This provides a standardized file explorer-style interface,
1034 /// and also allows file selection from part of third-party apps or cloud storage.
1035 ///
1036 /// Removing and reading the returned files is also supported in most cases,
1037 /// but note that files provided by third-party apps may not.
1038 ///
1039 /// # Args
1040 /// - ***initial_location*** :
1041 /// Indicate the initial location of dialog.
1042 /// There is no need to use this if there is no special reason.
1043 /// System will do its best to launch the dialog in the specified entry
1044 /// if it's a directory, or the directory that contains the specified file if not.
1045 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
1046 /// This must be a URI taken from following :
1047 /// - [`AndroidFs::resolve_initial_location`]
1048 /// - [`AndroidFs::show_open_file_dialog`]
1049 /// - [`AndroidFs::show_save_file_dialog`]
1050 /// - [`AndroidFs::show_manage_dir_dialog`]
1051 /// - [`AndroidFs::resolve_uri`]
1052 /// - [`AndroidFs::try_resolve_file_uri`]
1053 /// - [`AndroidFs::try_resolve_dir_uri`]
1054 /// - [`AndroidFs::read_dir`]
1055 /// - [`AndroidFs::create_file`]
1056 ///
1057 /// - ***initial_file_name*** :
1058 /// An initial file name, but the user may change this value before creating the file.
1059 ///
1060 /// - ***mime_type*** :
1061 /// The MIME type of the file to be saved.
1062 /// If this is None, MIME type is inferred from the extension of ***initial_file_name*** (not file name by user input)
1063 /// and if that fails, `application/octet-stream` is used.
1064 ///
1065 /// # Support
1066 /// All.
1067 ///
1068 /// # References
1069 /// <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
1070 pub fn show_save_file_dialog(
1071 &self,
1072 initial_location: Option<&FileUri>,
1073 initial_file_name: impl AsRef<str>,
1074 mime_type: Option<&str>,
1075 ) -> crate::Result<Option<FileUri>> {
1076
1077 on_android!({
1078 impl_se!(struct Req<'a> {
1079 initial_file_name: &'a str,
1080 mime_type: Option<&'a str>,
1081 initial_location: Option<&'a FileUri>
1082 });
1083 impl_de!(struct Res { uri: Option<FileUri> });
1084
1085 let initial_file_name = initial_file_name.as_ref();
1086
1087 let _guard = self.intent_lock.lock();
1088 self.api
1089 .run_mobile_plugin::<Res>("showSaveFileDialog", Req { initial_file_name, mime_type, initial_location })
1090 .map(|v| v.uri)
1091 .map_err(Into::into)
1092 })
1093 }
1094
1095 /// Create an **restricted** URI for the specified directory.
1096 /// This should only be used as `initial_location` in the dialog.
1097 /// It must not be used for any other purpose.
1098 ///
1099 /// This is useful when selecting (creating) new files and folders,
1100 /// but when selecting existing entries, `initial_location` is often better with None.
1101 ///
1102 /// Note this is an informal method and is not guaranteed to work reliably.
1103 /// But this URI does not cause the dialog to error.
1104 /// So please use this with the mindset that it's better than doing nothing.
1105 ///
1106 /// # Examples
1107 /// ```
1108 /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
1109 ///
1110 /// fn sample(app: tauri::AppHandle) {
1111 /// let api = app.android_fs();
1112 ///
1113 /// // Get URI of the top directory
1114 /// let initial_location = api.resolve_initial_location(
1115 /// InitialLocation::TopPublicDir,
1116 /// false,
1117 /// ).expect("Should be on Android");
1118 ///
1119 /// // Get URI of ~/Pictures/
1120 /// let initial_location = api.resolve_initial_location(
1121 /// PublicImageDir::Pictures,
1122 /// false
1123 /// ).expect("Should be on Android");
1124 ///
1125 /// // Get URI of ~/Documents/sub_dir1/sub_dir2/
1126 /// let initial_location = api.resolve_initial_location(
1127 /// InitialLocation::DirInPublicDir {
1128 /// base_dir: PublicGeneralPurposeDir::Documents.into(),
1129 /// relative_path: "sub_dir1/sub_dir2"
1130 /// },
1131 /// true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
1132 /// ).expect("Should be on Android");
1133 ///
1134 /// // Open dialog with initial_location
1135 /// let _ = api.show_save_file_dialog(Some(&initial_location), "", None);
1136 /// let _ = api.show_open_file_dialog(Some(&initial_location), &[], true);
1137 /// let _ = api.show_manage_dir_dialog(Some(&initial_location));
1138 /// }
1139 /// ```
1140 ///
1141 /// # Support
1142 /// All.
1143 pub fn resolve_initial_location<'a>(
1144 &self,
1145 dir: impl Into<InitialLocation<'a>>,
1146 create_dirs: bool
1147 ) -> crate::Result<FileUri> {
1148
1149 on_android!({
1150 const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary%3A";
1151
1152 let uri = match dir.into() {
1153 InitialLocation::TopPublicDir => TOP_DIR.into(),
1154 InitialLocation::PublicDir(dir) => format!("{TOP_DIR}{dir}"),
1155 InitialLocation::DirInPublicDir { base_dir, relative_path } => {
1156 let relative_path = relative_path.trim_matches('/');
1157
1158 if relative_path.is_empty() {
1159 format!("{TOP_DIR}{base_dir}")
1160 }
1161 else {
1162 if create_dirs {
1163 let _ = self.public_storage()
1164 .create_file(base_dir, format!("{relative_path}/tmp"), Some("application/octet-stream"))
1165 .and_then(|u| self.remove_file(&u));
1166 }
1167
1168 let sub_dirs = encode_document_id(relative_path);
1169 format!("{TOP_DIR}{base_dir}%2F{sub_dirs}")
1170 }
1171 }
1172 };
1173
1174 Ok(FileUri { uri, document_top_tree_uri: None })
1175 })
1176 }
1177
1178 /// Opens a dialog for sharing file to other apps.
1179 ///
1180 /// An error will occur if there is no app that can handle the request.
1181 /// Please use [`AndroidFs::can_share_file`] to confirm.
1182 ///
1183 /// # Args
1184 /// - **uri** :
1185 /// Target file uri to share.
1186 /// This needs to be **readable**.
1187 /// Files in [`PrivateStorage`] ***cannot*** be used.
1188 ///
1189 /// # Support
1190 /// All.
1191 pub fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1192 on_android!({
1193 impl_se!(struct Req<'a> { uri: &'a FileUri });
1194 impl_de!(struct Res;);
1195
1196 self.api
1197 .run_mobile_plugin::<Res>("shareFile", Req { uri })
1198 .map(|_| ())
1199 .map_err(Into::into)
1200 })
1201 }
1202
1203 /// Opens a dialog for viewing file on other apps.
1204 /// This performs the general "open file" action.
1205 ///
1206 /// An error will occur if there is no app that can handle the request.
1207 /// Please use [`AndroidFs::can_view_file`] to confirm.
1208 ///
1209 /// # Args
1210 /// - **uri** :
1211 /// Target file uri to view.
1212 /// This needs to be **readable**.
1213 /// Files in [`PrivateStorage`] ***cannot*** be used.
1214 ///
1215 /// # Support
1216 /// All.
1217 pub fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1218 on_android!({
1219 impl_se!(struct Req<'a> { uri: &'a FileUri });
1220 impl_de!(struct Res;);
1221
1222 self.api
1223 .run_mobile_plugin::<Res>("viewFile", Req { uri })
1224 .map(|_| ())
1225 .map_err(Into::into)
1226 })
1227 }
1228
1229 /// Determines whether the specified file can be used with [`AndroidFs::show_share_file_dialog`].
1230 /// # Args
1231 /// - **uri** :
1232 /// Target file uri.
1233 /// This needs to be **readable**.
1234 ///
1235 /// # Support
1236 /// All.
1237 pub fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool> {
1238 on_android!({
1239 impl_se!(struct Req<'a> { uri: &'a FileUri });
1240 impl_de!(struct Res { value: bool });
1241
1242 self.api
1243 .run_mobile_plugin::<Res>("canShareFile", Req { uri })
1244 .map(|v| v.value)
1245 .map_err(Into::into)
1246 })
1247 }
1248
1249 /// Determines whether the specified file can be used with [`AndroidFs::show_view_file_dialog`].
1250 ///
1251 /// # Args
1252 /// - **uri** :
1253 /// Target file uri.
1254 /// This needs to be **readable**.
1255 ///
1256 /// # Support
1257 /// All.
1258 pub fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool> {
1259 on_android!({
1260 impl_se!(struct Req<'a> { uri: &'a FileUri });
1261 impl_de!(struct Res { value: bool });
1262
1263 self.api
1264 .run_mobile_plugin::<Res>("canViewFile", Req { uri })
1265 .map(|v| v.value)
1266 .map_err(Into::into)
1267 })
1268 }
1269
1270 /// Take persistent permission to access the file, directory and its descendants.
1271 /// This is a prolongation of an already acquired permission, not the acquisition of a new one.
1272 ///
1273 /// This works by just calling, without displaying any confirmation to the user.
1274 ///
1275 /// 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)
1276 /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].
1277 /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1278 /// So check by [`AndroidFs::check_persisted_uri_permission`].
1279 /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1280 ///
1281 /// # Args
1282 /// - **uri** :
1283 /// URI of the target file or directory. This must be a URI taken from following :
1284 /// - [`AndroidFs::show_open_file_dialog`]
1285 /// - [`AndroidFs::show_open_visual_media_dialog`]
1286 /// - [`AndroidFs::show_save_file_dialog`]
1287 /// - [`AndroidFs::show_manage_dir_dialog`]
1288 /// - [`AndroidFs::read_dir`] :
1289 /// If this, the permissions of the origin directory URI is persisted, not a entry iteself.
1290 /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1291 ///
1292 /// # Support
1293 /// All.
1294 pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1295 on_android!({
1296 impl_se!(struct Req<'a> { uri: &'a FileUri });
1297 impl_de!(struct Res;);
1298
1299 self.api
1300 .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1301 .map(|_| ())
1302 .map_err(Into::into)
1303 })
1304 }
1305
1306 /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1307 /// Returns false if there are only non-persistent permissions or no permissions.
1308 ///
1309 /// # Args
1310 /// - **uri** :
1311 /// URI of the target file or directory.
1312 /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself.
1313 /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1314 ///
1315 /// - **mode** :
1316 /// The mode of permission you want to check.
1317 ///
1318 /// # Support
1319 /// All.
1320 pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1321 on_android!({
1322 impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1323 impl_de!(struct Res { value: bool });
1324
1325 self.api
1326 .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1327 .map(|v| v.value)
1328 .map_err(Into::into)
1329 })
1330 }
1331
1332 /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.
1333 ///
1334 /// # Support
1335 /// All.
1336 pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1337 on_android!(std::iter::Empty::<_>, {
1338 impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1339 impl_de!(struct Res { items: Vec<Obj> });
1340
1341 self.api
1342 .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1343 .map(|v| v.items.into_iter())
1344 .map(|v| v.map(|v| {
1345 let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1346 match v.d {
1347 true => PersistedUriPermission::Dir { uri, can_read, can_write },
1348 false => PersistedUriPermission::File { uri, can_read, can_write }
1349 }
1350 }))
1351 .map_err(Into::into)
1352 })
1353 }
1354
1355 /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1356 ///
1357 /// # Args
1358 /// - ***uri*** :
1359 /// URI of the target file or directory.
1360 ///
1361 /// # Support
1362 /// All.
1363 pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1364 on_android!({
1365 impl_se!(struct Req<'a> { uri: &'a FileUri });
1366 impl_de!(struct Res;);
1367
1368 self.api
1369 .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1370 .map(|_| ())
1371 .map_err(Into::into)
1372 })
1373 }
1374
1375 /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].
1376 ///
1377 /// # Support
1378 /// All.
1379 pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1380 on_android!({
1381 impl_de!(struct Res);
1382
1383 self.api
1384 .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1385 .map(|_| ())
1386 .map_err(Into::into)
1387 })
1388 }
1389
1390 /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
1391 ///
1392 /// # Support
1393 /// All.
1394 pub fn is_visual_media_dialog_available(&self) -> crate::Result<bool> {
1395 on_android!({
1396 impl_de!(struct Res { value: bool });
1397
1398 self.api
1399 .run_mobile_plugin::<Res>("isVisualMediaDialogAvailable", "")
1400 .map(|v| v.value)
1401 .map_err(Into::into)
1402 })
1403 }
1404
1405 /// File storage intended for the app's use only.
1406 pub fn private_storage(&self) -> PrivateStorage<'_, R> {
1407 PrivateStorage(self)
1408 }
1409
1410 /// File storage that is available to other applications and users.
1411 pub fn public_storage(&self) -> PublicStorage<'_, R> {
1412 PublicStorage(self)
1413 }
1414}