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/// fn example(app: &tauri::AppHandle) {
11/// use tauri_plugin_android_fs::AndroidFsExt as _;
12///
13/// let api = app.android_fs();
14/// let public_storage = api.public_storage();
15/// }
16/// ```
17#[sync_async]
18pub struct PublicStorage<'a, R: tauri::Runtime> {
19 #[cfg(target_os = "android")]
20 pub(crate) handle: &'a tauri::plugin::PluginHandle<R>,
21
22 #[cfg(not(target_os = "android"))]
23 #[allow(unused)]
24 pub(crate) handle: &'a std::marker::PhantomData<fn() -> R>,
25}
26
27#[cfg(target_os = "android")]
28#[sync_async(
29 use(if_sync) impls::SyncImpls as Impls;
30 use(if_async) impls::AsyncImpls as Impls;
31)]
32impl<'a, R: tauri::Runtime> PublicStorage<'a, R> {
33
34 #[always_sync]
35 fn impls(&self) -> Impls<'_, R> {
36 Impls { handle: &self.handle }
37 }
38}
39
40#[sync_async(
41 use(if_async) api_async::{AndroidFs, FileOpener, FilePicker, PrivateStorage, WritableStream};
42 use(if_sync) api_sync::{AndroidFs, FileOpener, FilePicker, PrivateStorage, WritableStream};
43)]
44impl<'a, R: tauri::Runtime> PublicStorage<'a, R> {
45
46 /// Gets a list of currently available storage volumes (internal storage, SD card, USB drive, etc.).
47 /// Be aware of TOCTOU.
48 ///
49 /// Since read-only SD cards and similar cases may be included,
50 /// please use [`StorageVolume { is_readonly, .. }`](StorageVolume) for filtering as needed.
51 ///
52 /// This typically includes [`primary storage volume`](PublicStorage::get_primary_volume),
53 /// but it may occasionally be absent if the primary volume is inaccessible
54 /// (e.g., mounted on a computer, removed, or another issue).
55 ///
56 /// Primary storage volume is always listed first, if included.
57 /// But the order of the others is not guaranteed.
58 ///
59 /// # Note
60 /// The volume represents the logical view of a storage volume for an individual user:
61 /// each user may have a different view for the same physical volume.
62 /// In other words, it provides a separate area for each user in a multi-user environment.
63 ///
64 /// # Support
65 /// Android 10 (API level 29) or higher.
66 #[maybe_async]
67 pub fn get_volumes(&self) -> Result<Vec<StorageVolume>> {
68 #[cfg(not(target_os = "android"))] {
69 Err(Error::NOT_ANDROID)
70 }
71 #[cfg(target_os = "android")] {
72 self.impls().get_available_storage_volumes_for_public_storage().await
73 }
74 }
75
76 /// Gets a primary storage volume.
77 /// This is the most common and recommended storage volume for placing files that can be accessed by other apps or user.
78 /// In many cases, it is device's built-in storage.
79 ///
80 /// A device always has one (and one only) primary storage volume.
81 ///
82 /// Primary volume may not currently be accessible
83 /// if it has been mounted by the user on their computer,
84 /// has been removed from the device, or some other problem has happened.
85 /// If so, this returns `None`.
86 ///
87 /// # Note
88 /// The volume represents the logical view of a storage volume for an individual user:
89 /// each user may have a different view for the same physical volume.
90 /// In other words, it provides a separate area for each user in a multi-user environment.
91 ///
92 /// # Support
93 /// Android 10 (API level 29) or higher.
94 #[maybe_async]
95 pub fn get_primary_volume(&self) -> Result<Option<StorageVolume>> {
96 #[cfg(not(target_os = "android"))] {
97 Err(Error::NOT_ANDROID)
98 }
99 #[cfg(target_os = "android")] {
100 self.impls().get_primary_storage_volume_if_available_for_public_storage().await
101 }
102 }
103
104 /// Creates a new empty file in the specified public directory of the storage volume.
105 /// This returns a **persistent read-write** URI.
106 ///
107 /// The created file has the following features:
108 /// - It is registered with the appropriate MediaStore as needed.
109 /// - The app can fully manage it until the app is uninstalled.
110 /// - It is **not** removed when the app itself is uninstalled.
111 ///
112 /// # Args
113 /// - ***volume_id*** :
114 /// ID of the storage volume, such as internal storage, SD card, etc.
115 /// Usually, you don't need to specify this unless there is a special reason.
116 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
117 ///
118 /// - ***base_dir*** :
119 /// The base directory.
120 /// When using [`PublicImageDir`], use only image MIME types for ***mime_type***, which is discussed below.; using other types may cause errors.
121 /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
122 /// Only [`PublicGeneralPurposeDir`] supports all MIME types.
123 ///
124 /// - ***relative_path*** :
125 /// The file path relative to the base directory.
126 /// To avoid cluttering files, it is helpful to place the app name directory at the top level.
127 /// Any missing subdirectories in the specified path will be created automatically.
128 /// If a file with the same name already exists,
129 /// the system append a sequential number to ensure uniqueness.
130 /// If no extension is present,
131 /// the system may infer one from ***mime_type*** and may append it to the file name.
132 /// But this append-extension operation depends on the model and version.
133 /// The system may sanitize these strings as needed, so those strings may not be used as it is.
134 ///
135 /// - ***mime_type*** :
136 /// The MIME type of the file to be created.
137 /// If this is None, MIME type is inferred from the extension of ***relative_path***
138 /// and if that fails, `application/octet-stream` is used.
139 ///
140 /// # Support
141 /// Android 10 (API level 29) or higher.
142 ///
143 /// Note :
144 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
145 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
146 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
147 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
148 /// - Others dirs are available in all Android versions.
149 #[maybe_async]
150 pub fn create_new_file(
151 &self,
152 volume_id: Option<&StorageVolumeId>,
153 base_dir: impl Into<PublicDir>,
154 relative_path: impl AsRef<std::path::Path>,
155 mime_type: Option<&str>
156 ) -> Result<FileUri> {
157
158 #[cfg(not(target_os = "android"))] {
159 Err(Error::NOT_ANDROID)
160 }
161 #[cfg(target_os = "android")] {
162 self.impls().create_new_file_in_public_storage(volume_id, base_dir, relative_path, mime_type, false).await
163 }
164 }
165
166 /// Creates a new empty file in the specified public directory of the storage volume.
167 /// This returns a **persistent read-write** URI.
168 ///
169 /// The created file has the following features:
170 /// - Marked as pending and will not be visible to other apps until [`PublicStorage::set_pending(..., false)`](PublicStorage::set_pending) is called.
171 /// - It is registered with the appropriate MediaStore as needed.
172 /// - The app can fully manage it until the app is uninstalled.
173 /// - It is **not** removed when the app itself is uninstalled.
174 ///
175 /// # Args
176 /// - ***volume_id*** :
177 /// ID of the storage volume, such as internal storage, SD card, etc.
178 /// Usually, you don't need to specify this unless there is a special reason.
179 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
180 ///
181 /// - ***base_dir*** :
182 /// The base directory.
183 /// When using [`PublicImageDir`], use only image MIME types for ***mime_type***, which is discussed below.; using other types may cause errors.
184 /// Similarly, use only the corresponding media types for [`PublicVideoDir`] and [`PublicAudioDir`].
185 /// Only [`PublicGeneralPurposeDir`] supports all MIME types.
186 ///
187 /// - ***relative_path*** :
188 /// The file path relative to the base directory.
189 /// To avoid cluttering files, it is helpful to place the app name directory at the top level.
190 /// Any missing subdirectories in the specified path will be created automatically.
191 /// If a file with the same name already exists,
192 /// the system append a sequential number to ensure uniqueness.
193 /// If no extension is present,
194 /// the system may infer one from ***mime_type*** and may append it to the file name.
195 /// But this append-extension operation depends on the model and version.
196 /// The system may sanitize these strings as needed, so those strings may not be used as it is.
197 ///
198 /// - ***mime_type*** :
199 /// The MIME type of the file to be created.
200 /// If this is None, MIME type is inferred from the extension of ***relative_path***
201 /// and if that fails, `application/octet-stream` is used.
202 ///
203 /// # Support
204 /// Android 10 (API level 29) or higher.
205 ///
206 /// Note :
207 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
208 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
209 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
210 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
211 /// - Others dirs are available in all Android versions.
212 #[maybe_async]
213 pub fn create_new_file_with_pending(
214 &self,
215 volume_id: Option<&StorageVolumeId>,
216 base_dir: impl Into<PublicDir>,
217 relative_path: impl AsRef<std::path::Path>,
218 mime_type: Option<&str>
219 ) -> Result<FileUri> {
220
221 #[cfg(not(target_os = "android"))] {
222 Err(Error::NOT_ANDROID)
223 }
224 #[cfg(target_os = "android")] {
225 self.impls().create_new_file_in_public_storage(volume_id, base_dir, relative_path, mime_type, true).await
226 }
227 }
228
229 /// Recursively create a directory and all of its parent components if they are missing.
230 /// If it already exists, do nothing.
231 ///
232 /// [`PublicStorage::create_new_file`] does this automatically, so there is no need to use it together.
233 ///
234 /// # Args
235 /// - ***volume_id*** :
236 /// ID of the storage volume, such as internal storage, SD card, etc.
237 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
238 ///
239 /// - ***base_dir*** :
240 /// The base directory.
241 ///
242 /// - ***relative_path*** :
243 /// The directory path relative to the base directory.
244 /// The system may sanitize these strings as needed, so those strings may not be used as it is.
245 ///
246 /// # Support
247 /// Android 10 (API level 29) or higher.
248 ///
249 /// Note :
250 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
251 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
252 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
253 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
254 /// - Others dirs are available in all Android versions.
255 #[maybe_async]
256 pub fn create_dir_all(
257 &self,
258 volume_id: Option<&StorageVolumeId>,
259 base_dir: impl Into<PublicDir>,
260 relative_path: impl AsRef<std::path::Path>,
261 ) -> Result<()> {
262
263 #[cfg(not(target_os = "android"))] {
264 Err(Error::NOT_ANDROID)
265 }
266 #[cfg(target_os = "android")] {
267 self.impls().create_dir_all_in_public_storage(volume_id, base_dir, relative_path).await
268 }
269 }
270
271 /// Specifies whether the specified file on PublicStorage is marked as pending.
272 /// When set to `true`, the app has exclusive access to the file, and it becomes invisible to other apps.
273 ///
274 /// If it remains `true` for more than seven days,
275 /// the system will automatically delete the file.
276 ///
277 /// # Args
278 /// - ***uri*** :
279 /// Target file URI on PublicStorage.
280 /// This must be **read-writable**.
281 ///
282 /// # Support
283 /// Android 10 (API level 29) or higher.
284 ///
285 /// # References
286 /// <https://developer.android.com/reference/android/provider/MediaStore.MediaColumns#IS_PENDING>
287 /// <https://developer.android.com/training/data-storage/shared/media?hl=en#toggle-pending-status>
288 #[maybe_async]
289 pub fn set_pending(&self, uri: &FileUri, is_pending: bool) -> Result<()> {
290 #[cfg(not(target_os = "android"))] {
291 Err(Error::NOT_ANDROID)
292 }
293 #[cfg(target_os = "android")] {
294 self.impls().set_file_pending_in_public_storage(uri, is_pending).await
295 }
296 }
297
298 /// Retrieves the absolute path for a specified public directory within the given storage volume.
299 /// This function does **not** create any directories; it only constructs the path.
300 ///
301 /// **Please avoid using this whenever possible.**
302 /// Use it only in cases that cannot be handled by [`PublicStorage::create_new_file`] or [`PrivateStorage::resolve_path`],
303 /// such as when you need to pass the absolute path of a user-accessible file as an argument to any database library, debug logger, and etc.
304 ///
305 /// Since **Android 11** (not Android 10),
306 /// you can create files and folders under this directory and read or write **only** them.
307 /// If not, you can do nothing with this path.
308 ///
309 /// When using [`PublicImageDir`], use only image type for file name extension,
310 /// using other type extension or none may cause errors.
311 /// Similarly, use only the corresponding extesions for [`PublicVideoDir`] and [`PublicAudioDir`].
312 /// Only [`PublicGeneralPurposeDir`] supports all extensions and no extension.
313 ///
314 /// # Note
315 /// Filesystem access via this path may be heavily impacted by emulation overhead.
316 /// And those files will not be registered in MediaStore.
317 /// It might eventually be registered over time, but this should not be expected.
318 /// As a result, it may not appear in gallery apps or photo picker tools.
319 ///
320 /// You cannot access files created by other apps.
321 /// Additionally, if the app is uninstalled,
322 /// you will no longer be able to access the files you created,
323 /// even if the app is reinstalled.
324 /// Android tends to restrict public file access using paths, so this may stop working in the future.
325 ///
326 /// # Args
327 /// - ***volume_id*** :
328 /// ID of the storage volume, such as internal storage, SD card, etc.
329 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
330 ///
331 /// - ***base_dir*** :
332 /// The base directory.
333 ///
334 /// # Support
335 /// Android 10 (API level 29) or higher.
336 ///
337 /// Note :
338 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
339 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
340 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
341 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
342 /// - Others dirs are available in all Android versions.
343 #[maybe_async]
344 pub fn resolve_path(
345 &self,
346 volume_id: Option<&StorageVolumeId>,
347 base_dir: impl Into<PublicDir>,
348 ) -> Result<std::path::PathBuf> {
349
350 #[cfg(not(target_os = "android"))] {
351 Err(Error::NOT_ANDROID)
352 }
353 #[cfg(target_os = "android")] {
354 self.impls().resolve_path(volume_id, base_dir).await
355 }
356 }
357
358 /// Create the specified directory URI that has **no permissions**.
359 ///
360 /// This should only be used as `initial_location` in the file picker.
361 /// It must not be used for any other purpose.
362 ///
363 /// This is useful when selecting save location,
364 /// but when selecting existing entries, `initial_location` is often better with None.
365 ///
366 /// # Args
367 /// - ***volume_id*** :
368 /// ID of the storage volume, such as internal storage, SD card, etc.
369 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
370 ///
371 /// - ***base_dir*** :
372 /// The base directory.
373 ///
374 /// - ***relative_path*** :
375 /// The directory path relative to the base directory.
376 ///
377 /// # Support
378 /// All Android version.
379 ///
380 /// Note :
381 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
382 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
383 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
384 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
385 /// - Others dirs are available in all Android versions.
386 #[maybe_async]
387 pub fn resolve_initial_location(
388 &self,
389 volume_id: Option<&StorageVolumeId>,
390 base_dir: impl Into<PublicDir>,
391 relative_path: impl AsRef<std::path::Path>,
392 create_dir_all: bool
393 ) -> Result<FileUri> {
394
395 #[cfg(not(target_os = "android"))] {
396 Err(Error::NOT_ANDROID)
397 }
398 #[cfg(target_os = "android")] {
399 self.impls().resolve_public_storage_initial_location(volume_id, base_dir, relative_path, create_dir_all).await
400 }
401 }
402
403 /// Create the specified directory URI that has **no permissions**.
404 ///
405 /// This should only be used as `initial_location` in the file picker.
406 /// It must not be used for any other purpose.
407 ///
408 /// This is useful when selecting save location,
409 /// but when selecting existing entries, `initial_location` is often better with None.
410 ///
411 /// # Args
412 /// - ***volume_id*** :
413 /// ID of the storage volume, such as internal storage, SD card, etc.
414 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
415 ///
416 /// # Support
417 /// All Android version.
418 ///
419 /// Note :
420 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
421 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
422 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
423 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
424 /// - Others dirs are available in all Android versions.
425 #[maybe_async]
426 pub fn resolve_initial_location_top(
427 &self,
428 volume_id: Option<&StorageVolumeId>
429 ) -> Result<FileUri> {
430
431 #[cfg(not(target_os = "android"))] {
432 Err(Error::NOT_ANDROID)
433 }
434 #[cfg(target_os = "android")] {
435 self.impls().resolve_public_storage_initial_location_top(volume_id).await
436 }
437 }
438
439 /// Verify whether the basic functions of PublicStorage
440 /// (such as [`PublicStorage::create_new_file`]) can be performed.
441 ///
442 /// If on Android 9 (API level 28) and lower, this returns false.
443 /// If on Android 10 (API level 29) or higher, this returns true.
444 ///
445 /// # Support
446 /// All Android version.
447 #[always_sync]
448 pub fn is_available(&self) -> Result<bool> {
449 #[cfg(not(target_os = "android"))] {
450 Err(Error::NOT_ANDROID)
451 }
452 #[cfg(target_os = "android")] {
453 Ok(api_level::ANDROID_10 <= self.impls().api_level()?)
454 }
455 }
456
457 /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
458 ///
459 /// If on Android 9 (API level 28) and lower, this returns false.
460 /// If on Android 10 (API level 29) or higher, this returns true.
461 ///
462 /// # Support
463 /// All Android version.
464 #[always_sync]
465 pub fn is_audiobooks_dir_available(&self) -> Result<bool> {
466 #[cfg(not(target_os = "android"))] {
467 Err(Error::NOT_ANDROID)
468 }
469 #[cfg(target_os = "android")] {
470 Ok(self.impls().consts()?.env_dir_audiobooks.is_some())
471 }
472 }
473
474 /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
475 ///
476 /// If on Android 11 (API level 30) and lower, this returns false.
477 /// If on Android 12 (API level 31) or higher, this returns true.
478 ///
479 /// # Support
480 /// All Android version.
481 #[always_sync]
482 pub fn is_recordings_dir_available(&self) -> Result<bool> {
483 #[cfg(not(target_os = "android"))] {
484 Err(Error::NOT_ANDROID)
485 }
486 #[cfg(target_os = "android")] {
487 Ok(self.impls().consts()?.env_dir_recordings.is_some())
488 }
489 }
490}