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