tauri_plugin_android_fs/lib.rs
1//! Overview and usage is [here](https://crates.io/crates/tauri-plugin-android-fs)
2
3mod models;
4mod impls;
5mod error;
6
7use std::io::{Read as _, Write as _};
8
9pub use models::*;
10pub use error::{Error, Result};
11pub use impls::{AndroidFsExt, init};
12
13/// API
14pub trait AndroidFs<R: tauri::Runtime> {
15
16 /// Verify whether this plugin is available.
17 ///
18 /// On Android, this returns true.
19 /// On other platforms, this returns false.
20 fn is_available(&self) -> bool {
21 #[cfg(not(target_os = "android"))] {
22 false
23 }
24 #[cfg(target_os = "android")] {
25 true
26 }
27 }
28
29 /// Get the file or directory name.
30 ///
31 /// # Args
32 /// - ***uri*** :
33 /// Target URI.
34 /// This needs to be **readable**.
35 ///
36 /// # Support
37 /// All Android version.
38 fn get_name(&self, uri: &FileUri) -> crate::Result<String>;
39
40 /// Query the provider to get mime type.
41 /// If the directory, this returns `None`.
42 /// If the file, this returns no `None`.
43 /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.
44 ///
45 /// # Args
46 /// - ***uri*** :
47 /// Target URI.
48 /// This needs to be **readable**.
49 ///
50 /// # Support
51 /// All Android version.
52 fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>>;
53
54 /// Queries the file system to get information about a file, directory.
55 ///
56 /// # Args
57 /// - ***uri*** :
58 /// Target URI.
59 /// This needs to be **readable**.
60 ///
61 /// # Note
62 /// This uses [`AndroidFs::open_file`] internally.
63 ///
64 /// # Support
65 /// All Android version.
66 fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
67 let file = self.open_file(uri, FileAccessMode::Read)?;
68 Ok(file.metadata()?)
69 }
70
71 /// Open a file in the specified mode.
72 ///
73 /// # Args
74 /// - ***uri*** :
75 /// Target file URI.
76 /// This must have corresponding permissions (read, write, or both) for the specified **mode**.
77 ///
78 /// - ***mode*** :
79 /// Indicates how the file is opened and the permissions granted.
80 /// Note that files provided by third-party apps may not support [`FileAccessMode::WriteAppend`]. (ex: Files on GoogleDrive)
81 ///
82 /// # Note
83 /// This method uses a FileDescriptor internally.
84 /// However, if the target file does not physically exist on the device, such as cloud-based files,
85 /// the write operation using a FileDescriptor may not be reflected properly.
86 /// In such cases, consider using [AndroidFs::write_via_kotlin],
87 /// which writes using a standard method,
88 /// or [AndroidFs::write], which automatically falls back to that approach when necessary.
89 /// If you specifically need to write using std::fs::File not entire contents, see [AndroidFs::write_via_kotlin_in] or [AndroidFs::copy_via_kotlin].
90 ///
91 /// It seems that the issue does not occur on all cloud storage platforms. At least, files on Google Drive have issues,
92 /// but files on Dropbox can be written to correctly using a FileDescriptor.
93 /// 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).
94 /// This information will be used in [AndroidFs::need_write_via_kotlin] used by `AndroidFs::write`.
95 ///
96 /// There are no problems with file reading.
97 ///
98 /// # Support
99 /// All Android version.
100 fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File>;
101
102 /// Reads the entire contents of a file into a bytes vector.
103 ///
104 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
105 ///
106 /// # Args
107 /// - ***uri*** :
108 /// Target file URI.
109 /// This needs to be **readable**.
110 ///
111 /// # Support
112 /// All Android version.
113 fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
114 let mut file = self.open_file(uri, FileAccessMode::Read)?;
115 let mut buf = file.metadata().ok()
116 .map(|m| m.len() as usize)
117 .map(Vec::with_capacity)
118 .unwrap_or_else(Vec::new);
119
120 file.read_to_end(&mut buf)?;
121 Ok(buf)
122 }
123
124 /// Reads the entire contents of a file into a string.
125 ///
126 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
127 ///
128 /// # Args
129 /// - ***uri*** :
130 /// Target file URI.
131 /// This needs to be **readable**.
132 ///
133 /// # Support
134 /// All Android version.
135 fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
136 let mut file = self.open_file(uri, FileAccessMode::Read)?;
137 let mut buf = file.metadata().ok()
138 .map(|m| m.len() as usize)
139 .map(String::with_capacity)
140 .unwrap_or_else(String::new);
141
142 file.read_to_string(&mut buf)?;
143 Ok(buf)
144 }
145
146 /// Writes a slice as the entire contents of a file.
147 /// This function will entirely replace its contents if it does exist.
148 ///
149 /// If you want to operate the file, use [`AndroidFs::open_file`] instead.
150 ///
151 /// # Args
152 /// - ***uri*** :
153 /// Target file URI.
154 /// This needs to be **writable**.
155 ///
156 /// # Support
157 /// All Android version.
158 fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
159 if self.need_write_via_kotlin(uri)? {
160 self.write_via_kotlin(uri, contents)?;
161 return Ok(())
162 }
163
164 let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
165 file.write_all(contents.as_ref())?;
166 Ok(())
167 }
168
169 /// Writes a slice as the entire contents of a file.
170 /// This function will entirely replace its contents if it does exist.
171 ///
172 /// Differences from `std::fs::File::write_all` is the process is done on Kotlin side.
173 /// See [`AndroidFs::open_file`] for why this function exists.
174 ///
175 /// If [`AndroidFs::write`] is used, it automatically fall back to this by [`AndroidFs::need_write_via_kotlin`],
176 /// so there should be few opportunities to use this.
177 ///
178 /// If you want to write using `std::fs::File`, not entire contents, use [`AndroidFs::write_via_kotlin_in`].
179 ///
180 /// # Inner process
181 /// The contents is written to a temporary file by Rust side
182 /// and then copied to the specified file on Kotlin side by [`AndroidFs::copy_via_kotlin`].
183 ///
184 /// # Support
185 /// All Android version.
186 fn write_via_kotlin(
187 &self,
188 uri: &FileUri,
189 contents: impl AsRef<[u8]>
190 ) -> crate::Result<()> {
191
192 self.write_via_kotlin_in(uri, |file| file.write_all(contents.as_ref()))
193 }
194
195 /// See [`AndroidFs::write_via_kotlin`] for information.
196 /// Use this if you want to write using `std::fs::File`, not entire contents.
197 ///
198 /// If you want to retain the file outside the closure,
199 /// you can perform the same operation using [`AndroidFs::copy_via_kotlin`] and [`PrivateStorage`].
200 /// For details, please refer to the internal implementation of this function.
201 ///
202 /// # Args
203 /// - ***uri*** :
204 /// Target file URI to write.
205 ///
206 /// - **contetns_writer** :
207 /// A closure that accepts a mutable reference to a `std::fs::File`
208 /// and performs the actual write operations. Note that this represents a temporary file.
209 fn write_via_kotlin_in<T>(
210 &self,
211 uri: &FileUri,
212 contents_writer: impl FnOnce(&mut std::fs::File) -> std::io::Result<T>
213 ) -> crate::Result<T> {
214
215 static TMP_FILE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
216
217 // 一時ファイルの排他アクセスを保証
218 let _guard = TMP_FILE_LOCK.lock();
219
220 // 一時ファイルのパスを取得
221 let tmp_file_path = self.app_handle()
222 .android_fs()
223 .private_storage()
224 .resolve_path_with(PrivateDir::Cache, "tauri-plugin-android-fs-tempfile-write-via-kotlin")?;
225
226 // 一時ファイルに内容を書き込む
227 // エラー処理は一時ファイルを削除するまで保留
228 let result = {
229 let ref mut file = std::fs::File::create(&tmp_file_path)?;
230 contents_writer(file)
231 };
232
233 // 上記処理が成功していれば、一時ファイルの内容をkotlin側で指定されたファイルにコピーする
234 // エラー処理は一時ファイルを削除するまで保留
235 let result = result
236 .map_err(crate::Error::from)
237 .and_then(|t| self.copy_via_kotlin(&(&tmp_file_path).into(), uri).map(|_| t));
238
239 // 一時ファイルを削除
240 let _ = std::fs::remove_file(&tmp_file_path);
241
242 result
243 }
244
245 /// Determines if the file needs to be written via Kotlin side instead of Rust side.
246 /// Currently, this returns true only if the file is on GoogleDrive.
247 ///
248 /// # Support
249 /// All Android version.
250 fn need_write_via_kotlin(&self, uri: &FileUri) -> crate::Result<bool> {
251 Ok(uri.uri.starts_with("content://com.google.android.apps.docs.storage"))
252 }
253
254 /// Copies the contents of src file to dest.
255 ///
256 /// This copy process is done on Kotlin side, not on Rust.
257 /// Large files in GB units are also supported.
258 ///
259 /// See [`AndroidFs::write_via_kotlin`] for why this function exists.
260 ///
261 /// # Args
262 /// - ***src*** :
263 /// The URI of source file.
264 /// This needs to be **readable**.
265 ///
266 /// - ***dest*** :
267 /// The URI of destination file.
268 /// This needs to be **writable**.
269 ///
270 /// # Support
271 /// All Android version.
272 fn copy_via_kotlin(&self, src: &FileUri, dest: &FileUri) -> crate::Result<()>;
273
274 /// Remove the file.
275 ///
276 /// # Args
277 /// - ***uri*** :
278 /// Target file URI.
279 /// This needs to be **writable**, at least. But even if it is,
280 /// removing may not be possible in some cases.
281 /// For details, refer to the documentation of the function that provided the URI.
282 ///
283 /// # Support
284 /// All Android version.
285 fn remove_file(&self, uri: &FileUri) -> crate::Result<()>;
286
287 /// Remove the **empty** directory.
288 ///
289 /// # Args
290 /// - ***uri*** :
291 /// Target directory URI.
292 /// This needs to be **writable**.
293 ///
294 /// # Support
295 /// All Android version.
296 fn remove_dir(&self, uri: &FileUri) -> crate::Result<()>;
297
298 /// Creates a new empty file in the specified location and returns a URI.
299 ///
300 /// The permissions and validity period of the returned URIs depend on the origin directory
301 /// (e.g., the top directory selected by [`AndroidFs::show_open_dir_dialog`])
302 ///
303 /// # Args
304 /// - ***dir*** :
305 /// The URI of the base directory.
306 /// This needs to be **read-write**.
307 ///
308 /// - ***relative_path*** :
309 /// The file path relative to the base directory.
310 /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.
311 /// Any missing subdirectories in the specified path will be created automatically.
312 ///
313 /// - ***mime_type*** :
314 /// The MIME type of the file to be created.
315 /// If this is None, MIME type is inferred from the extension
316 /// and if that fails, `application/octet-stream` is used.
317 ///
318 /// # Support
319 /// All Android version.
320 fn create_file(
321 &self,
322 dir: &FileUri,
323 relative_path: impl AsRef<str>,
324 mime_type: Option<&str>
325 ) -> crate::Result<FileUri>;
326
327 /// Returns the child files and directories of the specified directory.
328 /// The order of the entries is not guaranteed.
329 ///
330 /// The permissions and validity period of the returned URIs depend on the origin directory
331 /// (e.g., the top directory selected by [`AndroidFs::show_open_dir_dialog`])
332 ///
333 /// # Args
334 /// - **uri** :
335 /// Target directory URI.
336 /// This needs to be **readable**.
337 ///
338 /// # Note
339 /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily.
340 /// Thus, for directories with thousands or tens of thousands of elements, it may take several seconds.
341 ///
342 /// # Support
343 /// All Android version.
344 fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>>;
345
346 /// Opens a system file picker and returns a **read-write** URIs.
347 /// If no file is selected or the user cancels, an empty vec is returned.
348 ///
349 /// This provides a relatively consistent interface regardless of version or device,
350 /// and also allows file selection from third-party apps or cloud storage.
351 ///
352 /// By default, returned URI is valid until the app is terminated.
353 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
354 ///
355 /// Removing the returned files is also supported in most cases,
356 /// but note that files provided by third-party apps may not be removable.
357 ///
358 /// Just to read images and videos, consider using [`AndroidFs::show_open_visual_media_dialog`] instead.
359 ///
360 /// # Args
361 /// - ***initial_location*** :
362 /// Indicate the initial location of dialog.
363 /// System will do its best to launch the dialog in the specified entry
364 /// if it's a directory, or the directory that contains the specified file if not.
365 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
366 ///
367 /// - ***mime_types*** :
368 /// The MIME types of the file to be selected.
369 /// However, there is no guarantee that the returned file will match the specified types.
370 /// If left empty, all file types will be available (equivalent to `["*/*"]`).
371 ///
372 /// - ***multiple*** :
373 /// Indicates whether multiple file selection is allowed.
374 ///
375 /// # Issue
376 /// This dialog has known issues. See the following for details and workarounds
377 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
378 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
379 ///
380 /// # Support
381 /// All Android version.
382 ///
383 /// # References
384 /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT>
385 fn show_open_file_dialog(
386 &self,
387 initial_location: Option<&FileUri>,
388 mime_types: &[&str],
389 multiple: bool,
390 ) -> crate::Result<Vec<FileUri>>;
391
392 /// Opens a media picker and returns a **readonly** URIs.
393 /// If no file is selected or the user cancels, an empty vec is returned.
394 ///
395 /// This media picker provides a browsable interface that presents the user with their media library,
396 /// sorted by date from newest to oldest.
397 ///
398 /// By default, returned URI is valid until the app is terminated.
399 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
400 ///
401 /// # Args
402 /// - ***target*** :
403 /// The media type of the file to be selected.
404 /// Images or videos, or both.
405 ///
406 /// - ***multiple*** :
407 /// Indicates whether multiple file selection is allowed.
408 ///
409 /// # Issue
410 /// This dialog has known issues. See the following for details and workarounds
411 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
412 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
413 ///
414 /// # Note
415 /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].
416 /// Instead, it will be assigned a sequential number, such as `1000091523.png`.
417 /// And this is marked intended behavior, not a bug.
418 /// - <https://issuetracker.google.com/issues/268079113>
419 ///
420 /// # Support
421 /// This feature is available on devices that meet the following criteria:
422 /// - Running Android 11 (API level 30) or higher
423 /// - Receive changes to Modular System Components through Google System Updates
424 ///
425 /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].
426 /// If not supported, this function behaves the same as [`AndroidFs::show_open_file_dialog`].
427 ///
428 /// # References
429 /// <https://developer.android.com/training/data-storage/shared/photopicker>
430 fn show_open_visual_media_dialog(
431 &self,
432 target: VisualMediaTarget,
433 multiple: bool,
434 ) -> crate::Result<Vec<FileUri>>;
435
436 /// Opens a system directory picker, allowing the creation of a new directory or the selection of an existing one,
437 /// and returns a **read-write** directory URI.
438 /// App can fully manage entries within the returned directory.
439 /// If no directory is selected or the user cancels, `None` is returned.
440 ///
441 /// By default, returned URI is valid until the app is terminated.
442 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
443 ///
444 /// # Args
445 /// - ***initial_location*** :
446 /// Indicate the initial location of dialog.
447 /// System will do its best to launch the dialog in the specified entry
448 /// if it's a directory, or the directory that contains the specified file if not.
449 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
450 ///
451 /// # Issue
452 /// This dialog has known issues. See the following for details and workarounds
453 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
454 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
455 ///
456 /// # Support
457 /// All Android version.
458 ///
459 /// # References
460 /// <https://developer.android.com/reference/android/content/Intent#ACTION_OPEN_DOCUMENT_TREE>
461 fn show_manage_dir_dialog(
462 &self,
463 initial_location: Option<&FileUri>,
464 ) -> crate::Result<Option<FileUri>>;
465
466 /// Please use [`AndroidFs::show_manage_dir_dialog`] instead.
467 #[deprecated = "Confusing name. Please use show_manage_dir_dialog instead."]
468 #[warn(deprecated)]
469 fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>> {
470 self.show_manage_dir_dialog(None)
471 }
472
473 /// Opens a dialog to save a file and returns a **writeonly** URI.
474 /// The returned file may be a newly created file with no content,
475 /// or it may be an existing file with the requested MIME type.
476 /// If the user cancels, `None` is returned.
477 ///
478 /// By default, returned URI is valid until the app is terminated.
479 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
480 ///
481 /// Removing and reading the returned files is also supported in most cases,
482 /// but note that files provided by third-party apps may not.
483 ///
484 /// # Args
485 /// - ***initial_location*** :
486 /// Indicate the initial location of dialog.
487 /// System will do its best to launch the dialog in the specified entry
488 /// if it's a directory, or the directory that contains the specified file if not.
489 /// If this is missing or failed to resolve the desired initial location, the initial location is system specific.
490 ///
491 /// - ***initial_file_name*** :
492 /// An initial file name, but the user may change this value before creating the file.
493 ///
494 /// - ***mime_type*** :
495 /// The MIME type of the file to be saved.
496 /// If this is None, MIME type is inferred from the extension
497 /// and if that fails, `application/octet-stream` is used.
498 ///
499 /// # Issue
500 /// This dialog has known issues. See the following for details and workarounds
501 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
502 /// - <https://github.com/aiueo13/tauri-plugin-android-fs/blob/main/README.md>
503 ///
504 /// # Support
505 /// All Android version.
506 ///
507 /// # References
508 /// <https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT>
509 fn show_save_file_dialog(
510 &self,
511 initial_location: Option<&FileUri>,
512 initial_file_name: impl AsRef<str>,
513 mime_type: Option<&str>,
514 ) -> crate::Result<Option<FileUri>>;
515
516 /// Opens a dialog for sharing file to other apps.
517 ///
518 /// An error will occur if there is no app that can handle the request.
519 /// Please use [`AndroidFs::can_share_file`] to confirm.
520 ///
521 /// # Args
522 /// - **uri** :
523 /// Target file uri to share.
524 /// This needs to be **readable**.
525 /// This given from [`PrivateStorage`] or [`AndroidFs::show_open_visual_media_dialog`] ***cannot*** be used.
526 ///
527 /// # Support
528 /// All Android version.
529 fn show_share_file_dialog(&self, uri: &FileUri) -> crate::Result<()>;
530
531 /// Opens a dialog for viewing file on other apps.
532 /// This performs the general "open file" action.
533 ///
534 /// An error will occur if there is no app that can handle the request.
535 /// Please use [`AndroidFs::can_view_file`] to confirm.
536 ///
537 /// # Args
538 /// - **uri** :
539 /// Target file uri to view.
540 /// This needs to be **readable**.
541 /// This given from [`PrivateStorage`] or [`AndroidFs::show_open_visual_media_dialog`] ***cannot*** be used.
542 ///
543 /// # Support
544 /// All Android version.
545 fn show_view_file_dialog(&self, uri: &FileUri) -> crate::Result<()>;
546
547 /// Determines whether the specified file can be used with [`AndroidFs::show_share_file_dialog`].
548 /// # Args
549 /// - **uri** :
550 /// Target file uri.
551 /// This needs to be **readable**.
552 ///
553 /// # Support
554 /// All Android version.
555 fn can_share_file(&self, uri: &FileUri) -> crate::Result<bool>;
556
557 /// Determines whether the specified file can be used with [`AndroidFs::show_view_file_dialog`].
558 ///
559 /// # Args
560 /// - **uri** :
561 /// Target file uri.
562 /// This needs to be **readable**.
563 ///
564 /// # Support
565 /// All Android version.
566 fn can_view_file(&self, uri: &FileUri) -> crate::Result<bool>;
567
568 /// Take persistent permission to access the file, directory and its descendants.
569 /// This is a prolongation of an already acquired permission, not the acquisition of a new one.
570 ///
571 /// This works by just calling, without displaying any confirmation to the user.
572 ///
573 /// 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)
574 /// Therefore, it is recommended to relinquish the unnecessary persisted URI by [`AndroidFs::release_persisted_uri_permission`] or [`AndroidFs::release_all_persisted_uri_permissions`].
575 /// Persisted permissions may be relinquished by other apps, user, or by moving/removing entries.
576 /// So check by [`AndroidFs::check_persisted_uri_permission`].
577 /// And you can retrieve the list of persisted uris using [`AndroidFs::get_all_persisted_uri_permissions`].
578 ///
579 /// # Args
580 /// - **uri** :
581 /// URI of the target file or directory. This must be a URI taken from following :
582 /// - [`AndroidFs::show_open_file_dialog`]
583 /// - [`AndroidFs::show_open_visual_media_dialog`]
584 /// - [`AndroidFs::show_save_file_dialog`]
585 /// - [`AndroidFs::show_manage_dir_dialog`]
586 /// - [`AndroidFs::read_dir`] :
587 /// If this, the permissions of the origin directory URI is persisted, not a entry iteself.
588 /// Because the permissions and validity period of the entry URIs depend on the origin directory.
589 ///
590 /// # Support
591 /// All Android version.
592 fn take_persistable_uri_permission(&self, uri: &FileUri) -> crate::Result<()>;
593
594 /// Check a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
595 /// Returns false if there are only non-persistent permissions or no permissions.
596 ///
597 /// # Args
598 /// - **uri** :
599 /// URI of the target file or directory.
600 ///
601 /// - **mode** :
602 /// The mode of permission you want to check.
603 ///
604 /// # Support
605 /// All Android version.
606 fn check_persisted_uri_permission(&self, uri: &FileUri, mode: PersistableAccessMode) -> crate::Result<bool>;
607
608 /// Return list of all URI permission grants that have been persisted by [`AndroidFs::take_persistable_uri_permission`].
609 ///
610 /// # Support
611 /// All Android version.
612 fn get_all_persisted_uri_permissions(&self) -> crate::Result<impl Iterator<Item = PersistedUriPermission>>;
613
614 /// Relinquish a persisted URI permission grant by [`AndroidFs::take_persistable_uri_permission`].
615 ///
616 /// # Args
617 /// - ***uri*** :
618 /// URI of the target file or directory.
619 ///
620 /// # Support
621 /// All Android version.
622 fn release_persisted_uri_permission(&self, uri: &FileUri) -> crate::Result<()>;
623
624 /// Relinquish a all persisted uri permission grants by [`AndroidFs::take_persistable_uri_permission`].
625 ///
626 /// # Support
627 /// All Android version.
628 fn release_all_persisted_uri_permissions(&self) -> crate::Result<()>;
629
630 /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
631 ///
632 /// # Support
633 /// All Android version.
634 fn is_visual_media_dialog_available(&self) -> crate::Result<bool>;
635
636 /// File storage intended for the app’s use only.
637 fn private_storage(&self) -> &impl PrivateStorage<R>;
638
639 /// File storage that is available to other applications and users.
640 fn public_storage(&self) -> &impl PublicStorage<R>;
641
642 fn app_handle(&self) -> &tauri::AppHandle<R>;
643}
644
645/// File storage that is available to other applications and users.
646pub trait PublicStorage<R: tauri::Runtime> {
647
648 /// See [`PublicStorage::create_file_in_public_dir`] for description.
649 ///
650 /// This is the same as following:
651 /// ``````ignore
652 /// create_file_in_public_dir(
653 /// dir,
654 /// format!("{app_name}/{relative_path}"),
655 /// mime_type
656 /// );
657 /// ``````
658 fn create_file_in_public_app_dir(
659 &self,
660 dir: impl Into<PublicDir>,
661 relative_path: impl AsRef<str>,
662 mime_type: Option<&str>
663 ) -> crate::Result<FileUri> {
664
665 let config = self.app_handle().config();
666 let app_name = config.product_name.as_deref().unwrap_or("");
667 let app_name = match app_name.is_empty() {
668 true => &config.identifier,
669 false => app_name
670 };
671 let app_name = app_name.replace('/', " ");
672 let relative_path = relative_path.as_ref().trim_start_matches('/');
673 let relative_path_with_subdir = format!("{app_name}/{relative_path}");
674
675 self.create_file_in_public_dir(dir, relative_path_with_subdir, mime_type)
676 }
677
678 /// Creates a new empty file in the specified public directory and returns a **persistent read-write** URI.
679 ///
680 /// The created file has following features :
681 /// - Will be registered with the corresponding MediaStore as needed.
682 /// - Always supports remove.
683 /// - Not removed when the app is uninstalled.
684 ///
685 /// # Args
686 /// - ***dir*** :
687 /// The base directory.
688 ///
689 /// - ***relative_path_with_subdir*** :
690 /// The file path relative to the base directory.
691 /// If a file with the same name already exists, a sequential number will be appended to ensure uniqueness.
692 /// Any missing subdirectories in the specified path will be created automatically.
693 /// Please specify a subdirectory in this,
694 /// such as `MyApp/file.txt` or `MyApp/2025-2-11/file.txt`. Do not use `file.txt`.
695 /// As shown above, it is customary to specify the app name at the beginning of the subdirectory,
696 /// and in this case, using [`PublicStorage::create_file_in_public_app_dir`] is recommended.
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
701 /// and if that fails, `application/octet-stream` is used.
702 /// When using [`PublicImageDir`], please use only image MIME types; using other types may cause errors.
703 /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
704 /// Only [`PublicGeneralPurposeDir`] supports all MIME types.
705 ///
706 /// # Support
707 /// Android 10 (API level 29) or higher.
708 /// Lower are need runtime request of `WRITE_EXTERNAL_STORAGE`. (This option will be made available in the future)
709 ///
710 /// [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
711 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
712 /// [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
713 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
714 /// Others are available in all Android versions.
715 fn create_file_in_public_dir(
716 &self,
717 dir: impl Into<PublicDir>,
718 relative_path_with_subdir: impl AsRef<str>,
719 mime_type: Option<&str>
720 ) -> crate::Result<FileUri>;
721
722 /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
723 ///
724 /// # Support
725 /// All Android version.
726 fn is_audiobooks_dir_available(&self) -> crate::Result<bool>;
727
728 /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
729 ///
730 /// # Support
731 /// All Android version.
732 fn is_recordings_dir_available(&self) -> crate::Result<bool>;
733
734 fn app_handle(&self) -> &tauri::AppHandle<R>;
735}
736
737/// File storage intended for the app’s use only.
738pub trait PrivateStorage<R: tauri::Runtime> {
739
740 /// Get the absolute path of the specified directory.
741 /// App can fully manage entries within this directory without any permission via std::fs.
742 ///
743 /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s initialising request.
744 /// When using [`PrivateDir::Cache`], the system will automatically delete files in this directory as disk space is needed elsewhere on the device.
745 ///
746 /// The returned path may change over time if the calling app is moved to an adopted storage device,
747 /// so only relative paths should be persisted.
748 ///
749 /// # Examples
750 /// ```no_run
751 /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, PrivateDir, PrivateStorage};
752 ///
753 /// fn example(app: tauri::AppHandle) {
754 /// let api = app.android_fs().private_storage();
755 ///
756 /// let dir_path = api.resolve_path(PrivateDir::Data).unwrap();
757 /// let file_path = dir_path.join("2025-2-12/data.txt");
758 ///
759 /// // Write file
760 /// std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
761 /// std::fs::write(&file_path, "aaaa").unwrap();
762 ///
763 /// // Read file
764 /// let _ = std::fs::read_to_string(&file_path).unwrap();
765 ///
766 /// // Remove file
767 /// std::fs::remove_file(&file_path).unwrap();
768 /// }
769 /// ```
770 ///
771 /// # Support
772 /// All Android version.
773 fn resolve_path(&self, dir: PrivateDir) -> crate::Result<std::path::PathBuf>;
774
775 /// Get the absolute path of the specified relative path and base directory.
776 /// App can fully manage entries of this path without any permission via std::fs.
777 ///
778 /// See [`PrivateStorage::resolve_path`] for details.
779 ///
780 /// # Support
781 /// All Android version.
782 fn resolve_path_with(
783 &self,
784 dir: PrivateDir,
785 relative_path: impl AsRef<str>
786 ) -> crate::Result<std::path::PathBuf> {
787
788 let relative_path = relative_path.as_ref().trim_start_matches('/');
789 let path = self.resolve_path(dir)?.join(relative_path);
790 Ok(path)
791 }
792
793 fn resolve_uri(&self, dir: PrivateDir) -> crate::Result<FileUri> {
794 self.resolve_path(dir).map(Into::into)
795 }
796
797 fn resolve_uri_with(&self, dir: PrivateDir, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
798 self.resolve_path_with(dir, relative_path).map(Into::into)
799 }
800
801 /// Writes a slice as the entire contents of a file.
802 ///
803 /// This function will create a file if it does not exist, and will entirely replace its contents if it does.
804 /// Recursively create parent directories if they are missing.
805 ///
806 /// This internally uses [`PrivateStorage::resolve_path`] , [`std::fs::create_dir_all`], and [`std::fs::write`].
807 /// See [`PrivateStorage::resolve_path`] for details.
808 ///
809 /// # Support
810 /// All Android version.
811 fn write(
812 &self,
813 base_dir: PrivateDir,
814 relative_path: impl AsRef<str>,
815 contents: impl AsRef<[u8]>
816 ) -> crate::Result<()> {
817
818 let path = self.resolve_path_with(base_dir, relative_path)?;
819
820 if let Some(parent_dir) = path.parent() {
821 std::fs::create_dir_all(parent_dir)?;
822 }
823
824 std::fs::write(path, contents)?;
825
826 Ok(())
827 }
828
829 /// Open a file in read-only mode.
830 ///
831 /// If you only need to read the entire file contents, consider using [`PrivateStorage::read`] or [`PrivateStorage::read_to_string`] instead.
832 ///
833 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::open`].
834 /// See [`PrivateStorage::resolve_path`] for details.
835 ///
836 /// # Support
837 /// All Android version.
838 fn open_file(
839 &self,
840 base_dir: PrivateDir,
841 relative_path: impl AsRef<str>,
842 ) -> crate::Result<std::fs::File> {
843
844 let path = self.resolve_path_with(base_dir, relative_path)?;
845 Ok(std::fs::File::open(path)?)
846 }
847
848 /// Opens a file in write-only mode.
849 /// This function will create a file if it does not exist, and will truncate it if it does.
850 ///
851 /// If you only need to write the contents, consider using [`PrivateStorage::write`] instead.
852 ///
853 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create`].
854 /// See [`PrivateStorage::resolve_path`] for details.
855 ///
856 /// # Support
857 /// All Android version.
858 fn create_file(
859 &self,
860 base_dir: PrivateDir,
861 relative_path: impl AsRef<str>,
862 ) -> crate::Result<std::fs::File> {
863
864 let path = self.resolve_path_with(base_dir, relative_path)?;
865 Ok(std::fs::File::create(path)?)
866 }
867
868 /// Creates a new file in read-write mode; error if the file exists.
869 ///
870 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create_new`].
871 /// See [`PrivateStorage::resolve_path`] for details.
872 ///
873 /// # Support
874 /// All Android version.
875 fn create_new_file(
876 &self,
877 base_dir: PrivateDir,
878 relative_path: impl AsRef<str>,
879 ) -> crate::Result<std::fs::File> {
880
881 let path = self.resolve_path_with(base_dir, relative_path)?;
882 Ok(std::fs::File::create_new(path)?)
883 }
884
885 /// Reads the entire contents of a file into a bytes vector.
886 ///
887 /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.
888 ///
889 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read`].
890 /// See [`PrivateStorage::resolve_path`] for details.
891 ///
892 /// # Support
893 /// All Android version.
894 fn read(
895 &self,
896 base_dir: PrivateDir,
897 relative_path: impl AsRef<str>,
898 ) -> crate::Result<Vec<u8>> {
899
900 let path = self.resolve_path_with(base_dir, relative_path)?;
901 Ok(std::fs::read(path)?)
902 }
903
904 /// Reads the entire contents of a file into a string.
905 ///
906 /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.
907 ///
908 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_to_string`].
909 /// See [`PrivateStorage::resolve_path`] for details.
910 ///
911 /// # Support
912 /// All Android version.
913 fn read_to_string(
914 &self,
915 base_dir: PrivateDir,
916 relative_path: impl AsRef<str>,
917 ) -> crate::Result<String> {
918
919 let path = self.resolve_path_with(base_dir, relative_path)?;
920 Ok(std::fs::read_to_string(path)?)
921 }
922
923 /// Returns an iterator over the entries within a directory.
924 ///
925 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_dir`].
926 /// See [`PrivateStorage::resolve_path`] for details.
927 ///
928 /// # Support
929 /// All Android version.
930 fn read_dir(
931 &self,
932 base_dir: PrivateDir,
933 relative_path: Option<&str>,
934 ) -> crate::Result<std::fs::ReadDir> {
935
936 let path = match relative_path {
937 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
938 None => self.resolve_path(base_dir)?,
939 };
940
941 Ok(std::fs::read_dir(path)?)
942 }
943
944 /// Removes a file from the filesystem.
945 ///
946 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_file`].
947 /// See [`PrivateStorage::resolve_path`] for details.
948 ///
949 /// # Support
950 /// All Android version.
951 fn remove_file(
952 &self,
953 base_dir: PrivateDir,
954 relative_path: impl AsRef<str>,
955 ) -> crate::Result<()> {
956
957 let path = self.resolve_path_with(base_dir, relative_path)?;
958 Ok(std::fs::remove_file(path)?)
959 }
960
961 /// Removes an empty directory.
962 /// If you want to remove a directory that is not empty, as well as all of its contents recursively, consider using [`PrivateStorage::remove_dir_all`] instead.
963 ///
964 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir`].
965 /// See [`PrivateStorage::resolve_path`] for details.
966 ///
967 /// # Support
968 /// All Android version.
969 fn remove_dir(
970 &self,
971 base_dir: PrivateDir,
972 relative_path: Option<&str>,
973 ) -> crate::Result<()> {
974
975 let path = match relative_path {
976 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
977 None => self.resolve_path(base_dir)?,
978 };
979
980 std::fs::remove_dir(path)?;
981 Ok(())
982 }
983
984 /// Removes a directory at this path, after removing all its contents. Use carefully!
985 ///
986 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir_all`].
987 /// See [`PrivateStorage::resolve_path`] for details.
988 ///
989 /// # Support
990 /// All Android version.
991 fn remove_dir_all(
992 &self,
993 base_dir: PrivateDir,
994 relative_path: Option<&str>,
995 ) -> crate::Result<()> {
996
997 let path = match relative_path {
998 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
999 None => self.resolve_path(base_dir)?,
1000 };
1001
1002 std::fs::remove_dir_all(path)?;
1003 Ok(())
1004 }
1005
1006 /// Returns Ok(true) if the path points at an existing entity.
1007 ///
1008 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::exists`].
1009 /// See [`PrivateStorage::resolve_path`] for details.
1010 ///
1011 /// # Support
1012 /// All Android version.
1013 fn exists(
1014 &self,
1015 base_dir: PrivateDir,
1016 relative_path: impl AsRef<str>
1017 ) -> crate::Result<bool> {
1018
1019 let path = self.resolve_path_with(base_dir, relative_path)?;
1020 Ok(std::fs::exists(path)?)
1021 }
1022
1023 /// Queries the file system to get information about a file, directory.
1024 ///
1025 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::metadata`].
1026 /// See [`PrivateStorage::resolve_path`] for details.
1027 ///
1028 /// # Support
1029 /// All Android version.
1030 fn metadata(
1031 &self,
1032 base_dir: PrivateDir,
1033 relative_path: Option<&str>,
1034 ) -> crate::Result<std::fs::Metadata> {
1035
1036 let path = match relative_path {
1037 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
1038 None => self.resolve_path(base_dir)?,
1039 };
1040
1041 Ok(std::fs::metadata(path)?)
1042 }
1043
1044 fn app_handle(&self) -> &tauri::AppHandle<R>;
1045}