tauri_plugin_android_fs/api/public_storage.rs
1use sync_async::sync_async;
2use crate::*;
3use super::*;
4
5
6/// API of file storage that is available to other applications and users.
7///
8/// # Examples
9/// ```no_run
10/// use tauri_plugin_android_fs::{AndroidFsExt, Error, PublicGeneralPurposeDir, PublicImageDir, Result};
11///
12/// async fn example(app: &tauri::AppHandle<impl tauri::Runtime>) -> Result<()> {
13/// let api = app.android_fs_async();
14///
15/// // Request permission to access public storage.
16/// //
17/// // NOTE:
18/// // Enable the `legacy_storage_permission` feature
19/// // when supporting Android 9 (API level 28) or lower.
20/// if !api.public_storage().request_permission().await? {
21/// return Err(Error::with("Permission denied by user"));
22/// }
23///
24/// // Save a new file.
25/// //
26/// // Destination:
27/// // ~/Pictures/MyApp/my-image.png
28/// api.public_storage().write_new(
29/// // Storage volume (e.g. internal storage or SD card).
30/// // If `None`, uses the primary storage volume.
31/// None,
32///
33/// // Base directory.
34/// // One of:
35/// // `PublicImageDir`, `PublicVideoDir`,
36/// // `PublicAudioDir`, `PublicGeneralPurposeDir`.
37/// PublicImageDir::Pictures,
38///
39/// // Relative file path.
40/// // Parent directories will be created recursively if necessary.
41/// "MyApp/my-image.png",
42///
43/// // MIME type.
44/// Some("image/png"),
45///
46/// // File contents.
47/// &[],
48/// )
49/// .await?;
50///
51///
52/// // Select a writable SD card if available;
53/// // otherwise, pass `None` to use the primary storage volume.
54/// let volume = api
55/// .public_storage()
56/// .get_volumes()
57/// .await?
58/// .into_iter()
59/// .filter(|v| !v.is_readonly)
60/// .filter(|v| v.is_stable)
61/// .filter(|v| v.is_removable)
62/// .next();
63///
64/// // Create an empty file and mark it as pending,
65/// // making it invisible to other apps until writing is complete.
66/// let uri = api
67/// .public_storage()
68/// .create_new_file_with_pending(
69/// volume.as_ref().map(|v| &v.id),
70/// PublicGeneralPurposeDir::Documents,
71/// "MyApp/2025-9-14/data.txt",
72/// Some("text/plain"),
73/// )
74/// .await?;
75///
76/// // Open the file for writing.
77/// //
78/// // NOTE:
79/// // Existing contents will be truncated.
80/// let mut file: std::fs::File = api.open_file_writable(&uri).await?;
81///
82/// // Write the file contents on a blocking thread.
83/// let result = tauri::async_runtime::spawn_blocking(move || -> Result<()> {
84/// use std::io::Write;
85///
86/// // Write the file contents.
87/// file.write_all(&[])?;
88///
89/// Ok(())
90/// })
91/// .await
92/// .map_err(Into::into)
93/// .and_then(|r| r);
94///
95/// // Delete the incomplete file if writing failed.
96/// if let Err(err) = result {
97/// api.remove_file(&uri).await.ok();
98/// return Err(err);
99/// }
100///
101/// // Make the file visible to other apps.
102/// api.public_storage().set_pending(&uri, false).await?;
103///
104/// // Notify the media scanner about the new file.
105/// api.public_storage().scan(&uri).await?;
106///
107/// Ok(())
108/// }
109/// ```
110#[sync_async]
111pub struct PublicStorage<'a, R: tauri::Runtime> {
112 #[cfg(target_os = "android")]
113 pub(crate) handle: &'a tauri::plugin::PluginHandle<R>,
114
115 #[cfg(not(target_os = "android"))]
116 #[allow(unused)]
117 pub(crate) handle: &'a std::marker::PhantomData<fn() -> R>,
118}
119
120#[cfg(target_os = "android")]
121#[sync_async(
122 use(if_sync) impls::SyncImpls as Impls;
123 use(if_async) impls::AsyncImpls as Impls;
124)]
125impl<'a, R: tauri::Runtime> PublicStorage<'a, R> {
126
127 #[always_sync]
128 fn impls(&self) -> Impls<'_, R> {
129 Impls { handle: &self.handle }
130 }
131}
132
133#[sync_async(
134 use(if_async) api_async::{AndroidFs, Opener, Picker, AppStorage, PrivateStorage};
135 use(if_sync) api_sync::{AndroidFs, Opener, Picker, AppStorage, PrivateStorage};
136)]
137impl<'a, R: tauri::Runtime> PublicStorage<'a, R> {
138
139 /// Requests file access permission from the user if needed.
140 ///
141 /// When this function returns `true`,
142 /// the app is allowed to create files in `PublicStorage` and read/write the files it creates.
143 /// Access to files created by other apps is not guaranteed.
144 /// Additionally, after the app is uninstalled and reinstalled,
145 /// previously created files may become inaccessible.
146 ///
147 /// # Version behavior
148 /// ### Android 11 or higher
149 /// Requests no permission.
150 /// This function always returns `true`.
151 ///
152 /// ### Android 10
153 /// Selects either the behavior for Android 11 or higher or for Android 9 or lower, as needed.
154 ///
155 /// ### Android 9 or lower
156 /// Requests [`WRITE_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE) and [`READ_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permissions.
157 /// To request the permissions, you must declare it in `AndroidManifest.xml`.
158 /// By enabling the `legacy_storage_permission` feature,
159 /// the permissions will be declared automatically only for Android 9 or lower.
160 ///
161 /// # Support
162 /// All Android versions
163 #[maybe_async]
164 pub fn request_permission(&self) -> Result<bool> {
165 #[cfg(not(target_os = "android"))] {
166 Err(Error::NOT_ANDROID)
167 }
168 #[cfg(target_os = "android")] {
169 self.impls().request_storage_permission_for_public_storage().await
170 }
171 }
172
173 /// Indicates whether the app has file access permission.
174 ///
175 /// When this function returns `true`,
176 /// the app is allowed to create files in `PublicStorage` and read/write the files it creates.
177 /// Access to files created by other apps is not guaranteed.
178 /// Additionally, after the app is uninstalled and reinstalled,
179 /// previously created files may become inaccessible.
180 ///
181 /// # Version behavior
182 /// ### Android 11 or higher
183 /// Always returns `true`.
184 ///
185 /// ### Android 10
186 /// Selects either the behavior for Android 11 or higher or for Android 9 or lower, as needed.
187 ///
188 /// ### Android 9 or lower
189 /// Returns `true` if the app has been granted [`WRITE_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE) and [`READ_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permissions.
190 /// See [`PublicStorage::request_permission`] for requesting the permissions.
191 ///
192 /// # Support
193 /// All Android versions.
194 #[maybe_async]
195 pub fn check_permission(&self) -> Result<bool> {
196 #[cfg(not(target_os = "android"))] {
197 Err(Error::NOT_ANDROID)
198 }
199 #[cfg(target_os = "android")] {
200 self.impls().check_storage_permission_for_public_storage().await
201 }
202 }
203
204 /// Gets a list of currently available storage volumes (internal storage, SD card, USB drive, etc.).
205 /// Be aware of TOCTOU.
206 ///
207 /// Since read-only SD cards and similar cases may be included,
208 /// please use [`StorageVolume { is_readonly, .. }`](StorageVolume) for filtering as needed.
209 ///
210 /// This typically includes [`primary storage volume`](PublicStorage::get_primary_volume),
211 /// but it may occasionally be absent if the primary volume is inaccessible
212 /// (e.g., mounted on a computer, removed, or another issue).
213 ///
214 /// Primary storage volume is always listed first, if included.
215 /// But the order of the others is not guaranteed.
216 ///
217 /// # Note
218 /// For Android 9 (API level 28) or lower,
219 /// this does not include any storage volumes other than the primary one.
220 ///
221 /// The volume represents the logical view of a storage volume for an individual user:
222 /// each user may have a different view for the same physical volume.
223 /// In other words, it provides a separate area for each user in a multi-user environment.
224 ///
225 /// # Support
226 /// All Android versions supported by Tauri.
227 #[maybe_async]
228 pub fn get_volumes(&self) -> Result<Vec<StorageVolume>> {
229 #[cfg(not(target_os = "android"))] {
230 Err(Error::NOT_ANDROID)
231 }
232 #[cfg(target_os = "android")] {
233 self.impls().get_available_storage_volumes_for_public_storage().await
234 }
235 }
236
237 /// Gets a primary storage volume.
238 /// This is the most common and recommended storage volume for placing files that can be accessed by other apps or user.
239 /// In many cases, it is device's built-in storage.
240 ///
241 /// A device always has one (and one only) primary storage volume.
242 ///
243 /// Primary volume may not currently be accessible
244 /// if it has been mounted by the user on their computer,
245 /// has been removed from the device, or some other problem has happened.
246 /// If so, this returns `None`.
247 ///
248 /// # Note
249 /// The volume represents the logical view of a storage volume for an individual user:
250 /// each user may have a different view for the same physical volume.
251 /// In other words, it provides a separate area for each user in a multi-user environment.
252 ///
253 /// # Support
254 /// All Android versions supported by Tauri.
255 #[maybe_async]
256 pub fn get_primary_volume(&self) -> Result<Option<StorageVolume>> {
257 #[cfg(not(target_os = "android"))] {
258 Err(Error::NOT_ANDROID)
259 }
260 #[cfg(target_os = "android")] {
261 self.impls().get_primary_storage_volume_if_available_for_public_storage().await
262 }
263 }
264
265 /// Creates a new empty file in the specified public directory of the storage volume.
266 /// This returns a **persistent read-write** URI.
267 ///
268 /// The app can read/write it until the app is uninstalled.
269 /// And it is **not** removed when the app itself is uninstalled.
270 ///
271 /// # Note
272 /// ### Android 10 or higher.
273 /// Files are automatically registered in the appropriate MediaStore as needed.
274 /// Scanning is triggered when the file descriptor is closed
275 /// or as part of the [`pending`](PublicStorage::set_pending) lifecycle.
276 ///
277 /// ### Android 9 or lower
278 /// [`WRITE_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE) and [`READ_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permissions are required.
279 /// This needs two steps:
280 ///
281 /// 1. Declare :
282 /// By enabling the `legacy_storage_permission` feature,
283 /// you can declare the permissions only for Android 9 or lower automatically at build time.
284 ///
285 /// 2. Runtime request :
286 /// By calling [`PublicStorage::request_permission`],
287 /// you can request the permissions from the user at runtime.
288 ///
289 /// After writing content to the file, call [`PublicStorage::scan`].
290 /// Until then, the file may not appear in the gallery or other apps.
291 ///
292 /// # Args
293 /// - ***volume_id*** :
294 /// The ID of the storage volume, such as internal storage or an SD card.
295 /// Usually, you don't need to specify this unless there is a special reason.
296 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
297 ///
298 /// - ***base_dir*** :
299 /// The base directory for the file.
300 /// When using [`PublicImageDir`], only image MIME types should be used for ***mime_type***; using other types may cause errors.
301 /// Similarly, [`PublicVideoDir`] and [`PublicAudioDir`] should only be used with their respective media types.
302 /// Only [`PublicGeneralPurposeDir`] supports all MIME types.
303 ///
304 /// - ***relative_path*** :
305 /// The file path relative to the base directory.
306 /// To keep files organized, it is recommended to place your app's name directory at the top level.
307 /// Any missing parent directories will be created automatically.
308 /// If a file with the same name already exists, a sequential number is appended to ensure uniqueness.
309 /// If the file has no extension, one may be inferred from ***mime_type*** and appended to the file name.
310 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
311 /// Note that append-exntesion and sanitize-path operation may vary depending on the device model and Android version.
312 ///
313 /// - ***mime_type*** :
314 /// The MIME type of the file to be created.
315 /// If `None`, the MIME type will be inferred from the extension of ***relative_path***.
316 /// If that also fails, `application/octet-stream` will be used.
317 ///
318 /// # Support
319 /// All Android versions supported by Tauri.
320 ///
321 /// Note :
322 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
323 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
324 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
325 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
326 /// - Others dirs are available in all Android versions.
327 #[maybe_async]
328 pub fn create_new_file(
329 &self,
330 volume_id: Option<&StorageVolumeId>,
331 base_dir: impl Into<PublicDir>,
332 relative_path: impl AsRef<std::path::Path>,
333 mime_type: Option<&str>
334 ) -> Result<FsUri> {
335
336 #[cfg(not(target_os = "android"))] {
337 Err(Error::NOT_ANDROID)
338 }
339 #[cfg(target_os = "android")] {
340 self.impls().create_new_file_in_public_storage(
341 volume_id,
342 base_dir,
343 relative_path,
344 mime_type,
345 false
346 ).await
347 }
348 }
349
350 /// Creates a new empty file in the specified public directory of the storage volume.
351 /// This returns a **persistent read-write** URI.
352 ///
353 /// The app can read/write it until the app is uninstalled.
354 /// And it is **not** removed when the app itself is uninstalled.
355 ///
356 /// # Note
357 /// ### Android 10 or higher
358 /// Files are automatically registered in the appropriate MediaStore as needed.
359 /// Scanning is triggered when the file descriptor is closed
360 /// or as part of the [`pending`](PublicStorage::set_pending) lifecycle.
361 ///
362 /// Diffrences from [`PublicStorage::create_new_file`] are that
363 /// files are marked as pending and will not be visible to other apps until
364 /// [`PublicStorage::set_pending(..., false)`](PublicStorage::set_pending) is called.
365 ///
366 /// ### Android 9 or lower
367 /// This behavior is equal to [`PublicStorage::create_new_file`].
368 /// So `pending` is ignored.
369 ///
370 /// [`WRITE_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE) and [`READ_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permissions are required.
371 /// This needs two steps:
372 ///
373 /// 1. Declare :
374 /// By enabling the `legacy_storage_permission` feature,
375 /// you can declare the permissions only for Android 9 or lower automatically at build time.
376 ///
377 /// 2. Runtime request :
378 /// By calling [`PublicStorage::request_permission`],
379 /// you can request the permissions from the user at runtime.
380 ///
381 /// After writing content to the file, call [`PublicStorage::scan`].
382 /// Until then, the file may not appear in the gallery or other apps.
383 ///
384 /// # Args
385 /// - ***volume_id*** :
386 /// The ID of the storage volume, such as internal storage or an SD card.
387 /// Usually, you don't need to specify this unless there is a special reason.
388 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
389 ///
390 /// - ***base_dir*** :
391 /// The base directory for the file.
392 /// When using [`PublicImageDir`], only image MIME types should be used for ***mime_type***; using other types may cause errors.
393 /// Similarly, [`PublicVideoDir`] and [`PublicAudioDir`] should only be used with their respective media types.
394 /// Only [`PublicGeneralPurposeDir`] supports all MIME types.
395 ///
396 /// - ***relative_path*** :
397 /// The file path relative to the base directory.
398 /// To keep files organized, it is recommended to place your app's name directory at the top level.
399 /// Any missing parent directories will be created automatically.
400 /// If a file with the same name already exists, a sequential number is appended to ensure uniqueness.
401 /// If the file has no extension, one may be inferred from ***mime_type*** and appended to the file name.
402 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
403 /// Note that append-exntesion and sanitize-path operation may vary depending on the device model and Android version.
404 ///
405 /// - ***mime_type*** :
406 /// The MIME type of the file to be created.
407 /// If `None`, the MIME type will be inferred from the extension of ***relative_path***.
408 /// If that also fails, `application/octet-stream` will be used.
409 ///
410 /// # Support
411 /// All Android versions supported by Tauri.
412 ///
413 /// Note :
414 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
415 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
416 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
417 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
418 /// - Others dirs are available in all Android versions.
419 #[maybe_async]
420 pub fn create_new_file_with_pending(
421 &self,
422 volume_id: Option<&StorageVolumeId>,
423 base_dir: impl Into<PublicDir>,
424 relative_path: impl AsRef<std::path::Path>,
425 mime_type: Option<&str>
426 ) -> Result<FsUri> {
427
428 #[cfg(not(target_os = "android"))] {
429 Err(Error::NOT_ANDROID)
430 }
431 #[cfg(target_os = "android")] {
432 self.impls().create_new_file_in_public_storage(
433 volume_id,
434 base_dir,
435 relative_path,
436 mime_type,
437 true
438 ).await
439 }
440 }
441
442 /// Recursively create a directory and all of its parent components if they are missing.
443 /// If it already exists, do nothing.
444 ///
445 /// [`PublicStorage::create_new_file`] and [`PublicStorage::create_new_file_with_pending`]
446 /// do this automatically, so there is no need to use it together.
447 ///
448 /// # Note
449 /// On Android 9 or lower,
450 /// [`WRITE_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE) and [`READ_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permissions are required.
451 /// This needs two steps:
452 ///
453 /// 1. Declare :
454 /// By enabling the `legacy_storage_permission` feature,
455 /// you can declare the permissions only for Android 9 or lower automatically at build time.
456 ///
457 /// 2. Runtime request :
458 /// By calling [`PublicStorage::request_permission`],
459 /// you can request the permissions from the user at runtime.
460 ///
461 /// # Args
462 /// - ***volume_id*** :
463 /// ID of the storage volume, such as internal storage, SD card, etc.
464 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
465 ///
466 /// - ***base_dir*** :
467 /// The base directory.
468 ///
469 /// - ***relative_path*** :
470 /// The directory path relative to the base directory.
471 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
472 /// Note that sanitize-path operation may vary depending on the device model and Android version.
473 ///
474 /// # Support
475 /// All Android versions supported by Tauri.
476 ///
477 /// Note :
478 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
479 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
480 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
481 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
482 /// - Others dirs are available in all Android versions.
483 #[maybe_async]
484 pub fn create_dir_all(
485 &self,
486 volume_id: Option<&StorageVolumeId>,
487 base_dir: impl Into<PublicDir>,
488 relative_path: impl AsRef<std::path::Path>,
489 ) -> Result<()> {
490
491 #[cfg(not(target_os = "android"))] {
492 Err(Error::NOT_ANDROID)
493 }
494 #[cfg(target_os = "android")] {
495 self.impls().create_dir_all_in_public_storage(volume_id, base_dir, relative_path).await
496 }
497 }
498
499 /// Writes contents to the new file in the specified public directory of the storage volume.
500 /// This returns a **persistent read-write** URI.
501 ///
502 /// The app can read/write it until the app is uninstalled.
503 /// And it is **not** removed when the app itself is uninstalled.
504 ///
505 /// # Note
506 /// On Android 9 or lower,
507 /// [`WRITE_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE) and [`READ_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permissions are required.
508 /// This needs two steps:
509 ///
510 /// 1. Declare :
511 /// By enabling the `legacy_storage_permission` feature,
512 /// you can declare the permissions only for Android 9 or lower automatically at build time.
513 ///
514 /// 2. Runtime request :
515 /// By calling [`PublicStorage::request_permission`],
516 /// you can request the permissions from the user at runtime.
517 ///
518 /// # Args
519 /// - ***volume_id*** :
520 /// The ID of the storage volume, such as internal storage or an SD card.
521 /// Usually, you don't need to specify this unless there is a special reason.
522 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
523 ///
524 /// - ***base_dir*** :
525 /// The base directory for the file.
526 /// When using [`PublicImageDir`], only image MIME types should be used for ***mime_type***; using other types may cause errors.
527 /// Similarly, [`PublicVideoDir`] and [`PublicAudioDir`] should only be used with their respective media types.
528 /// Only [`PublicGeneralPurposeDir`] supports all MIME types.
529 ///
530 /// - ***relative_path*** :
531 /// The file path relative to the base directory.
532 /// To keep files organized, it is recommended to place your app's name directory at the top level.
533 /// Any missing parent directories will be created automatically.
534 /// If a file with the same name already exists, a sequential number is appended to ensure uniqueness.
535 /// If the file has no extension, one may be inferred from ***mime_type*** and appended to the file name.
536 /// Strings may also be sanitized as needed, so they may not be used exactly as provided.
537 /// Note that append-exntesion and sanitize-path operation may vary depending on the device model and Android version.
538 ///
539 /// - ***mime_type*** :
540 /// The MIME type of the file to be created.
541 /// If `None`, the MIME type will be inferred from the extension of ***relative_path***.
542 /// If that also fails, `application/octet-stream` will be used.
543 ///
544 /// - ***contents*** :
545 /// Contents.
546 ///
547 /// # Support
548 /// All Android versions supported by Tauri.
549 ///
550 /// Note :
551 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
552 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
553 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
554 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
555 /// - Others dirs are available in all Android versions.
556 #[maybe_async]
557 pub fn write_new(
558 &self,
559 volume_id: Option<&StorageVolumeId>,
560 base_dir: impl Into<PublicDir>,
561 relative_path: impl AsRef<std::path::Path>,
562 mime_type: Option<&str>,
563 contents: impl AsRef<[u8]>
564 ) -> Result<FsUri> {
565
566 #[cfg(not(target_os = "android"))] {
567 Err(Error::NOT_ANDROID)
568 }
569 #[cfg(target_os = "android")] {
570 self.impls().write_new_file_in_public_storage(volume_id, base_dir, relative_path, mime_type, contents).await
571 }
572 }
573
574 /// Scans the specified file in MediaStore.
575 /// By doing this, the file will be visible with corrent metadata in the Gallery and etc.
576 ///
577 /// You don’t need to call this after [`PublicStorage::write_new`].
578 ///
579 /// # Version behavior
580 /// ### Android 10 or higher
581 /// This function does nothing,
582 /// because files are automatically registered in the appropriate MediaStore as needed.
583 /// Scanning is triggered when the file descriptor is closed
584 /// or as part of the [`pending`](PublicStorage::set_pending) lifecycle.
585 ///
586 /// If you really need to perform this operation in this version,
587 /// please use [`PublicStorage::_scan`].
588 ///
589 /// ### Android 9 or lower
590 /// Requests the specified file to be scanned by MediaStore.
591 /// This function returns when the scan request has been initiated.
592 ///
593 /// # Args
594 /// - ***uri*** :
595 /// The target file URI.
596 /// This must be a URI obtained from one of the following:
597 /// - [`PublicStorage::write_new`]
598 /// - [`PublicStorage::create_new_file`]
599 /// - [`PublicStorage::create_new_file_with_pending`]
600 /// - [`PublicStorage::scan_by_path`]
601 ///
602 /// # Support
603 /// All Android versions.
604 #[maybe_async]
605 pub fn scan(
606 &self,
607 uri: &FsUri,
608 ) -> Result<()> {
609
610 #[cfg(not(target_os = "android"))] {
611 Err(Error::NOT_ANDROID)
612 }
613 #[cfg(target_os = "android")] {
614 self.impls().scan_file_in_public_storage(uri, false).await
615 }
616 }
617
618 /// Scans the specified file in MediaStore and returns it's URI if success.
619 /// By doing this, the file will be visible in the Gallery and etc.
620 ///
621 /// # Note
622 /// Unlike [`PublicStorage::scan`],
623 /// this function waits until the scan is complete and then returns either success or an error.
624 ///
625 /// # Args
626 /// - ***uri*** :
627 /// Absolute path of the target file.
628 /// This must be a path obtained from one of the following:
629 /// - [`PublicStorage::resolve_path`] and it's descendants path.
630 /// - [`PublicStorage::get_path`]
631 ///
632 /// - ***mime_type*** :
633 /// The MIME type of the file.
634 /// If `None`, the MIME type will be inferred from the extension of the path.
635 /// If that also fails, `application/octet-stream` will be used.
636 ///
637 /// # Support
638 /// All Android versions supported by Tauri.
639 #[maybe_async]
640 pub fn scan_by_path(
641 &self,
642 path: impl AsRef<std::path::Path>,
643 mime_type: Option<&str>
644 ) -> Result<FsUri> {
645
646 #[cfg(not(target_os = "android"))] {
647 Err(Error::NOT_ANDROID)
648 }
649 #[cfg(target_os = "android")] {
650 self.impls().scan_file_by_path_in_public_storage(path, mime_type).await
651 }
652 }
653
654 /// Specifies whether the specified file on PublicStorage is marked as pending.
655 /// When set to `true`, the app has exclusive access to the file, and it becomes invisible to other apps.
656 ///
657 /// If it remains `true` for more than seven days,
658 /// the system will automatically delete the file.
659 ///
660 /// # Note
661 /// This is available for Android 10 or higher.
662 /// On Android 9 or lower, this does nothing.
663 ///
664 /// # Args
665 /// - ***uri*** :
666 /// Target file URI.
667 /// This must be a URI obtained from one of the following:
668 /// - [`PublicStorage::write_new`]
669 /// - [`PublicStorage::create_new_file`]
670 /// - [`PublicStorage::create_new_file_with_pending`]
671 /// - [`PublicStorage::scan_by_path`]
672 ///
673 /// # Support
674 /// All Android versions supported by Tauri.
675 ///
676 /// # References
677 /// - <https://developer.android.com/reference/android/provider/MediaStore.MediaColumns#IS_PENDING>
678 /// - <https://developer.android.com/training/data-storage/shared/media?hl=en#toggle-pending-status>
679 #[maybe_async]
680 pub fn set_pending(&self, uri: &FsUri, is_pending: bool) -> Result<()> {
681 #[cfg(not(target_os = "android"))] {
682 Err(Error::NOT_ANDROID)
683 }
684 #[cfg(target_os = "android")] {
685 self.impls().set_file_pending_in_public_storage(uri, is_pending).await
686 }
687 }
688
689 /// Gets the absolute path of the specified file.
690 ///
691 /// # Note
692 /// For description and notes on path permissions and handling,
693 /// see [`PublicStorage::resolve_path`].
694 /// It involves important constraints and required settings.
695 /// Therefore, **operate files via paths only when it is truly necessary**.
696 ///
697 /// # Args
698 /// - ***uri*** :
699 /// Target file URI.
700 /// This must be a URI obtained from one of the following:
701 /// - [`PublicStorage::write_new`]
702 /// - [`PublicStorage::create_new_file`]
703 /// - [`PublicStorage::create_new_file_with_pending`]
704 /// - [`PublicStorage::scan_by_path`]
705 ///
706 /// # Support
707 /// All Android versions supported by Tauri.
708 #[deprecated = "File operations via paths may result in unstable behaviour and inconsistent outcomes."]
709 #[maybe_async]
710 pub fn get_path(
711 &self,
712 uri: &FsUri,
713 ) -> Result<std::path::PathBuf> {
714
715 #[cfg(not(target_os = "android"))] {
716 Err(Error::NOT_ANDROID)
717 }
718 #[cfg(target_os = "android")] {
719 self.impls().get_file_path_in_public_storage(uri).await
720 }
721 }
722
723 /// Retrieves the absolute path for a specified public directory within the given storage volume.
724 /// This function does **not** create any directories; it only constructs the path.
725 ///
726 /// The app is allowed to create files in the directory and read/write the files it creates.
727 /// Access to files created by other apps is not guaranteed.
728 /// Additionally, after the app is uninstalled and reinstalled,
729 /// previously created files may become inaccessible.
730 ///
731 /// The directory is **not** removed when the app itself is uninstalled.
732 ///
733 /// It is strongly recommended to call [`PublicStorage::scan_by_path`]
734 /// after writing to the file to request registration in MediaStore.
735 ///
736 /// # Note
737 /// As shown below, this involves important constraints and required settings.
738 /// Therefore, **operate files via paths only when it is truly necessary**.
739 ///
740 /// Do not use operations such as rename or remove that rely on paths
741 /// (including URIs obtained via [`FileUri::from_path`] with this paths),
742 /// as they may break consistency with the MediaStore on old version.
743 /// Instead, use the URI obtained through [`PublicStorage::scan_by_path`] together with methods
744 /// such as [`AndroidFs::rename`] or [`AndroidFs::remove_file`].
745 ///
746 /// ### Android 11 or higher
747 /// When using [`PublicImageDir`], use only image type for file name extension,
748 /// using other type extension or none may cause errors.
749 /// Similarly, use only the corresponding extesions for [`PublicVideoDir`] and [`PublicAudioDir`].
750 /// Only [`PublicGeneralPurposeDir`] supports all extensions and no extension.
751 ///
752 /// ### Android 10 or lower
753 /// [`WRITE_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE) and [`READ_EXTERNAL_STORAGE`](https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE) permissions are required.
754 /// This needs two steps:
755 ///
756 /// 1. Declare :
757 /// By enabling the `legacy_storage_permission_include_android_10` feature,
758 /// you can declare the permissions only for Android 10 or lower automatically at build time.
759 ///
760 /// 2. Runtime request :
761 /// By calling [`PublicStorage::request_permission`],
762 /// you can request the permissions from the user at runtime.
763 ///
764 /// ### Android 10
765 /// Files within PublicStorage cannot be accessed via file paths,
766 /// so it is necessary to declare that the app need access `PublicStorage` using the method of Android 9 or lower.
767 ///
768 /// For it, please [set `android:requestLegacyExternalStorage="true"`](https://developer.android.com/training/data-storage/use-cases#opt-out-in-production-app).
769 ///
770 /// `src-tauri/gen/android/app/src/main/AndroidManifest.xml`
771 /// ```xml
772 /// <manifest ... >
773 /// <application
774 /// android:requestLegacyExternalStorage="true"
775 /// ...
776 /// >
777 /// ...
778 /// </application>
779 /// </manifest>
780 /// ```
781 /// And it is not possible to access `PublicStorage`
782 /// on volumes other than the primary storage via paths, just like on Android 9 or lower.
783 /// But filtering using [`PublicStorage::get_volumes`]
784 /// or [`StorageVolume::is_available_for_public_storage`] may not work correctly,
785 /// as these are intended for access via URIs.
786 ///
787 /// # Args
788 /// - ***volume_id*** :
789 /// ID of the storage volume, such as internal storage, SD card, etc.
790 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
791 ///
792 /// - ***base_dir*** :
793 /// The base directory.
794 /// One of [`PublicImageDir`], [`PublicVideoDir`], [`PublicAudioDir`], [`PublicGeneralPurposeDir`].
795 ///
796 /// # Support
797 /// All Android versions supported by Tauri.
798 ///
799 /// Note :
800 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
801 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
802 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
803 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
804 /// - Others dirs are available in all Android versions.
805 #[deprecated = "File operations via paths may result in unstable behaviour and inconsistent outcomes."]
806 #[maybe_async]
807 pub fn resolve_path(
808 &self,
809 volume_id: Option<&StorageVolumeId>,
810 base_dir: impl Into<PublicDir>,
811 ) -> Result<std::path::PathBuf> {
812
813 #[cfg(not(target_os = "android"))] {
814 Err(Error::NOT_ANDROID)
815 }
816 #[cfg(target_os = "android")] {
817 self.impls().resolve_dir_path_in_public_storage(volume_id, base_dir).await
818 }
819 }
820
821 /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
822 ///
823 /// If on Android 10 (API level 29) or higher, this returns true.
824 /// If on Android 9 (API level 28) and lower, this returns false.
825 ///
826 /// # Support
827 /// All Android versions supported by Tauri.
828 #[always_sync]
829 pub fn is_audiobooks_dir_available(&self) -> Result<bool> {
830 #[cfg(not(target_os = "android"))] {
831 Err(Error::NOT_ANDROID)
832 }
833 #[cfg(target_os = "android")] {
834 Ok(self.impls().consts()?.env_dir_audiobooks.is_some())
835 }
836 }
837
838 /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
839 ///
840 /// If on Android 12 (API level 31) or higher, this returns true.
841 /// If on Android 11 (API level 30) and lower, this returns false.
842 ///
843 /// # Support
844 /// All Android versions supported by Tauri.
845 #[always_sync]
846 pub fn is_recordings_dir_available(&self) -> Result<bool> {
847 #[cfg(not(target_os = "android"))] {
848 Err(Error::NOT_ANDROID)
849 }
850 #[cfg(target_os = "android")] {
851 Ok(self.impls().consts()?.env_dir_recordings.is_some())
852 }
853 }
854
855 /// For details, see [`PublicStorage::scan`].
856 ///
857 /// The difference is that this method scans the file even on Android 10 or higher.
858 ///
859 /// On Android 10 or higher, files are automatically registered in the appropriate MediaStore as needed.
860 /// Scanning is triggered when the file descriptor is closed
861 /// or as part of the [`pending`](PublicStorage::set_pending) lifecycle.
862 /// Therefore, please consider carefully whether this is truly necessary.
863 #[maybe_async]
864 pub fn _scan(
865 &self,
866 uri: &FsUri,
867 ) -> Result<()> {
868
869 #[cfg(not(target_os = "android"))] {
870 Err(Error::NOT_ANDROID)
871 }
872 #[cfg(target_os = "android")] {
873 self.impls().scan_file_in_public_storage(uri, true).await
874 }
875 }
876
877 /// For details, see [`PublicStorage::_scan`].
878 ///
879 /// The difference is that this function waits until the scan is complete and then returns either success or an error.
880 #[maybe_async]
881 pub fn _scan_for_result(
882 &self,
883 uri: &FsUri,
884 ) -> Result<()> {
885
886 #[cfg(not(target_os = "android"))] {
887 Err(Error::NOT_ANDROID)
888 }
889 #[cfg(target_os = "android")] {
890 self.impls().scan_file_in_public_storage_for_result(uri, true).await
891 }
892 }
893
894
895
896 #[deprecated = "Use `Picker::resolve_public_storage_initial_location` instead."]
897 #[maybe_async]
898 pub fn resolve_initial_location(
899 &self,
900 volume_id: Option<&StorageVolumeId>,
901 base_dir: impl Into<PublicDir>,
902 relative_path: impl AsRef<std::path::Path>,
903 create_dir_all: bool
904 ) -> Result<FsUri> {
905
906 #[cfg(not(target_os = "android"))] {
907 Err(Error::NOT_ANDROID)
908 }
909 #[cfg(target_os = "android")] {
910 self.impls().resolve_initial_location_in_public_storage(volume_id, base_dir, relative_path, create_dir_all).await
911 }
912 }
913}