Skip to content

Commit e21cd8b

Browse files
authored
Merge pull request kivy#1598 from JonasT/storage_android
Add functions for obtaining the default storage paths
2 parents 0e8e945 + c63588d commit e21cd8b

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed

doc/source/apis.rst

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,60 @@ Working on Android
55
This page gives details on accessing Android APIs and managing other
66
interactions on Android.
77

8+
Storage paths
9+
-------------
10+
11+
If you want to store and retrieve data, you shouldn't just save to
12+
the current directory, and not hardcode `/sdcard/` or some other
13+
path either - it might differ per device.
14+
15+
Instead, the `android` module which you can add to your `--requirements`
16+
allows you to query the most commonly required paths::
17+
18+
from android.storage import app_storage_path
19+
settings_path = app_storage_path()
20+
21+
from android.storage import primary_external_storage_path
22+
primary_ext_storage = primary_external_storage_path()
23+
24+
from android.storage import secondary_external_storage_path
25+
secondary_ext_storage = secondary_external_storage_path()
26+
27+
`app_storage_path()` gives you Android's so-called "internal storage"
28+
which is specific to your app and cannot seen by others or the user.
29+
It compares best to the AppData directory on Windows.
30+
31+
`primary_external_storage_path()` returns Android's so-called
32+
"primary external storage", often found at `/sdcard/` and potentially
33+
accessible to any other app.
34+
It compares best to the Documents directory on Windows.
35+
Requires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.
36+
37+
`secondary_external_storage_path()` returns Android's so-called
38+
"secondary external storage", often found at `/storage/External_SD/`.
39+
It compares best to an external disk plugged to a Desktop PC, and can
40+
after a device restart become inaccessible if removed.
41+
Requires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.
42+
43+
.. warning::
44+
Even if `secondary_external_storage_path` returns a path
45+
the external sd card may still not be present.
46+
Only non-empty contents or a successful write indicate that it is.
47+
48+
Read more on all the different storage types and what to use them for
49+
in the Android documentation:
50+
51+
https://developer.android.com/training/data-storage/files
52+
53+
A note on permissions
54+
~~~~~~~~~~~~~~~~~~~~~
55+
56+
Only the internal storage is always accessible with no additional
57+
permissions. For both primary and secondary external storage, you need
58+
to obtain `Permission.WRITE_EXTERNAL_STORAGE` **and the user may deny it.**
59+
Also, if you get it, both forms of external storage may only allow
60+
your app to write to the common pre-existing folders like "Music",
61+
"Documents", and so on. (see the Android Docs linked above for details)
862

963
Runtime permissions
1064
-------------------
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from jnius import autoclass, cast
2+
import os
3+
4+
5+
Environment = autoclass('android.os.Environment')
6+
File = autoclass('java.io.File')
7+
8+
9+
def _android_has_is_removable_func():
10+
VERSION = autoclass('android.os.Build$VERSION')
11+
return (VERSION.SDK_INT >= 24)
12+
13+
14+
def _get_sdcard_path():
15+
""" Internal function to return getExternalStorageDirectory()
16+
path. This is internal because it may either return the internal,
17+
or an external sd card, depending on the device.
18+
Use primary_external_storage_path()
19+
or secondary_external_storage_path() instead which try to
20+
distinguish this properly.
21+
"""
22+
return (
23+
Environment.getExternalStorageDirectory().getAbsolutePath()
24+
)
25+
26+
27+
def _get_activity():
28+
"""
29+
Retrieves the activity from `PythonActivity` fallback to `PythonService`.
30+
"""
31+
PythonActivity = autoclass('org.kivy.android.PythonActivity')
32+
activity = PythonActivity.mActivity
33+
if activity is None:
34+
# assume we're running from the background service
35+
PythonService = autoclass('org.kivy.android.PythonService')
36+
activity = PythonService.mService
37+
return activity
38+
39+
40+
def app_storage_path():
41+
""" Locate the built-in device storage used for this app only.
42+
43+
This storage is APP-SPECIFIC, and not visible to other apps.
44+
It will be wiped when your app is uninstalled.
45+
46+
Returns directory path to storage.
47+
"""
48+
activity = _get_activity()
49+
currentActivity = cast('android.app.Activity', activity)
50+
context = cast('android.content.ContextWrapper',
51+
currentActivity.getApplicationContext())
52+
file_p = cast('java.io.File', context.getFilesDir())
53+
return os.path.normpath(os.path.abspath(
54+
file_p.getAbsolutePath().replace("/", os.path.sep)))
55+
56+
57+
def primary_external_storage_path():
58+
""" Locate the built-in device storage that user can see via file browser.
59+
Often found at: /sdcard/
60+
61+
This is storage is SHARED, and visible to other apps and the user.
62+
It will remain untouched when your app is uninstalled.
63+
64+
Returns directory path to storage.
65+
66+
WARNING: You need storage permissions to access this storage.
67+
"""
68+
if _android_has_is_removable_func():
69+
sdpath = _get_sdcard_path()
70+
# Apparently this can both return primary (built-in) or
71+
# secondary (removable) external storage depending on the device,
72+
# therefore check that we got what we wanted:
73+
if not Environment.isExternalStorageRemovable(File(sdpath)):
74+
return sdpath
75+
if "EXTERNAL_STORAGE" in os.environ:
76+
return os.environ["EXTERNAL_STORAGE"]
77+
raise RuntimeError(
78+
"unexpectedly failed to determine " +
79+
"primary external storage path"
80+
)
81+
82+
83+
def secondary_external_storage_path():
84+
""" Locate the external SD Card storage, which may not be present.
85+
Often found at: /sdcard/External_SD/
86+
87+
This storage is SHARED, visible to other apps, and may not be
88+
be available if the user didn't put in an external SD card.
89+
It will remain untouched when your app is uninstalled.
90+
91+
Returns None if not found, otherwise path to storage.
92+
93+
WARNING: You need storage permissions to access this storage.
94+
If it is not writable and presents as empty even with
95+
permissions, then the external sd card may not be present.
96+
"""
97+
if _android_has_is_removable_func:
98+
# See if getExternalStorageDirectory() returns secondary ext storage:
99+
sdpath = _get_sdcard_path()
100+
# Apparently this can both return primary (built-in) or
101+
# secondary (removable) external storage depending on the device,
102+
# therefore check that we got what we wanted:
103+
if Environment.isExternalStorageRemovable(File(sdpath)):
104+
if os.path.exists(sdpath):
105+
return sdpath
106+
107+
# See if we can take a guess based on environment variables:
108+
p = None
109+
if "SECONDARY_STORAGE" in os.environ:
110+
p = os.environ["SECONDARY_STORAGE"]
111+
elif "EXTERNAL_SDCARD_STORAGE" in os.environ:
112+
p = os.environ["EXTERNAL_SDCARD_STORAGE"]
113+
if p is not None and os.path.exists(p):
114+
return p
115+
return None

0 commit comments

Comments
 (0)