Skip to content

Add functions for obtaining the default storage paths #1598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from Jul 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions doc/source/apis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,60 @@ Working on Android
This page gives details on accessing Android APIs and managing other
interactions on Android.

Storage paths
-------------

If you want to store and retrieve data, you shouldn't just save to
the current directory, and not hardcode `/sdcard/` or some other
path either - it might differ per device.

Instead, the `android` module which you can add to your `--requirements`
allows you to query the most commonly required paths::

from android.storage import app_storage_path
settings_path = app_storage_path()

from android.storage import primary_external_storage_path
primary_ext_storage = primary_external_storage_path()

from android.storage import secondary_external_storage_path
secondary_ext_storage = secondary_external_storage_path()

`app_storage_path()` gives you Android's so-called "internal storage"
which is specific to your app and cannot seen by others or the user.
It compares best to the AppData directory on Windows.

`primary_external_storage_path()` returns Android's so-called
"primary external storage", often found at `/sdcard/` and potentially
accessible to any other app.
It compares best to the Documents directory on Windows.
Requires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.

`secondary_external_storage_path()` returns Android's so-called
"secondary external storage", often found at `/storage/External_SD/`.
It compares best to an external disk plugged to a Desktop PC, and can
after a device restart become inaccessible if removed.
Requires `Permission.WRITE_EXTERNAL_STORAGE` to read and write to.

.. warning::
Even if `secondary_external_storage_path` returns a path
the external sd card may still not be present.
Only non-empty contents or a successful write indicate that it is.

Read more on all the different storage types and what to use them for
in the Android documentation:

https://developer.android.com/training/data-storage/files

A note on permissions
~~~~~~~~~~~~~~~~~~~~~

Only the internal storage is always accessible with no additional
permissions. For both primary and secondary external storage, you need
to obtain `Permission.WRITE_EXTERNAL_STORAGE` **and the user may deny it.**
Also, if you get it, both forms of external storage may only allow
your app to write to the common pre-existing folders like "Music",
"Documents", and so on. (see the Android Docs linked above for details)

Runtime permissions
-------------------
Expand Down
115 changes: 115 additions & 0 deletions pythonforandroid/recipes/android/src/android/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from jnius import autoclass, cast
import os


Environment = autoclass('android.os.Environment')
File = autoclass('java.io.File')


def _android_has_is_removable_func():
VERSION = autoclass('android.os.Build$VERSION')
return (VERSION.SDK_INT >= 24)


def _get_sdcard_path():
""" Internal function to return getExternalStorageDirectory()
path. This is internal because it may either return the internal,
or an external sd card, depending on the device.
Use primary_external_storage_path()
or secondary_external_storage_path() instead which try to
distinguish this properly.
"""
return (
Environment.getExternalStorageDirectory().getAbsolutePath()
)


def _get_activity():
"""
Retrieves the activity from `PythonActivity` fallback to `PythonService`.
"""
PythonActivity = autoclass('org.kivy.android.PythonActivity')
activity = PythonActivity.mActivity
if activity is None:
# assume we're running from the background service
PythonService = autoclass('org.kivy.android.PythonService')
activity = PythonService.mService
return activity


def app_storage_path():
""" Locate the built-in device storage used for this app only.

This storage is APP-SPECIFIC, and not visible to other apps.
It will be wiped when your app is uninstalled.

Returns directory path to storage.
"""
activity = _get_activity()
currentActivity = cast('android.app.Activity', activity)
context = cast('android.content.ContextWrapper',
currentActivity.getApplicationContext())
file_p = cast('java.io.File', context.getFilesDir())
return os.path.normpath(os.path.abspath(
file_p.getAbsolutePath().replace("/", os.path.sep)))


def primary_external_storage_path():
""" Locate the built-in device storage that user can see via file browser.
Often found at: /sdcard/

This is storage is SHARED, and visible to other apps and the user.
It will remain untouched when your app is uninstalled.

Returns directory path to storage.

WARNING: You need storage permissions to access this storage.
"""
if _android_has_is_removable_func():
sdpath = _get_sdcard_path()
# Apparently this can both return primary (built-in) or
# secondary (removable) external storage depending on the device,
# therefore check that we got what we wanted:
if not Environment.isExternalStorageRemovable(File(sdpath)):
return sdpath
if "EXTERNAL_STORAGE" in os.environ:
return os.environ["EXTERNAL_STORAGE"]
raise RuntimeError(
"unexpectedly failed to determine " +
"primary external storage path"
)


def secondary_external_storage_path():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I right to read that this function is actually identical to the previous one?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the isExternalStorageRemovable condition is different, which marks the difference between internal and external storage

""" Locate the external SD Card storage, which may not be present.
Often found at: /sdcard/External_SD/

This storage is SHARED, visible to other apps, and may not be
be available if the user didn't put in an external SD card.
It will remain untouched when your app is uninstalled.

Returns None if not found, otherwise path to storage.

WARNING: You need storage permissions to access this storage.
If it is not writable and presents as empty even with
permissions, then the external sd card may not be present.
"""
if _android_has_is_removable_func:
# See if getExternalStorageDirectory() returns secondary ext storage:
sdpath = _get_sdcard_path()
# Apparently this can both return primary (built-in) or
# secondary (removable) external storage depending on the device,
# therefore check that we got what we wanted:
if Environment.isExternalStorageRemovable(File(sdpath)):
if os.path.exists(sdpath):
return sdpath

# See if we can take a guess based on environment variables:
p = None
if "SECONDARY_STORAGE" in os.environ:
p = os.environ["SECONDARY_STORAGE"]
elif "EXTERNAL_SDCARD_STORAGE" in os.environ:
p = os.environ["EXTERNAL_SDCARD_STORAGE"]
if p is not None and os.path.exists(p):
return p
return None