| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- """Android."""
- from __future__ import annotations
- import os
- import re
- import sys
- from functools import lru_cache
- from typing import TYPE_CHECKING, cast
- from .api import PlatformDirsABC
- class Android(PlatformDirsABC): # noqa: PLR0904
- """Platform directories for Android.
- Follows the guidance `from here <https://android.stackexchange.com/a/216132>`_. Directories are typically located
- under the app's private storage (``/data/user/<userid>/<packagename>/``).
- Makes use of the `appname <platformdirs.api.PlatformDirsABC.appname>`, `version
- <platformdirs.api.PlatformDirsABC.version>`, `opinion <platformdirs.api.PlatformDirsABC.opinion>`, `ensure_exists
- <platformdirs.api.PlatformDirsABC.ensure_exists>`.
- """
- @property
- def user_data_dir(self) -> str:
- """:returns: data directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/<AppName>``"""
- return self._append_app_name_and_version(cast("str", _android_folder()), "files")
- @property
- def site_data_dir(self) -> str:
- """:returns: data directory shared by users, same as `user_data_dir`"""
- return self.user_data_dir
- @property
- def user_config_dir(self) -> str:
- """:returns: config directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/shared_prefs/<AppName>``"""
- return self._append_app_name_and_version(cast("str", _android_folder()), "shared_prefs")
- @property
- def site_config_dir(self) -> str:
- """:returns: config directory shared by users, same as `user_config_dir`"""
- return self.user_config_dir
- @property
- def user_cache_dir(self) -> str:
- """:returns: cache directory tied to the user, e.g.,``/data/user/<userid>/<packagename>/cache/<AppName>``"""
- return self._append_app_name_and_version(cast("str", _android_folder()), "cache")
- @property
- def site_cache_dir(self) -> str:
- """:returns: cache directory shared by users, same as `user_cache_dir`"""
- return self.user_cache_dir
- @property
- def user_state_dir(self) -> str:
- """:returns: state directory tied to the user, same as `user_data_dir`"""
- return self.user_data_dir
- @property
- def site_state_dir(self) -> str:
- """:returns: state directory shared by users, same as `user_state_dir`"""
- return self.user_state_dir
- @property
- def user_log_dir(self) -> str:
- """:returns: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it, e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/log``"""
- path = self.user_cache_dir
- if self.opinion:
- path = os.path.join(path, "log") # noqa: PTH118
- self._optionally_create_directory(path)
- return path
- @property
- def site_log_dir(self) -> str:
- """:returns: log directory shared by users, same as `user_log_dir`"""
- return self.user_log_dir
- @property
- def user_documents_dir(self) -> str:
- """:returns: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``"""
- return _android_documents_folder()
- @property
- def user_downloads_dir(self) -> str:
- """:returns: downloads directory tied to the user e.g. ``/storage/emulated/0/Downloads``"""
- return _android_downloads_folder()
- @property
- def user_pictures_dir(self) -> str:
- """:returns: pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``"""
- return _android_pictures_folder()
- @property
- def user_videos_dir(self) -> str:
- """:returns: videos directory tied to the user e.g. ``/storage/emulated/0/DCIM/Camera``"""
- return _android_videos_folder()
- @property
- def user_music_dir(self) -> str:
- """:returns: music directory tied to the user e.g. ``/storage/emulated/0/Music``"""
- return _android_music_folder()
- @property
- def user_desktop_dir(self) -> str:
- """:returns: desktop directory tied to the user e.g. ``/storage/emulated/0/Desktop``"""
- return "/storage/emulated/0/Desktop"
- @property
- def user_bin_dir(self) -> str:
- """:returns: bin directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/bin``"""
- return os.path.join(cast("str", _android_folder()), "files", "bin") # noqa: PTH118
- @property
- def site_bin_dir(self) -> str:
- """:returns: bin directory shared by users, same as `user_bin_dir`"""
- return self.user_bin_dir
- @property
- def user_applications_dir(self) -> str:
- """:returns: applications directory tied to the user, same as `user_data_dir`"""
- return self.user_data_dir
- @property
- def site_applications_dir(self) -> str:
- """:returns: applications directory shared by users, same as `user_applications_dir`"""
- return self.user_applications_dir
- @property
- def user_runtime_dir(self) -> str:
- """:returns: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it, e.g. ``/data/user/<userid>/<packagename>/cache/<AppName>/tmp``"""
- path = self.user_cache_dir
- if self.opinion:
- path = os.path.join(path, "tmp") # noqa: PTH118
- self._optionally_create_directory(path)
- return path
- @property
- def site_runtime_dir(self) -> str:
- """:returns: runtime directory shared by users, same as `user_runtime_dir`"""
- return self.user_runtime_dir
- @lru_cache(maxsize=1)
- def _android_folder() -> str | None: # noqa: C901
- """:returns: base folder for the Android OS or None if it cannot be found"""
- result: str | None = None
- # type checker isn't happy with our "import android", just don't do this when type checking see
- # https://stackoverflow.com/a/61394121
- if not TYPE_CHECKING:
- try:
- # First try to get a path to android app using python4android (if available)...
- from android import mActivity # noqa: PLC0415
- context = cast("android.content.Context", mActivity.getApplicationContext()) # noqa: F821
- result = context.getFilesDir().getParentFile().getAbsolutePath()
- except Exception: # noqa: BLE001
- result = None
- if result is None:
- try:
- # ...and fall back to using plain pyjnius, if python4android isn't available or doesn't deliver any useful
- # result...
- from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import]
- context = autoclass("android.content.Context")
- result = context.getFilesDir().getParentFile().getAbsolutePath()
- except Exception: # noqa: BLE001
- result = None
- if result is None:
- # and if that fails, too, find an android folder looking at path on the sys.path
- # warning: only works for apps installed under /data, not adopted storage etc.
- pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
- for path in sys.path:
- if pattern.match(path):
- result = path.split("/files")[0]
- break
- else:
- result = None
- if result is None:
- # one last try: find an android folder looking at path on the sys.path taking adopted storage paths into
- # account
- pattern = re.compile(r"/mnt/expand/[a-fA-F0-9-]{36}/(data|user/\d+)/(.+)/files")
- for path in sys.path:
- if pattern.match(path):
- result = path.split("/files")[0]
- break
- else:
- result = None
- return result
- @lru_cache(maxsize=1)
- def _android_documents_folder() -> str:
- """:returns: documents folder for the Android OS"""
- # Get directories with pyjnius
- try:
- from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import]
- context = autoclass("android.content.Context")
- environment = autoclass("android.os.Environment")
- documents_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
- except Exception: # noqa: BLE001
- documents_dir = "/storage/emulated/0/Documents"
- return documents_dir
- @lru_cache(maxsize=1)
- def _android_downloads_folder() -> str:
- """:returns: downloads folder for the Android OS"""
- # Get directories with pyjnius
- try:
- from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import]
- context = autoclass("android.content.Context")
- environment = autoclass("android.os.Environment")
- downloads_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DOWNLOADS).getAbsolutePath()
- except Exception: # noqa: BLE001
- downloads_dir = "/storage/emulated/0/Downloads"
- return downloads_dir
- @lru_cache(maxsize=1)
- def _android_pictures_folder() -> str:
- """:returns: pictures folder for the Android OS"""
- # Get directories with pyjnius
- try:
- from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import]
- context = autoclass("android.content.Context")
- environment = autoclass("android.os.Environment")
- pictures_dir: str = context.getExternalFilesDir(environment.DIRECTORY_PICTURES).getAbsolutePath()
- except Exception: # noqa: BLE001
- pictures_dir = "/storage/emulated/0/Pictures"
- return pictures_dir
- @lru_cache(maxsize=1)
- def _android_videos_folder() -> str:
- """:returns: videos folder for the Android OS"""
- # Get directories with pyjnius
- try:
- from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import]
- context = autoclass("android.content.Context")
- environment = autoclass("android.os.Environment")
- videos_dir: str = context.getExternalFilesDir(environment.DIRECTORY_DCIM).getAbsolutePath()
- except Exception: # noqa: BLE001
- videos_dir = "/storage/emulated/0/DCIM/Camera"
- return videos_dir
- @lru_cache(maxsize=1)
- def _android_music_folder() -> str:
- """:returns: music folder for the Android OS"""
- # Get directories with pyjnius
- try:
- from jnius import autoclass # noqa: PLC0415 # ty: ignore[unresolved-import]
- context = autoclass("android.content.Context")
- environment = autoclass("android.os.Environment")
- music_dir: str = context.getExternalFilesDir(environment.DIRECTORY_MUSIC).getAbsolutePath()
- except Exception: # noqa: BLE001
- music_dir = "/storage/emulated/0/Music"
- return music_dir
- __all__ = [
- "Android",
- ]
|