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, Write};
8
9pub use models::*;
10pub use error::{Error, Result, PathError};
11pub use impls::{AndroidFsExt, init};
12
13/// API
14pub trait AndroidFs {
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 /// # Support
32 /// All Android version.
33 fn get_name(&self, uri: &FileUri) -> crate::Result<String>;
34
35 /// Query the provider to get mime type.
36 /// If the directory, this returns `None`.
37 /// If the file, this returns no `None`.
38 /// If the file type is unknown or unset, this returns `Some("application/octet-stream")`.
39 ///
40 /// # Support
41 /// All Android version.
42 fn get_mime_type(&self, uri: &FileUri) -> crate::Result<Option<String>>;
43
44 /// Queries the file system to get information about a file, directory.
45 ///
46 /// # Note
47 /// This uses [`AndroidFs::open_file`] internally.
48 ///
49 /// # Support
50 /// All Android version.
51 fn get_metadata(&self, uri: &FileUri) -> crate::Result<std::fs::Metadata> {
52 let file = self.open_file(uri, FileAccessMode::Read)?;
53 Ok(file.metadata()?)
54 }
55
56 /// Open a file in the specified mode.
57 ///
58 /// # Note
59 /// Other than [`FileAccessMode::Read`] is only for **writable** uri.
60 ///
61 /// # Support
62 /// All Android version.
63 fn open_file(&self, uri: &FileUri, mode: FileAccessMode) -> crate::Result<std::fs::File>;
64
65 /// Reads the entire contents of a file into a bytes vector.
66 ///
67 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
68 ///
69 /// # Support
70 /// All Android version.
71 fn read(&self, uri: &FileUri) -> crate::Result<Vec<u8>> {
72 let mut file = self.open_file(uri, FileAccessMode::Read)?;
73 let mut buf = file.metadata().ok()
74 .map(|m| m.len() as usize)
75 .map(Vec::with_capacity)
76 .unwrap_or_else(Vec::new);
77
78 file.read_to_end(&mut buf)?;
79 Ok(buf)
80 }
81
82 /// Reads the entire contents of a file into a string.
83 ///
84 /// If you need to operate the file, use [`AndroidFs::open_file`] instead.
85 ///
86 /// # Support
87 /// All Android version.
88 fn read_to_string(&self, uri: &FileUri) -> crate::Result<String> {
89 let mut file = self.open_file(uri, FileAccessMode::Read)?;
90 let mut buf = file.metadata().ok()
91 .map(|m| m.len() as usize)
92 .map(String::with_capacity)
93 .unwrap_or_else(String::new);
94
95 file.read_to_string(&mut buf)?;
96 Ok(buf)
97 }
98
99 /// Writes a slice as the entire contents of a file.
100 /// This function will entirely replace its contents if it does exist.
101 ///
102 /// If you want to operate the file, use [`AndroidFs::open_file`] instead.
103 ///
104 /// # Note
105 /// This is only for **writable** file uri.
106 ///
107 /// # Support
108 /// All Android version.
109 fn write(&self, uri: &FileUri, contents: impl AsRef<[u8]>) -> crate::Result<()> {
110 let mut file = self.open_file(uri, FileAccessMode::WriteTruncate)?;
111 file.write_all(contents.as_ref())?;
112 Ok(())
113 }
114
115 /// Remove the file.
116 ///
117 /// # Note
118 /// This is only for **removeable** uri.
119 ///
120 /// # Support
121 /// All Android version.
122 fn remove_file(&self, uri: &FileUri) -> crate::Result<()>;
123
124 /// Remove the **empty** file.
125 ///
126 /// # Note
127 /// This is only for **removeable** uri.
128 ///
129 /// # Support
130 /// All Android version.
131 fn remove_dir(&self, uri: &FileUri) -> crate::Result<()>;
132
133 /// Creates a new file at the specified directory, and returns **read-write-removeable** uri.
134 /// If the same file name already exists, a sequential number is added to the name. And recursively create sub directories if they are missing.
135 ///
136 /// If you want to create a new file without a `FileUri`, use [`AndroidFs::create_file_in_public_location`].
137 ///
138 /// # Note
139 /// `mime_type` specify the type of the file to be created.
140 /// It should be provided whenever possible. If not specified, `application/octet-stream` is used, as generic, unknown, or undefined file types.
141 ///
142 /// # Support
143 /// All Android version.
144 fn create_file(
145 &self,
146 dir: &FileUri,
147 relative_path: impl AsRef<str>,
148 mime_type: Option<&str>
149 ) -> crate::Result<FileUri>;
150
151 /// Creates a new file at the specified public location, and returns **read-write-removeable** uri.
152 /// If the same file name already exists, a sequential number is added to the name. And recursively create sub directories if they are missing.
153 ///
154 /// # Note
155 /// `mime_type` specify the type of the file to be created.
156 /// It should be provided whenever possible. If not specified, `application/octet-stream` is used, as generic, unknown, or undefined file types.
157 ///
158 /// # Support
159 /// All Android version.
160 fn create_file_in_public_location(
161 &self,
162 dir: impl Into<PublicDir>,
163 relative_path: impl AsRef<str>,
164 mime_type: Option<&str>
165 ) -> crate::Result<FileUri>;
166
167 /// Returns the unordered child entries of the specified directory.
168 /// Returned [`Entry`](crate::Entry) contains file or directory uri.
169 ///
170 /// # Note
171 /// By default, children are valid until the app is terminated.
172 /// To persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
173 /// However, if you have obtained persistent permissions for the origin directory (e.g. parent, grand parents), it is unnecessary.
174 ///
175 /// The returned type is an iterator because of the data formatting and the file system call is not executed lazily.
176 ///
177 /// # Support
178 /// All Android version.
179 fn read_dir(&self, uri: &FileUri) -> crate::Result<impl Iterator<Item = Entry>>;
180
181 /// Take persistent permission to access the file, directory and its descendants.
182 ///
183 /// Preserve access across app and device restarts.
184 /// If you only need it until the end of the app session, you do not need to use this.
185 ///
186 /// This works by just calling, without displaying any confirmation to the user.
187 ///
188 /// # Note
189 /// Even after calling this, the app doesn't retain access to the entry if it is moved or deleted.
190 ///
191 /// # Support
192 /// All Android version.
193 fn take_persistable_uri_permission(&self, uri: FileUri, mode: PersistableAccessMode) -> crate::Result<()>;
194
195 /// Open a dialog for file selection.
196 /// This returns a **read-only** uris. If no file is selected or canceled by user, it is an empty.
197 ///
198 /// For images and videos, consider using [`AndroidFs::show_open_visual_media_dialog`] instead.
199 ///
200 /// # Issue
201 /// **Dialog has an issue. Details and resolution are following.**
202 /// <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
203 ///
204 /// # Note
205 /// `mime_types` represents the types of files that should be selected.
206 /// However, there is no guarantee that the returned file will match the specified types.
207 /// If this is empty, all file types will be available for selection.
208 /// This is equivalent to `["*/*"]`, and it will invoke the standard file picker in most cases.
209 ///
210 /// By default, returned uri is valid until the app is terminated.
211 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
212 ///
213 /// # Support
214 /// All Android version.
215 fn show_open_file_dialog(
216 &self,
217 mime_types: &[&str],
218 multiple: bool
219 ) -> crate::Result<Vec<FileUri>>;
220
221 /// Opens a dialog for media file selection, such as images and videos.
222 /// This returns a **read-only** uris. If no file is selected or canceled by user, it is an empty.
223 ///
224 /// This is more user-friendly than [`AndroidFs::show_open_file_dialog`].
225 ///
226 /// # Issue
227 /// **Dialog has an issue. Details and resolution are following.**
228 /// <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
229 ///
230 /// # Note
231 /// By default, returned uri is valid until the app is terminated.
232 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
233 ///
234 /// The file obtained from this function cannot retrieve the correct file name using [`AndroidFs::get_name`].
235 /// Instead, it will be assigned a sequential number, such as `1000091523.png`.
236 /// <https://issuetracker.google.com/issues/268079113>
237 ///
238 /// # Support
239 /// This is available on devices that meet the following criteria:
240 /// - Run Android 11 (API level 30) or higher
241 /// - Receive changes to Modular System Components through Google System Updates
242 ///
243 /// Availability on a given device can be verified by calling [`AndroidFs::is_visual_media_dialog_available`].
244 /// If not supported, this functions the same as [`AndroidFs::show_open_file_dialog`].
245 fn show_open_visual_media_dialog(
246 &self,
247 target: VisualMediaTarget,
248 multiple: bool
249 ) -> crate::Result<Vec<FileUri>>;
250
251 /// Open a dialog for directory selection,
252 /// allowing the app to read and write any file in the selected directory and its subdirectories.
253 /// If canceled by the user, return None.
254 ///
255 /// # Issue
256 /// **Dialog has an issue. Details and resolution are following.**
257 /// <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
258 ///
259 /// # Note
260 /// By default, retruned uri is valid until the app is terminated.
261 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
262 /// If you take permission for a directory, you can recursively obtain it for its descendants.
263 ///
264 /// # Support
265 /// All Android version.
266 fn show_open_dir_dialog(&self) -> crate::Result<Option<FileUri>>;
267
268 /// Open a dialog for file saving, and return the selected path.
269 /// This returns a **read-write-removeable** uri. If canceled by the user, return None.
270 ///
271 /// When storing media files such as images, videos, and audio, consider using [`AndroidFs::create_file_in_public_location`].
272 /// When a file does not need to be accessed by other applications and users, consider using [`PrivateStorage::write`].
273 /// These are easier because the destination does not need to be selected in a dialog.
274 ///
275 /// # Issue
276 /// **Dialog has an issue. Details and resolution are following.**
277 /// <https://github.com/aiueo13/tauri-plugin-android-fs/issues/1>
278 ///
279 /// # Note
280 /// `mime_type` specify the type of the target file to be saved.
281 /// It should be provided whenever possible. If not specified, `application/octet-stream` is used, as generic, unknown, or undefined file types.
282 ///
283 /// By default, returned uri is valid until the app is terminated.
284 /// If you want to persist it across app restarts, use [`AndroidFs::take_persistable_uri_permission`].
285 ///
286 /// # Support
287 /// All Android version.
288 fn show_save_file_dialog(
289 &self,
290 default_file_name: impl AsRef<str>,
291 mime_type: Option<&str>,
292 ) -> crate::Result<Option<FileUri>>;
293
294 /// Verify whether [`AndroidFs::show_open_visual_media_dialog`] is available on a given device.
295 ///
296 /// # Support
297 /// All Android version.
298 fn is_visual_media_dialog_available(&self) -> crate::Result<bool>;
299
300 /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
301 ///
302 /// # Support
303 /// All Android version.
304 fn is_public_audiobooks_dir_available(&self) -> crate::Result<bool>;
305
306 /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
307 ///
308 /// # Support
309 /// All Android version.
310 fn is_public_recordings_dir_available(&self) -> crate::Result<bool>;
311
312 /// File storage API intended for the app’s use only.
313 fn private_storage(&self) -> &impl PrivateStorage;
314}
315
316/// File storage intended for the app’s use only.
317pub trait PrivateStorage {
318
319 /// Get the absolute path of the specified directory.
320 /// Apps require no permissions to read or write to the returned path, since this path lives in their private storage.
321 ///
322 /// These files will be deleted when the app is uninstalled and may also be deleted at the user’s request.
323 /// When using [`PrivateDir::Cache`], the system will automatically delete files in this directory as disk space is needed elsewhere on the device.
324 ///
325 /// The returned path may change over time if the calling app is moved to an adopted storage device, so only relative paths should be persisted.
326 ///
327 /// # Examples
328 /// ```no_run
329 /// use tauri_plugin_android_fs::{AndroidFs, AndroidFsExt, PrivateDir, PrivateStorage};
330 ///
331 /// fn example(app: tauri::AppHandle) {
332 /// let api = app.android_fs().private_storage();
333 ///
334 /// let dir_path = api.resolve_path(PrivateDir::Data).unwrap();
335 /// let file_path = dir_path.join("2025-2-12/data.txt");
336 ///
337 /// // Write file
338 /// std::fs::create_dir_all(file_path.parent().unwrap()).unwrap();
339 /// std::fs::write(&file_path, "aaaa").unwrap();
340 ///
341 /// // Read file
342 /// let _ = std::fs::read_to_string(&file_path).unwrap();
343 ///
344 /// // Remove file
345 /// std::fs::remove_file(&file_path).unwrap();
346 /// }
347 /// ```
348 ///
349 /// # Support
350 /// All Android version.
351 fn resolve_path(&self, dir: PrivateDir) -> crate::Result<std::path::PathBuf>;
352
353 /// Get the absolute path of the specified relative path and base directory.
354 /// Apps require no extra permissions to read or write to the returned path, since this path lives in their private storage.
355 ///
356 /// See [`PrivateStorage::resolve_path`] for details.
357 ///
358 /// # Support
359 /// All Android version.
360 fn resolve_path_with(
361 &self,
362 dir: PrivateDir,
363 relative_path: impl AsRef<str>
364 ) -> crate::Result<std::path::PathBuf> {
365
366 let relative_path = relative_path.as_ref().trim_start_matches('/');
367 let path = self.resolve_path(dir)?.join(relative_path);
368 Ok(path)
369 }
370
371 fn resolve_uri(&self, dir: PrivateDir) -> crate::Result<FileUri> {
372 Ok(FileUri::from(tauri_plugin_fs::FilePath::Path(self.resolve_path(dir)?)))
373 }
374
375 fn resolve_uri_with(&self, dir: PrivateDir, relative_path: impl AsRef<str>) -> crate::Result<FileUri> {
376 Ok(FileUri::from(tauri_plugin_fs::FilePath::Path(self.resolve_path_with(dir, relative_path)?)))
377 }
378
379 /// Writes a slice as the entire contents of a file.
380 ///
381 /// This function will create a file if it does not exist, and will entirely replace its contents if it does.
382 /// Recursively create parent directories if they are missing.
383 ///
384 /// This internally uses [`PrivateStorage::resolve_path`] , [`std::fs::create_dir_all`], and [`std::fs::write`].
385 /// See [`PrivateStorage::resolve_path`] for details.
386 ///
387 /// # Support
388 /// All Android version.
389 fn write(
390 &self,
391 base_dir: PrivateDir,
392 relative_path: impl AsRef<str>,
393 contents: impl AsRef<[u8]>
394 ) -> crate::Result<()> {
395
396 let path = self.resolve_path_with(base_dir, relative_path)?;
397
398 if let Some(parent_dir) = path.parent() {
399 std::fs::create_dir_all(parent_dir)?;
400 }
401
402 std::fs::write(path, contents)?;
403
404 Ok(())
405 }
406
407 /// Open a file in read-only mode.
408 ///
409 /// If you only need to read the entire file contents, consider using [`PrivateStorage::read`] or [`PrivateStorage::read_to_string`] instead.
410 ///
411 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::open`].
412 /// See [`PrivateStorage::resolve_path`] for details.
413 ///
414 /// # Support
415 /// All Android version.
416 fn open_file(
417 &self,
418 base_dir: PrivateDir,
419 relative_path: impl AsRef<str>,
420 ) -> crate::Result<std::fs::File> {
421
422 let path = self.resolve_path_with(base_dir, relative_path)?;
423 Ok(std::fs::File::open(path)?)
424 }
425
426 /// Opens a file in write-only mode.
427 /// This function will create a file if it does not exist, and will truncate it if it does.
428 ///
429 /// If you only need to write the contents, consider using [`PrivateStorage::write`] instead.
430 ///
431 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::File::create`].
432 /// See [`PrivateStorage::resolve_path`] for details.
433 ///
434 /// # Support
435 /// All Android version.
436 fn create_file(
437 &self,
438 base_dir: PrivateDir,
439 relative_path: impl AsRef<str>,
440 ) -> crate::Result<std::fs::File> {
441
442 let path = self.resolve_path_with(base_dir, relative_path)?;
443 Ok(std::fs::File::create(path)?)
444 }
445
446 /// Reads the entire contents of a file into a bytes vector.
447 ///
448 /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.
449 ///
450 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read`].
451 /// See [`PrivateStorage::resolve_path`] for details.
452 ///
453 /// # Support
454 /// All Android version.
455 fn read(
456 &self,
457 base_dir: PrivateDir,
458 relative_path: impl AsRef<str>,
459 ) -> crate::Result<Vec<u8>> {
460
461 let path = self.resolve_path_with(base_dir, relative_path)?;
462 Ok(std::fs::read(path)?)
463 }
464
465 /// Reads the entire contents of a file into a string.
466 ///
467 /// If you need [`std::fs::File`], use [`PrivateStorage::open_file`] insted.
468 ///
469 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_to_string`].
470 /// See [`PrivateStorage::resolve_path`] for details.
471 ///
472 /// # Support
473 /// All Android version.
474 fn read_to_string(
475 &self,
476 base_dir: PrivateDir,
477 relative_path: impl AsRef<str>,
478 ) -> crate::Result<String> {
479
480 let path = self.resolve_path_with(base_dir, relative_path)?;
481 Ok(std::fs::read_to_string(path)?)
482 }
483
484 /// Returns an iterator over the entries within a directory.
485 ///
486 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::read_dir`].
487 /// See [`PrivateStorage::resolve_path`] for details.
488 ///
489 /// # Support
490 /// All Android version.
491 fn read_dir(
492 &self,
493 base_dir: PrivateDir,
494 relative_path: Option<&str>,
495 ) -> crate::Result<std::fs::ReadDir> {
496
497 let path = match relative_path {
498 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
499 None => self.resolve_path(base_dir)?,
500 };
501
502 Ok(std::fs::read_dir(path)?)
503 }
504
505 /// Removes a file from the filesystem.
506 ///
507 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_file`].
508 /// See [`PrivateStorage::resolve_path`] for details.
509 ///
510 /// # Support
511 /// All Android version.
512 fn remove_file(
513 &self,
514 base_dir: PrivateDir,
515 relative_path: impl AsRef<str>,
516 ) -> crate::Result<()> {
517
518 let path = self.resolve_path_with(base_dir, relative_path)?;
519 Ok(std::fs::remove_file(path)?)
520 }
521
522 /// Removes an empty directory.
523 /// 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.
524 ///
525 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir`].
526 /// See [`PrivateStorage::resolve_path`] for details.
527 ///
528 /// # Support
529 /// All Android version.
530 fn remove_dir(
531 &self,
532 base_dir: PrivateDir,
533 relative_path: Option<&str>,
534 ) -> crate::Result<()> {
535
536 let path = match relative_path {
537 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
538 None => self.resolve_path(base_dir)?,
539 };
540
541 std::fs::remove_dir(path)?;
542 Ok(())
543 }
544
545 /// Removes a directory at this path, after removing all its contents. Use carefully!
546 ///
547 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::remove_dir_all`].
548 /// See [`PrivateStorage::resolve_path`] for details.
549 ///
550 /// # Support
551 /// All Android version.
552 fn remove_dir_all(
553 &self,
554 base_dir: PrivateDir,
555 relative_path: Option<&str>,
556 ) -> crate::Result<()> {
557
558 let path = match relative_path {
559 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
560 None => self.resolve_path(base_dir)?,
561 };
562
563 std::fs::remove_dir_all(path)?;
564 Ok(())
565 }
566
567 /// Returns Ok(true) if the path points at an existing entity.
568 ///
569 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::exists`].
570 /// See [`PrivateStorage::resolve_path`] for details.
571 ///
572 /// # Support
573 /// All Android version.
574 fn exists(
575 &self,
576 base_dir: PrivateDir,
577 relative_path: impl AsRef<str>
578 ) -> crate::Result<bool> {
579
580 let path = self.resolve_path_with(base_dir, relative_path)?;
581 Ok(std::fs::exists(path)?)
582 }
583
584 /// Queries the file system to get information about a file, directory.
585 ///
586 /// This internally uses [`PrivateStorage::resolve_path`] and [`std::fs::metadata`].
587 /// See [`PrivateStorage::resolve_path`] for details.
588 ///
589 /// # Support
590 /// All Android version.
591 fn metadata(
592 &self,
593 base_dir: PrivateDir,
594 relative_path: Option<&str>,
595 ) -> crate::Result<std::fs::Metadata> {
596
597 let path = match relative_path {
598 Some(relative_path) => self.resolve_path_with(base_dir, relative_path)?,
599 None => self.resolve_path(base_dir)?,
600 };
601
602 Ok(std::fs::metadata(path)?)
603 }
604}