Skip to content
Draft
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
62 changes: 61 additions & 1 deletion doc/source/apis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,74 @@ unless you blacklist it. Use it in your app like this::
from android.permissions import request_permissions, Permission
request_permissions([Permission.WRITE_EXTERNAL_STORAGE])

The available permissions are listed here:
The available permissions are listed here:/^^^

https://developer.android.com/reference/android/Manifest.permission


Other common tasks
------------------

Running executables
~~~~~~~~~~~~~~~~~~~

Android enforces strict restrictions on executing files from application
data directories.

The ``android`` module works around this by creating symlinks to a small
set of supported executables in an internal, executable directory. This
directory is added to ``PATH``, allowing these executables to be invoked
from anywhere.

Currently supported executables are:

- ``python``
- ``python3``
- ``ffmpeg``

Importing the ``android`` module is relatively expensive (≈1–2 seconds
on low-end devices), so it should be avoided during early startup unless
required.

Minimal FFmpeg example
^^^^^^^^^^^^^^^^^^^^^^

The following example performs a minimal FFmpeg sanity check using an
in-memory test source and discarding the output::

import android
import subprocess

subprocess.run(
["ffmpeg", "-f", "lavfi", "-i", "testsrc", "-t", "1", "-f", "null", "-"],
check=True
)

This can be used to verify that ``ffmpeg`` is available and executable on
the device.

Requirements
^^^^^^^^^^^^

If you plan to use ``ffmpeg``, ensure it is included in your build
requirements.

If video encoding is required, the following codec options must also be
enabled in the build configuration:

- ``av_codecs``
- ``libx264``

Without these, FFmpeg may be present but lack the necessary codec
support.

See also
^^^^^^^^

.. _APK native library execution restrictions:
https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md#apk-native-library


Dismissing the splash screen
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
4 changes: 4 additions & 0 deletions pythonforandroid/androidndk.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ def llvm_readelf(self):
def llvm_strip(self):
return f"{self.llvm_binutils_prefix}strip"

@property
def llvm_nm(self):
return f"{self.llvm_binutils_prefix}nm"

@property
def sysroot(self):
return os.path.join(self.llvm_prebuilt_dir, "sysroot")
Expand Down
36 changes: 36 additions & 0 deletions pythonforandroid/recipes/android/src/android/_android.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,42 @@ class AndroidBrowser(object):
import webbrowser
webbrowser.register('android', AndroidBrowser)

# Native android executable support
# Ref:
# https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md#apk-native-library

import os
import sys
from os.path import join, isdir, islink, isfile

_EXECUTABLES = {
'python': 'libpythonbin.so',
'python3': 'libpythonbin.so',
'ffmpeg': 'libffmpegbin.so',
}

app_info = mActivity.getApplicationInfo()
native_lib_dir = app_info.nativeLibraryDir
files_dir = mActivity.getFilesDir().getAbsolutePath()
bin_dir = join(files_dir, 'app', '.bin')

os.makedirs(bin_dir, exist_ok=True)

for exe, exe_lib in _EXECUTABLES.items():
_exe = join(bin_dir, exe)
_exe_lib = join(native_lib_dir, exe_lib)
if isfile(_exe_lib) and not islink(_exe):
try:
os.symlink(_exe_lib, _exe)
print(f'Symlink executable: {exe_lib} -> {exe}')
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of print, try using logging instead

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah I think that would be overkill.

This would require to setup logger in android module itself, which will increase startup time.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would be nice having. And perhaps add logging for the rest of the file. I don't think a setup is actually needed so python can automatically deal with it

except Exception as e:
print(f'Symlink failed for {exe_lib} -> {exe}')
print(e)

os.environ['LD_LIBRARY_PATH'] = native_lib_dir
os.environ['PATH'] = bin_dir
os.environ['PYTHONPATH'] = ":".join(sys.path)
sys.executable = join(bin_dir, 'python')

def start_service(title="Background Service",
description="", arg="",
Expand Down
37 changes: 24 additions & 13 deletions pythonforandroid/recipes/ffmpeg/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
from pythonforandroid.toolchain import Recipe, current_directory, shprint
from os.path import exists, join, realpath
import sh
from multiprocessing import cpu_count


class FFMpegRecipe(Recipe):
version = 'n6.1.2'
version = '8.0.1'
# Moved to github.com instead of ffmpeg.org to improve download speed
url = 'https://github.com/FFmpeg/FFmpeg/archive/{version}.zip'
depends = ['sdl2'] # Need this to build correct recipe order
url = 'https://www.ffmpeg.org/releases/ffmpeg-{version}.tar.xz'
depends = [('sdl2', 'sdl3')] # Need this to build correct recipe order
opts_depends = ['openssl', 'ffpyplayer_codecs', 'av_codecs']
patches = ['patches/configure.patch']
patches = ['patches/configure.patch', 'patches/backport-Android15-MediaCodec-fix.patch']
_libs = [
"libavcodec.so",
"libavfilter.so",
"libavutil.so",
"libswscale.so",
"libavdevice.so",
"libavformat.so",
"libswresample.so",
"libffmpegbin.so",
]
built_libraries = dict.fromkeys(_libs, "./lib")

def should_build(self, arch):
build_dir = self.get_build_dir(arch.arch)
Expand All @@ -36,15 +48,15 @@ def build_arch(self, arch):

if 'openssl' in self.ctx.recipe_build_order:
flags += [
'--enable-version3',
'--enable-openssl',
'--enable-nonfree',
'--enable-protocol=https,tls_openssl',
]
build_dir = Recipe.get_recipe(
'openssl', self.ctx).get_build_dir(arch.arch)
cflags += ['-I' + build_dir + '/include/',
'-DOPENSSL_API_COMPAT=0x10002000L']
ldflags += ['-L' + build_dir]
cflags += ['-I' + build_dir + '/include/']
ldflags += ['-L' + build_dir, '-lssl', '-lcrypto']

codecs_opts = {"ffpyplayer_codecs", "av_codecs"}
if codecs_opts.intersection(self.ctx.recipe_build_order):
Expand Down Expand Up @@ -98,9 +110,8 @@ def build_arch(self, arch):
'--disable-symver',
]

# disable binaries / doc
# disable doc
flags += [
'--disable-programs',
'--disable-doc',
]

Expand Down Expand Up @@ -131,13 +142,15 @@ def build_arch(self, arch):
'--cross-prefix={}-'.format(arch.target),
'--arch={}'.format(arch_flag),
'--strip={}'.format(self.ctx.ndk.llvm_strip),
'--nm={}'.format(self.ctx.ndk.llvm_nm),
'--sysroot={}'.format(self.ctx.ndk.sysroot),
'--enable-neon',
'--prefix={}'.format(realpath('.')),
]

if arch_flag == 'arm':
cflags += [
'-Wno-error=incompatible-pointer-types',
'-mfpu=vfpv3-d16',
'-mfloat-abi=softfp',
'-fPIC',
Expand All @@ -148,11 +161,9 @@ def build_arch(self, arch):

configure = sh.Command('./configure')
shprint(configure, *flags, _env=env)
shprint(sh.make, '-j4', _env=env)
shprint(sh.make, '-j', f"{cpu_count()}", _env=env)
shprint(sh.make, 'install', _env=env)
# copy libs:
sh.cp('-a', sh.glob('./lib/lib*.so'),
self.ctx.get_libs_dir(arch.arch))
shprint(sh.cp, "ffmpeg", "./lib/libffmpegbin.so")


recipe = FFMpegRecipe()
Loading
Loading