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 /// Please note that this has a different meaning from `std::fs::create` that open the file in write mod.
689 /// If you need it, use [`AndroidFs::open_file`] with [`FileAccessMode::WriteTrucncate`].
690 ///
691 /// # Args
692 /// - ***dir*** :
693 /// The URI of the base directory.
694 /// This needs to be **read-write**.
695 ///
696 /// - ***relative_path*** :
697 /// The file path relative to the base directory.
698 /// Any missing subdirectories in the specified path will be created automatically.
699 /// If a file with the same name already exists,
700 /// the system append a sequential number to ensure uniqueness.
701 /// If no extension is present,
702 /// the system may infer one from ***mime_type*** and may append it to the file name.
703 /// But this append-extension operation depends on the model and version.
704 ///
705 /// - ***mime_type*** :
706 /// The MIME type of the file to be created.
707 /// If this is None, MIME type is inferred from the extension of ***relative_path***
708 /// and if that fails, `application/octet-stream` is used.
709 ///
710 /// # Support
711 /// All.
712 pub fn create_file(
713 &self,
714 dir: &FileUri,
715 relative_path: impl AsRef<str>,
716 mime_type: Option<&str>
717 ) -> crate::Result<FileUri> {
718
719 on_android!({
720 impl_se!(struct Req<'a> { dir: &'a FileUri, mime_type: Option<&'a str>, relative_path: &'a str });
721
722 let relative_path = relative_path.as_ref();
723
724 self.api
725 .run_mobile_plugin::<FileUri>("createFile", Req { dir, mime_type, relative_path })
726 .map_err(Into::into)
727 })
728 }
729
730 /// Recursively create a directory and all of its parent components if they are missing,
731 /// then return the URI.
732 /// If it already exists, do nothing and just return the direcotry uri.
733 ///
734 /// [`AndroidFs::create_file`] does this automatically, so there is no need to use it together.
735 ///
736 /// # Args
737 /// - ***dir*** :
738 /// The URI of the base directory.
739 /// This needs to be **read-write**.
740 ///
741 /// - ***relative_path*** :
742 /// The directory path relative to the base directory.
743 ///
744 /// # Support
745 /// All.
746 pub fn create_dir_all(
747 &self,
748 dir: &FileUri,
749 relative_path: impl AsRef<str>,
750 ) -> Result<FileUri> {
751
752 on_android!({
753 let relative_path = relative_path.as_ref().trim_matches('/');
754 if relative_path.is_empty() {
755 return Ok(dir.clone())
756 }
757
758 // TODO:
759 // create_file経由ではなく folder作成専用のkotlin apiを作成し呼び出すようにする
760 let tmp_file_uri = self.create_file(
761 dir,
762 format!("{relative_path}/TMP-01K3CGCKYSAQ1GHF8JW5FGD4RW"),
763 Some("application/octet-stream")
764 )?;
765 let _ = self.remove_file(&tmp_file_uri);
766 let uri = self.resolve_uri(dir, relative_path)?;
767
768 Ok(uri)
769 })
770 }
771
772 /// Returns the child files and directories of the specified directory.
773 /// The order of the entries is not guaranteed.
774 ///
775 /// The permissions and validity period of the returned URIs depend on the origin directory
776 /// (e.g., the top directory selected by [`AndroidFs::show_manage_dir_dialog`])
777 ///
778 /// # Args
779 /// - ***uri*** :
780 /// Target directory URI.
781 /// This needs to be **readable**.
782 ///
783 /// # Note
784 /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily.
785 /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.
786 ///
787 /// # Support
788 /// All.
789 pub fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>> {
790 on_android!(std::iter::Empty::<_>, {
791 impl_se!(struct Req<'a> { uri: &'a FileUri });
792 impl_de!(struct Obj { name: String, uri: FileUri, last_modified: i64, byte_size: i64, mime_type: Option<String> });
793 impl_de!(struct Res { entries: Vec<Obj> });
794
795 self.api
796 .run_mobile_plugin::<Res>("readDir", Req { uri })
797 .map(|v| v.entries.into_iter())
798 .map(|v| v.map(|v| match v.mime_type {
799 Some(mime_type) => Entry::File {
800 name: v.name,
801 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
802 len: v.byte_size as u64,
803 mime_type,
804 uri: v.uri,
805 },
806 None => Entry::Dir {
807 name: v.name,
808 last_modified: std::time::UNIX_EPOCH + std::time::Duration::from_millis(v.last_modified as u64),
809 uri: v.uri,
810 }
811 }))
812 .map_err(Into::into)
813 })
814 }
815
816 /// Opens a system file picker and returns a **read-write** URIs.
817 /// If no file is selected or the user cancels, an empty vec is returned.
818 ///
819 /// By default, returned URI is valid until the app is terminated.
820 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
821 ///
822 /// This provides a standardized file explorer-style interface,
823 /// and also allows file selection from part of third-party apps or cloud storage.
824 ///
825 /// Removing the returned files is also supported in most cases,
826 /// but note that files provided by third-party apps may not be removable.
827 ///
828 /// # Args
829 /// - ***initial_location*** :
830 /// Indicate the initial location of dialog.
831 /// There is no need to use this if there is no special reason.
832 /// System will do its best to launch the dialog in the specified entry
833 /// if it's a directory, or the directory that contains the specified file if not.
834 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
835 /// This must be a URI taken from following :
836 /// - [`AndroidFs::resolve_initial_location`]
837 /// - [`AndroidFs::show_open_file_dialog`]
838 /// - [`AndroidFs::show_save_file_dialog`]
839 /// - [`AndroidFs::show_manage_dir_dialog`]
840 /// - [`AndroidFs::resolve_uri`]
841 /// - [`AndroidFs::try_resolve_file_uri`]
842 /// - [`AndroidFs::try_resolve_dir_uri`]
843 /// - [`AndroidFs::read_dir`]
844 /// - [`AndroidFs::create_file`]
845 ///
846 /// - ***mime_types*** :
847 /// The MIME types of the file to be selected.
848 /// However, there is no guarantee that the returned file will match the specified types.
849 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
850 ///
851 /// - ***multiple*** :
852 /// Indicates whether multiple file selection is allowed.
853 ///
854 /// # Support
855 /// All.
856 ///
857 /// # References
858 /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
859 pub fn show_open_file_dialog(
860 &self,
861 initial_location: Option<&FileUri>,
862 mime_types: &[&str],
863 multiple: bool,
864 ) -> crate::Result<Vec<FileUri>> {
865
866 on_android!({
867 impl_se!(struct Req<'a> {
868 mime_types: &'a [&'a str],
869 multiple: bool,
870 initial_location: Option<&'a FileUri>
871 });
872 impl_de!(struct Res { uris: Vec<FileUri> });
873
874 let _guard = self.intent_lock.lock();
875 self.api
876 .run_mobile_plugin::<Res>("showOpenFileDialog", Req { mime_types, multiple, initial_location })
877 .map(|v| v.uris)
878 .map_err(Into::into)
879 })
880 }
881
882 /// Opens a file picker and returns a **readonly** URIs.
883 /// If no file is selected or the user cancels, an empty vec is returned.
884 ///
885 /// Returned URI is valid until the app is terminated. Can not persist it.
886 ///
887 /// This works differently depending on the model and version.
888 /// But recent devices often have the similar behaviour as [`AndroidFs::show_open_visual_media_dialog`] or [`AndroidFs::show_open_file_dialog`].
889 /// Use this, if you want your app to simply read/import data.
890 ///
891 /// # Args
892 /// - ***mime_types*** :
893 /// The MIME types of the file to be selected.
894 /// However, there is no guarantee that the returned file will match the specified types.
895 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
896 ///
897 /// - ***multiple*** :
898 /// Indicates whether multiple file selection is allowed.
899 ///
900 /// # Support
901 /// All.
902 ///
903 /// # References
904 /// <https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT>
905 pub fn show_open_content_dialog(
906 &self,
907 mime_types: &[&str],
908 multiple: bool
909 ) -> crate::Result<Vec<FileUri>> {
910
911 on_android!({
912 impl_se!(struct Req<'a> { mime_types: &'a [&'a str], multiple: bool });
913 impl_de!(struct Res { uris: Vec<FileUri> });
914
915 let _guard = self.intent_lock.lock();
916 self.api
917 .run_mobile_plugin::<Res>("showOpenContentDialog", Req { mime_types, multiple })
918 .map(|v| v.uris)
919 .map_err(Into::into)
920 })
921 }
922
923 /// Opens a media picker and returns a **readonly** URIs.
924 /// If no file is selected or the user cancels, an empty vec is returned.
925 ///
926 /// By default, returned URI is valid until the app is terminated.
927 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
928 ///
929 /// This media picker provides a browsable interface that presents the user with their media library,
930 /// sorted by date from newest to oldest.
931 ///
932 /// # Args
933 /// - ***target*** :
934 /// The media type of the file to be selected.
935 /// Images or videos, or both.
936 ///
937 /// - ***multiple*** :
938 /// Indicates whether multiple file selection is allowed.
939 ///
940 /// # Note
941 /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].
942 /// Instead, it will be assigned a sequential number, such as `1000091523.png`.
943 /// And this is marked intended behavior, not a bug.
944 /// - <https://issuetracker.google.com/issues/268079113>
945 ///
946 /// # Support
947 /// This feature is available on devices that meet the following criteria:
948 /// - Running Android 11 (API level 30) or higher
949 /// - Receive changes to Modular System Components through Google System Updates
950 ///
951 /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].
952 /// If not supported, this function behaves the same as [`AndroidFs::show_open_file_dialog`].
953 ///
954 /// # References
955 /// <https://developer.android.com/training/data-storage/shared/photopicker>
956 pub fn show_open_visual_media_dialog(
957 &self,
958 target: VisualMediaTarget,
959 multiple: bool,
960 ) -> crate::Result<Vec<FileUri>> {
961
962 on_android!({
963 impl_se!(struct Req { multiple: bool, target: VisualMediaTarget });
964 impl_de!(struct Res { uris: Vec<FileUri> });
965
966 let _guard = self.intent_lock.lock();
967 self.api
968 .run_mobile_plugin::<Res>("showOpenVisualMediaDialog", Req { multiple, target })
969 .map(|v| v.uris)
970 .map_err(Into::into)
971 })
972 }
973
974 /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one,
975 /// and returns a **read-write** directory URI.
976 /// App can fully manage entries within the returned directory.
977 /// If no directory is selected or the user cancels, `None` is returned.
978 ///
979 /// By default, returned URI is valid until the app is terminated.
980 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
981 ///
982 /// This provides a standardized file explorer-style interface,
983 /// and also allows file selection from part of third-party apps or cloud storage.
984 ///
985 /// # Args
986 /// - ***initial_location*** :
987 /// Indicate the initial location of dialog.
988 /// There is no need to use this if there is no special reason.
989 /// System will do its best to launch the dialog in the specified entry
990 /// if it's a directory, or the directory that contains the specified file if not.
991 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
992 /// This must be a URI taken from following :
993 /// - [`AndroidFs::resolve_initial_location`]
994 /// - [`AndroidFs::show_open_file_dialog`]
995 /// - [`AndroidFs::show_save_file_dialog`]
996 /// - [`AndroidFs::show_manage_dir_dialog`]
997 /// - [`AndroidFs::resolve_uri`]
998 /// - [`AndroidFs::try_resolve_file_uri`]
999 /// - [`AndroidFs::try_resolve_dir_uri`]
1000 /// - [`AndroidFs::read_dir`]
1001 /// - [`AndroidFs::create_file`]
1002 ///
1003 /// # Support
1004 /// All.
1005 ///
1006 /// # References
1007 /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
1008 pub fn show_manage_dir_dialog(
1009 &self,
1010 initial_location: Option<&FileUri>,
1011 ) -> crate::Result<Option<FileUri>> {
1012
1013 on_android!({
1014 impl_se!(struct Req<'a> { initial_location: Option<&'a FileUri> });
1015 impl_de!(struct Res { uri: Option<FileUri> });
1016
1017 let _guard = self.intent_lock.lock();
1018 self.api
1019 .run_mobile_plugin::<Res>("showManageDirDialog", Req { initial_location })
1020 .map(|v| v.uri)
1021 .map_err(Into::into)
1022 })
1023 }
1024
1025 /// Please use [`AndroidFs::show_manage_dir_dialog`] instead.
1026 #[deprecated = "Confusing name. Please use show_manage_dir_dialog instead."]
1027 #[warn(deprecated)]
1028 pub fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
1029 on_android!({
1030 self.show_manage_dir_dialog(None)
1031 })
1032 }
1033
1034 /// Opens a dialog to save a file and returns a **writeonly** URI.
1035 /// The returned file may be a newly created file with no content,
1036 /// or it may be an existing file with the requested MIME type.
1037 /// If the user cancels, `None` is returned.
1038 ///
1039 /// By default, returned URI is valid until the app is terminated.
1040 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
1041 ///
1042 /// This provides a standardized file explorer-style interface,
1043 /// and also allows file selection from part of third-party apps or cloud storage.
1044 ///
1045 /// Removing and reading the returned files is also supported in most cases,
1046 /// but note that files provided by third-party apps may not.
1047 ///
1048 /// # Args
1049 /// - ***initial_location*** :
1050 /// Indicate the initial location of dialog.
1051 /// There is no need to use this if there is no special reason.
1052 /// System will do its best to launch the dialog in the specified entry
1053 /// if it's a directory, or the directory that contains the specified file if not.
1054 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
1055 /// This must be a URI taken from following :
1056 /// - [`AndroidFs::resolve_initial_location`]
1057 /// - [`AndroidFs::show_open_file_dialog`]
1058 /// - [`AndroidFs::show_save_file_dialog`]
1059 /// - [`AndroidFs::show_manage_dir_dialog`]
1060 /// - [`AndroidFs::resolve_uri`]
1061 /// - [`AndroidFs::try_resolve_file_uri`]
1062 /// - [`AndroidFs::try_resolve_dir_uri`]
1063 /// - [`AndroidFs::read_dir`]
1064 /// - [`AndroidFs::create_file`]
1065 ///
1066 /// - ***initial_file_name*** :
1067 /// An initial file name.
1068 /// The user may change this value before creating the file.
1069 /// If no extension is present,
1070 /// the system may infer one from ***mime_type*** and may append it to the file name.
1071 /// But this append-extension operation depends on the model and version.
1072 ///
1073 /// - ***mime_type*** :
1074 /// The MIME type of the file to be saved.
1075 /// If this is None, MIME type is inferred from the extension of ***initial_file_name*** (not file name by user input)
1076 /// and if that fails, `application/octet-stream` is used.
1077 ///
1078 /// # Support
1079 /// All.
1080 ///
1081 /// # References
1082 /// <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
1083 pub fn show_save_file_dialog(
1084 &self,
1085 initial_location: Option<&FileUri>,
1086 initial_file_name: impl AsRef<str>,
1087 mime_type: Option<&str>,
1088 ) -> crate::Result<Option<FileUri>> {
1089
1090 on_android!({
1091 impl_se!(struct Req<'a> {
1092 initial_file_name: &'a str,
1093 mime_type: Option<&'a str>,
1094 initial_location: Option<&'a FileUri>
1095 });
1096 impl_de!(struct Res { uri: Option<FileUri> });
1097
1098 let initial_file_name = initial_file_name.as_ref();
1099
1100 let _guard = self.intent_lock.lock();
1101 self.api
1102 .run_mobile_plugin::<Res>("showSaveFileDialog", Req { initial_file_name, mime_type, initial_location })
1103 .map(|v| v.uri)
1104 .map_err(Into::into)
1105 })
1106 }
1107
1108 /// Create an **restricted** URI for the specified directory.
1109 /// This should only be used as `initial_location` in the dialog.
1110 /// It must not be used for any other purpose.
1111 ///
1112 /// This is useful when selecting (creating) new files and folders,
1113 /// but when selecting existing entries, `initial_location` is often better with None.
1114 ///
1115 /// Note this is an informal method and is not guaranteed to work reliably.
1116 /// But this URI does not cause the dialog to error.
1117 /// So please use this with the mindset that it's better than doing nothing.
1118 ///
1119 /// # Examples
1120 /// ```
1121 /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, InitialLocation, PublicGeneralPurposeDir, PublicImageDir};
1122 ///
1123 /// fn sample(app: tauri::AppHandle) {
1124 /// let api = app.android_fs();
1125 ///
1126 /// // Get URI of the top public directory in primary volume
1127 /// let initial_location = api.resolve_initial_location(
1128 /// InitialLocation::TopPublicDir,
1129 /// false,
1130 /// ).expect("Should be on Android");
1131 ///
1132 /// // Get URI of ~/Pictures/
1133 /// let initial_location = api.resolve_initial_location(
1134 /// PublicImageDir::Pictures,
1135 /// false
1136 /// ).expect("Should be on Android");
1137 ///
1138 /// // Get URI of ~/Documents/sub_dir1/sub_dir2/
1139 /// let initial_location = api.resolve_initial_location(
1140 /// InitialLocation::DirInPublicDir {
1141 /// base_dir: PublicGeneralPurposeDir::Documents.into(),
1142 /// relative_path: "sub_dir1/sub_dir2"
1143 /// },
1144 /// true // Create dirs of 'sub_dir1' and 'sub_dir2', if not exists
1145 /// ).expect("Should be on Android");
1146 ///
1147 /// // Open dialog with initial_location
1148 /// let _ = api.show_save_file_dialog(Some(&initial_location), "", None);
1149 /// let _ = api.show_open_file_dialog(Some(&initial_location), &[], true);
1150 /// let _ = api.show_manage_dir_dialog(Some(&initial_location));
1151 /// }
1152 /// ```
1153 ///
1154 /// # Support
1155 /// All.
1156 pub fn resolve_initial_location<'a>(
1157 &self,
1158 dir: impl Into<InitialLocation<'a>>,
1159 create_dirs: bool
1160 ) -> crate::Result<FileUri> {
1161
1162 on_android!({
1163 const TOP_DIR: &str = "content://com.android.externalstorage.documents/document/primary";
1164
1165 let uri = match dir.into() {
1166 InitialLocation::TopPublicDir => format!("{TOP_DIR}%3A"),
1167 InitialLocation::PublicDir(dir) => format!("{TOP_DIR}%3A{dir}"),
1168 InitialLocation::DirInPublicDir { base_dir, relative_path } => {
1169 let relative_path = relative_path.trim_matches('/');
1170
1171 if relative_path.is_empty() {
1172 format!("{TOP_DIR}%3A{base_dir}")
1173 }
1174 else {
1175 if create_dirs {
1176 let _ = self.public_storage().create_dir_all(base_dir, relative_path);
1177 }
1178 let sub_dirs = encode_document_id(relative_path);
1179 format!("{TOP_DIR}%3A{base_dir}%2F{sub_dirs}")
1180 }
1181 },
1182 InitialLocation::DirInPublicAppDir { base_dir, relative_path } => {
1183 let relative_path = &format!(
1184 "{}/{}",
1185 self.public_storage().app_dir_name()?,
1186 relative_path.trim_matches('/'),
1187 );
1188
1189 return self.resolve_initial_location(
1190 InitialLocation::DirInPublicDir { base_dir, relative_path },
1191 create_dirs
1192 )
1193 }
1194 };
1195
1196 Ok(FileUri { uri, document_top_tree_uri: None })
1197 })
1198 }
1199
1200 /// Opens a dialog for sharing file to other apps.
1201 ///
1202 /// An error will occur if there is no app that can handle the request.
1203 /// Please use [`AndroidFs::can_share_file`] to confirm.
1204 ///
1205 /// # Args
1206 /// - **uri** :
1207 /// Target file uri to share.
1208 /// This needs to be **readable**.
1209 /// Files in [`PrivateStorage`] ***cannot*** be used.
1210 ///
1211 /// # Support
1212 /// All.
1213 pub fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1214 on_android!({
1215 impl_se!(struct Req<'a> { uri: &'a FileUri });
1216 impl_de!(struct Res;);
1217
1218 self.api
1219 .run_mobile_plugin::<Res>("shareFile", Req { uri })
1220 .map(|_| ())
1221 .map_err(Into::into)
1222 })
1223 }
1224
1225 /// Opens a dialog for viewing file on other apps.
1226 /// This performs the general "open file" action.
1227 ///
1228 /// An error will occur if there is no app that can handle the request.
1229 /// Please use [`AndroidFs::can_view_file`] to confirm.
1230 ///
1231 /// # Args
1232 /// - **uri** :
1233 /// Target file uri to view.
1234 /// This needs to be **readable**.
1235 /// Files in [`PrivateStorage`] ***cannot*** be used.
1236 ///
1237 /// # Support
1238 /// All.
1239 pub fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()> {
1240 on_android!({
1241 impl_se!(struct Req<'a> { uri: &'a FileUri });
1242 impl_de!(struct Res;);
1243
1244 self.api
1245 .run_mobile_plugin::<Res>("viewFile", Req { uri })
1246 .map(|_| ())
1247 .map_err(Into::into)
1248 })
1249 }
1250
1251 /// Determines whether the specified file can be used with [`AndroidFs::show_share_file_dialog`].
1252 /// # Args
1253 /// - **uri** :
1254 /// Target file uri.
1255 /// This needs to be **readable**.
1256 ///
1257 /// # Support
1258 /// All.
1259 pub fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool> {
1260 on_android!({
1261 impl_se!(struct Req<'a> { uri: &'a FileUri });
1262 impl_de!(struct Res { value: bool });
1263
1264 self.api
1265 .run_mobile_plugin::<Res>("canShareFile", Req { uri })
1266 .map(|v| v.value)
1267 .map_err(Into::into)
1268 })
1269 }
1270
1271 /// Determines whether the specified file can be used with [`AndroidFs::show_view_file_dialog`].
1272 ///
1273 /// # Args
1274 /// - **uri** :
1275 /// Target file uri.
1276 /// This needs to be **readable**.
1277 ///
1278 /// # Support
1279 /// All.
1280 pub fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool> {
1281 on_android!({
1282 impl_se!(struct Req<'a> { uri: &'a FileUri });
1283 impl_de!(struct Res { value: bool });
1284
1285 self.api
1286 .run_mobile_plugin::<Res>("canViewFile", Req { uri })
1287 .map(|v| v.value)
1288 .map_err(Into::into)
1289 })
1290 }
1291
1292 /// Take persistent permission to access the file, directory and its descendants.
1293 /// This is a prolongation of an already acquired permission, not the acquisition of a new one.
1294 ///
1295 /// This works by just calling, without displaying any confirmation to the user.
1296 ///
1297 /// 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)
1298 /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].
1299 /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
1300 /// So check by [`AndroidFs::check_persisted_uri_permission`].
1301 /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
1302 ///
1303 /// # Args
1304 /// - **uri** :
1305 /// URI of the target file or directory. This must be a URI taken from following :
1306 /// - [`AndroidFs::show_open_file_dialog`]
1307 /// - [`AndroidFs::show_open_visual_media_dialog`]
1308 /// - [`AndroidFs::show_save_file_dialog`]
1309 /// - [`AndroidFs::show_manage_dir_dialog`]
1310 /// - [`AndroidFs::read_dir`] :
1311 /// If this, the permissions of the origin directory URI is persisted, not a entry iteself.
1312 /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1313 ///
1314 /// # Support
1315 /// All.
1316 pub fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1317 on_android!({
1318 impl_se!(struct Req<'a> { uri: &'a FileUri });
1319 impl_de!(struct Res;);
1320
1321 self.api
1322 .run_mobile_plugin::<Res>("takePersistableUriPermission", Req { uri })
1323 .map(|_| ())
1324 .map_err(Into::into)
1325 })
1326 }
1327
1328 /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1329 /// Returns false if there are only non-persistent permissions or no permissions.
1330 ///
1331 /// # Args
1332 /// - **uri** :
1333 /// URI of the target file or directory.
1334 /// If this is via [`AndroidFs::read_dir`], the permissions of the origin directory URI is checked, not a entry iteself.
1335 /// Because the permissions and validity period of the entry URIs depend on the origin directory.
1336 ///
1337 /// - **mode** :
1338 /// The mode of permission you want to check.
1339 ///
1340 /// # Support
1341 /// All.
1342 pub fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool> {
1343 on_android!({
1344 impl_se!(struct Req<'a> { uri: &'a FileUri, mode: PersistableAccessMode });
1345 impl_de!(struct Res { value: bool });
1346
1347 self.api
1348 .run_mobile_plugin::<Res>("checkPersistedUriPermission", Req { uri, mode })
1349 .map(|v| v.value)
1350 .map_err(Into::into)
1351 })
1352 }
1353
1354 /// Return list of all persisted URIs that have been persisted by [`AndroidFs::take_persistable_uri_permission`] and currently valid.
1355 ///
1356 /// # Support
1357 /// All.
1358 pub fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>> {
1359 on_android!(std::iter::Empty::<_>, {
1360 impl_de!(struct Obj { uri: FileUri, r: bool, w: bool, d: bool });
1361 impl_de!(struct Res { items: Vec<Obj> });
1362
1363 self.api
1364 .run_mobile_plugin::<Res>("getAllPersistedUriPermissions", "")
1365 .map(|v| v.items.into_iter())
1366 .map(|v| v.map(|v| {
1367 let (uri, can_read, can_write) = (v.uri, v.r, v.w);
1368 match v.d {
1369 true => PersistedUriPermission::Dir { uri, can_read, can_write },
1370 false => PersistedUriPermission::File { uri, can_read, can_write }
1371 }
1372 }))
1373 .map_err(Into::into)
1374 })
1375 }
1376
1377 /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
1378 ///
1379 /// # Args
1380 /// - ***uri*** :
1381 /// URI of the target file or directory.
1382 ///
1383 /// # Support
1384 /// All.
1385 pub fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()> {
1386 on_android!({
1387 impl_se!(struct Req<'a> { uri: &'a FileUri });
1388 impl_de!(struct Res;);
1389
1390 self.api
1391 .run_mobile_plugin::<Res>("releasePersistedUriPermission", Req { uri })
1392 .map(|_| ())
1393 .map_err(Into::into)
1394 })
1395 }
1396
1397 /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].
1398 ///
1399 /// # Support
1400 /// All.
1401 pub fn release_all_persisted_uri_permissions(&self) -> crate::Result<()> {
1402 on_android!({
1403 impl_de!(struct Res);
1404
1405 self.api
1406 .run_mobile_plugin::<Res>("releaseAllPersistedUriPermissions", "")
1407 .map(|_| ())
1408 .map_err(Into::into)
1409 })
1410 }
1411
1412 /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
1413 ///
1414 /// # Support
1415 /// All.
1416 pub fn is_visual_media_dialog_available(&self) -> crate::Result<bool> {
1417 on_android!({
1418 impl_de!(struct Res { value: bool });
1419
1420 self.api
1421 .run_mobile_plugin::<Res>("isVisualMediaDialogAvailable", "")
1422 .map(|v| v.value)
1423 .map_err(Into::into)
1424 })
1425 }
1426
1427 /// File storage intended for the app's use only.
1428 pub fn private_storage(&self) -> PrivateStorage<'_, R> {
1429 PrivateStorage(self)
1430 }
1431
1432 /// File storage that is available to other applications and users.
1433 pub fn public_storage(&self) -> PublicStorage<'_, R> {
1434 PublicStorage(self)
1435 }
1436}