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).await
163 }
164 }
165
166 /// Recursively create a directory and all of its parent components if they are missing.
167 /// If it already exists, do nothing.
168 ///
169 /// [`PublicStorage::create_new_file`] does this automatically, so there is no need to use it together.
170 ///
171 /// # Args
172 /// - ***volume_id*** :
173 /// ID of the storage volume, such as internal storage, SD card, etc.
174 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
175 ///
176 /// - ***base_dir*** :
177 /// The base directory.
178 ///
179 /// - ***relative_path*** :
180 /// The directory path relative to the base directory.
181 /// The system may sanitize these strings as needed, so those strings may not be used as it is.
182 ///
183 /// # Support
184 /// Android 10 (API level 29) or higher.
185 ///
186 /// Note :
187 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
188 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
189 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
190 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
191 /// - Others dirs are available in all Android versions.
192 #[maybe_async]
193 pub fn create_dir_all(
194 &self,
195 volume_id: Option<&StorageVolumeId>,
196 base_dir: impl Into<PublicDir>,
197 relative_path: impl AsRef<std::path::Path>,
198 ) -> Result<()> {
199
200 #[cfg(not(target_os = "android"))] {
201 Err(Error::NOT_ANDROID)
202 }
203 #[cfg(target_os = "android")] {
204 self.impls().create_dir_all_in_public_storage(volume_id, base_dir, relative_path).await
205 }
206 }
207
208 /// Retrieves the absolute path for a specified public directory within the given storage volume.
209 /// This function does **not** create any directories; it only constructs the path.
210 ///
211 /// **Please avoid using this whenever possible.**
212 /// Use it only in cases that cannot be handled by [`PublicStorage::create_new_file`] or [`PrivateStorage::resolve_path`],
213 /// 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.
214 ///
215 /// Since **Android 11** (not Android 10),
216 /// you can create files and folders under this directory and read or write **only** them.
217 /// If not, you can do nothing with this path.
218 ///
219 /// When using [`PublicImageDir`], use only image type for file name extension,
220 /// using other type extension or none may cause errors.
221 /// Similarly, use only the corresponding extesions for [`PublicVideoDir`] and [`PublicAudioDir`].
222 /// Only [`PublicGeneralPurposeDir`] supports all extensions and no extension.
223 ///
224 /// # Note
225 /// Filesystem access via this path may be heavily impacted by emulation overhead.
226 /// And those files will not be registered in MediaStore.
227 /// It might eventually be registered over time, but this should not be expected.
228 /// As a result, it may not appear in gallery apps or photo picker tools.
229 ///
230 /// You cannot access files created by other apps.
231 /// Additionally, if the app is uninstalled,
232 /// you will no longer be able to access the files you created,
233 /// even if the app is reinstalled.
234 /// Android tends to restrict public file access using paths, so this may stop working in the future.
235 ///
236 /// # Args
237 /// - ***volume_id*** :
238 /// ID of the storage volume, such as internal storage, SD card, etc.
239 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
240 ///
241 /// - ***base_dir*** :
242 /// The base directory.
243 ///
244 /// # Support
245 /// Android 10 (API level 29) or higher.
246 ///
247 /// Note :
248 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
249 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
250 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
251 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
252 /// - Others dirs are available in all Android versions.
253 #[maybe_async]
254 pub fn resolve_path(
255 &self,
256 volume_id: Option<&StorageVolumeId>,
257 base_dir: impl Into<PublicDir>,
258 ) -> Result<std::path::PathBuf> {
259
260 #[cfg(not(target_os = "android"))] {
261 Err(Error::NOT_ANDROID)
262 }
263 #[cfg(target_os = "android")] {
264 self.impls().resolve_path(volume_id, base_dir).await
265 }
266 }
267
268 /// Create the specified directory URI that has **no permissions**.
269 ///
270 /// This should only be used as `initial_location` in the file picker.
271 /// It must not be used for any other purpose.
272 ///
273 /// This is useful when selecting save location,
274 /// but when selecting existing entries, `initial_location` is often better with None.
275 ///
276 /// # Args
277 /// - ***volume_id*** :
278 /// ID of the storage volume, such as internal storage, SD card, etc.
279 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
280 ///
281 /// - ***base_dir*** :
282 /// The base directory.
283 ///
284 /// - ***relative_path*** :
285 /// The directory path relative to the base directory.
286 ///
287 /// # Support
288 /// All Android version.
289 ///
290 /// Note :
291 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
292 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
293 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
294 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
295 /// - Others dirs are available in all Android versions.
296 #[maybe_async]
297 pub fn resolve_initial_location(
298 &self,
299 volume_id: Option<&StorageVolumeId>,
300 base_dir: impl Into<PublicDir>,
301 relative_path: impl AsRef<std::path::Path>,
302 create_dir_all: bool
303 ) -> Result<FileUri> {
304
305 #[cfg(not(target_os = "android"))] {
306 Err(Error::NOT_ANDROID)
307 }
308 #[cfg(target_os = "android")] {
309 self.impls().resolve_public_storage_initial_location(volume_id, base_dir, relative_path, create_dir_all).await
310 }
311 }
312
313 /// Create the specified directory URI that has **no permissions**.
314 ///
315 /// This should only be used as `initial_location` in the file picker.
316 /// It must not be used for any other purpose.
317 ///
318 /// This is useful when selecting save location,
319 /// but when selecting existing entries, `initial_location` is often better with None.
320 ///
321 /// # Args
322 /// - ***volume_id*** :
323 /// ID of the storage volume, such as internal storage, SD card, etc.
324 /// If `None` is provided, [`the primary storage volume`](PublicStorage::get_primary_volume) will be used.
325 ///
326 /// # Support
327 /// All Android version.
328 ///
329 /// Note :
330 /// - [`PublicAudioDir::Audiobooks`] is not available on Android 9 (API level 28) and lower.
331 /// Availability on a given device can be verified by calling [`PublicStorage::is_audiobooks_dir_available`].
332 /// - [`PublicAudioDir::Recordings`] is not available on Android 11 (API level 30) and lower.
333 /// Availability on a given device can be verified by calling [`PublicStorage::is_recordings_dir_available`].
334 /// - Others dirs are available in all Android versions.
335 #[maybe_async]
336 pub fn resolve_initial_location_top(
337 &self,
338 volume_id: Option<&StorageVolumeId>
339 ) -> Result<FileUri> {
340
341 #[cfg(not(target_os = "android"))] {
342 Err(Error::NOT_ANDROID)
343 }
344 #[cfg(target_os = "android")] {
345 self.impls().resolve_public_storage_initial_location_top(volume_id).await
346 }
347 }
348
349 /// Verify whether the basic functions of PublicStorage
350 /// (such as [`PublicStorage::create_new_file`]) can be performed.
351 ///
352 /// If on Android 9 (API level 28) and lower, this returns false.
353 /// If on Android 10 (API level 29) or higher, this returns true.
354 ///
355 /// # Support
356 /// All Android version.
357 #[always_sync]
358 pub fn is_available(&self) -> Result<bool> {
359 #[cfg(not(target_os = "android"))] {
360 Err(Error::NOT_ANDROID)
361 }
362 #[cfg(target_os = "android")] {
363 Ok(api_level::ANDROID_10 <= self.impls().api_level()?)
364 }
365 }
366
367 /// Verify whether [`PublicAudioDir::Audiobooks`] is available on a given device.
368 ///
369 /// If on Android 9 (API level 28) and lower, this returns false.
370 /// If on Android 10 (API level 29) or higher, this returns true.
371 ///
372 /// # Support
373 /// All Android version.
374 #[always_sync]
375 pub fn is_audiobooks_dir_available(&self) -> Result<bool> {
376 #[cfg(not(target_os = "android"))] {
377 Err(Error::NOT_ANDROID)
378 }
379 #[cfg(target_os = "android")] {
380 Ok(self.impls().consts()?.env_dir_audiobooks.is_some())
381 }
382 }
383
384 /// Verify whether [`PublicAudioDir::Recordings`] is available on a given device.
385 ///
386 /// If on Android 11 (API level 30) and lower, this returns false.
387 /// If on Android 12 (API level 31) or higher, this returns true.
388 ///
389 /// # Support
390 /// All Android version.
391 #[always_sync]
392 pub fn is_recordings_dir_available(&self) -> Result<bool> {
393 #[cfg(not(target_os = "android"))] {
394 Err(Error::NOT_ANDROID)
395 }
396 #[cfg(target_os = "android")] {
397 Ok(self.impls().consts()?.env_dir_recordings.is_some())
398 }
399 }
400}