diff --git a/.travis.yml b/.travis.yml
index 7938918ade..d857dc03bc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,8 +6,8 @@ services:
- docker
before_install:
- - sudo apt update -qq
- - sudo apt install -qq --no-install-recommends python2.7 python3
+ - travis_retry sudo apt update -qq
+ - travis_retry sudo apt install -qq --no-install-recommends python2.7 python3
- sudo pip install tox>=2.0
# https://github.com/travis-ci/travis-ci/issues/6069#issuecomment-266546552
- git remote set-branches --add origin master
@@ -31,5 +31,5 @@ before_script:
- tox
script:
- - docker build --tag=p4a --file Dockerfile.py2 .
+ - docker build --tag=p4a --file Dockerfile.py3 .
- docker run p4a /bin/sh -c "$COMMAND"
diff --git a/Dockerfile.py2 b/Dockerfile.py2
index f8825906eb..05f956aa22 100644
--- a/Dockerfile.py2
+++ b/Dockerfile.py2
@@ -3,13 +3,13 @@
# - python-for-android dependencies
#
# Build with:
-# docker build --tag=p4a --file Dockerfile.py2 .
+# docker build --tag=p4apy2 --file Dockerfile.py2 .
#
# Run with:
-# docker run -it --rm p4a /bin/sh -c '. venv/bin/activate && p4a apk --help'
+# docker run -it --rm p4apy2 /bin/sh -c '. venv/bin/activate && p4a apk --help'
#
# Or for interactive shell:
-# docker run -it --rm p4a
+# docker run -it --rm p4apy2
#
# Note:
# Use 'docker run' without '--rm' flag for keeping the container and use
@@ -19,11 +19,23 @@ FROM ubuntu:18.04
ENV ANDROID_HOME="/opt/android"
+# configure locale
+RUN apt update -qq > /dev/null && apt install -qq --yes --no-install-recommends \
+ locales && \
+ locale-gen en_US.UTF-8
+ENV LANG="en_US.UTF-8" \
+ LANGUAGE="en_US.UTF-8" \
+ LC_ALL="en_US.UTF-8"
+
RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends curl unzip \
- && apt -y autoremove \
- && apt -y clean
+ && apt -y install -qq --no-install-recommends curl unzip ca-certificates \
+ && apt -y autoremove
+# retry helper script, refs:
+# https://github.com/kivy/python-for-android/issues/1306
+ENV RETRY="retry -t 3 --"
+RUN curl https://raw.githubusercontent.com/kadwanev/retry/1.0.1/retry \
+ --output /usr/local/bin/retry && chmod +x /usr/local/bin/retry
ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk"
ENV ANDROID_NDK_VERSION="17c"
@@ -34,7 +46,7 @@ ENV ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux-x86_64.zip"
ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}"
# download and install Android NDK
-RUN curl --location --progress-bar --insecure \
+RUN ${RETRY} curl --location --progress-bar --insecure \
"${ANDROID_NDK_DL_URL}" \
--output "${ANDROID_NDK_ARCHIVE}" \
&& mkdir --parents "${ANDROID_NDK_HOME_V}" \
@@ -47,11 +59,12 @@ ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk"
# get the latest version from https://developer.android.com/studio/index.html
ENV ANDROID_SDK_TOOLS_VERSION="3859397"
+ENV ANDROID_SDK_BUILD_TOOLS_VERSION="26.0.2"
ENV ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip"
ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}"
# download and install Android SDK
-RUN curl --location --progress-bar --insecure \
+RUN ${RETRY} curl --location --progress-bar --insecure \
"${ANDROID_SDK_TOOLS_DL_URL}" \
--output "${ANDROID_SDK_TOOLS_ARCHIVE}" \
&& mkdir --parents "${ANDROID_SDK_HOME}" \
@@ -64,16 +77,14 @@ RUN mkdir --parents "${ANDROID_SDK_HOME}/.android/" \
> "${ANDROID_SDK_HOME}/.android/repositories.cfg"
# accept Android licenses (JDK necessary!)
-RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends openjdk-8-jdk \
- && apt -y autoremove \
- && apt -y clean
-RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" --licenses > /dev/null
+RUN ${RETRY} apt -y install -qq --no-install-recommends openjdk-8-jdk \
+ && apt -y autoremove
+RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null
# download platforms, API, build tools
RUN "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-19" && \
"${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-27" && \
- "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;26.0.2" && \
+ "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" && \
chmod +x "${ANDROID_SDK_HOME}/tools/bin/avdmanager"
@@ -83,27 +94,23 @@ ENV WORK_DIR="${HOME_DIR}" \
PATH="${HOME_DIR}/.local/bin:${PATH}"
# install system dependencies
-RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends \
+RUN ${RETRY} apt -y install -qq --no-install-recommends \
python virtualenv python-pip wget lbzip2 patch sudo \
- && apt -y autoremove \
- && apt -y clean
+ && apt -y autoremove
# build dependencies
# https://buildozer.readthedocs.io/en/latest/installation.html#android-on-ubuntu-16-04-64bit
RUN dpkg --add-architecture i386 \
- && apt -y update -qq \
- && apt -y install -qq --no-install-recommends \
+ && ${RETRY} apt -y update -qq \
+ && ${RETRY} apt -y install -qq --no-install-recommends \
build-essential ccache git python2.7 python2.7-dev \
libncurses5:i386 libstdc++6:i386 libgtk2.0-0:i386 \
libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386 \
zip zlib1g-dev zlib1g:i386 \
- && apt -y autoremove \
- && apt -y clean
+ && apt -y autoremove
# specific recipes dependencies (e.g. libffi requires autoreconf binary)
-RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends \
+RUN ${RETRY} apt -y install -qq --no-install-recommends \
libffi-dev autoconf automake cmake gettext libltdl-dev libtool pkg-config \
&& apt -y autoremove \
&& apt -y clean
@@ -120,10 +127,8 @@ RUN echo "%sudo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN pip install --upgrade cython==0.28.6
WORKDIR ${WORK_DIR}
-COPY . ${WORK_DIR}
-
-# user needs ownership/write access to these directories
-RUN chown --recursive ${USER} ${WORK_DIR} ${ANDROID_SDK_HOME}
+COPY --chown=user:user . ${WORK_DIR}
+RUN chown --recursive ${USER} ${ANDROID_SDK_HOME}
USER ${USER}
# install python-for-android from current branch
diff --git a/Dockerfile.py3 b/Dockerfile.py3
index 878804c76a..307f8ddfda 100644
--- a/Dockerfile.py3
+++ b/Dockerfile.py3
@@ -3,13 +3,13 @@
# - python-for-android dependencies
#
# Build with:
-# docker build --tag=p4apy3 .
+# docker build --tag=p4a --file Dockerfile.py3 .
#
# Run with:
-# docker run -it --rm p4apy3 /bin/sh -c '. venv/bin/activate && p4a apk --help'
+# docker run -it --rm p4a /bin/sh -c '. venv/bin/activate && p4a apk --help'
#
# Or for interactive shell:
-# docker run -it --rm p4apy3
+# docker run -it --rm p4a
#
# Note:
# Use 'docker run' without '--rm' flag for keeping the container and use
@@ -19,11 +19,23 @@ FROM ubuntu:18.04
ENV ANDROID_HOME="/opt/android"
+# configure locale
+RUN apt update -qq > /dev/null && apt install -qq --yes --no-install-recommends \
+ locales && \
+ locale-gen en_US.UTF-8
+ENV LANG="en_US.UTF-8" \
+ LANGUAGE="en_US.UTF-8" \
+ LC_ALL="en_US.UTF-8"
+
RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends curl unzip \
- && apt -y autoremove \
- && apt -y clean
+ && apt -y install -qq --no-install-recommends curl unzip ca-certificates \
+ && apt -y autoremove
+# retry helper script, refs:
+# https://github.com/kivy/python-for-android/issues/1306
+ENV RETRY="retry -t 3 --"
+RUN curl https://raw.githubusercontent.com/kadwanev/retry/1.0.1/retry \
+ --output /usr/local/bin/retry && chmod +x /usr/local/bin/retry
ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk"
ENV ANDROID_NDK_VERSION="17c"
@@ -34,7 +46,7 @@ ENV ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux-x86_64.zip"
ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}"
# download and install Android NDK
-RUN curl --location --progress-bar --insecure \
+RUN ${RETRY} curl --location --progress-bar --insecure \
"${ANDROID_NDK_DL_URL}" \
--output "${ANDROID_NDK_ARCHIVE}" \
&& mkdir --parents "${ANDROID_NDK_HOME_V}" \
@@ -47,11 +59,12 @@ ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk"
# get the latest version from https://developer.android.com/studio/index.html
ENV ANDROID_SDK_TOOLS_VERSION="3859397"
+ENV ANDROID_SDK_BUILD_TOOLS_VERSION="26.0.2"
ENV ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip"
ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}"
# download and install Android SDK
-RUN curl --location --progress-bar --insecure \
+RUN ${RETRY} curl --location --progress-bar --insecure \
"${ANDROID_SDK_TOOLS_DL_URL}" \
--output "${ANDROID_SDK_TOOLS_ARCHIVE}" \
&& mkdir --parents "${ANDROID_SDK_HOME}" \
@@ -64,16 +77,14 @@ RUN mkdir --parents "${ANDROID_SDK_HOME}/.android/" \
> "${ANDROID_SDK_HOME}/.android/repositories.cfg"
# accept Android licenses (JDK necessary!)
-RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends openjdk-8-jdk \
- && apt -y autoremove \
- && apt -y clean
-RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" --licenses > /dev/null
+RUN ${RETRY} apt -y install -qq --no-install-recommends openjdk-8-jdk \
+ && apt -y autoremove
+RUN yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null
# download platforms, API, build tools
RUN "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-19" && \
"${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-27" && \
- "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;26.0.2" && \
+ "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" && \
chmod +x "${ANDROID_SDK_HOME}/tools/bin/avdmanager"
@@ -83,27 +94,23 @@ ENV WORK_DIR="${HOME_DIR}" \
PATH="${HOME_DIR}/.local/bin:${PATH}"
# install system dependencies
-RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends \
+RUN ${RETRY} apt -y install -qq --no-install-recommends \
python3 virtualenv python3-pip wget lbzip2 patch sudo \
- && apt -y autoremove \
- && apt -y clean
+ && apt -y autoremove
# build dependencies
# https://buildozer.readthedocs.io/en/latest/installation.html#android-on-ubuntu-16-04-64bit
RUN dpkg --add-architecture i386 \
- && apt -y update -qq \
- && apt -y install -qq --no-install-recommends \
+ && ${RETRY} apt -y update -qq \
+ && ${RETRY} apt -y install -qq --no-install-recommends \
build-essential ccache git python3 python3-dev \
libncurses5:i386 libstdc++6:i386 libgtk2.0-0:i386 \
libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386 \
zip zlib1g-dev zlib1g:i386 \
- && apt -y autoremove \
- && apt -y clean
+ && apt -y autoremove
# specific recipes dependencies (e.g. libffi requires autoreconf binary)
-RUN apt -y update -qq \
- && apt -y install -qq --no-install-recommends \
+RUN ${RETRY} apt -y install -qq --no-install-recommends \
libffi-dev autoconf automake cmake gettext libltdl-dev libtool pkg-config \
&& apt -y autoremove \
&& apt -y clean
@@ -120,10 +127,8 @@ RUN echo "%sudo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN pip3 install --upgrade cython==0.28.6
WORKDIR ${WORK_DIR}
-COPY . ${WORK_DIR}
-
-# user needs ownership/write access to these directories
-RUN chown --recursive ${USER} ${WORK_DIR} ${ANDROID_SDK_HOME}
+COPY --chown=user:user . ${WORK_DIR}
+RUN chown --recursive ${USER} ${ANDROID_SDK_HOME}
USER ${USER}
# install python-for-android from current branch
diff --git a/MANIFEST.in b/MANIFEST.in
index 06c844dd38..1c26fb073d 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -7,7 +7,7 @@ prune doc/build
recursive-include pythonforandroid *.py *.tmpl biglink liblink
recursive-include pythonforandroid/recipes *.py *.patch *.c *.pyx Setup *.h
-recursive-include pythonforandroid/bootstraps *.properties *.xml *.java *.tmpl *.txt *.png *.aidl *.py *.sh *.c *.h *.html
+recursive-include pythonforandroid/bootstraps *.properties *.xml *.java *.tmpl *.txt *.png *.aidl *.py *.sh *.c *.h *.html *.patch
prune .git
prune pythonforandroid/bootstraps/pygame/build/libs
diff --git a/README.rst b/README.md
similarity index 53%
rename from README.rst
rename to README.md
index 9f0fca181c..e5eadf4a05 100644
--- a/README.rst
+++ b/README.md
@@ -1,10 +1,9 @@
python-for-android
==================
-|Build Status|
-
-.. |Build Status| image:: https://secure.travis-ci.org/kivy/python-for-android.png?branch=master
- :target: https://travis-ci.org/kivy/python-for-android
+[![Build Status](https://travis-ci.org/kivy/python-for-android.svg?branch=master)](https://travis-ci.org/kivy/python-for-android)
+[![Backers on Open Collective](https://opencollective.com/kivy/backers/badge.svg)](#backers)
+[![Sponsors on Open Collective](https://opencollective.com/kivy/sponsors/badge.svg)](#sponsors)
python-for-android is a packager for Python apps on Android. You can
create your own Python distribution including the modules and
@@ -27,45 +26,17 @@ For documentation and support, see:
- Mailing list: https://groups.google.com/forum/#!forum/kivy-users or
https://groups.google.com/forum/#!forum/python-android.
-In 2015 these tools were rewritten to provide a new, easier to use and
-extend interface. If you are looking for the old toolchain with
-distribute.sh and build.py, it is still available at
-https://github.com/kivy/python-for-android/tree/old\_toolchain, and
-issues and PRs relating to this branch are still accepted. However, the
-new toolchain contains all the same functionality via the built in
-pygame bootstrap.
+## Documentation
-In the last quarter of 2018 the python recipes has been changed, the new recipe
-for python3 (3.7.1) has a new build system which has been applied to the ancient
-python recipe, allowing us to bump the python2 version number to 2.7.15. This
-change, unifies the build process for both python recipes, and probably solve
-some issues detected over the years, but unfortunately, this change breaks the
-pygame bootstrap (in a near future we will fix it or remove it). Also should be
-mentioned that the old python recipe is still usable, and has been renamed to
-`python2legacy`. This `python2legacy` recipe allow us to build with a minimum
-api lower than 21, which is not the case for the new python recipes which are
-limited to a minimum api of 21. All this work has been done using android ndk
-version r17c, and your build should success with that version...but be aware
-that the project is in constant development so...the ndk version will change
-at some time.
-
-Those mentioned changes has been done this way to make easier the transition
-between python3 and python2. We will slowly phase out python2 support
-towards 2020...so...if you are using python2 in your projects you should
-consider to migrate it into python3.
-
-Documentation
-=============
-
-Follow the `quickstart
-instructions `__
+Follow the [quickstart
+instructions]()
to install and begin creating APKs.
-Quick instructions to start would be::
+Quick instructions to start would be:
pip install python-for-android
-or to test the master branch::
+or to test the master branch:
pip install git+https://github.com/kivy/python-for-android.git
@@ -79,28 +50,26 @@ This should return a list of recipes available to be built.
To build any distributions, you need to set up the Android SDK and NDK
as described in the documentation linked above.
-If you did this, to build an APK with SDL2 you can try e.g.::
+If you did this, to build an APK with SDL2 you can try e.g.:
p4a apk --requirements=kivy --private /home/asandy/devel/planewave_frozen/ --package=net.inclem.planewavessdl2 --name="planewavessdl2" --version=0.5 --bootstrap=sdl2
-For full instructions and parameter options, see `the
-documentation `__.
+For full instructions and parameter options, see [the
+documentation](https://python-for-android.readthedocs.io/en/latest/quickstart/#usage).
-Support
-=======
+## Support
If you need assistance, you can ask for help on our mailing list:
- User Group: https://groups.google.com/group/kivy-users
- Email: kivy-users@googlegroups.com
-We also have `#support Discord channel `_.
+We also have [#support Discord channel](https://chat.kivy.org/).
-Contributing
-============
+## Contributing
We love pull requests and discussing novel ideas. Check out our
-`contribution guide `__ and feel
+[contribution guide](http://kivy.org/docs/contribute.html) and feel
free to improve python-for-android.
The following mailing list and IRC channel are used exclusively for
@@ -109,10 +78,67 @@ discussions about developing the Kivy framework and its sister projects:
- Dev Group: https://groups.google.com/group/kivy-dev
- Email: kivy-dev@googlegroups.com
-We also have `#dev Discord channel `_.
+We also have [#dev Discord channel](https://chat.kivy.org/).
-License
-=======
+## License
python-for-android is released under the terms of the MIT License.
Please refer to the LICENSE file.
+
+## History
+
+In 2015 these tools were rewritten to provide a new, easier to use and
+extend interface. If you are looking for the old toolchain with
+distribute.sh and build.py, it is still available at
+https://github.com/kivy/python-for-android/tree/old_toolchain, and
+issues and PRs relating to this branch are still accepted. However, the
+new toolchain contains all the same functionality via the built in
+pygame bootstrap.
+
+In the last quarter of 2018 the python recipes has been changed, the new recipe
+for python3 (3.7.1) has a new build system which has been applied to the ancient
+python recipe, allowing us to bump the python2 version number to 2.7.15. This
+change, unifies the build process for both python recipes, and probably solve
+some issues detected over the years, but unfortunately, this change breaks the
+pygame bootstrap (in a near future we will fix it or remove it). Also should be
+mentioned that the old python recipe is still usable, and has been renamed to
+`python2legacy`. This `python2legacy` recipe allow us to build with a minimum
+api lower than 21, which is not the case for the new python recipes which are
+limited to a minimum api of 21. All this work has been done using android ndk
+version r17c, and your build should success with that version...but be aware
+that the project is in constant development so...the ndk version will change
+at some time.
+
+Those mentioned changes has been done this way to make easier the transition
+between python3 and python2. We will slowly phase out python2 support
+towards 2020...so...if you are using python2 in your projects you should
+consider to migrate it into python3.
+
+## Contributors
+
+This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+
+
+
+## Backers
+
+Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/kivy#backer)]
+
+
+
+
+## Sponsors
+
+Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/kivy#sponsor)]
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ci/constants.py b/ci/constants.py
index a6a8f99702..5484f15de3 100644
--- a/ci/constants.py
+++ b/ci/constants.py
@@ -16,8 +16,6 @@ class TargetPython(Enum):
# https://github.com/kivy/python-for-android/issues/550
'audiostream',
'brokenrecipe',
- # https://github.com/kivy/python-for-android/issues/1409
- 'enaml',
'evdev',
# distutils.errors.DistutilsError
# Could not find suitable distribution for Requirement.parse('cython')
@@ -25,10 +23,7 @@ class TargetPython(Enum):
'flask',
'groestlcoin_hash',
'hostpython3crystax',
- # https://github.com/kivy/python-for-android/issues/1398
- 'ifaddrs',
# https://github.com/kivy/python-for-android/issues/1354
- 'kivent_core', 'kivent_cymunk', 'kivent_particles', 'kivent_polygen',
'kiwisolver',
# https://github.com/kivy/python-for-android/issues/1399
'libglob',
@@ -37,17 +32,23 @@ class TargetPython(Enum):
'libtribler',
'ndghttpsclient',
'm2crypto',
+ # ImportError: No module named setuptools
'netifaces',
'Pillow',
# depends on cffi that still seems to have compilation issues
'protobuf_cpp',
'xeddsa',
'x3dh',
+ # fatal error: crypt.h: No such file or directory
+ 'pyleveldb',
'pynacl',
'doubleratchet',
+ # The opencv recipe fails to pass travis tests due to the long processing
+ # when building it and the lack of console output, so, it's only broken
+ # for travis, see: https://github.com/kivy/python-for-android/pull/1661
+ 'opencv',
'omemo',
- 'cryptography',
- # https://github.com/kivy/python-for-android/issues/1405
+ # requires `libpq-dev` system dependency e.g. for `pg_config` binary
'psycopg2',
'pygame',
# most likely some setup in the Docker container, because it works in host
@@ -65,31 +66,27 @@ class TargetPython(Enum):
'zeroconf',
'zope',
])
-BROKEN_RECIPES_PYTHON3_CRYSTAX = set([
+BROKEN_RECIPES_PYTHON3 = set([
'brokenrecipe',
# enum34 is not compatible with Python 3.6 standard library
# https://stackoverflow.com/a/45716067/185510
'enum34',
- # https://github.com/kivy/python-for-android/issues/1398
- 'ifaddrs',
# https://github.com/kivy/python-for-android/issues/1399
'libglob',
- # cannot find -lcrystax
- 'cffi', 'pycryptodome', 'pymuk', 'secp256k1',
- # https://github.com/kivy/python-for-android/issues/1404
- 'cryptography',
- # https://github.com/kivy/python-for-android/issues/1294
- 'ffmpeg', 'ffpyplayer',
- # https://github.com/kivy/python-for-android/pull/1307 ?
- 'gevent',
+ # build_dir = glob.glob('build/lib.*')[0]
+ # IndexError: list index out of range
+ 'secp256k1',
+ 'ffpyplayer',
'icu',
# https://github.com/kivy/python-for-android/issues/1354
- 'kivent_core', 'kivent_cymunk', 'kivent_particles', 'kivent_polygen',
- # https://github.com/kivy/python-for-android/issues/1405
- 'libpq', 'psycopg2',
- 'netifaces',
- # https://github.com/kivy/python-for-android/issues/1315 ?
+ # The opencv recipe fails to pass travis tests due to the long processing
+ # when building it and the lack of console output, so, it's only broken
+ # for travis, see: https://github.com/kivy/python-for-android/pull/1661
'opencv',
+ # requires `libpq-dev` system dependency e.g. for `pg_config` binary
+ 'psycopg2',
+ # fatal error: crypt.h: No such file or directory
+ 'pyleveldb',
'protobuf_cpp',
# most likely some setup in the Docker container, because it works in host
'pyjnius', 'pyopenal',
@@ -99,13 +96,9 @@ class TargetPython(Enum):
'sympy',
'vlc',
])
-# to be created via https://github.com/kivy/python-for-android/issues/1514
-BROKEN_RECIPES_PYTHON3 = set([
-])
BROKEN_RECIPES = {
TargetPython.python2: BROKEN_RECIPES_PYTHON2,
- TargetPython.python3crystax: BROKEN_RECIPES_PYTHON3_CRYSTAX,
TargetPython.python3: BROKEN_RECIPES_PYTHON3,
}
# recipes that were already built will be skipped
diff --git a/ci/rebuild_updated_recipes.py b/ci/rebuild_updated_recipes.py
index f2d8d0a387..a2e070b009 100755
--- a/ci/rebuild_updated_recipes.py
+++ b/ci/rebuild_updated_recipes.py
@@ -24,8 +24,10 @@
import sh
import os
from pythonforandroid.build import Context
+from pythonforandroid import logger
from pythonforandroid.graph import get_recipe_order_and_bootstrap
from pythonforandroid.toolchain import current_directory
+from pythonforandroid.util import BuildInterruptingException
from ci.constants import TargetPython, CORE_RECIPES, BROKEN_RECIPES
@@ -46,7 +48,7 @@ def modified_recipes(branch='origin/master'):
return recipes
-def build(target_python, target_bootstrap, requirements):
+def build(target_python, requirements, bootstrap='sdl2'):
"""
Builds an APK given a target Python and a set of requirements.
"""
@@ -59,40 +61,40 @@ def build(target_python, target_bootstrap, requirements):
testapp = 'setup_testapp_python3.py'
requirements.add(target_python.name)
requirements = ','.join(requirements)
- print('requirements:', requirements)
+ logger.info('requirements: {}'.format(requirements))
with current_directory('testapps/'):
- try:
- for line in sh.python(
- testapp, 'apk',
- '--sdk-dir', android_sdk_home,
- '--ndk-dir', android_ndk_home,
- '--bootstrap', target_bootstrap,
- '--requirements', requirements,
- _err_to_out=True, _iter=True):
- print(line)
- except sh.ErrorReturnCode as e:
- raise
+ for line in sh.python(
+ testapp, 'apk',
+ '--sdk-dir', android_sdk_home,
+ '--ndk-dir', android_ndk_home,
+ '--bootstrap', bootstrap,
+ '--requirements', requirements,
+ _err_to_out=True, _iter=True):
+ print(line)
def main():
target_python = TargetPython.python3
recipes = modified_recipes()
- print('recipes modified:', recipes)
+ logger.info('recipes modified: {}'.format(recipes))
recipes -= CORE_RECIPES
- print('recipes to build:', recipes)
+ logger.info('recipes to build: {}'.format(recipes))
context = Context()
- build_order, python_modules, bs = get_recipe_order_and_bootstrap(
- context, recipes, None)
- # fallback to python2 if default target is not compatible
- if target_python.name not in build_order:
- print('incompatible with {}'.format(target_python.name))
+ # forces the default target
+ recipes_and_target = recipes | set([target_python.name])
+ try:
+ build_order, python_modules, bs = get_recipe_order_and_bootstrap(
+ context, recipes_and_target, None)
+ except BuildInterruptingException:
+ # fallback to python2 if default target is not compatible
+ logger.info('incompatible with {}'.format(target_python.name))
target_python = TargetPython.python2
- print('falling back to {}'.format(target_python.name))
+ logger.info('falling back to {}'.format(target_python.name))
# removing the known broken recipe for the given target
broken_recipes = BROKEN_RECIPES[target_python]
recipes -= broken_recipes
- print('recipes to build (no broken):', recipes)
- build(target_python, bs.name, recipes)
+ logger.info('recipes to build (no broken): {}'.format(recipes))
+ build(target_python, recipes, bs.name)
if __name__ == '__main__':
diff --git a/doc/source/apis.rst b/doc/source/apis.rst
index bb46f2e339..abd4a22447 100644
--- a/doc/source/apis.rst
+++ b/doc/source/apis.rst
@@ -6,177 +6,42 @@ This page gives details on accessing Android APIs and managing other
interactions on Android.
-Accessing Android APIs
-----------------------
+Runtime permissions
+-------------------
-When writing an Android application you may want to access the normal
-Android Java APIs, in order to control your application's appearance
-(fullscreen, orientation etc.), interact with other apps or use
-hardware like vibration and sensors.
+With API level >= 21, you will need to request runtime permissions
+to access the SD card, the camera, and other things.
-You can access these with `Pyjnius
-`_, a Python library for
-automatically wrapping Java and making it callable from Python
-code. Pyjnius is fairly simple to use, but not very Pythonic and it
-inherits Java's verbosity. For this reason the Kivy organisation also
-created `Plyer `_, which
-further wraps specific APIs in a Pythonic and cross-platform way; you
-can call the same code in Python but have it do the right thing also
-on platforms other than Android.
+This can be done through the `android` module which is *available per default*
+unless you blacklist it. Use it in your app like this::
-Pyjnius and Plyer are independent projects whose documentation is
-linked above. See below for some simple introductory examples, and
-explanation of how to include these modules in your APKs.
+ from android.permissions import request_permission, Permission
+ request_permission(Permission.WRITE_EXTERNAL_STORAGE)
-This page also documents the ``android`` module which you can include
-with p4a, but this is mostly replaced by Pyjnius and is not
-recommended for use in new applications.
+The available permissions are listed here:
+https://developer.android.com/reference/android/Manifest.permission
-Using Pyjnius
-~~~~~~~~~~~~~
-Pyjnius lets you call the Android API directly from Python Pyjnius is
-works by dynamically wrapping Java classes, so you don't have to wait
-for any particular feature to be pre-supported.
-
-You can include Pyjnius in your APKs by adding `pyjnius` to your build
-requirements, e.g. :code:`--requirements=flask,pyjnius`. It is
-automatically included in any APK containing Kivy, in which case you
-don't need to specify it manually.
-
-The basic mechanism of Pyjnius is the `autoclass` command, which wraps
-a Java class. For instance, here is the code to vibrate your device::
-
- from jnius import autoclass
-
- # We need a reference to the Java activity running the current
- # application, this reference is stored automatically by
- # Kivy's PythonActivity bootstrap
-
- # This one works with Pygame
- # PythonActivity = autoclass('org.renpy.android.PythonActivity')
-
- # This one works with SDL2
- PythonActivity = autoclass('org.kivy.android.PythonActivity')
-
- activity = PythonActivity.mActivity
-
- Context = autoclass('android.content.Context')
- vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
-
- vibrator.vibrate(10000) # the argument is in milliseconds
-
-Things to note here are:
-
-- The class that must be wrapped depends on the bootstrap. This is
- because Pyjnius is using the bootstrap's java source code to get a
- reference to the current activity, which both the Pygame and SDL2
- bootstraps store in the ``mActivity`` static variable. This
- difference isn't always important, but it's important to know about.
-- The code closely follows the Java API - this is exactly the same set
- of function calls that you'd use to achieve the same thing from Java
- code.
-- This is quite verbose - it's a lot of lines to achieve a simple
- vibration!
-
-These emphasise both the advantages and disadvantage of Pyjnius; you
-*can* achieve just about any API call with it (though the syntax is
-sometimes a little more involved, particularly if making Java classes
-from Python code), but it's not Pythonic and it's not short. These are
-problems that Plyer, explained below, attempts to address.
-
-You can check the `Pyjnius documentation `_ for further details.
-
-
-Using Plyer
-~~~~~~~~~~~
-
-Plyer provides a much less verbose, Pythonic wrapper to
-platform-specific APIs. It supports Android as well as iOS and desktop
-operating systems, though plyer is a work in progress and not all
-platforms support all Plyer calls yet.
-
-Plyer does not support all APIs yet, but you can always use Pyjnius to
-call anything that is currently missing.
-
-You can include Plyer in your APKs by adding the `Plyer` recipe to
-your build requirements, e.g. :code:`--requirements=plyer`.
-
-You should check the `Plyer documentation `_ for details of all supported
-facades (platform APIs), but as an example the following is how you
-would achieve vibration as described in the Pyjnius section above::
-
- from plyer.vibrator import vibrate
- vibrate(10) # in Plyer, the argument is in seconds
-
-This is obviously *much* less verbose than with Pyjnius!
-
-
-Using ``android``
-~~~~~~~~~~~~~~~~~
-
-This Cython module was used for Android API interaction with Kivy's old
-interface, but is now mostly replaced by Pyjnius.
-
-The ``android`` Python module can be included by adding it to your
-requirements, e.g. :code:`--requirements=kivy,android`. It is not
-automatically included by Kivy unless you use the old (Pygame)
-bootstrap.
-
-This module is not separately documented. You can read the source `on
-Github
-`__.
-
-One useful facility of this module is to make
-:code:`webbrowser.open()` work on Android. You can replicate this
-effect without using the android module via the following
-code::
-
- from jnius import autoclass
-
- def open_url(url):
- Intent = autoclass('android.content.Intent')
- Uri = autoclass('android.net.Uri')
- browserIntent = Intent()
- browserIntent.setAction(Intent.ACTION_VIEW)
- browserIntent.setData(Uri.parse(url))
- currentActivity = cast('android.app.Activity', mActivity)
- currentActivity.startActivity(browserIntent)
-
- class AndroidBrowser(object):
- def open(self, url, new=0, autoraise=True):
- open_url(url)
- def open_new(self, url):
- open_url(url)
- def open_new_tab(self, url):
- open_url(url)
-
- import webbrowser
- webbrowser.register('android', AndroidBrowser, None, -1)
-
-
-Working with the App lifecycle
-------------------------------
+Other common tasks
+------------------
Dismissing the splash screen
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-With the SDL2 bootstrap, the app's splash screen may not be dismissed
-immediately when your app has finished loading, due to a limitation
-with the way we check if the app has properly started. In this case,
-the splash screen overlaps the app gui for a short time.
+With the SDL2 bootstrap, the app's splash screen may be visible
+longer than necessary (with your app already being loaded) due to a
+limitation with the way we check if the app has properly started.
+In this case, the splash screen overlaps the app gui for a short time.
-You can dismiss the splash screen by running this code from your
-app build method (or use ``kivy.clock.Clock.schedule_once`` to run it
-in the following frame)::
+To dismiss the loading screen explicitely in your code, use the `android`
+module::
- from jnius import autoclass
- activity = autoclass('org.kivy.android.PythonActivity').mActivity
- activity.removeLoadingScreen()
+ from android import hide_loading_screen
+ hide_loading_screen()
-This problem does not affect the Pygame bootstrap, as it uses a
-different splash screen method.
+You can call it e.g. using ``kivy.clock.Clock.schedule_once`` to run it
+in the first active frame of your app, or use the app build method.
Handling the back button
@@ -222,3 +87,109 @@ With Kivy, add an ``on_pause`` method to your App class, which returns True::
With the webview bootstrap, pausing should work automatically.
Under SDL2, you can handle the `appropriate events `__ (see SDL_APP_WILLENTERBACKGROUND etc.).
+
+
+Advanced Android API use
+------------------------
+
+.. _reference-label-for-android-module:
+
+`android` for Android API access
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As mentioned above, the ``android`` Python module provides a simple
+wrapper around many native Android APIS, and it is *included per default*
+unless you blacklist it.
+
+The available functionality of this module is not separately documented.
+You can read the source `on
+Github
+`__.
+
+Also please note you can replicate most functionality without it using
+`pyjnius`. (see below)
+
+
+`Plyer` - a more comprehensive API wrapper
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Plyer provides a more thorough wrapper than `android` for a much larger
+area of platform-specific APIs, supporting not only Android but also
+iOS and desktop operating systems.
+(Though plyer is a work in progress and not all
+platforms support all Plyer calls yet)
+
+Plyer does not support all APIs yet, but you can always use Pyjnius to
+call anything that is currently missing.
+
+You can include Plyer in your APKs by adding the `Plyer` recipe to
+your build requirements, e.g. :code:`--requirements=plyer`.
+
+You should check the `Plyer documentation `_ for details of all supported
+facades (platform APIs), but as an example the following is how you
+would achieve vibration as described in the Pyjnius section above::
+
+ from plyer.vibrator import vibrate
+ vibrate(10) # in Plyer, the argument is in seconds
+
+This is obviously *much* less verbose than with Pyjnius!
+
+
+`Pyjnius` - raw lowlevel API access
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Pyjnius lets you call the Android API directly from Python Pyjnius is
+works by dynamically wrapping Java classes, so you don't have to wait
+for any particular feature to be pre-supported.
+
+This is particularly useful when `android` and `plyer` don't already
+provide a convenient access to the API, or you need more control.
+
+You can include Pyjnius in your APKs by adding `pyjnius` to your build
+requirements, e.g. :code:`--requirements=flask,pyjnius`. It is
+automatically included in any APK containing Kivy, in which case you
+don't need to specify it manually.
+
+The basic mechanism of Pyjnius is the `autoclass` command, which wraps
+a Java class. For instance, here is the code to vibrate your device::
+
+ from jnius import autoclass
+
+ # We need a reference to the Java activity running the current
+ # application, this reference is stored automatically by
+ # Kivy's PythonActivity bootstrap
+
+ # This one works with Pygame
+ # PythonActivity = autoclass('org.renpy.android.PythonActivity')
+
+ # This one works with SDL2
+ PythonActivity = autoclass('org.kivy.android.PythonActivity')
+
+ activity = PythonActivity.mActivity
+
+ Context = autoclass('android.content.Context')
+ vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
+
+ vibrator.vibrate(10000) # the argument is in milliseconds
+
+Things to note here are:
+
+- The class that must be wrapped depends on the bootstrap. This is
+ because Pyjnius is using the bootstrap's java source code to get a
+ reference to the current activity, which both the Pygame and SDL2
+ bootstraps store in the ``mActivity`` static variable. This
+ difference isn't always important, but it's important to know about.
+- The code closely follows the Java API - this is exactly the same set
+ of function calls that you'd use to achieve the same thing from Java
+ code.
+- This is quite verbose - it's a lot of lines to achieve a simple
+ vibration!
+
+These emphasise both the advantages and disadvantage of Pyjnius; you
+*can* achieve just about any API call with it (though the syntax is
+sometimes a little more involved, particularly if making Java classes
+from Python code), but it's not Pythonic and it's not short. These are
+problems that Plyer, explained below, attempts to address.
+
+You can check the `Pyjnius documentation `_ for further details.
+
diff --git a/doc/source/buildoptions.rst b/doc/source/buildoptions.rst
index 9af0554b51..4a078e6846 100644
--- a/doc/source/buildoptions.rst
+++ b/doc/source/buildoptions.rst
@@ -243,3 +243,25 @@ options (this list may not be exhaustive):
- ``add-source``: Add a source directory to the app's Java code.
- ``--compile-pyo``: Optimise .py files to .pyo.
- ``--resource``: A key=value pair to add in the string.xml resource file.
+
+
+Requirements blacklist (APK size optimization)
+----------------------------------------------
+
+To optimize the size of the `.apk` file that p4a builds for you,
+you can **blacklist** certain core components. Per default, p4a
+will add python *with batteries included* as would be expected on
+desktop, including openssl, sqlite3 and other components you may
+not use.
+
+To blacklist an item, specify the ``--blacklist-requirements`` option::
+
+ p4a apk ... --blacklist-requirements=sqlite3
+
+At the moment, the following core components can be blacklisted
+(if you don't want to use them) to decrease APK size:
+
+- ``android`` disables p4a's android module (see :ref:`reference-label-for-android-module`)
+- ``libffi`` disables ctypes stdlib module
+- ``openssl`` disables ssl stdlib module
+- ``sqlite3`` disables sqlite3 stdlib module
diff --git a/doc/source/commands.rst b/doc/source/commands.rst
index 02af5cfdb0..836387cba1 100644
--- a/doc/source/commands.rst
+++ b/doc/source/commands.rst
@@ -67,7 +67,7 @@ supply those that you need.
distribution must contain, as a comma separated list. These must be
names of recipes or the pypi names of Python modules.
-``--force_build BOOL``
+``--force-build BOOL``
Whether the distribution must be compiled from scratch.
``--arch``
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 49bc2f058d..16d6162dc3 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -29,16 +29,15 @@ Contents
quickstart
buildoptions
commands
+ apis
+ launcher
distutils
recipes
bootstraps
services
- apis
troubleshooting
- launcher
docker
contribute
- old_toolchain/index.rst
Indices and tables
diff --git a/doc/source/old_toolchain/Screenshot_Kivy_Kompass.png b/doc/source/old_toolchain/Screenshot_Kivy_Kompass.png
deleted file mode 100644
index 828ce41952..0000000000
Binary files a/doc/source/old_toolchain/Screenshot_Kivy_Kompass.png and /dev/null differ
diff --git a/doc/source/old_toolchain/android.rst b/doc/source/old_toolchain/android.rst
deleted file mode 100644
index 5d898749af..0000000000
--- a/doc/source/old_toolchain/android.rst
+++ /dev/null
@@ -1,369 +0,0 @@
-Python API
-==========
-
-The Python for Android project includes a Python module called
-``android`` which consists of multiple parts that are mostly there to
-facilitate the use of the Java API.
-
-This module is not designed to be comprehensive. Most of the Java API
-is also accessible with PyJNIus, so if you can't find what you need
-here you can try using the Java API directly instead.
-
-
-Android (``android``)
----------------------
-
-.. module:: android
-
-.. function:: check_pause()
-
- This should be called on a regular basis to check to see if Android
- expects the application to pause. If it returns true, the app should call
- :func:`android.wait_for_resume()`, after storing its state as necessary.
-
-.. function:: wait_for_resume()
-
- This function should be called after :func:`android.check_pause()` and returns
- true. It does not return until Android has resumed from the pause. While in
- this function, Android may kill the app without further notice.
-
-.. function:: map_key(keycode, keysym)
-
- This maps an android keycode to a python keysym. The android
- keycodes are available as constants in the android module.
-
-
-Activity (``android.activity``)
--------------------------------
-
-.. module:: android.activity
-
-The default PythonActivity has a observer pattern for `onActivityResult `_ and `onNewIntent `_.
-
-.. function:: bind(eventname=callback, ...)
-
- This allows you to bind a callback to an Android event:
- - ``on_new_intent`` is the event associated to the onNewIntent java call
- - ``on_activity_result`` is the event associated to the onActivityResult java call
-
- .. warning::
-
- This method is not thread-safe. Call it in the mainthread of your app. (tips: use kivy.clock.mainthread decorator)
-
-.. function:: unbind(eventname=callback, ...)
-
- Unregister a previously registered callback with :func:`bind`.
-
-Example::
-
- # This example is a snippet from an NFC p2p app implemented with Kivy.
-
- from android import activity
-
- def on_new_intent(self, intent):
- if intent.getAction() != NfcAdapter.ACTION_NDEF_DISCOVERED:
- return
- rawmsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
- if not rawmsgs:
- return
- for message in rawmsgs:
- message = cast(NdefMessage, message)
- payload = message.getRecords()[0].getPayload()
- print 'payload: {}'.format(''.join(map(chr, payload)))
-
- def nfc_enable(self):
- activity.bind(on_new_intent=self.on_new_intent)
- # ...
-
- def nfc_disable(self):
- activity.unbind(on_new_intent=self.on_new_intent)
- # ...
-
-
-Billing (``android.billing``)
------------------------------
-
-.. module:: android.billing
-
-This billing module gives an access to the `In-App Billing `_:
-
-#. `Setup a test account `_, and get your Public Key
-#. Export your public key::
-
- export BILLING_PUBKEY="Your public key here"
-
-#. `Setup some In-App product `_ to buy. Let's say you've created a product with the id "org.kivy.gopremium"
-
-#. In your application, you can use the ``billing`` module like this::
-
-
- from android.billing import BillingService
- from kivy.clock import Clock
-
- class MyBillingService(object):
-
- def __init__(self):
- super(MyBillingService, self).__init__()
-
- # Start the billing service, and attach our callback
- self.service = BillingService(billing_callback)
-
- # Start a clock to check billing service message every second
- Clock.schedule_interval(self.service.check, 1)
-
- def billing_callback(self, action, *largs):
- '''Callback that will receive all the events from the Billing service
- '''
- if action == BillingService.BILLING_ACTION_ITEMSCHANGED:
- items = largs[0]
- if 'org.kivy.gopremium' in items:
- print "Congratulations, you have a premium acess"
- else:
- print "Unfortunately, you don't have premium access"
-
- def buy(self, sku):
- # Method to buy something.
- self.service.buy(sku)
-
- def get_purchased_items(self):
- # Return all the items purchased
- return self.service.get_purchased_items()
-
-#. To initiate an in-app purchase, just call the ``buy()`` method::
-
- # Note: start the service at the start, and never twice!
- bs = MyBillingService()
- bs.buy('org.kivy.gopremium')
-
- # Later, when you get the notification that items have been changed, you
- # can still check all the items you bought:
- print bs.get_purchased_items()
- {'org.kivy.gopremium': {'qt: 1}}
-
-#. You'll receive all the notifications about the billing process in the callback.
-
-#. Last step, create your application with ``--with-billing $BILLING_PUBKEY``::
-
- ./build.py ... --with-billing $BILLING_PUBKEY
-
-
-Broadcast (``android.broadcast``)
----------------------------------
-
-.. module:: android.broadcast
-
-Implementation of the android `BroadcastReceiver
-`_.
-You can specify the callback that will receive the broadcast event, and actions
-or categories filters.
-
-.. class:: BroadcastReceiver
-
- .. warning::
-
- The callback will be called in another thread than the main thread. In
- that thread, be careful not to access OpenGL or something like that.
-
- .. method:: __init__(callback, actions=None, categories=None)
-
- :param callback: function or method that will receive the event. Will
- receive the context and intent as argument.
- :param actions: list of strings that represent an action.
- :param categories: list of strings that represent a category.
-
- For actions and categories, the string must be in lower case, without the prefix::
-
- # In java: Intent.ACTION_HEADSET_PLUG
- # In python: 'headset_plug'
-
- .. method:: start()
-
- Register the receiver with all the actions and categories, and start
- handling events.
-
- .. method:: stop()
-
- Unregister the receiver with all the actions and categories, and stop
- handling events.
-
-Example::
-
- class TestApp(App):
-
- def build(self):
- self.br = BroadcastReceiver(
- self.on_broadcast, actions=['headset_plug'])
- self.br.start()
- # ...
-
- def on_broadcast(self, context, intent):
- extras = intent.getExtras()
- headset_state = bool(extras.get('state'))
- if headset_state:
- print 'The headset is plugged'
- else:
- print 'The headset is unplugged'
-
- # Don't forget to stop and restart the receiver when the app is going
- # to pause / resume mode
-
- def on_pause(self):
- self.br.stop()
- return True
-
- def on_resume(self):
- self.br.start()
-
-
-Mixer (``android.mixer``)
--------------------------
-
-.. module:: android.mixer
-
-The `android.mixer` module contains a subset of the functionality in found
-in the `pygame.mixer `_ module. It's
-intended to be imported as an alternative to pygame.mixer, using code like: ::
-
- try:
- import pygame.mixer as mixer
- except ImportError:
- import android.mixer as mixer
-
-Note that if you're using the `kivy.core.audio
-`_ module, you don't have to do
-anything, it is all automatic.
-
-The `android.mixer` module is a wrapper around the Android MediaPlayer
-class. This allows it to take advantage of any hardware acceleration
-present, and also eliminates the need to ship codecs as part of an
-application.
-
-It has several differences with the pygame mixer:
-
-* The init() and pre_init() methods work, but are ignored - Android chooses
- appropriate settings automatically.
-
-* Only filenames and true file objects can be used - file-like objects
- will probably not work.
-
-* Fadeout does not work - it causes a stop to occur.
-
-* Looping is all or nothing, there is no way to choose the number of
- loops that occur. For looping to work, the
- :func:`android.mixer.periodic` function should be called on a
- regular basis.
-
-* Volume control is ignored.
-
-* End events are not implemented.
-
-* The mixer.music object is a class (with static methods on it),
- rather than a module. Calling methods like :func:`mixer.music.play`
- should work.
-
-
-Runnable (``android.runnable``)
--------------------------------
-
-.. module:: android.runnable
-
-:class:`Runnable` is a wrapper around the Java `Runnable
-`_ class. This
-class can be used to schedule a call of a Python function into the
-`PythonActivity` thread.
-
-Example::
-
- from android.runnable import Runnable
-
- def helloworld(arg):
- print 'Called from PythonActivity with arg:', arg
-
- Runnable(helloworld)('hello')
-
-Or use our decorator::
-
- from android.runnable import run_on_ui_thread
-
- @run_on_ui_thread
- def helloworld(arg):
- print 'Called from PythonActivity with arg:', arg
-
- helloworld('arg1')
-
-
-This can be used to prevent errors like:
-
- - W/System.err( 9514): java.lang.RuntimeException: Can't create handler
- inside thread that has not called Looper.prepare()
- - NullPointerException in ActivityThread.currentActivityThread()
-
-.. warning::
-
- Because the python function is called from the PythonActivity thread, you
- need to be careful about your own calls.
-
-
-
-Service (``android.service``)
------------------------------
-
-Services of an application are controlled through the class :class:`AndroidService`.
-
-.. module:: android.service
-
-.. class:: AndroidService(title, description)
-
- Run ``service/main.py`` from the application directory as a service.
-
- :param title: Notification title, default to 'Python service'
- :param description: Notification text, default to 'Kivy Python service started'
- :type title: str
- :type description: str
-
- .. method:: start(arg)
-
- Start the service.
-
- :param arg: Argument to pass to a service, through the environment variable
- ``PYTHON_SERVICE_ARGUMENT``. Defaults to ''
- :type arg: str
-
- .. method:: stop()
-
- Stop the service.
-
-Application activity part example, ``main.py``:
-
-.. code-block:: python
-
- from android import AndroidService
-
- ...
-
- class ServiceExample(App):
-
- ...
-
- def start_service(self):
- self.service = AndroidService('Sevice example', 'service is running')
- self.service.start('Hello From Service')
-
- def stop_service(self):
- self.service.stop()
-
-Application service part example, ``service/main.py``:
-
-.. code-block:: python
-
- import os
- import time
-
- # get the argument passed
- arg = os.getenv('PYTHON_SERVICE_ARGUMENT')
-
- while True:
- # this will print 'Hello From Service' continually, even when the application is switched
- print arg
- time.sleep(1)
-
diff --git a/doc/source/old_toolchain/conf.py b/doc/source/old_toolchain/conf.py
deleted file mode 100644
index 02498acb27..0000000000
--- a/doc/source/old_toolchain/conf.py
+++ /dev/null
@@ -1,242 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Python for Android documentation build configuration file, created by
-# sphinx-quickstart on Wed Jan 11 02:31:33 2012.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys, os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Python for Android'
-copyright = u'2012/2013, Kivy organisation'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '1.2'
-# The full version, including alpha/beta/rc tags.
-release = '1.2'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = []
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# " v documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'PythonForAndroiddoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'PythonForAndroid.tex', u'Python for Android Documentation',
- u'Mathieu Virbel', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'pythonforandroid', u'Python for Android Documentation',
- [u'Mathieu Virbel'], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output ------------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-# dir menu entry, description, category)
-texinfo_documents = [
- ('index', 'PythonForAndroid', u'Python for Android Documentation',
- u'Mathieu Virbel', 'PythonForAndroid', 'One line description of project.',
- 'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
diff --git a/doc/source/old_toolchain/contribute.rst b/doc/source/old_toolchain/contribute.rst
deleted file mode 100644
index dcc0fbe7c6..0000000000
--- a/doc/source/old_toolchain/contribute.rst
+++ /dev/null
@@ -1,105 +0,0 @@
-Contribute
-==========
-
-Extending Python for android native support
--------------------------------------------
-
-So, you want to get into python-for-android and extend what's available
-to Python on Android ?
-
-Turns out it's not very complicated, here is a little introduction on how to go
-about it. Without Pyjnius, the schema to access the Java API from Cython is::
-
- [1] Cython -> [2] C JNI -> [3] Java
-
-Think about acceleration sensors: you want to get the acceleration
-values in Python, but nothing is available natively. Lukcily you have
-a Java API for that : the Google API is available here
-http://developer.android.com/reference/android/hardware/Sensor.html
-
-You can't use it directly, you need to do your own API to use it in python,
-this is done in 3 steps
-
-Step 1: write the java code to create very simple functions to use
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-like : accelerometer Enable/Reading
-In our project, this is done in the Hardware.java:
-https://github.com/kivy/python-for-android/blob/master/src/src/org/renpy/android/Hardware.java
-you can see how it's implemented
-
-Step 2 : write a jni wrapper
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This is a C file to be able to invoke/call Java functions from C, in our case,
-step 2 (and 3) are done in the android python module. The JNI part is done in
-the android_jni.c:
-https://github.com/kivy/python-for-android/blob/master/recipes/android/src/android_jni.c
-
-Step 3 : you have the java part, that you can call from the C
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can now do the Python extension around it, all the android python part is
-done in
-https://github.com/kivy/python-for-android/blob/master/recipes/android/src/android.pyx
-
-→ [python] android.accelerometer_reading ⇒ [C] android_accelerometer_reading
-⇒ [Java] Hardware.accelerometer_reading()
-
-The jni part is really a C api to call java methods. a little bit hard to get
-it with the syntax, but working with current example should be ok
-
-Example with bluetooth
-~~~~~~~~~~~~~~~~~~~~~~
-Start directly from a fork of https://github.com/kivy/python-for-android
-
-The first step is to identify where and how they are doing it in sl4a, it's
-really easy, because everything is already done as a client/server
-client/consumer approach, for bluetooth, they have a "Bluetooth facade" in
-java.
-
-http://code.google.com/p/android-scripting/source/browse/android/BluetoothFacade/src/com/googlecode/android_scripting/facade/BluetoothFacade.java
-
-You can learn from it, and see how is it's can be used as is, or if you can
-simplify / remove stuff you don't want.
-
-From this point, create a bluetooth file in
-python-for-android/tree/master/src/src/org/renpy/android in Java.
-
-Do a good API (enough simple to be able to write the jni in a very easy manner,
-like, don't pass any custom java object in argument).
-
-Then write the JNI, and then the python part.
-
-3 steps, once you get it, the real difficult part is to write the java part :)
-
-Jni gottchas
-~~~~~~~~~~~~
-
-- package must be org.renpy.android, don't change it.
-
-
-Create your own recipes
------------------------
-
-A recipe is a script that contains the "definition" of a module to compile.
-The directory layout of a recipe for a is something like::
-
- python-for-android/recipes//recipe.sh
- python-for-android/recipes//patches/
- python-for-android/recipes//patches/fix-path.patch
-
-When building, all the recipe builds must go to::
-
- python-for-android/build//
-
-For example, if you want to create a recipe for sdl, do::
-
- cd python-for-android/recipes
- mkdir sdl
- cp recipe.sh.tmpl sdl/recipe.sh
- sed -i 's#XXX#sdl#' sdl/recipe.sh
-
-Then, edit the sdl/recipe.sh to adjust other information (version, url) and
-complete the build function.
-
diff --git a/doc/source/old_toolchain/customize.rst b/doc/source/old_toolchain/customize.rst
deleted file mode 100644
index 5b9d954d27..0000000000
--- a/doc/source/old_toolchain/customize.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-Customize your distribution
----------------------------
-
-The basic layout of a distribution is::
-
- AndroidManifest.xml - (*) android manifest (generated from templates)
- assets/
- private.mp3 - (*) fake package that will contain all the python installation
- public.mp3 - (*) fake package that will contain your application
- bin/ - contain all the apk generated from build.py
- blacklist.txt - list of file patterns to not include in the APK
- buildlib/ - internals libraries for build.py
- build.py - build script to use for packaging your application
- build.xml - (*) build settings (generated from templates)
- default.properties - settings generated from your distribute.sh
- libs/ - contain all the compiled libraries
- local.properties - settings generated from your distribute.sh
- private/ - private directory containing all the python files
- lib/ this is where you can remove or add python libs.
- python2.7/ by default, some modules are already removed (tests, idlelib, ...)
- project.properties - settings generated from your distribute.sh
- python-install/ - the whole python installation, generated from distribute.sh
- not included in the final package.
- res/ - (*) android resource (generated from build.py)
- src/ - Java bootstrap
- templates/ - Templates used by build.py
-
- (*): Theses files are automatically generated from build.py, don't change them directly !
-
-
diff --git a/doc/source/old_toolchain/example_compass.rst b/doc/source/old_toolchain/example_compass.rst
deleted file mode 100644
index bff430eafe..0000000000
--- a/doc/source/old_toolchain/example_compass.rst
+++ /dev/null
@@ -1,61 +0,0 @@
-Compass
--------
-
-The following example is an extract from the Compass app as provided in the Kivy
-`examples/android/compass `__
-folder:
-
-.. code-block:: python
-
- # ... imports
- Hardware = autoclass('org.renpy.android.Hardware')
-
- class CompassApp(App):
-
- needle_angle = NumericProperty(0)
-
- def build(self):
- self._anim = None
- Hardware.magneticFieldSensorEnable(True)
- Clock.schedule_interval(self.update_compass, 1 / 10.)
-
- def update_compass(self, *args):
- # read the magnetic sensor from the Hardware class
- (x, y, z) = Hardware.magneticFieldSensorReading()
-
- # calculate the angle
- needle_angle = Vector(x , y).angle((0, 1)) + 90.
-
- # animate the needle
- if self._anim:
- self._anim.stop(self)
- self._anim = Animation(needle_angle=needle_angle, d=.2, t='out_quad')
- self._anim.start(self)
-
- def on_pause(self):
- # when you are going on pause, don't forget to stop the sensor
- Hardware.magneticFieldSensorEnable(False)
- return True
-
- def on_resume(self):
- # reactivate the sensor when you are back to the app
- Hardware.magneticFieldSensorEnable(True)
-
- if __name__ == '__main__':
- CompassApp().run()
-
-
-If you compile this app, you will get an APK which outputs the following
-screen:
-
-.. figure:: Screenshot_Kivy_Kompass.png
- :width: 100%
- :scale: 60%
- :figwidth: 80%
- :alt: Screenshot Kivy Compass
-
- Screenshot of the Kivy Compass App
- (Source of the Compass Windrose: `Wikipedia `__)
-
-
-
diff --git a/doc/source/old_toolchain/example_helloworld.rst b/doc/source/old_toolchain/example_helloworld.rst
deleted file mode 100644
index dab760ad4b..0000000000
--- a/doc/source/old_toolchain/example_helloworld.rst
+++ /dev/null
@@ -1,96 +0,0 @@
-Hello world
------------
-
-If you don't know how to start with Python for Android, here is a simple
-tutorial for creating an UI using `Kivy `_, and make an APK
-with this project.
-
-.. note::
-
- Don't forget that Python for Android is not Kivy only, and you
- might want to use other toolkit libraries. When other toolkits
- will be available, this documentation will be enhanced.
-
-Let's create a simple Hello world application, with one Label and one Button.
-
-#. Ensure you've correctly installed and configured the project as said in the
- :doc:`prerequisites`
-
-#. Create a directory named ``helloworld``::
-
- mkdir helloworld
- cd helloworld
-
-#. Create a file named ``main.py``, with this content::
-
- import kivy
- kivy.require('1.0.9')
- from kivy.lang import Builder
- from kivy.uix.gridlayout import GridLayout
- from kivy.properties import NumericProperty
- from kivy.app import App
-
- Builder.load_string('''
- :
- cols: 1
- Label:
- text: 'Welcome to the Hello world'
- Button:
- text: 'Click me! %d' % root.counter
- on_release: root.my_callback()
- ''')
-
- class HelloWorldScreen(GridLayout):
- counter = NumericProperty(0)
- def my_callback(self):
- print 'The button has been pushed'
- self.counter += 1
-
- class HelloWorldApp(App):
- def build(self):
- return HelloWorldScreen()
-
- if __name__ == '__main__':
- HelloWorldApp().run()
-
-#. Go to the ``python-for-android`` directory
-
-#. Create a distribution with kivy::
-
- ./distribute.sh -m kivy
-
-#. Go to the newly created ``default`` distribution::
-
- cd dist/default
-
-#. Plug your android device, and ensure you can install development
- application
-
-#. Build your hello world application in debug mode::
-
- ./build.py --package org.hello.world --name "Hello world" \
- --version 1.0 --dir /PATH/TO/helloworld debug installd
-
-#. Take your device, and start the application!
-
-#. If something goes wrong, open the logcat by doing::
-
- adb logcat
-
-The final debug APK will be located in ``bin/hello-world-1.0-debug.apk``.
-
-If you want to release your application instead of just making a debug APK, you must:
-
-#. Generate a non-signed APK::
-
- ./build.py --package org.hello.world --name "Hello world" \
- --version 1.0 --dir /PATH/TO/helloworld release
-
-#. Continue by reading http://developer.android.com/guide/publishing/app-signing.html
-
-
-.. seealso::
-
- `Kivy demos `_
- You can use them for creating APK too.
-
diff --git a/doc/source/old_toolchain/examples.rst b/doc/source/old_toolchain/examples.rst
deleted file mode 100644
index 4d7408fa0c..0000000000
--- a/doc/source/old_toolchain/examples.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-Examples
-========
-
-Prebuilt VirtualBox
--------------------
-
-A good starting point to build an APK are prebuilt VirtualBox images,
-where the Android NDK, the Android SDK, and the Kivy
-Python-For-Android sources are prebuilt in an VirtualBox image. Please
-search the `Download Section `__ for such
-an image. You will also need to create a device filter for the Android
-USB device using the VirtualBox OS settings.
-
-.. include:: example_helloworld.rst
-.. include:: example_compass.rst
-
-.. toctree::
- :hidden:
-
- example_helloworld
- example_compass
diff --git a/doc/source/old_toolchain/faq.rst b/doc/source/old_toolchain/faq.rst
deleted file mode 100644
index 22ffe11512..0000000000
--- a/doc/source/old_toolchain/faq.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-FAQ
-===
-
-arm-linux-androideabi-gcc: Internal error: Killed (program cc1)
----------------------------------------------------------------
-
-This could happen if you are not using a validated SDK/NDK with Python for
-Android. Go to :doc:`prerequisites` to see which one are working.
-
-_sqlite3.so not found
----------------------
-
-We recently fixed sqlite3 compilation. In case of this error, you
-must:
-
-* Install development headers for sqlite3 if they are not already
- installed. On Ubuntu:
-
- apt-get install libsqlite3-dev
-
-* Compile the distribution with (sqlite3 must be the first argument)::
-
- ./distribute.sh -m 'sqlite3 kivy'
-
-* Go into your distribution at `dist/default`
-* Edit blacklist.txt, and remove all the lines concerning sqlite3::
-
- sqlite3/*
- lib-dynload/_sqlite3.so
-
-Then sqlite3 will be compiled and included in your APK.
-
-Too many levels of symbolic links
------------------------------------------------------
-
-Python for Android does not work within a virtual enviroment. The Python for
-Android directory must be outside of the virtual enviroment prior to running
-
- ./distribute.sh -m "kivy"
-
-or else you may encounter OSError: [Errno 40] Too many levels of symbolic links.
\ No newline at end of file
diff --git a/doc/source/old_toolchain/index.rst b/doc/source/old_toolchain/index.rst
deleted file mode 100644
index 1b873be657..0000000000
--- a/doc/source/old_toolchain/index.rst
+++ /dev/null
@@ -1,38 +0,0 @@
-
-Old p4a toolchain doc
-=====================
-
-This is the documentation for the old python-for-android toolchain,
-using distribute.sh and build.py. This it entirely superseded by the
-new toolchain, you do not need to read it unless using this old
-method.
-
-.. warning:: The old toolchain is deprecated and no longer
- supported. You should instead use the :doc:`current version
- <../quickstart>`.
-
-Python for android is a project to create your own Python distribution
-including the modules you want, and create an apk including python, libs, and
-your application.
-
-- Forum: https://groups.google.com/forum/#!forum/python-android
-- Mailing list: python-android@googlegroups.com
-
-.. toctree::
- :maxdepth: 2
-
- toolchain.rst
- examples.rst
- android.rst
- javaapi.rst
- contribute.rst
- related.rst
- faq.rst
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
diff --git a/doc/source/old_toolchain/javaapi.rst b/doc/source/old_toolchain/javaapi.rst
deleted file mode 100644
index 73fc888f8e..0000000000
--- a/doc/source/old_toolchain/javaapi.rst
+++ /dev/null
@@ -1,239 +0,0 @@
-Java API (pyjnius)
-==================
-
-Using `PyJNIus `__ to access the Android API
-restricts the usage to a simple call of the **autoclass** constructor function
-and a second call to instantiate this class.
-
-You can access through this method the entire Java Android API, e.g.,
-the ``DisplayMetrics`` of an Android device could be fetched using the
-following piece of code:
-
-.. code-block:: python
-
- DisplayMetrics = autoclass('android.util.DisplayMetrics')
- metrics = DisplayMetrics()
- metrics.setToDefaults()
- self.densityDpi = metrics.densityDpi
-
-You can access all fields and methods as described in the `Java
-Android DisplayMetrics API
-`__
-as shown here with the method `setToDefaults()` and the field
-`densityDpi`. Before you use a view field, you should always call
-`setToDefaults` to initiate to the default values of the device.
-
-Currently only JavaMethod, JavaStaticMethod, JavaField,
-JavaStaticField and JavaMultipleMethod are built into PyJNIus,
-therefore such constructs like registerListener or something like this
-must still be coded in Java. For this the Android module described
-below is available to access some of the hardware on Android devices.
-
-.. module:: org.renpy.android
-
-
-Activity
---------
-
-If you want the instance of the current Activity, use:
-
-- :data:`PythonActivity.mActivity` if you are running an application
-- :data:`PythonService.mService` if you are running a service
-
-.. class:: PythonActivity
-
- .. data:: mInfo
-
- Instance of an `ApplicationInfo
- `_
-
- .. data:: mActivity
-
- Instance of :class:`PythonActivity`.
-
- .. method:: registerNewIntentListener(NewIntentListener listener)
-
- Register a new instance of :class:`NewIntentListener` to be called when
- `onNewIntent
- `_
- is called.
-
- .. method:: unregisterNewIntentListener(NewIntentListener listener)
-
- Unregister a previously registered listener from
- :meth:`registerNewIntentListener`
-
- .. method:: registerActivityResultListener(ActivityResultListener listener)
-
- Register a new instance of :class:`ActivityResultListener` to be called
- when `onActivityResult
- `_ is called.
-
- .. method:: unregisterActivityResultListener(ActivityResultListener listener)
-
- Unregister a previously registered listener from
- :meth:`PythonActivity.registerActivityResultListener`
-
-.. class:: PythonActivity_ActivityResultListener
-
- .. note::
-
- This class is a subclass of PythonActivity, so the notation will be
- ``PythonActivity$ActivityResultListener``
-
- Listener interface for onActivityResult. You need to implementing it,
- create an instance and use it with :meth:`PythonActivity.registerActivityResultListener`.
-
- .. method:: onActivityResult(int requestCode, int resultCode, Intent data)
-
- Method to implement
-
-.. class:: PythonActivity_NewIntentListener
-
- .. note::
-
- This class is a subclass of PythonActivity, so the notation will be
- ``PythonActivity$NewIntentListener``
-
- Listener interface for onNewIntent. You need to implementing it, create
- an instance and use it with :meth:`registerNewIntentListener`.
-
- .. method:: onNewIntent(Intent intent)
-
- Method to implement
-
-
-Action
-------
-
-.. class:: Action
-
- This module is built to deliver data to someone else.
-
- .. method:: send(mimetype, filename, subject, text, chooser_title)
-
- Deliver data to someone else. This method is a wrapper around `ACTION_SEND
- `_
-
- :Parameters:
- `mimetype`: str
- Must be a valid mimetype, that represent the content to sent.
- `filename`: str, default to None
- (optional) Name of the file to attach. Must be a absolute path.
- `subject`: str, default to None
- (optional) Default subject
- `text`: str, default to None
- (optional) Content to send.
- `chooser_title`: str, default to None
- (optional) Title of the android chooser window, default to 'Send email...'
-
- Sending a simple hello world text::
-
- android.action_send('text/plain', text='Hello world',
- subject='Test from python')
-
- Sharing an image file::
-
- # let's say you've make an image in /sdcard/image.png
- android.action_send('image/png', filename='/sdcard/image.png')
-
- Sharing an image with a default text too::
-
- android.action_send('image/png', filename='/sdcard/image.png',
- text='Hi,\n\tThis is my awesome image, what do you think about it ?')
-
-
-Hardware
---------
-
-.. class:: Hardware
-
- This module is built for accessing hardware devices of an Android device.
- All the methods are static and public, you don't need an instance.
-
-
- .. method:: vibrate(s)
-
- Causes the phone to vibrate for `s` seconds. This requires that your
- application have the VIBRATE permission.
-
-
- .. method:: getHardwareSensors()
-
- Returns a string of all hardware sensors of an Android device where each
- line lists the informations about one sensor in the following format:
-
- Name=name,Vendor=vendor,Version=version,MaximumRange=maximumRange,MinDelay=minDelay,Power=power,Type=type
-
- For more information about this informations look into the original Java
- API for the `Sensors Class
- `__
-
- .. attribute:: accelerometerSensor
-
- This variable links to a generic3AxisSensor instance and their functions to
- access the accelerometer sensor
-
- .. attribute:: orientationSensor
-
- This variable links to a generic3AxisSensor instance and their functions to
- access the orientation sensor
-
- .. attribute:: magenticFieldSensor
-
-
- The following two instance methods of the generic3AxisSensor class should be
- used to enable/disable the sensor and to read the sensor
-
-
- .. method:: changeStatus(boolean enable)
-
- Changes the status of the sensor, the status of the sensor is enabled,
- if `enable` is true or disabled, if `enable` is false.
-
- .. method:: readSensor()
-
- Returns an (x, y, z) tuple of floats that gives the sensor reading, the
- units depend on the sensor as shown on the Java API page for
- `SensorEvent
- `_.
- The sesnor must be enabled before this function is called. If the tuple
- contains three zero values, the accelerometer is not enabled, not
- available, defective, has not returned a reading, or the device is in
- free-fall.
-
- .. method:: get_dpi()
-
- Returns the screen density in dots per inch.
-
- .. method:: show_keyboard()
-
- Shows the soft keyboard.
-
- .. method:: hide_keyboard()
-
- Hides the soft keyboard.
-
- .. method:: wifi_scanner_enable()
-
- Enables wifi scanning.
-
- .. note::
-
- ACCESS_WIFI_STATE and CHANGE_WIFI_STATE permissions are required.
-
- .. method:: wifi_scan()
-
- Returns a String for each visible WiFi access point
-
- (SSID, BSSID, SignalLevel)
-
-Further Modules
-~~~~~~~~~~~~~~~
-
-Some further modules are currently available but not yet documented. Please
-have a look into the code and you are very welcome to contribute to this
-documentation.
-
-
diff --git a/doc/source/old_toolchain/prerequisites.rst b/doc/source/old_toolchain/prerequisites.rst
deleted file mode 100644
index eb5cd6dcd0..0000000000
--- a/doc/source/old_toolchain/prerequisites.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-Prerequisites
--------------
-
-.. note:: There is a VirtualBox Image we provide with the
- prerequisites along with the Android SDK and NDK preinstalled to
- ease your installation woes. You can download it from `here
- `__.
-
-.. warning::
-
- The current version is tested only on Ubuntu oneiric (11.10) and
- precise (12.04). If it doesn't work on other platforms, send us a
- patch, not a bug report. Python for Android works on Linux and Mac
- OS X, not Windows.
-
-You need the minimal environment for building python. Note that other
-libraries might need other tools (cython is used by some recipes, and
-ccache to speedup the build)::
-
- sudo apt-get install build-essential patch git-core ccache ant python-pip python-dev
-
-If you are on a 64 bit distro, you should install these packages too ::
-
- sudo apt-get install ia32-libs libc6-dev-i386
-
-On debian Squeeze amd64, those packages were found to be necessary ::
-
- sudo apt-get install lib32stdc++6 lib32z1
-
-Ensure you have the latest Cython version::
-
- pip install --upgrade cython
-
-You must have android SDK and NDK. The SDK defines the Android
-functions you can use. The NDK is used for compilation. Right now,
-it's preferred to use:
-
-- SDK API 8 or 14 (15 will only work with a newly released NDK)
-- NDK r5b or r7
-
-You can download them at::
-
- http://developer.android.com/sdk/index.html
- http://developer.android.com/sdk/ndk/index.html
-
-
-In general, Python for Android currently works with Android 2.3 to L.
-
-If it's your very first time using the Android SDK, don't forget to
-follow the documentation for recommended components at::
-
- http://developer.android.com/sdk/installing/adding-packages.html
-
- You need to download at least one platform into your environment, so
- that you will be able to compile your application and set up an Android
- Virtual Device (AVD) to run it on (in the emulator). To start with,
- just download the latest version of the platform. Later, if you plan to
- publish your application, you will want to download other platforms as
- well, so that you can test your application on the full range of
- Android platform versions that your application supports.
-
-After installing them, export both installation paths, NDK version,
-and API to use::
-
- export ANDROIDSDK=/path/to/android-sdk
- export ANDROIDNDK=/path/to/android-ndk
- export ANDROIDNDKVER=rX
- export ANDROIDAPI=X
-
- # example
- export ANDROIDSDK="/home/tito/code/android/android-sdk-linux_86"
- export ANDROIDNDK="/home/tito/code/android/android-ndk-r7"
- export ANDROIDNDKVER=r7
- export ANDROIDAPI=14
-
-Also, you must configure your PATH to add the ``android`` binary::
-
- export PATH=$ANDROIDNDK:$ANDROIDSDK/platform-tools:$ANDROIDSDK/tools:$PATH
-
diff --git a/doc/source/old_toolchain/related.rst b/doc/source/old_toolchain/related.rst
deleted file mode 100644
index ea694f619c..0000000000
--- a/doc/source/old_toolchain/related.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Related projects
-================
-
-- PGS4A: http://pygame.renpy.org/ (thanks to Renpy to make it possible)
-- Android scripting: http://code.google.com/p/android-scripting/
-- Python on a chip: http://code.google.com/p/python-on-a-chip/
-
diff --git a/doc/source/old_toolchain/toolchain.rst b/doc/source/old_toolchain/toolchain.rst
deleted file mode 100644
index b301a5a9f5..0000000000
--- a/doc/source/old_toolchain/toolchain.rst
+++ /dev/null
@@ -1,66 +0,0 @@
-Toolchain
-=========
-
-Introduction
-------------
-
-In terms of comparaison, you can check how Python for android can be useful
-compared to other projects.
-
-+--------------------+---------------+---------------+----------------+--------------+
-| Project | Native Python | GUI libraries | APK generation | Custom build |
-+====================+===============+===============+================+==============+
-| Python for android | Yes | Yes | Yes | Yes |
-+--------------------+---------------+---------------+----------------+--------------+
-| PGS4A | Yes | Yes | Yes | No |
-+--------------------+---------------+---------------+----------------+--------------+
-| Android scripting | No | No | No | No |
-+--------------------+---------------+---------------+----------------+--------------+
-| Python on a chip | No | No | No | No |
-+--------------------+---------------+---------------+----------------+--------------+
-
-.. note::
-
- For the moment, we are shipping only one "java bootstrap" (needed for
- decompressing your packaged zip file project, create an OpenGL ES 2.0
- surface, handle touch input and manage an audio thread).
-
- If you want to use it without kivy module (an opengl es 2.0 ui toolkit),
- then you might want a lighter java bootstrap, that we don't have right now.
- Help is welcome :)
-
- So for the moment, Python for Android can only be used with the kivy GUI toolkit:
- http://kivy.org/#home
-
-
-How does it work ?
-------------------
-
-To be able to run Python on android, you need to compile it for android. And
-you need to compile all the libraries you want for android too.
-Since Python is a language, not a toolkit, you cannot draw any user interface
-with it: you need to use a toolkit for it. Kivy can be one of them.
-
-So for a simple ui project, the first step is to compile Python + Kivy + all
-others libraries. Then you'll have what we call a "distribution".
-A distribution is composed of:
-
-- Python
-- Python libraries
-- All selected libraries (kivy, pygame, pil...)
-- A java bootstrap
-- A build script
-
-You'll use the build script for create an "apk": an android package.
-
-
-.. include:: prerequisites.rst
-.. include:: usage.rst
-.. include:: customize.rst
-
-.. toctree::
- :hidden:
-
- prerequisites
- usage
- customize
diff --git a/doc/source/old_toolchain/usage.rst b/doc/source/old_toolchain/usage.rst
deleted file mode 100644
index 7d83b23888..0000000000
--- a/doc/source/old_toolchain/usage.rst
+++ /dev/null
@@ -1,167 +0,0 @@
-Usage
------
-
-Step 1: compile the toolchain
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you want to compile the toolchain with only the kivy module::
-
- ./distribute.sh -m "kivy"
-
-.. warning::
- Do not run the above command from `within a virtual enviroment <../faq/#too-many-levels-of-symbolic-links>`_.
-
-After a long time, you'll get a "dist/default" directory containing
-all the compiled libraries and a build.py script to package your
-application using thoses libraries.
-
-You can include other modules (or "recipes") to compile using `-m`::
-
- ./distribute.sh -m "openssl kivy"
- ./distribute.sh -m "pil ffmpeg kivy"
-
-.. note::
-
- Recipes are instructions for compiling Python modules that require C extensions.
- The list of recipes we currently have is at:
- https://github.com/kivy/python-for-android/tree/master/recipes
-
-You can also specify a specific version for each package. Please note
-that the compilation might **break** if you don't use the default
-version. Most recipes have patches to fix Android issues, and might
-not apply if you specify a version. We also recommend to clean build
-before changing version.::
-
- ./distribute.sh -m "openssl kivy==master"
-
-Python modules that don't need C extensions don't need a recipe and
-can be included this way. From python-for-android 1.1 on, you can now
-specify pure-python package into the distribution. It will use
-virtualenv and pip to install pure-python modules into the
-distribution. Please note that the compiler is deactivated, and will
-break any module which tries to compile something. If compilation is
-needed, write a recipe::
-
- ./distribute.sh -m "requests pygments kivy"
-
-.. note::
-
- Recipes download a defined version of their needed package from the
- internet, and build from it. If you know what you are doing, and
- want to override that, you can export the env variable
- `P4A_recipe_name_DIR` and this directory will be copied and used
- instead.
-
-Available options to `distribute.sh`::
-
- -d directory Name of the distribution directory
- -h Show this help
- -l Show a list of available modules
- -m 'mod1 mod2' Modules to include
- -f Restart from scratch (remove the current build)
- -u 'mod1 mod2' Modules to update (if already compiled)
-
-Step 2: package your application
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Go to your custom Python distribution::
-
- cd dist/default
-
-Use the build.py for creating the APK::
-
- ./build.py --package org.test.touchtracer --name touchtracer \
- --version 1.0 --dir ~/code/kivy/examples/demo/touchtracer debug
-
-Then, the Android package (APK) will be generated at:
-
- bin/touchtracer-1.0-debug.apk
-
-.. warning::
-
- Some files and modules for python are blacklisted by default to
- save a few megabytes on the final APK file. In case your
- applications doesn't find a standard python module, check the
- src/blacklist.txt file, remove the module you need from the list,
- and try again.
-
-Available options to `build.py`::
-
- -h, --help show this help message and exit
- --package PACKAGE The name of the java package the project will be
- packaged under.
- --name NAME The human-readable name of the project.
- --version VERSION The version number of the project. This should consist
- of numbers and dots, and should have the same number
- of groups of numbers as previous versions.
- --numeric-version NUMERIC_VERSION
- The numeric version number of the project. If not
- given, this is automatically computed from the
- version.
- --dir DIR The directory containing public files for the project.
- --private PRIVATE The directory containing additional private files for
- the project.
- --launcher Provide this argument to build a multi-app launcher,
- rather than a single app.
- --icon-name ICON_NAME
- The name of the project's launcher icon.
- --orientation ORIENTATION
- The orientation that the game will display in. Usually
- one of "landscape", "portrait" or "sensor".
- --permission PERMISSIONS
- The permissions to give this app.
- --ignore-path IGNORE_PATH
- Ignore path when building the app
- --icon ICON A png file to use as the icon for the application.
- --presplash PRESPLASH
- A jpeg file to use as a screen while the application
- is loading.
- --install-location INSTALL_LOCATION
- The default install location. Should be "auto",
- "preferExternal" or "internalOnly".
- --compile-pyo Compile all .py files to .pyo, and only distribute the
- compiled bytecode.
- --intent-filters INTENT_FILTERS
- Add intent-filters xml rules to AndroidManifest.xml
- --blacklist BLACKLIST
- Use a blacklist file to match unwanted file in the
- final APK
- --sdk SDK_VERSION Android SDK version to use. Default to 8
- --minsdk MIN_SDK_VERSION
- Minimum Android SDK version to use. Default to 8
- --window Indicate if the application will be windowed
-
-Meta-data
----------
-
-.. versionadded:: 1.3
-
-You can extend the `AndroidManifest.xml` with application meta-data. If you are
-using external toolkits like Google Maps, you might want to set your API key in
-the meta-data. You could do it like this::
-
- ./build.py ... --meta-data com.google.android.maps.v2.API_KEY=YOURAPIKEY
-
-Some meta-data can be used to interact with the behavior of our internal
-component.
-
-.. list-table::
- :widths: 100 500
- :header-rows: 1
-
- * - Token
- - Description
- * - `surface.transparent`
- - If set to 1, the created surface will be transparent (can be used
- to add background Android widget in the background, or use accelerated
- widgets)
- * - `surface.depth`
- - Size of the depth component, default to 0. 0 means automatic, but you
- can force it to a specific value. Be warned, some old phone might not
- support the depth you want.
- * - `surface.stencil`
- - Size of the stencil component, default to 8.
- * - `android.background_color`
- - Color (32bits RGBA color), used for the background window. Usually, the
- background is covered by the OpenGL Background, unless
- `surface.transparent` is set.
diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst
index 3797ffacec..967d6ed755 100644
--- a/doc/source/quickstart.rst
+++ b/doc/source/quickstart.rst
@@ -55,7 +55,7 @@ p4a has several dependencies that must be installed:
- ant
- python2
- cython (can be installed via pip)
-- a Java JDK (e.g. openjdk-7)
+- a Java JDK (e.g. openjdk-8)
- zlib (including 32 bit)
- libncurses (including 32 bit)
- unzip
@@ -70,7 +70,7 @@ install most of these with::
sudo dpkg --add-architecture i386
sudo apt-get update
- sudo apt-get install -y build-essential ccache git zlib1g-dev python2.7 python2.7-dev libncurses5:i386 libstdc++6:i386 zlib1g:i386 openjdk-7-jdk unzip ant ccache autoconf libtool
+ sudo apt-get install -y build-essential ccache git zlib1g-dev python2.7 python2.7-dev libncurses5:i386 libstdc++6:i386 zlib1g:i386 openjdk-8-jdk unzip ant ccache autoconf libtool
On Arch Linux (64 bit) you should be able to run the following to
install most of the dependencies (note: this list may not be
@@ -115,8 +115,10 @@ the latest usable NDK version is r10e, which can be downloaded here:
release with the legacy version of python is version
`0.6.0 `_.
-First, install a platform to target (you can also replace ``27`` with
-a different platform number, this will be used again later)::
+First, install an API platform to target. You can replace ``27`` with
+a different platform number, but keep in mind **other API versions
+are less well-tested**, and older devices are still supported
+(down to the specified *minimum* API/NDK API level):
$SDK_DIR/tools/bin/sdkmanager "platforms;android-27"
@@ -147,18 +149,34 @@ You have the possibility to configure on any command the PATH to the SDK, NDK an
Usage
-----
-Build a Kivy application
-~~~~~~~~~~~~~~~~~~~~~~~~
+Build a Kivy or SDL2 application
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To build your application, you need to have a name, version, a package
-identifier, and explicitly write the bootstrap you want to use, as
-well as the requirements::
+To build your application, you need to specify name, version, a package
+identifier, the bootstrap you want to use (`sdl2` for kivy or sdl2 apps)
+and the requirements::
+
+ p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python3,kivy
- p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python2,kivy
+**Note on `--requirements`: you must add all
+libraries/dependencies your app needs to run.**
+Example: `--requirements=python3,kivy,vispy`. For an SDL2 app,
+`kivy` is not needed, but you need to add any wrappers you might
+use (e.g. `pysdl2`).
-This will first build a distribution that contains `python2` and `kivy`, and using a SDL2 bootstrap. Python2 is here explicitely written as kivy can work with python2 or python3.
+This `p4a apk ...` command builds a distribution with `python3`,
+`kivy`, and everything else you specified in the requirements.
+It will be packaged using a SDL2 bootstrap, and produce
+an `.apk` file.
-You can also use ``--bootstrap=pygame``, but this bootstrap is deprecated for use with Kivy and SDL2 is preferred.
+*Compatibility notes:*
+
+- While python2 is still supported by python-for-android,
+ it will possibly no longer receive patches by the python creators
+ themselves in 2020. Migration to Python 3 is recommended!
+
+- You can also use ``--bootstrap=pygame``, but this bootstrap
+ is deprecated and not well-tested.
Build a WebView application
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -169,26 +187,14 @@ well as the requirements::
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My WebView Application" --version 0.1 --bootstrap=webview --requirements=flask --port=5000
+**Please note as with kivy/SDL2, you need to specify all your
+additional requirements/depenencies.**
+
You can also replace flask with another web framework.
Replace ``--port=5000`` with the port on which your app will serve a
website. The default for Flask is 5000.
-Build an SDL2 based application
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This includes e.g. `PySDL2
-`__.
-
-To build your application, you need to have a name, version, a package
-identifier, and explicitly write the sdl2 bootstrap, as well as the
-requirements::
-
- p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My SDL2 application" --version 0.1 --bootstrap=sdl2 --requirements=your_requirements
-
-Add your required modules in place of ``your_requirements``,
-e.g. ``--requirements=pysdl2`` or ``--requirements=vispy``.
-
Other options
~~~~~~~~~~~~~
@@ -196,7 +202,7 @@ You can pass other command line arguments to control app behaviours
such as orientation, wakelock and app permissions. See
:ref:`bootstrap_build_options`.
-
+
Rebuild everything
~~~~~~~~~~~~~~~~~~
@@ -204,11 +210,11 @@ Rebuild everything
If anything goes wrong and you want to clean the downloads and builds to retry everything, run::
p4a clean_all
-
+
If you just want to clean the builds to avoid redownloading dependencies, run::
p4a clean_builds && p4a clean_dists
-
+
Getting help
~~~~~~~~~~~~
@@ -267,7 +273,7 @@ You can list the available distributions::
And clean all of them::
p4a clean_dists
-
+
Configuration file
~~~~~~~~~~~~~~~~~~
@@ -280,6 +286,17 @@ include such as::
--android_api 27
--requirements kivy,openssl
+Overriding recipes sources
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can override the source of any recipe using the
+``$P4A_recipename_DIR`` environment variable. For instance, to test
+your own Kivy branch you might set::
+
+ export P4A_kivy_DIR=/home/username/kivy
+
+The specified directory will be copied into python-for-android instead
+of downloading from the normal url specified in the recipe.
Going further
~~~~~~~~~~~~~
diff --git a/pythonforandroid/__init__.py b/pythonforandroid/__init__.py
index 1f199f195d..3699b0d245 100644
--- a/pythonforandroid/__init__.py
+++ b/pythonforandroid/__init__.py
@@ -1,2 +1,2 @@
-__version__ = '0.6.0'
+__version__ = '0.7.1'
diff --git a/pythonforandroid/archs.py b/pythonforandroid/archs.py
index 8e897dbc6e..ec5a6fb3d5 100644
--- a/pythonforandroid/archs.py
+++ b/pythonforandroid/archs.py
@@ -1,11 +1,10 @@
+from distutils.spawn import find_executable
+from os import environ
from os.path import (exists, join, dirname, split)
-from os import environ, uname
from glob import glob
-import sys
-from distutils.spawn import find_executable
from pythonforandroid.recipe import Recipe
-from pythonforandroid.util import BuildInterruptingException
+from pythonforandroid.util import BuildInterruptingException, build_platform
class Arch(object):
@@ -20,6 +19,12 @@ def __init__(self, ctx):
super(Arch, self).__init__()
self.ctx = ctx
+ # Allows injecting additional linker paths used by any recipe.
+ # This can also be modified by recipes (like the librt recipe)
+ # to make sure that some sort of global resource is available &
+ # linked for all others.
+ self.extra_global_link_paths = []
+
def __str__(self):
return self.arch
@@ -51,11 +56,18 @@ def get_env(self, with_flags_in_cc=True, clang=False):
toolchain = '{android_host}-{toolchain_version}'.format(
android_host=self.ctx.toolchain_prefix,
toolchain_version=self.ctx.toolchain_version)
- toolchain = join(self.ctx.ndk_dir, 'toolchains', toolchain, 'prebuilt', 'linux-x86_64')
+ toolchain = join(self.ctx.ndk_dir, 'toolchains', toolchain,
+ 'prebuilt', build_platform)
cflags.append('-gcc-toolchain {}'.format(toolchain))
env['CFLAGS'] = ' '.join(cflags)
- env['LDFLAGS'] = ' '
+
+ # Link the extra global link paths first before anything else
+ # (such that overriding system libraries with them is possible)
+ env['LDFLAGS'] = ' ' + " ".join([
+ "-L'" + l.replace("'", "'\"'\"'") + "'" # no shlex.quote in py2
+ for l in self.extra_global_link_paths
+ ]) + ' '
sysroot = join(self.ctx._ndk_dir, 'sysroot')
if exists(sysroot):
@@ -83,10 +95,6 @@ def get_env(self, with_flags_in_cc=True, clang=False):
if self.ctx.ndk == 'crystax':
env['LDFLAGS'] += ' -L{}/sources/crystax/libs/{} -lcrystax'.format(self.ctx.ndk_dir, self.arch)
- py_platform = sys.platform
- if py_platform in ['linux2', 'linux3']:
- py_platform = 'linux'
-
toolchain_prefix = self.ctx.toolchain_prefix
toolchain_version = self.ctx.toolchain_version
command_prefix = self.command_prefix
@@ -106,7 +114,7 @@ def get_env(self, with_flags_in_cc=True, clang=False):
llvm_dirname = split(
glob(join(self.ctx.ndk_dir, 'toolchains', 'llvm*'))[-1])[-1]
clang_path = join(self.ctx.ndk_dir, 'toolchains', llvm_dirname,
- 'prebuilt', 'linux-x86_64', 'bin')
+ 'prebuilt', build_platform, 'bin')
environ['PATH'] = '{clang_path}:{path}'.format(
clang_path=clang_path, path=environ['PATH'])
exe = join(clang_path, 'clang')
@@ -159,8 +167,8 @@ def get_env(self, with_flags_in_cc=True, clang=False):
'host' + self.ctx.python_recipe.name, self.ctx)
env['BUILDLIB_PATH'] = join(
hostpython_recipe.get_build_dir(self.arch),
- 'build', 'lib.linux-{}-{}'.format(
- uname()[-1], self.ctx.python_recipe.major_minor_version_string)
+ 'build', 'lib.{}-{}'.format(
+ build_platform, self.ctx.python_recipe.major_minor_version_string)
)
env['PATH'] = environ['PATH']
diff --git a/pythonforandroid/bootstrap.py b/pythonforandroid/bootstrap.py
old mode 100644
new mode 100755
index 2304f281ff..b4a9a9e4c2
--- a/pythonforandroid/bootstrap.py
+++ b/pythonforandroid/bootstrap.py
@@ -1,6 +1,7 @@
from os.path import (join, dirname, isdir, normpath, splitext, basename)
from os import listdir, walk, sep
import sh
+import shlex
import glob
import importlib
import os
@@ -9,7 +10,7 @@
from pythonforandroid.logger import (warning, shprint, info, logger,
debug)
from pythonforandroid.util import (current_directory, ensure_dir,
- temp_directory, which)
+ temp_directory)
from pythonforandroid.recipe import Recipe
@@ -48,7 +49,11 @@ class Bootstrap(object):
dist_name = None
distribution = None
- recipe_depends = ['sdl2']
+ # All bootstraps should include Python in some way:
+ recipe_depends = [
+ ("python2", "python2legacy", "python3", "python3crystax"),
+ 'android',
+ ]
can_be_chosen_automatically = True
'''Determines whether the bootstrap can be chosen as one that
@@ -166,7 +171,7 @@ def get_bootstrap_from_recipes(cls, recipes, ctx):
for recipe in recipes:
try:
recipe = Recipe.get_recipe(recipe, ctx)
- except IOError:
+ except ValueError:
conflicts = []
else:
conflicts = recipe.conflicts
@@ -174,7 +179,7 @@ def get_bootstrap_from_recipes(cls, recipes, ctx):
for conflict in conflicts]):
ok = False
break
- if ok:
+ if ok and bs not in acceptable_bootstraps:
acceptable_bootstraps.append(bs)
info('Found {} acceptable bootstraps: {}'.format(
len(acceptable_bootstraps),
@@ -263,11 +268,10 @@ def strip_libraries(self, arch):
info('Python was loaded from CrystaX, skipping strip')
return
env = arch.get_env()
- strip = which('arm-linux-androideabi-strip', env['PATH'])
- if strip is None:
- warning('Can\'t find strip in PATH...')
- return
- strip = sh.Command(strip)
+ tokens = shlex.split(env['STRIP'])
+ strip = sh.Command(tokens[0])
+ if len(tokens) > 1:
+ strip = strip.bake(tokens[1:])
libs_dir = join(self.dist_dir, '_python_bundle',
'_python_bundle', 'modules')
@@ -278,6 +282,8 @@ def strip_libraries(self, arch):
logger.info('Stripping libraries in private dir')
for filen in filens.split('\n'):
+ if not filen:
+ continue # skip the last ''
try:
strip(filen, _env=env)
except sh.ErrorReturnCode_1:
diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py
index e55676f7fb..f455e06d64 100644
--- a/pythonforandroid/bootstraps/common/build/build.py
+++ b/pythonforandroid/bootstraps/common/build/build.py
@@ -14,6 +14,7 @@
import subprocess
import sys
import tarfile
+import tempfile
import time
from zipfile import ZipFile
@@ -22,16 +23,28 @@
import jinja2
-def get_bootstrap_name():
+def get_dist_info_for(key):
try:
with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh:
info = json.load(fileh)
- bootstrap = str(info["bootstrap"])
+ value = str(info[key])
except (OSError, KeyError) as e:
- print("BUILD FAILURE: Couldn't extract bootstrap name " +
+ print("BUILD FAILURE: Couldn't extract the key `" + key + "` " +
"from dist_info.json: " + str(e))
sys.exit(1)
- return bootstrap
+ return value
+
+
+def get_hostpython():
+ return get_dist_info_for('hostpython')
+
+
+def get_python_version():
+ return get_dist_info_for('python_version')
+
+
+def get_bootstrap_name():
+ return get_dist_info_for('bootstrap')
if os.name == 'nt':
@@ -43,9 +56,9 @@ def get_bootstrap_name():
curdir = dirname(__file__)
-# Try to find a host version of Python that matches our ARM version.
-PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
-if not exists(PYTHON):
+PYTHON = get_hostpython()
+PYTHON_VERSION = get_python_version()
+if PYTHON is not None and not exists(PYTHON):
PYTHON = None
BLACKLIST_PATTERNS = [
@@ -55,17 +68,18 @@ def get_bootstrap_name():
'^*.bzr/*',
'^*.svn/*',
- # pyc/py
- '*.pyc',
- # '*.py',
-
# temp files
'~',
'*.bak',
'*.swp',
]
+# pyc/py
if PYTHON is not None:
BLACKLIST_PATTERNS.append('*.py')
+ if PYTHON_VERSION and int(PYTHON_VERSION[0]) == 2:
+ # we only blacklist `.pyc` for python2 because in python3 the compiled
+ # extension is `.pyc` (.pyo files not exists for python >= 3.6)
+ BLACKLIST_PATTERNS.append('*.pyc')
WHITELIST_PATTERNS = []
if get_bootstrap_name() in ('sdl2', 'webview', 'service_only'):
@@ -236,16 +250,23 @@ def compile_dir(dfn, optimize_python=True):
Compile *.py in directory `dfn` to *.pyo
'''
- if get_bootstrap_name() != "sdl2":
- # HISTORICALLY DISABLED for other than sdl2. NEEDS REVIEW! -JonasT
- return
- # -OO = strip docstrings
if PYTHON is None:
return
- args = [PYTHON, '-m', 'compileall', '-f', dfn]
+
+ if int(PYTHON_VERSION[0]) >= 3:
+ args = [PYTHON, '-m', 'compileall', '-b', '-f', dfn]
+ else:
+ args = [PYTHON, '-m', 'compileall', '-f', dfn]
if optimize_python:
+ # -OO = strip docstrings
args.insert(1, '-OO')
- subprocess.call(args)
+ return_code = subprocess.call(args)
+
+ if return_code != 0:
+ print('Error while running "{}"'.format(' '.join(args)))
+ print('This probably means one of your Python files has a syntax '
+ 'error, see logs above')
+ exit(1)
def make_package(args):
@@ -273,8 +294,17 @@ def make_package(args):
# construct a python27.zip
make_python_zip()
+ # Add extra environment variable file into tar-able directory:
+ env_vars_tarpath = tempfile.mkdtemp(prefix="p4a-extra-env-")
+ with open(os.path.join(env_vars_tarpath, "p4a_env_vars.txt"), "w") as f:
+ f.write("P4A_IS_WINDOWED=" + str(args.window) + "\n")
+ if hasattr(args, "orientation"):
+ f.write("P4A_ORIENTATION=" + str(args.orientation) + "\n")
+ f.write("P4A_NUMERIC_VERSION=" + str(args.numeric_version) + "\n")
+ f.write("P4A_MINSDK=" + str(args.min_sdk_version) + "\n")
+
# Package up the private data (public not supported).
- tar_dirs = []
+ tar_dirs = [env_vars_tarpath]
if args.private:
tar_dirs.append(args.private)
for python_bundle_dir in ('private', 'crystax_python', '_python_bundle'):
@@ -287,6 +317,9 @@ def make_package(args):
join(assets_dir, 'private.mp3'), tar_dirs, args.ignore_path,
optimize_python=args.optimize_python)
+ # Remove extra env vars tar-able directory:
+ shutil.rmtree(env_vars_tarpath)
+
# Prepare some variables for templating process
res_dir = "src/main/res"
default_icon = 'templates/kivy-icon.png'
@@ -308,7 +341,7 @@ def make_package(args):
if not exists(jarname):
print('Requested jar does not exist: {}'.format(jarname))
sys.exit(-1)
- shutil.copy(jarname, 'libs')
+ shutil.copy(jarname, 'src/main/libs')
jars.append(basename(jarname))
# If extra aar were requested, copy them into the libs directory
@@ -336,9 +369,11 @@ def make_package(args):
with open(args.intent_filters) as fd:
args.intent_filters = fd.read()
- # if get_bootstrap_name() == "sdl2":
- args.add_activity = args.add_activity or []
- args.activity_launch_mode = args.activity_launch_mode or ''
+ if not args.add_activity:
+ args.add_activity = []
+
+ if not args.activity_launch_mode:
+ args.activity_launch_mode = ''
if args.extra_source_dirs:
esd = []
@@ -391,7 +426,7 @@ def make_package(args):
target = fileh.read().strip()
android_api = target.split('-')[1]
try:
- android_api_int_test = int(android_api)
+ int(android_api)
except (ValueError, TypeError):
raise ValueError(
"failed to extract the Android API level from " +
@@ -480,6 +515,31 @@ def make_package(args):
if exists('build.properties'):
os.remove('build.properties')
+ # Apply java source patches if any are present:
+ if exists(join('src', 'patches')):
+ print("Applying Java source code patches...")
+ for patch_name in os.listdir(join('src', 'patches')):
+ patch_path = join('src', 'patches', patch_name)
+ print("Applying patch: " + str(patch_path))
+ try:
+ subprocess.check_output([
+ # -N: insist this is FORWARd patch, don't reverse apply
+ # -p1: strip first path component
+ # -t: batch mode, don't ask questions
+ "patch", "-N", "-p1", "-t", "-i", patch_path
+ ])
+ except subprocess.CalledProcessError as e:
+ if e.returncode == 1:
+ # Return code 1 means it didn't apply, this will
+ # usually mean it is already applied.
+ print("Warning: failed to apply patch (" +
+ "exit code 1), " +
+ "assuming it is already applied: " +
+ str(patch_path)
+ )
+ else:
+ raise e
+
def parse_args(args=None):
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
@@ -506,7 +566,8 @@ def parse_args(args=None):
# --private is required unless for sdl2, where there's also --launcher
ap.add_argument('--private', dest='private',
- help='the dir of user files',
+ help='the directory with the app source code files' +
+ ' (containing your main.py entrypoint)',
required=(get_bootstrap_name() != "sdl2"))
ap.add_argument('--package', dest='package',
help=('The name of the java package the project will be'
@@ -536,6 +597,9 @@ def parse_args(args=None):
ap.add_argument('--icon', dest='icon',
help=('A png file to use as the icon for '
'the application.'))
+ ap.add_argument('--service', dest='services', action='append',
+ help='Declare a new service entrypoint: '
+ 'NAME:PATH_TO_PY[:foreground]')
if get_bootstrap_name() != "service_only":
ap.add_argument('--presplash', dest='presplash',
help=('A jpeg file to use as a screen while the '
@@ -564,10 +628,6 @@ def parse_args(args=None):
'https://developer.android.com/guide/'
'topics/manifest/'
'activity-element.html'))
- else:
- ap.add_argument('--service', dest='services', action='append',
- help='Declare a new service entrypoint: '
- 'NAME:PATH_TO_PY[:foreground]')
ap.add_argument('--wakelock', dest='wakelock', action='store_true',
help=('Indicate if the application needs the device '
'to stay on'))
@@ -624,7 +684,9 @@ def parse_args(args=None):
'the appropriate environment variables.'))
ap.add_argument('--add-activity', dest='add_activity', action='append',
help='Add this Java class as an Activity to the manifest.')
- ap.add_argument('--activity-launch-mode', dest='activity_launch_mode',
+ ap.add_argument('--activity-launch-mode',
+ dest='activity_launch_mode',
+ default='singleTask',
help='Set the launch mode of the main activity in the manifest.')
ap.add_argument('--allow-backup', dest='allow_backup', default='true',
help="if set to 'false', then android won't backup the application.")
@@ -687,7 +749,7 @@ def _read_configuration():
if args.meta_data is None:
args.meta_data = []
- if (not hasattr(args, 'services')) or args.services is None:
+ if args.services is None:
args.services = []
if args.try_system_python_compile:
diff --git a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
index 7cb8dc7e5f..afc8c3ad1d 100644
--- a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
+++ b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c
@@ -67,7 +67,7 @@ int dir_exists(char *filename) {
int file_exists(const char *filename) {
FILE *file;
- if (file = fopen(filename, "r")) {
+ if ((file = fopen(filename, "r"))) {
fclose(file);
return 1;
}
@@ -84,24 +84,66 @@ int main(int argc, char *argv[]) {
int ret = 0;
FILE *fd;
- setenv("P4A_BOOTSTRAP", bootstrap_name, 1); // env var to identify p4a to applications
-
LOGP("Initializing Python for Android");
+
+ // Set a couple of built-in environment vars:
+ setenv("P4A_BOOTSTRAP", bootstrap_name, 1); // env var to identify p4a to applications
env_argument = getenv("ANDROID_ARGUMENT");
setenv("ANDROID_APP_PATH", env_argument, 1);
env_entrypoint = getenv("ANDROID_ENTRYPOINT");
env_logname = getenv("PYTHON_NAME");
-
if (!getenv("ANDROID_UNPACK")) {
/* ANDROID_UNPACK currently isn't set in services */
setenv("ANDROID_UNPACK", env_argument, 1);
}
-
if (env_logname == NULL) {
env_logname = "python";
setenv("PYTHON_NAME", "python", 1);
}
+ // Set additional file-provided environment vars:
+ LOGP("Setting additional env vars from p4a_env_vars.txt");
+ char env_file_path[256];
+ snprintf(env_file_path, sizeof(env_file_path),
+ "%s/p4a_env_vars.txt", getenv("ANDROID_UNPACK"));
+ FILE *env_file_fd = fopen(env_file_path, "r");
+ if (env_file_fd) {
+ char* line = NULL;
+ size_t len = 0;
+ while (getline(&line, &len, env_file_fd) != -1) {
+ if (strlen(line) > 0) {
+ char *eqsubstr = strstr(line, "=");
+ if (eqsubstr) {
+ size_t eq_pos = eqsubstr - line;
+
+ // Extract name:
+ char env_name[256];
+ strncpy(env_name, line, sizeof(env_name));
+ env_name[eq_pos] = '\0';
+
+ // Extract value (with line break removed:
+ char env_value[256];
+ strncpy(env_value, (char*)(line + eq_pos + 1), sizeof(env_value));
+ if (strlen(env_value) > 0 &&
+ env_value[strlen(env_value)-1] == '\n') {
+ env_value[strlen(env_value)-1] = '\0';
+ if (strlen(env_value) > 0 &&
+ env_value[strlen(env_value)-1] == '\r') {
+ // Also remove windows line breaks (\r\n)
+ env_value[strlen(env_value)-1] = '\0';
+ }
+ }
+
+ // Set value:
+ setenv(env_name, env_value, 1);
+ }
+ }
+ }
+ fclose(env_file_fd);
+ } else {
+ LOGP("Warning: no p4a_env_vars.txt found / failed to open!");
+ }
+
LOGP("Changing directory to the one provided by ANDROID_ARGUMENT");
LOGP(env_argument);
chdir(env_argument);
@@ -110,7 +152,11 @@ int main(int argc, char *argv[]) {
Py_NoSiteFlag=1;
#endif
+#if PY_MAJOR_VERSION < 3
+ Py_SetProgramName("android_python");
+#else
Py_SetProgramName(L"android_python");
+#endif
#if PY_MAJOR_VERSION >= 3
/* our logging module for android
@@ -263,6 +309,11 @@ int main(int argc, char *argv[]) {
/* Get the entrypoint, search the .pyo then .py
*/
char *dot = strrchr(env_entrypoint, '.');
+#if PY_MAJOR_VERSION > 2
+ char *ext = ".pyc";
+#else
+ char *ext = ".pyo";
+#endif
if (dot <= 0) {
LOGP("Invalid entrypoint, abort.");
return -1;
@@ -271,14 +322,14 @@ int main(int argc, char *argv[]) {
LOGP("Entrypoint path is too long, try increasing ENTRYPOINT_MAXLEN.");
return -1;
}
- if (!strcmp(dot, ".pyo")) {
+ if (!strcmp(dot, ext)) {
if (!file_exists(env_entrypoint)) {
/* fallback on .py */
strcpy(entrypoint, env_entrypoint);
entrypoint[strlen(env_entrypoint) - 1] = '\0';
LOGP(entrypoint);
if (!file_exists(entrypoint)) {
- LOGP("Entrypoint not found (.pyo, fallback on .py), abort");
+ LOGP("Entrypoint not found (.pyc/.pyo, fallback on .py), abort");
return -1;
}
} else {
@@ -288,7 +339,11 @@ int main(int argc, char *argv[]) {
/* if .py is passed, check the pyo version first */
strcpy(entrypoint, env_entrypoint);
entrypoint[strlen(env_entrypoint) + 1] = '\0';
+#if PY_MAJOR_VERSION > 2
+ entrypoint[strlen(env_entrypoint)] = 'c';
+#else
entrypoint[strlen(env_entrypoint)] = 'o';
+#endif
if (!file_exists(entrypoint)) {
/* fallback on pure python version */
if (!file_exists(env_entrypoint)) {
@@ -298,7 +353,7 @@ int main(int argc, char *argv[]) {
strcpy(entrypoint, env_entrypoint);
}
} else {
- LOGP("Entrypoint have an invalid extension (must be .py or .pyo), abort.");
+ LOGP("Entrypoint have an invalid extension (must be .py or .pyc/.pyo), abort.");
return -1;
}
// LOGP("Entrypoint is:");
@@ -313,6 +368,7 @@ int main(int argc, char *argv[]) {
/* run python !
*/
ret = PyRun_SimpleFile(fd, entrypoint);
+ fclose(fd);
if (PyErr_Occurred() != NULL) {
ret = 1;
@@ -323,12 +379,36 @@ int main(int argc, char *argv[]) {
PyErr_Clear();
}
- /* close everything
+ LOGP("Python for android ended.");
+
+ /* Shut down: since regular shutdown causes issues sometimes
+ (seems to be an incomplete shutdown breaking next launch)
+ we'll use sys.exit(ret) to shutdown, since that one works.
+
+ Reference discussion:
+
+ https://github.com/kivy/kivy/pull/6107#issue-246120816
+ */
+ char terminatecmd[256];
+ snprintf(
+ terminatecmd, sizeof(terminatecmd),
+ "import sys; sys.exit(%d)\n", ret
+ );
+ PyRun_SimpleString(terminatecmd);
+
+ /* This should never actually be reached, but we'll leave the clean-up
+ * here just to be safe.
*/
+#if PY_MAJOR_VERSION < 3
Py_Finalize();
- fclose(fd);
+ LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
+#else
+ if (Py_FinalizeEx() != 0) // properly check success on Python 3
+ LOGP("Unexpectedly reached Py_FinalizeEx(), and got error!");
+ else
+ LOGP("Unexpectedly reached Py_FinalizeEx(), but was successful.");
+#endif
- LOGP("Python for android ended.");
return ret;
}
@@ -378,19 +458,20 @@ JNIEXPORT void JNICALL Java_org_kivy_android_PythonService_nativeStart(
#if defined(BOOTSTRAP_NAME_WEBVIEW) || defined(BOOTSTRAP_NAME_SERVICEONLY)
// Webview and service_only uses some more functions:
-void Java_org_kivy_android_PythonActivity_nativeSetEnv(
- JNIEnv* env, jclass jcls,
- jstring j_name, jstring j_value)
-/* JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeSetEnv( */
-/* JNIEnv* env, jclass jcls, */
-/* jstring j_name, jstring j_value) */
+void Java_org_kivy_android_PythonActivity_nativeSetenv(
+ JNIEnv* env, jclass cls,
+ jstring name, jstring value)
+//JNIEXPORT void JNICALL SDL_JAVA_INTERFACE(nativeSetenv)(
+// JNIEnv* env, jclass cls,
+// jstring name, jstring value)
{
- jboolean iscopy;
- const char *name = (*env)->GetStringUTFChars(env, j_name, &iscopy);
- const char *value = (*env)->GetStringUTFChars(env, j_value, &iscopy);
- setenv(name, value, 1);
- (*env)->ReleaseStringUTFChars(env, j_name, name);
- (*env)->ReleaseStringUTFChars(env, j_value, value);
+ const char *utfname = (*env)->GetStringUTFChars(env, name, NULL);
+ const char *utfvalue = (*env)->GetStringUTFChars(env, value, NULL);
+
+ SDL_setenv(utfname, utfvalue, 1);
+
+ (*env)->ReleaseStringUTFChars(env, name, utfname);
+ (*env)->ReleaseStringUTFChars(env, value, utfvalue);
}
diff --git a/pythonforandroid/bootstraps/pygame/build/buildlib/argparse.py b/pythonforandroid/bootstraps/pygame/build/buildlib/argparse.py
index f8c3d305ef..347b67816b 100644
--- a/pythonforandroid/bootstraps/pygame/build/buildlib/argparse.py
+++ b/pythonforandroid/bootstraps/pygame/build/buildlib/argparse.py
@@ -1468,7 +1468,7 @@ def _check_conflict(self, action):
def _handle_conflict_error(self, action, conflicting_actions):
message = _('conflicting option string(s): %s')
conflict_string = ', '.join([option_string
- for option_string, action
+ for option_string, action # noqa F812
in conflicting_actions])
raise ArgumentError(action, message % conflict_string)
@@ -2071,7 +2071,7 @@ def _parse_optional(self, arg_string):
# if multiple actions match, the option string was ambiguous
if len(option_tuples) > 1:
options = ', '.join(
- [option_string for action, option_string, explicit_arg in option_tuples])
+ [option_string for action, option_string, explicit_arg in option_tuples]) # noqa F812
tup = arg_string, options
self.error(_('ambiguous option: %s could match %s') % tup)
diff --git a/pythonforandroid/bootstraps/pygame/build/jni/application/Android.mk b/pythonforandroid/bootstraps/pygame/build/jni/application/Android.mk
index e30f708b7f..dcf8d643f9 100644
--- a/pythonforandroid/bootstraps/pygame/build/jni/application/Android.mk
+++ b/pythonforandroid/bootstraps/pygame/build/jni/application/Android.mk
@@ -18,7 +18,7 @@ LOCAL_CFLAGS := $(foreach D, $(APP_SUBDIRS), -I$(LOCAL_PATH)/$(D)) \
-I$(LOCAL_PATH)/../jpeg \
-I$(LOCAL_PATH)/../intl \
-I$(LOCAL_PATH)/.. \
- -I$(LOCAL_PATH)/../../../../other_builds/$(MK_PYTHON_INCLUDE_ROOT)
+ -I$(PYTHON_INCLUDE_ROOT)
LOCAL_CFLAGS += $(APPLICATION_ADDITIONAL_CFLAGS)
@@ -38,7 +38,7 @@ LOCAL_LDLIBS := -lpython2.7 -lGLESv1_CM -ldl -llog -lz
# AND: Another hardcoded path that should be templated
# AND: NOT TEMPALTED! We can use $ARCH
-LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../other_builds/$(MK_PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
+LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
LIBS_WITH_LONG_SYMBOLS := $(strip $(shell \
for f in $(LOCAL_PATH)/../../libs/$ARCH/*.so ; do \
diff --git a/pythonforandroid/bootstraps/sdl2/__init__.py b/pythonforandroid/bootstraps/sdl2/__init__.py
index 971d23a39b..b865348907 100644
--- a/pythonforandroid/bootstraps/sdl2/__init__.py
+++ b/pythonforandroid/bootstraps/sdl2/__init__.py
@@ -8,14 +8,14 @@
class SDL2GradleBootstrap(Bootstrap):
name = 'sdl2'
- recipe_depends = ['sdl2']
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'sdl2'})
+ )
def run_distribute(self):
info_main("# Creating Android project ({})".format(self.name))
arch = self.ctx.archs[0]
- python_install_dir = self.ctx.get_python_install_dir()
- from_crystax = self.ctx.python_recipe.from_crystax
if len(self.ctx.archs) > 1:
raise ValueError("SDL2/gradle support only one arch")
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
index 91b2169469..327ae18f4f 100644
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -7,33 +7,40 @@
import java.io.FileWriter;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.UnsatisfiedLinkError;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.ArrayList;
+import java.util.Timer;
+import java.util.TimerTask;
-import android.view.ViewGroup;
-import android.view.SurfaceView;
import android.app.Activity;
-import android.content.Intent;
-import android.util.Log;
-import android.widget.Toast;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.PowerManager;
-import android.graphics.PixelFormat;
-import android.view.SurfaceHolder;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ApplicationInfo;
-import android.content.Intent;
-import android.widget.ImageView;
-import java.io.InputStream;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.Manifest;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.Toast;
+import org.libsdl.app.SDL;
import org.libsdl.app.SDLActivity;
import org.kivy.android.PythonUtil;
@@ -51,16 +58,16 @@ public class PythonActivity extends SDLActivity {
private ResourceManager resourceManager = null;
private Bundle mMetaData = null;
private PowerManager.WakeLock mWakeLock = null;
+ private static boolean appliedWindowedModeHack = false;
public String getAppRoot() {
String app_root = getFilesDir().getAbsolutePath() + "/app";
return app_root;
}
-
@Override
protected void onCreate(Bundle savedInstanceState) {
- Log.v(TAG, "My oncreate running");
+ Log.v(TAG, "PythonActivity onCreate running");
resourceManager = new ResourceManager(this);
Log.v(TAG, "About to do super onCreate");
@@ -143,9 +150,10 @@ protected void onPostExecute(String result) {
File path = new File(getIntent().getData().getSchemeSpecificPart());
Project p = Project.scanDirectory(path);
- SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", p.dir + "/main.py");
- SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", p.dir);
- SDLActivity.nativeSetEnv("ANDROID_APP_PATH", p.dir);
+ String entry_point = getEntryPoint(p.dir);
+ SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", p.dir + "/" + entry_point);
+ SDLActivity.nativeSetenv("ANDROID_ARGUMENT", p.dir);
+ SDLActivity.nativeSetenv("ANDROID_APP_PATH", p.dir);
if (p != null) {
if (p.landscape) {
@@ -164,18 +172,19 @@ protected void onPostExecute(String result) {
// pass
}
} else {
- SDLActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo");
- SDLActivity.nativeSetEnv("ANDROID_ARGUMENT", app_root_dir);
- SDLActivity.nativeSetEnv("ANDROID_APP_PATH", app_root_dir);
+ String entry_point = getEntryPoint(app_root_dir);
+ SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ SDLActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ SDLActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
}
String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
Log.v(TAG, "Setting env vars for start.c and Python to use");
- SDLActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory);
- SDLActivity.nativeSetEnv("ANDROID_UNPACK", app_root_dir);
- SDLActivity.nativeSetEnv("PYTHONHOME", app_root_dir);
- SDLActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
- SDLActivity.nativeSetEnv("PYTHONOPTIMIZE", "2");
+ SDLActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ SDLActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ SDLActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ SDLActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ SDLActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
try {
Log.v(TAG, "Access to our meta-data...");
@@ -184,8 +193,8 @@ protected void onPostExecute(String result) {
PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
if ( mActivity.mMetaData.getInt("wakelock") == 1 ) {
- mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
- mActivity.mWakeLock.acquire();
+ mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On");
+ mActivity.mWakeLock.acquire();
}
if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) {
Log.v(TAG, "Surface will be transparent.");
@@ -196,6 +205,20 @@ protected void onPostExecute(String result) {
}
} catch (PackageManager.NameNotFoundException e) {
}
+
+ // Launch app if that hasn't been done yet:
+ if (mActivity.mHasFocus && (
+ // never went into proper resume state:
+ mActivity.mCurrentNativeState == NativeState.INIT ||
+ (
+ // resumed earlier but wasn't ready yet
+ mActivity.mCurrentNativeState == NativeState.RESUMED &&
+ mActivity.mSDLThread == null
+ ))) {
+ // Because sometimes the app will get stuck here and never
+ // actually run, ensure that it gets launched if we're active:
+ mActivity.onResume();
+ }
}
@Override
@@ -341,15 +364,16 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent)
}
}
- public static void start_service(String serviceTitle, String serviceDescription,
+ public static void start_service(String serviceTitle, String serviceDescription,
String pythonServiceArgument) {
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
String filesDirectory = argument;
String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
serviceIntent.putExtra("androidPrivate", argument);
serviceIntent.putExtra("androidArgument", app_root_dir);
- serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo");
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
serviceIntent.putExtra("pythonName", "python");
serviceIntent.putExtra("pythonHome", app_root_dir);
serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
@@ -364,118 +388,234 @@ public static void stop_service() {
PythonActivity.mActivity.stopService(serviceIntent);
}
- /** Loading screen implementation
- * keepActive() is a method plugged in pollInputDevices in SDLActivity.
- * Once it's called twice, the loading screen will be removed.
- * The first call happen as soon as the window is created, but no image has been
- * displayed first. My tests showed that we can wait one more. This might delay
- * the real available of few hundred milliseconds.
- * The real deal is to know if a rendering has already happen. The previous
- * python-for-android and kivy was having something for that, but this new version
- * is not compatible, and would require a new kivy version.
- * In case of, the method PythonActivty.mActivity.removeLoadingScreen() can be called.
- */
+ /** Loading screen view **/
public static ImageView mImageView = null;
- int mLoadingCount = 2;
+ /** Whether main routine/actual app has started yet **/
+ protected boolean mAppConfirmedActive = false;
+ /** Timer for delayed loading screen removal. **/
+ protected Timer loadingScreenRemovalTimer = null;
+ // Overridden since it's called often, to check whether to remove the
+ // loading screen:
+ @Override
+ protected boolean sendCommand(int command, Object data) {
+ boolean result = super.sendCommand(command, data);
+ considerLoadingScreenRemoval();
+ return result;
+ }
+
+ /** Confirm that the app's main routine has been launched.
+ **/
@Override
- public void keepActive() {
- if (this.mLoadingCount > 0) {
- this.mLoadingCount -= 1;
- if (this.mLoadingCount == 0) {
- this.removeLoadingScreen();
+ public void appConfirmedActive() {
+ if (!mAppConfirmedActive) {
+ Log.v(TAG, "appConfirmedActive() -> preparing loading screen removal");
+ mAppConfirmedActive = true;
+ considerLoadingScreenRemoval();
}
- }
}
- public void removeLoadingScreen() {
- runOnUiThread(new Runnable() {
- public void run() {
- if (PythonActivity.mImageView != null &&
- PythonActivity.mImageView.getParent() != null) {
- ((ViewGroup)PythonActivity.mImageView.getParent()).removeView(
- PythonActivity.mImageView);
- PythonActivity.mImageView = null;
- }
- }
- });
+ /** This is called from various places to check whether the app's main
+ * routine has been launched already, and if it has, then the loading
+ * screen will be removed.
+ **/
+ public void considerLoadingScreenRemoval() {
+ if (loadingScreenRemovalTimer != null)
+ return;
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (((PythonActivity)PythonActivity.mSingleton).mAppConfirmedActive &&
+ loadingScreenRemovalTimer == null) {
+ // Remove loading screen but with a delay.
+ // (app can use p4a's android.loadingscreen module to
+ // do it quicker if it wants to)
+ // get a handler (call from main thread)
+ // this will run when timer elapses
+ TimerTask removalTask = new TimerTask() {
+ @Override
+ public void run() {
+ // post a runnable to the handler
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ PythonActivity activity =
+ ((PythonActivity)PythonActivity.mSingleton);
+ if (activity != null)
+ activity.removeLoadingScreen();
+ }
+ });
+ }
+ };
+ loadingScreenRemovalTimer = new Timer();
+ loadingScreenRemovalTimer.schedule(removalTask, 5000);
+ }
+ }
+ });
}
+ public void removeLoadingScreen() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (PythonActivity.mImageView != null &&
+ PythonActivity.mImageView.getParent() != null) {
+ ((ViewGroup)PythonActivity.mImageView.getParent()).removeView(
+ PythonActivity.mImageView);
+ PythonActivity.mImageView = null;
+ }
+ }
+ });
+ }
- protected void showLoadingScreen() {
- // load the bitmap
- // 1. if the image is valid and we don't have layout yet, assign this bitmap
- // as main view.
- // 2. if we have a layout, just set it in the layout.
- // 3. If we have an mImageView already, then do nothing because it will have
- // already been made the content view or added to the layout.
-
- if (mImageView == null) {
- int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
- InputStream is = this.getResources().openRawResource(presplashId);
- Bitmap bitmap = null;
- try {
- bitmap = BitmapFactory.decodeStream(is);
- } finally {
- try {
- is.close();
- } catch (IOException e) {};
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.pyo|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List entryPoints = new ArrayList();
+ entryPoints.add("main.pyo"); // python 2 compiled files
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
}
-
- mImageView = new ImageView(this);
- mImageView.setImageBitmap(bitmap);
-
- /*
- * Set the presplash loading screen background color
- * https://developer.android.com/reference/android/graphics/Color.html
- * Parse the color string, and return the corresponding color-int.
- * If the string cannot be parsed, throws an IllegalArgumentException exception.
- * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
- * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',
- * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',
- * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.
- */
- String backgroundColor = resourceManager.getString("presplash_color");
- if (backgroundColor != null) {
- try {
- mImageView.setBackgroundColor(Color.parseColor(backgroundColor));
- } catch (IllegalArgumentException e) {}
- }
- mImageView.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.FILL_PARENT,
- ViewGroup.LayoutParams.FILL_PARENT));
- mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
-
+ return "main.py";
}
- if (mLayout == null) {
- setContentView(mImageView);
- } else if (PythonActivity.mImageView.getParent() == null){
- mLayout.addView(mImageView);
- }
+ protected void showLoadingScreen() {
+ // load the bitmap
+ // 1. if the image is valid and we don't have layout yet, assign this bitmap
+ // as main view.
+ // 2. if we have a layout, just set it in the layout.
+ // 3. If we have an mImageView already, then do nothing because it will have
+ // already been made the content view or added to the layout.
+
+ if (mImageView == null) {
+ int presplashId = this.resourceManager.getIdentifier("presplash", "drawable");
+ InputStream is = this.getResources().openRawResource(presplashId);
+ Bitmap bitmap = null;
+ try {
+ bitmap = BitmapFactory.decodeStream(is);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {};
+ }
+
+ mImageView = new ImageView(this);
+ mImageView.setImageBitmap(bitmap);
+
+ /*
+ * Set the presplash loading screen background color
+ * https://developer.android.com/reference/android/graphics/Color.html
+ * Parse the color string, and return the corresponding color-int.
+ * If the string cannot be parsed, throws an IllegalArgumentException exception.
+ * Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
+ * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow',
+ * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia',
+ * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'.
+ */
+ String backgroundColor = resourceManager.getString("presplash_color");
+ if (backgroundColor != null) {
+ try {
+ mImageView.setBackgroundColor(Color.parseColor(backgroundColor));
+ } catch (IllegalArgumentException e) {}
+ }
+ mImageView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+ mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ }
+ try {
+ if (mLayout == null) {
+ setContentView(mImageView);
+ } else if (PythonActivity.mImageView.getParent() == null) {
+ mLayout.addView(mImageView);
+ }
+ } catch (IllegalStateException e) {
+ // The loading screen can be attempted to be applied twice if app
+ // is tabbed in/out, quickly.
+ // (Gives error "The specified child already has a parent.
+ // You must call removeView() on the child's parent first.")
+ }
}
@Override
protected void onPause() {
- // fooabc
- if ( this.mWakeLock != null && mWakeLock.isHeld()){
- this.mWakeLock.release();
+ if (this.mWakeLock != null && mWakeLock.isHeld()) {
+ this.mWakeLock.release();
}
Log.v(TAG, "onPause()");
- super.onPause();
+ try {
+ super.onPause();
+ } catch (UnsatisfiedLinkError e) {
+ // Catch pause while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
}
@Override
protected void onResume() {
- if ( this.mWakeLock != null){
- this.mWakeLock.acquire();
- }
- Log.v(TAG, "onResume()");
- super.onResume();
+ if (this.mWakeLock != null) {
+ this.mWakeLock.acquire();
+ }
+ Log.v(TAG, "onResume()");
+ try {
+ super.onResume();
+ } catch (UnsatisfiedLinkError e) {
+ // Catch resume while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
+ considerLoadingScreenRemoval();
}
-
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ try {
+ super.onWindowFocusChanged(hasFocus);
+ } catch (UnsatisfiedLinkError e) {
+ // Catch window focus while still in loading screen failing to
+ // call native function (since it's not yet loaded)
+ }
+ considerLoadingScreenRemoval();
+ }
+
+ /**
+ * Used by android.permissions p4a module to check a permission
+ **/
+ public boolean checkCurrentPermission(String permission) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return true;
+ try {
+ java.lang.reflect.Method methodCheckPermission =
+ Activity.class.getMethod("checkSelfPermission", java.lang.String.class);
+ Object resultObj = methodCheckPermission.invoke(this, permission);
+ int result = Integer.parseInt(resultObj.toString());
+ if (result == PackageManager.PERMISSION_GRANTED)
+ return true;
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Used by android.permissions p4a module to request a permission
+ **/
+ public void requestNewPermission(String permission) {
+ if (android.os.Build.VERSION.SDK_INT < 23)
+ return;
+
+ try {
+ java.lang.reflect.Method methodRequestPermission =
+ Activity.class.getMethod("requestPermissions",
+ java.lang.String[].class, int.class);
+ methodRequestPermission.invoke(this, new String[] {permission}, 1);
+ } catch (IllegalAccessException | NoSuchMethodException |
+ InvocationTargetException e) {
+ }
+ }
}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDevice.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDevice.java
new file mode 100644
index 0000000000..aa358d1fc3
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDevice.java
@@ -0,0 +1,19 @@
+package org.libsdl.app;
+
+interface HIDDevice
+{
+ public int getId();
+ public int getVendorId();
+ public int getProductId();
+ public String getSerialNumber();
+ public int getVersion();
+ public String getManufacturerName();
+ public String getProductName();
+ public boolean open();
+ public int sendFeatureReport(byte[] report);
+ public int sendOutputReport(byte[] report);
+ public boolean getFeatureReport(byte[] report);
+ public void setFrozen(boolean frozen);
+ public void close();
+ public void shutdown();
+}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java
new file mode 100644
index 0000000000..4cf114a299
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceBLESteamController.java
@@ -0,0 +1,642 @@
+package org.libsdl.app;
+
+import android.content.Context;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothGattService;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+//import com.android.internal.util.HexDump;
+
+import java.lang.Runnable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.UUID;
+
+class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDevice {
+
+ private static final String TAG = "hidapi";
+ private HIDDeviceManager mManager;
+ private BluetoothDevice mDevice;
+ private int mDeviceId;
+ private BluetoothGatt mGatt;
+ private boolean mIsRegistered = false;
+ private boolean mIsConnected = false;
+ private boolean mIsChromebook = false;
+ private boolean mIsReconnecting = false;
+ private boolean mFrozen = false;
+ private LinkedList mOperations;
+ GattOperation mCurrentOperation = null;
+ private Handler mHandler;
+
+ private static final int TRANSPORT_AUTO = 0;
+ private static final int TRANSPORT_BREDR = 1;
+ private static final int TRANSPORT_LE = 2;
+
+ private static final int CHROMEBOOK_CONNECTION_CHECK_INTERVAL = 10000;
+
+ static public final UUID steamControllerService = UUID.fromString("100F6C32-1735-4313-B402-38567131E5F3");
+ static public final UUID inputCharacteristic = UUID.fromString("100F6C33-1735-4313-B402-38567131E5F3");
+ static public final UUID reportCharacteristic = UUID.fromString("100F6C34-1735-4313-B402-38567131E5F3");
+ static private final byte[] enterValveMode = new byte[] { (byte)0xC0, (byte)0x87, 0x03, 0x08, 0x07, 0x00 };
+
+ static class GattOperation {
+ private enum Operation {
+ CHR_READ,
+ CHR_WRITE,
+ ENABLE_NOTIFICATION
+ }
+
+ Operation mOp;
+ UUID mUuid;
+ byte[] mValue;
+ BluetoothGatt mGatt;
+ boolean mResult = true;
+
+ private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid) {
+ mGatt = gatt;
+ mOp = operation;
+ mUuid = uuid;
+ }
+
+ private GattOperation(BluetoothGatt gatt, GattOperation.Operation operation, UUID uuid, byte[] value) {
+ mGatt = gatt;
+ mOp = operation;
+ mUuid = uuid;
+ mValue = value;
+ }
+
+ public void run() {
+ // This is executed in main thread
+ BluetoothGattCharacteristic chr;
+
+ switch (mOp) {
+ case CHR_READ:
+ chr = getCharacteristic(mUuid);
+ //Log.v(TAG, "Reading characteristic " + chr.getUuid());
+ if (!mGatt.readCharacteristic(chr)) {
+ Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
+ mResult = false;
+ break;
+ }
+ mResult = true;
+ break;
+ case CHR_WRITE:
+ chr = getCharacteristic(mUuid);
+ //Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
+ chr.setValue(mValue);
+ if (!mGatt.writeCharacteristic(chr)) {
+ Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
+ mResult = false;
+ break;
+ }
+ mResult = true;
+ break;
+ case ENABLE_NOTIFICATION:
+ chr = getCharacteristic(mUuid);
+ //Log.v(TAG, "Writing descriptor of " + chr.getUuid());
+ if (chr != null) {
+ BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
+ if (cccd != null) {
+ int properties = chr.getProperties();
+ byte[] value;
+ if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
+ value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
+ } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
+ value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE;
+ } else {
+ Log.e(TAG, "Unable to start notifications on input characteristic");
+ mResult = false;
+ return;
+ }
+
+ mGatt.setCharacteristicNotification(chr, true);
+ cccd.setValue(value);
+ if (!mGatt.writeDescriptor(cccd)) {
+ Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
+ mResult = false;
+ return;
+ }
+ mResult = true;
+ }
+ }
+ }
+ }
+
+ public boolean finish() {
+ return mResult;
+ }
+
+ private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+ BluetoothGattService valveService = mGatt.getService(steamControllerService);
+ if (valveService == null)
+ return null;
+ return valveService.getCharacteristic(uuid);
+ }
+
+ static public GattOperation readCharacteristic(BluetoothGatt gatt, UUID uuid) {
+ return new GattOperation(gatt, Operation.CHR_READ, uuid);
+ }
+
+ static public GattOperation writeCharacteristic(BluetoothGatt gatt, UUID uuid, byte[] value) {
+ return new GattOperation(gatt, Operation.CHR_WRITE, uuid, value);
+ }
+
+ static public GattOperation enableNotification(BluetoothGatt gatt, UUID uuid) {
+ return new GattOperation(gatt, Operation.ENABLE_NOTIFICATION, uuid);
+ }
+ }
+
+ public HIDDeviceBLESteamController(HIDDeviceManager manager, BluetoothDevice device) {
+ mManager = manager;
+ mDevice = device;
+ mDeviceId = mManager.getDeviceIDForIdentifier(getIdentifier());
+ mIsRegistered = false;
+ mIsChromebook = mManager.getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
+ mOperations = new LinkedList();
+ mHandler = new Handler(Looper.getMainLooper());
+
+ mGatt = connectGatt();
+ final HIDDeviceBLESteamController finalThis = this;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ finalThis.checkConnectionForChromebookIssue();
+ }
+ }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
+ }
+
+ public String getIdentifier() {
+ return String.format("SteamController.%s", mDevice.getAddress());
+ }
+
+ public BluetoothGatt getGatt() {
+ return mGatt;
+ }
+
+ // Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
+ // of TRANSPORT_LE. Let's force ourselves to connect low energy.
+ private BluetoothGatt connectGatt(boolean managed) {
+ try {
+ Method m = mDevice.getClass().getDeclaredMethod("connectGatt", Context.class, boolean.class, BluetoothGattCallback.class, int.class);
+ return (BluetoothGatt) m.invoke(mDevice, mManager.getContext(), managed, this, TRANSPORT_LE);
+ } catch (Exception e) {
+ return mDevice.connectGatt(mManager.getContext(), managed, this);
+ }
+ }
+
+ private BluetoothGatt connectGatt() {
+ return connectGatt(false);
+ }
+
+ protected int getConnectionState() {
+
+ Context context = mManager.getContext();
+ if (context == null) {
+ // We are lacking any context to get our Bluetooth information. We'll just assume disconnected.
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ BluetoothManager btManager = (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (btManager == null) {
+ // This device doesn't support Bluetooth. We should never be here, because how did
+ // we instantiate a device to start with?
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
+ }
+
+ public void reconnect() {
+
+ if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
+ mGatt.disconnect();
+ mGatt = connectGatt();
+ }
+
+ }
+
+ protected void checkConnectionForChromebookIssue() {
+ if (!mIsChromebook) {
+ // We only do this on Chromebooks, because otherwise it's really annoying to just attempt
+ // over and over.
+ return;
+ }
+
+ int connectionState = getConnectionState();
+
+ switch (connectionState) {
+ case BluetoothProfile.STATE_CONNECTED:
+ if (!mIsConnected) {
+ // We are in the Bad Chromebook Place. We can force a disconnect
+ // to try to recover.
+ Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
+ mIsReconnecting = true;
+ mGatt.disconnect();
+ mGatt = connectGatt(false);
+ break;
+ }
+ else if (!isRegistered()) {
+ if (mGatt.getServices().size() > 0) {
+ Log.v(TAG, "Chromebook: We are connected to a controller, but never got our registration. Trying to recover.");
+ probeService(this);
+ }
+ else {
+ Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
+ mIsReconnecting = true;
+ mGatt.disconnect();
+ mGatt = connectGatt(false);
+ break;
+ }
+ }
+ else {
+ Log.v(TAG, "Chromebook: We are connected, and registered. Everything's good!");
+ return;
+ }
+ break;
+
+ case BluetoothProfile.STATE_DISCONNECTED:
+ Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
+
+ mIsReconnecting = true;
+ mGatt.disconnect();
+ mGatt = connectGatt(false);
+ break;
+
+ case BluetoothProfile.STATE_CONNECTING:
+ Log.v(TAG, "Chromebook: We're still trying to connect. Waiting a bit longer.");
+ break;
+ }
+
+ final HIDDeviceBLESteamController finalThis = this;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ finalThis.checkConnectionForChromebookIssue();
+ }
+ }, CHROMEBOOK_CONNECTION_CHECK_INTERVAL);
+ }
+
+ private boolean isRegistered() {
+ return mIsRegistered;
+ }
+
+ private void setRegistered() {
+ mIsRegistered = true;
+ }
+
+ private boolean probeService(HIDDeviceBLESteamController controller) {
+
+ if (isRegistered()) {
+ return true;
+ }
+
+ if (!mIsConnected) {
+ return false;
+ }
+
+ Log.v(TAG, "probeService controller=" + controller);
+
+ for (BluetoothGattService service : mGatt.getServices()) {
+ if (service.getUuid().equals(steamControllerService)) {
+ Log.v(TAG, "Found Valve steam controller service " + service.getUuid());
+
+ for (BluetoothGattCharacteristic chr : service.getCharacteristics()) {
+ if (chr.getUuid().equals(inputCharacteristic)) {
+ Log.v(TAG, "Found input characteristic");
+ // Start notifications
+ BluetoothGattDescriptor cccd = chr.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
+ if (cccd != null) {
+ enableNotification(chr.getUuid());
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ if ((mGatt.getServices().size() == 0) && mIsChromebook && !mIsReconnecting) {
+ Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
+ mIsConnected = false;
+ mIsReconnecting = true;
+ mGatt.disconnect();
+ mGatt = connectGatt(false);
+ }
+
+ return false;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ private void finishCurrentGattOperation() {
+ GattOperation op = null;
+ synchronized (mOperations) {
+ if (mCurrentOperation != null) {
+ op = mCurrentOperation;
+ mCurrentOperation = null;
+ }
+ }
+ if (op != null) {
+ boolean result = op.finish(); // TODO: Maybe in main thread as well?
+
+ // Our operation failed, let's add it back to the beginning of our queue.
+ if (!result) {
+ mOperations.addFirst(op);
+ }
+ }
+ executeNextGattOperation();
+ }
+
+ private void executeNextGattOperation() {
+ synchronized (mOperations) {
+ if (mCurrentOperation != null)
+ return;
+
+ if (mOperations.isEmpty())
+ return;
+
+ mCurrentOperation = mOperations.removeFirst();
+ }
+
+ // Run in main thread
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mOperations) {
+ if (mCurrentOperation == null) {
+ Log.e(TAG, "Current operation null in executor?");
+ return;
+ }
+
+ mCurrentOperation.run();
+ // now wait for the GATT callback and when it comes, finish this operation
+ }
+ }
+ });
+ }
+
+ private void queueGattOperation(GattOperation op) {
+ synchronized (mOperations) {
+ mOperations.add(op);
+ }
+ executeNextGattOperation();
+ }
+
+ private void enableNotification(UUID chrUuid) {
+ GattOperation op = HIDDeviceBLESteamController.GattOperation.enableNotification(mGatt, chrUuid);
+ queueGattOperation(op);
+ }
+
+ public void writeCharacteristic(UUID uuid, byte[] value) {
+ GattOperation op = HIDDeviceBLESteamController.GattOperation.writeCharacteristic(mGatt, uuid, value);
+ queueGattOperation(op);
+ }
+
+ public void readCharacteristic(UUID uuid) {
+ GattOperation op = HIDDeviceBLESteamController.GattOperation.readCharacteristic(mGatt, uuid);
+ queueGattOperation(op);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////// BluetoothGattCallback overridden methods
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ public void onConnectionStateChange(BluetoothGatt g, int status, int newState) {
+ //Log.v(TAG, "onConnectionStateChange status=" + status + " newState=" + newState);
+ mIsReconnecting = false;
+ if (newState == 2) {
+ mIsConnected = true;
+ // Run directly, without GattOperation
+ if (!isRegistered()) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mGatt.discoverServices();
+ }
+ });
+ }
+ }
+ else if (newState == 0) {
+ mIsConnected = false;
+ }
+
+ // Disconnection is handled in SteamLink using the ACTION_ACL_DISCONNECTED Intent.
+ }
+
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ //Log.v(TAG, "onServicesDiscovered status=" + status);
+ if (status == 0) {
+ if (gatt.getServices().size() == 0) {
+ Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
+ mIsReconnecting = true;
+ mIsConnected = false;
+ gatt.disconnect();
+ mGatt = connectGatt(false);
+ }
+ else {
+ probeService(this);
+ }
+ }
+ }
+
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ //Log.v(TAG, "onCharacteristicRead status=" + status + " uuid=" + characteristic.getUuid());
+
+ if (characteristic.getUuid().equals(reportCharacteristic) && !mFrozen) {
+ mManager.HIDDeviceFeatureReport(getId(), characteristic.getValue());
+ }
+
+ finishCurrentGattOperation();
+ }
+
+ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ //Log.v(TAG, "onCharacteristicWrite status=" + status + " uuid=" + characteristic.getUuid());
+
+ if (characteristic.getUuid().equals(reportCharacteristic)) {
+ // Only register controller with the native side once it has been fully configured
+ if (!isRegistered()) {
+ Log.v(TAG, "Registering Steam Controller with ID: " + getId());
+ mManager.HIDDeviceConnected(getId(), getIdentifier(), getVendorId(), getProductId(), getSerialNumber(), getVersion(), getManufacturerName(), getProductName(), 0);
+ setRegistered();
+ }
+ }
+
+ finishCurrentGattOperation();
+ }
+
+ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+ // Enable this for verbose logging of controller input reports
+ //Log.v(TAG, "onCharacteristicChanged uuid=" + characteristic.getUuid() + " data=" + HexDump.dumpHexString(characteristic.getValue()));
+
+ if (characteristic.getUuid().equals(inputCharacteristic) && !mFrozen) {
+ mManager.HIDDeviceInputReport(getId(), characteristic.getValue());
+ }
+ }
+
+ public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ //Log.v(TAG, "onDescriptorRead status=" + status);
+ }
+
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+ BluetoothGattCharacteristic chr = descriptor.getCharacteristic();
+ //Log.v(TAG, "onDescriptorWrite status=" + status + " uuid=" + chr.getUuid() + " descriptor=" + descriptor.getUuid());
+
+ if (chr.getUuid().equals(inputCharacteristic)) {
+ boolean hasWrittenInputDescriptor = true;
+ BluetoothGattCharacteristic reportChr = chr.getService().getCharacteristic(reportCharacteristic);
+ if (reportChr != null) {
+ Log.v(TAG, "Writing report characteristic to enter valve mode");
+ reportChr.setValue(enterValveMode);
+ gatt.writeCharacteristic(reportChr);
+ }
+ }
+
+ finishCurrentGattOperation();
+ }
+
+ public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
+ //Log.v(TAG, "onReliableWriteCompleted status=" + status);
+ }
+
+ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+ //Log.v(TAG, "onReadRemoteRssi status=" + status);
+ }
+
+ public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+ //Log.v(TAG, "onMtuChanged status=" + status);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ //////// Public API
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public int getId() {
+ return mDeviceId;
+ }
+
+ @Override
+ public int getVendorId() {
+ // Valve Corporation
+ final int VALVE_USB_VID = 0x28DE;
+ return VALVE_USB_VID;
+ }
+
+ @Override
+ public int getProductId() {
+ // We don't have an easy way to query from the Bluetooth device, but we know what it is
+ final int D0G_BLE2_PID = 0x1106;
+ return D0G_BLE2_PID;
+ }
+
+ @Override
+ public String getSerialNumber() {
+ // This will be read later via feature report by Steam
+ return "12345";
+ }
+
+ @Override
+ public int getVersion() {
+ return 0;
+ }
+
+ @Override
+ public String getManufacturerName() {
+ return "Valve Corporation";
+ }
+
+ @Override
+ public String getProductName() {
+ return "Steam Controller";
+ }
+
+ @Override
+ public boolean open() {
+ return true;
+ }
+
+ @Override
+ public int sendFeatureReport(byte[] report) {
+ if (!isRegistered()) {
+ Log.e(TAG, "Attempted sendFeatureReport before Steam Controller is registered!");
+ if (mIsConnected) {
+ probeService(this);
+ }
+ return -1;
+ }
+
+ // We need to skip the first byte, as that doesn't go over the air
+ byte[] actual_report = Arrays.copyOfRange(report, 1, report.length - 1);
+ //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(actual_report));
+ writeCharacteristic(reportCharacteristic, actual_report);
+ return report.length;
+ }
+
+ @Override
+ public int sendOutputReport(byte[] report) {
+ if (!isRegistered()) {
+ Log.e(TAG, "Attempted sendOutputReport before Steam Controller is registered!");
+ if (mIsConnected) {
+ probeService(this);
+ }
+ return -1;
+ }
+
+ //Log.v(TAG, "sendFeatureReport " + HexDump.dumpHexString(report));
+ writeCharacteristic(reportCharacteristic, report);
+ return report.length;
+ }
+
+ @Override
+ public boolean getFeatureReport(byte[] report) {
+ if (!isRegistered()) {
+ Log.e(TAG, "Attempted getFeatureReport before Steam Controller is registered!");
+ if (mIsConnected) {
+ probeService(this);
+ }
+ return false;
+ }
+
+ //Log.v(TAG, "getFeatureReport");
+ readCharacteristic(reportCharacteristic);
+ return true;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void setFrozen(boolean frozen) {
+ mFrozen = frozen;
+ }
+
+ @Override
+ public void shutdown() {
+ close();
+
+ BluetoothGatt g = mGatt;
+ if (g != null) {
+ g.disconnect();
+ g.close();
+ mGatt = null;
+ }
+ mManager = null;
+ mIsRegistered = false;
+ mIsConnected = false;
+ mOperations.clear();
+ }
+
+}
+
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceManager.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceManager.java
new file mode 100644
index 0000000000..db9400f6d6
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceManager.java
@@ -0,0 +1,682 @@
+package org.libsdl.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.util.Log;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.hardware.usb.*;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
+
+public class HIDDeviceManager {
+ private static final String TAG = "hidapi";
+ private static final String ACTION_USB_PERMISSION = "org.libsdl.app.USB_PERMISSION";
+
+ private static HIDDeviceManager sManager;
+ private static int sManagerRefCount = 0;
+
+ public static HIDDeviceManager acquire(Context context) {
+ if (sManagerRefCount == 0) {
+ sManager = new HIDDeviceManager(context);
+ }
+ ++sManagerRefCount;
+ return sManager;
+ }
+
+ public static void release(HIDDeviceManager manager) {
+ if (manager == sManager) {
+ --sManagerRefCount;
+ if (sManagerRefCount == 0) {
+ sManager.close();
+ sManager = null;
+ }
+ }
+ }
+
+ private Context mContext;
+ private HashMap mDevicesById = new HashMap();
+ private HashMap mUSBDevices = new HashMap();
+ private HashMap mBluetoothDevices = new HashMap();
+ private int mNextDeviceId = 0;
+ private SharedPreferences mSharedPreferences = null;
+ private boolean mIsChromebook = false;
+ private UsbManager mUsbManager;
+ private Handler mHandler;
+ private BluetoothManager mBluetoothManager;
+ private List mLastBluetoothDevices;
+
+ private final BroadcastReceiver mUsbBroadcast = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ handleUsbDeviceAttached(usbDevice);
+ } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
+ UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ handleUsbDeviceDetached(usbDevice);
+ } else if (action.equals(HIDDeviceManager.ACTION_USB_PERMISSION)) {
+ UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ handleUsbDevicePermission(usbDevice, intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false));
+ }
+ }
+ };
+
+ private final BroadcastReceiver mBluetoothBroadcast = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ // Bluetooth device was connected. If it was a Steam Controller, handle it
+ if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ Log.d(TAG, "Bluetooth device connected: " + device);
+
+ if (isSteamController(device)) {
+ connectBluetoothDevice(device);
+ }
+ }
+
+ // Bluetooth device was disconnected, remove from controller manager (if any)
+ if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ Log.d(TAG, "Bluetooth device disconnected: " + device);
+
+ disconnectBluetoothDevice(device);
+ }
+ }
+ };
+
+ private HIDDeviceManager(final Context context) {
+ mContext = context;
+
+ // Make sure we have the HIDAPI library loaded with the native functions
+ try {
+ SDL.loadLibrary("hidapi");
+ } catch (Throwable e) {
+ Log.w(TAG, "Couldn't load hidapi: " + e.toString());
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setCancelable(false);
+ builder.setTitle("SDL HIDAPI Error");
+ builder.setMessage("Please report the following error to the SDL maintainers: " + e.getMessage());
+ builder.setNegativeButton("Quit", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ // If our context is an activity, exit rather than crashing when we can't
+ // call our native functions.
+ Activity activity = (Activity)context;
+
+ activity.finish();
+ }
+ catch (ClassCastException cce) {
+ // Context wasn't an activity, there's nothing we can do. Give up and return.
+ }
+ }
+ });
+ builder.show();
+
+ return;
+ }
+
+ HIDDeviceRegisterCallback();
+
+ mSharedPreferences = mContext.getSharedPreferences("hidapi", Context.MODE_PRIVATE);
+ mIsChromebook = mContext.getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
+
+// if (shouldClear) {
+// SharedPreferences.Editor spedit = mSharedPreferences.edit();
+// spedit.clear();
+// spedit.commit();
+// }
+// else
+ {
+ mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
+ }
+
+ initializeUSB();
+ initializeBluetooth();
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public int getDeviceIDForIdentifier(String identifier) {
+ SharedPreferences.Editor spedit = mSharedPreferences.edit();
+
+ int result = mSharedPreferences.getInt(identifier, 0);
+ if (result == 0) {
+ result = mNextDeviceId++;
+ spedit.putInt("next_device_id", mNextDeviceId);
+ }
+
+ spedit.putInt(identifier, result);
+ spedit.commit();
+ return result;
+ }
+
+ private void initializeUSB() {
+ mUsbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
+
+ /*
+ // Logging
+ for (UsbDevice device : mUsbManager.getDeviceList().values()) {
+ Log.i(TAG,"Path: " + device.getDeviceName());
+ Log.i(TAG,"Manufacturer: " + device.getManufacturerName());
+ Log.i(TAG,"Product: " + device.getProductName());
+ Log.i(TAG,"ID: " + device.getDeviceId());
+ Log.i(TAG,"Class: " + device.getDeviceClass());
+ Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
+ Log.i(TAG,"Vendor ID " + device.getVendorId());
+ Log.i(TAG,"Product ID: " + device.getProductId());
+ Log.i(TAG,"Interface count: " + device.getInterfaceCount());
+ Log.i(TAG,"---------------------------------------");
+
+ // Get interface details
+ for (int index = 0; index < device.getInterfaceCount(); index++) {
+ UsbInterface mUsbInterface = device.getInterface(index);
+ Log.i(TAG," ***** *****");
+ Log.i(TAG," Interface index: " + index);
+ Log.i(TAG," Interface ID: " + mUsbInterface.getId());
+ Log.i(TAG," Interface class: " + mUsbInterface.getInterfaceClass());
+ Log.i(TAG," Interface subclass: " + mUsbInterface.getInterfaceSubclass());
+ Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
+ Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
+
+ // Get endpoint details
+ for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
+ {
+ UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
+ Log.i(TAG," ++++ ++++ ++++");
+ Log.i(TAG," Endpoint index: " + epi);
+ Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
+ Log.i(TAG," Direction: " + mEndpoint.getDirection());
+ Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
+ Log.i(TAG," Interval: " + mEndpoint.getInterval());
+ Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
+ Log.i(TAG," Type: " + mEndpoint.getType());
+ }
+ }
+ }
+ Log.i(TAG," No more devices connected.");
+ */
+
+ // Register for USB broadcasts and permission completions
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
+ mContext.registerReceiver(mUsbBroadcast, filter);
+
+ for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
+ handleUsbDeviceAttached(usbDevice);
+ }
+ }
+
+ UsbManager getUSBManager() {
+ return mUsbManager;
+ }
+
+ private void shutdownUSB() {
+ try {
+ mContext.unregisterReceiver(mUsbBroadcast);
+ } catch (Exception e) {
+ // We may not have registered, that's okay
+ }
+ }
+
+ private boolean isHIDDeviceUSB(UsbDevice usbDevice) {
+ for (int interface_number = 0; interface_number < usbDevice.getInterfaceCount(); ++interface_number) {
+ if (isHIDDeviceInterface(usbDevice, interface_number)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isHIDDeviceInterface(UsbDevice usbDevice, int interface_number) {
+ UsbInterface usbInterface = usbDevice.getInterface(interface_number);
+ if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {
+ return true;
+ }
+ if (interface_number == 0) {
+ if (isXbox360Controller(usbDevice, usbInterface) || isXboxOneController(usbDevice, usbInterface)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isXbox360Controller(UsbDevice usbDevice, UsbInterface usbInterface) {
+ final int XB360_IFACE_SUBCLASS = 93;
+ final int XB360_IFACE_PROTOCOL = 1; // Wired only
+ final int[] SUPPORTED_VENDORS = {
+ 0x0079, // GPD Win 2
+ 0x044f, // Thrustmaster
+ 0x045e, // Microsoft
+ 0x046d, // Logitech
+ 0x056e, // Elecom
+ 0x06a3, // Saitek
+ 0x0738, // Mad Catz
+ 0x07ff, // Mad Catz
+ 0x0e6f, // Unknown
+ 0x0f0d, // Hori
+ 0x11c9, // Nacon
+ 0x12ab, // Unknown
+ 0x1430, // RedOctane
+ 0x146b, // BigBen
+ 0x1532, // Razer Sabertooth
+ 0x15e4, // Numark
+ 0x162e, // Joytech
+ 0x1689, // Razer Onza
+ 0x1bad, // Harmonix
+ 0x24c6, // PowerA
+ };
+
+ if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
+ usbInterface.getInterfaceSubclass() == XB360_IFACE_SUBCLASS &&
+ usbInterface.getInterfaceProtocol() == XB360_IFACE_PROTOCOL) {
+ int vendor_id = usbDevice.getVendorId();
+ for (int supportedVid : SUPPORTED_VENDORS) {
+ if (vendor_id == supportedVid) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterface) {
+ final int XB1_IFACE_SUBCLASS = 71;
+ final int XB1_IFACE_PROTOCOL = 208;
+ final int[] SUPPORTED_VENDORS = {
+ 0x045e, // Microsoft
+ 0x0738, // Mad Catz
+ 0x0e6f, // Unknown
+ 0x0f0d, // Hori
+ 0x1532, // Razer Wildcat
+ 0x24c6, // PowerA
+ };
+
+ if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
+ usbInterface.getInterfaceSubclass() == XB1_IFACE_SUBCLASS &&
+ usbInterface.getInterfaceProtocol() == XB1_IFACE_PROTOCOL) {
+ int vendor_id = usbDevice.getVendorId();
+ for (int supportedVid : SUPPORTED_VENDORS) {
+ if (vendor_id == supportedVid) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void handleUsbDeviceAttached(UsbDevice usbDevice) {
+ if (isHIDDeviceUSB(usbDevice)) {
+ connectHIDDeviceUSB(usbDevice);
+ }
+ }
+
+ private void handleUsbDeviceDetached(UsbDevice usbDevice) {
+ HIDDeviceUSB device = mUSBDevices.get(usbDevice);
+ if (device == null)
+ return;
+
+ int id = device.getId();
+ mUSBDevices.remove(usbDevice);
+ mDevicesById.remove(id);
+ device.shutdown();
+ HIDDeviceDisconnected(id);
+ }
+
+ private void handleUsbDevicePermission(UsbDevice usbDevice, boolean permission_granted) {
+ HIDDeviceUSB device = mUSBDevices.get(usbDevice);
+ if (device == null)
+ return;
+
+ boolean opened = false;
+ if (permission_granted) {
+ opened = device.open();
+ }
+ HIDDeviceOpenResult(device.getId(), opened);
+ }
+
+ private void connectHIDDeviceUSB(UsbDevice usbDevice) {
+ synchronized (this) {
+ for (int interface_number = 0; interface_number < usbDevice.getInterfaceCount(); interface_number++) {
+ if (isHIDDeviceInterface(usbDevice, interface_number)) {
+ HIDDeviceUSB device = new HIDDeviceUSB(this, usbDevice, interface_number);
+ int id = device.getId();
+ mUSBDevices.put(usbDevice, device);
+ mDevicesById.put(id, device);
+ HIDDeviceConnected(id, device.getIdentifier(), device.getVendorId(), device.getProductId(), device.getSerialNumber(), device.getVersion(), device.getManufacturerName(), device.getProductName(), interface_number);
+ break;
+ }
+ }
+ }
+ }
+
+ private void initializeBluetooth() {
+ Log.d(TAG, "Initializing Bluetooth");
+
+ if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
+ return;
+ }
+
+ // Find bonded bluetooth controllers and create SteamControllers for them
+ mBluetoothManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+ if (mBluetoothManager == null) {
+ // This device doesn't support Bluetooth.
+ return;
+ }
+
+ BluetoothAdapter btAdapter = mBluetoothManager.getAdapter();
+ if (btAdapter == null) {
+ // This device has Bluetooth support in the codebase, but has no available adapters.
+ return;
+ }
+
+ // Get our bonded devices.
+ for (BluetoothDevice device : btAdapter.getBondedDevices()) {
+
+ Log.d(TAG, "Bluetooth device available: " + device);
+ if (isSteamController(device)) {
+ connectBluetoothDevice(device);
+ }
+
+ }
+
+ // NOTE: These don't work on Chromebooks, to my undying dismay.
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+ filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ mContext.registerReceiver(mBluetoothBroadcast, filter);
+
+ if (mIsChromebook) {
+ mHandler = new Handler(Looper.getMainLooper());
+ mLastBluetoothDevices = new ArrayList<>();
+
+ // final HIDDeviceManager finalThis = this;
+ // mHandler.postDelayed(new Runnable() {
+ // @Override
+ // public void run() {
+ // finalThis.chromebookConnectionHandler();
+ // }
+ // }, 5000);
+ }
+ }
+
+ private void shutdownBluetooth() {
+ try {
+ mContext.unregisterReceiver(mBluetoothBroadcast);
+ } catch (Exception e) {
+ // We may not have registered, that's okay
+ }
+ }
+
+ // Chromebooks do not pass along ACTION_ACL_CONNECTED / ACTION_ACL_DISCONNECTED properly.
+ // This function provides a sort of dummy version of that, watching for changes in the
+ // connected devices and attempting to add controllers as things change.
+ public void chromebookConnectionHandler() {
+ if (!mIsChromebook) {
+ return;
+ }
+
+ ArrayList disconnected = new ArrayList<>();
+ ArrayList connected = new ArrayList<>();
+
+ List currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
+
+ for (BluetoothDevice bluetoothDevice : currentConnected) {
+ if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
+ connected.add(bluetoothDevice);
+ }
+ }
+ for (BluetoothDevice bluetoothDevice : mLastBluetoothDevices) {
+ if (!currentConnected.contains(bluetoothDevice)) {
+ disconnected.add(bluetoothDevice);
+ }
+ }
+
+ mLastBluetoothDevices = currentConnected;
+
+ for (BluetoothDevice bluetoothDevice : disconnected) {
+ disconnectBluetoothDevice(bluetoothDevice);
+ }
+ for (BluetoothDevice bluetoothDevice : connected) {
+ connectBluetoothDevice(bluetoothDevice);
+ }
+
+ final HIDDeviceManager finalThis = this;
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ finalThis.chromebookConnectionHandler();
+ }
+ }, 10000);
+ }
+
+ public boolean connectBluetoothDevice(BluetoothDevice bluetoothDevice) {
+ Log.v(TAG, "connectBluetoothDevice device=" + bluetoothDevice);
+ synchronized (this) {
+ if (mBluetoothDevices.containsKey(bluetoothDevice)) {
+ Log.v(TAG, "Steam controller with address " + bluetoothDevice + " already exists, attempting reconnect");
+
+ HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
+ device.reconnect();
+
+ return false;
+ }
+ HIDDeviceBLESteamController device = new HIDDeviceBLESteamController(this, bluetoothDevice);
+ int id = device.getId();
+ mBluetoothDevices.put(bluetoothDevice, device);
+ mDevicesById.put(id, device);
+
+ // The Steam Controller will mark itself connected once initialization is complete
+ }
+ return true;
+ }
+
+ public void disconnectBluetoothDevice(BluetoothDevice bluetoothDevice) {
+ synchronized (this) {
+ HIDDeviceBLESteamController device = mBluetoothDevices.get(bluetoothDevice);
+ if (device == null)
+ return;
+
+ int id = device.getId();
+ mBluetoothDevices.remove(bluetoothDevice);
+ mDevicesById.remove(id);
+ device.shutdown();
+ HIDDeviceDisconnected(id);
+ }
+ }
+
+ public boolean isSteamController(BluetoothDevice bluetoothDevice) {
+ // Sanity check. If you pass in a null device, by definition it is never a Steam Controller.
+ if (bluetoothDevice == null) {
+ return false;
+ }
+
+ // If the device has no local name, we really don't want to try an equality check against it.
+ if (bluetoothDevice.getName() == null) {
+ return false;
+ }
+
+ return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
+ }
+
+ private void close() {
+ shutdownUSB();
+ shutdownBluetooth();
+ synchronized (this) {
+ for (HIDDevice device : mDevicesById.values()) {
+ device.shutdown();
+ }
+ mDevicesById.clear();
+ mBluetoothDevices.clear();
+ HIDDeviceReleaseCallback();
+ }
+ }
+
+ public void setFrozen(boolean frozen) {
+ synchronized (this) {
+ for (HIDDevice device : mDevicesById.values()) {
+ device.setFrozen(frozen);
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ private HIDDevice getDevice(int id) {
+ synchronized (this) {
+ HIDDevice result = mDevicesById.get(id);
+ if (result == null) {
+ Log.v(TAG, "No device for id: " + id);
+ Log.v(TAG, "Available devices: " + mDevicesById.keySet());
+ }
+ return result;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////// JNI interface functions
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ public boolean openDevice(int deviceID) {
+ // Look to see if this is a USB device and we have permission to access it
+ for (HIDDeviceUSB device : mUSBDevices.values()) {
+ if (deviceID == device.getId()) {
+ UsbDevice usbDevice = device.getDevice();
+ if (!mUsbManager.hasPermission(usbDevice)) {
+ HIDDeviceOpenPending(deviceID);
+ try {
+ mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), 0));
+ } catch (Exception e) {
+ Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
+ HIDDeviceOpenResult(deviceID, false);
+ }
+ return false;
+ }
+ break;
+ }
+ }
+
+ try {
+ Log.v(TAG, "openDevice deviceID=" + deviceID);
+ HIDDevice device;
+ device = getDevice(deviceID);
+ if (device == null) {
+ HIDDeviceDisconnected(deviceID);
+ return false;
+ }
+
+ return device.open();
+ } catch (Exception e) {
+ Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+ }
+ return false;
+ }
+
+ public int sendOutputReport(int deviceID, byte[] report) {
+ try {
+ Log.v(TAG, "sendOutputReport deviceID=" + deviceID + " length=" + report.length);
+ HIDDevice device;
+ device = getDevice(deviceID);
+ if (device == null) {
+ HIDDeviceDisconnected(deviceID);
+ return -1;
+ }
+
+ return device.sendOutputReport(report);
+ } catch (Exception e) {
+ Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+ }
+ return -1;
+ }
+
+ public int sendFeatureReport(int deviceID, byte[] report) {
+ try {
+ Log.v(TAG, "sendFeatureReport deviceID=" + deviceID + " length=" + report.length);
+ HIDDevice device;
+ device = getDevice(deviceID);
+ if (device == null) {
+ HIDDeviceDisconnected(deviceID);
+ return -1;
+ }
+
+ return device.sendFeatureReport(report);
+ } catch (Exception e) {
+ Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+ }
+ return -1;
+ }
+
+ public boolean getFeatureReport(int deviceID, byte[] report) {
+ try {
+ Log.v(TAG, "getFeatureReport deviceID=" + deviceID);
+ HIDDevice device;
+ device = getDevice(deviceID);
+ if (device == null) {
+ HIDDeviceDisconnected(deviceID);
+ return false;
+ }
+
+ return device.getFeatureReport(report);
+ } catch (Exception e) {
+ Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+ }
+ return false;
+ }
+
+ public void closeDevice(int deviceID) {
+ try {
+ Log.v(TAG, "closeDevice deviceID=" + deviceID);
+ HIDDevice device;
+ device = getDevice(deviceID);
+ if (device == null) {
+ HIDDeviceDisconnected(deviceID);
+ return;
+ }
+
+ device.close();
+ } catch (Exception e) {
+ Log.e(TAG, "Got exception: " + Log.getStackTraceString(e));
+ }
+ }
+
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+ /////////////// Native methods
+ //////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ private native void HIDDeviceRegisterCallback();
+ private native void HIDDeviceReleaseCallback();
+
+ native void HIDDeviceConnected(int deviceID, String identifier, int vendorId, int productId, String serial_number, int release_number, String manufacturer_string, String product_string, int interface_number);
+ native void HIDDeviceOpenPending(int deviceID);
+ native void HIDDeviceOpenResult(int deviceID, boolean opened);
+ native void HIDDeviceDisconnected(int deviceID);
+
+ native void HIDDeviceInputReport(int deviceID, byte[] report);
+ native void HIDDeviceFeatureReport(int deviceID, byte[] report);
+}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceUSB.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceUSB.java
new file mode 100644
index 0000000000..c9fc58ece2
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/HIDDeviceUSB.java
@@ -0,0 +1,307 @@
+package org.libsdl.app;
+
+import android.hardware.usb.*;
+import android.os.Build;
+import android.util.Log;
+import java.util.Arrays;
+
+class HIDDeviceUSB implements HIDDevice {
+
+ private static final String TAG = "hidapi";
+
+ protected HIDDeviceManager mManager;
+ protected UsbDevice mDevice;
+ protected int mInterface;
+ protected int mDeviceId;
+ protected UsbDeviceConnection mConnection;
+ protected UsbEndpoint mInputEndpoint;
+ protected UsbEndpoint mOutputEndpoint;
+ protected InputThread mInputThread;
+ protected boolean mRunning;
+ protected boolean mFrozen;
+
+ public HIDDeviceUSB(HIDDeviceManager manager, UsbDevice usbDevice, int interface_number) {
+ mManager = manager;
+ mDevice = usbDevice;
+ mInterface = interface_number;
+ mDeviceId = manager.getDeviceIDForIdentifier(getIdentifier());
+ mRunning = false;
+ }
+
+ public String getIdentifier() {
+ return String.format("%s/%x/%x", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId());
+ }
+
+ @Override
+ public int getId() {
+ return mDeviceId;
+ }
+
+ @Override
+ public int getVendorId() {
+ return mDevice.getVendorId();
+ }
+
+ @Override
+ public int getProductId() {
+ return mDevice.getProductId();
+ }
+
+ @Override
+ public String getSerialNumber() {
+ String result = null;
+ if (Build.VERSION.SDK_INT >= 21) {
+ result = mDevice.getSerialNumber();
+ }
+ if (result == null) {
+ result = "";
+ }
+ return result;
+ }
+
+ @Override
+ public int getVersion() {
+ return 0;
+ }
+
+ @Override
+ public String getManufacturerName() {
+ String result = null;
+ if (Build.VERSION.SDK_INT >= 21) {
+ result = mDevice.getManufacturerName();
+ }
+ if (result == null) {
+ result = String.format("%x", getVendorId());
+ }
+ return result;
+ }
+
+ @Override
+ public String getProductName() {
+ String result = null;
+ if (Build.VERSION.SDK_INT >= 21) {
+ result = mDevice.getProductName();
+ }
+ if (result == null) {
+ result = String.format("%x", getProductId());
+ }
+ return result;
+ }
+
+ public UsbDevice getDevice() {
+ return mDevice;
+ }
+
+ public String getDeviceName() {
+ return getManufacturerName() + " " + getProductName() + "(0x" + String.format("%x", getVendorId()) + "/0x" + String.format("%x", getProductId()) + ")";
+ }
+
+ @Override
+ public boolean open() {
+ mConnection = mManager.getUSBManager().openDevice(mDevice);
+ if (mConnection == null) {
+ Log.w(TAG, "Unable to open USB device " + getDeviceName());
+ return false;
+ }
+
+ // Force claim all interfaces
+ for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
+ UsbInterface iface = mDevice.getInterface(i);
+
+ if (!mConnection.claimInterface(iface, true)) {
+ Log.w(TAG, "Failed to claim interfaces on USB device " + getDeviceName());
+ close();
+ return false;
+ }
+ }
+
+ // Find the endpoints
+ UsbInterface iface = mDevice.getInterface(mInterface);
+ for (int j = 0; j < iface.getEndpointCount(); j++) {
+ UsbEndpoint endpt = iface.getEndpoint(j);
+ switch (endpt.getDirection()) {
+ case UsbConstants.USB_DIR_IN:
+ if (mInputEndpoint == null) {
+ mInputEndpoint = endpt;
+ }
+ break;
+ case UsbConstants.USB_DIR_OUT:
+ if (mOutputEndpoint == null) {
+ mOutputEndpoint = endpt;
+ }
+ break;
+ }
+ }
+
+ // Make sure the required endpoints were present
+ if (mInputEndpoint == null || mOutputEndpoint == null) {
+ Log.w(TAG, "Missing required endpoint on USB device " + getDeviceName());
+ close();
+ return false;
+ }
+
+ // Start listening for input
+ mRunning = true;
+ mInputThread = new InputThread();
+ mInputThread.start();
+
+ return true;
+ }
+
+ @Override
+ public int sendFeatureReport(byte[] report) {
+ int res = -1;
+ int offset = 0;
+ int length = report.length;
+ boolean skipped_report_id = false;
+ byte report_number = report[0];
+
+ if (report_number == 0x0) {
+ ++offset;
+ --length;
+ skipped_report_id = true;
+ }
+
+ res = mConnection.controlTransfer(
+ UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_OUT,
+ 0x09/*HID set_report*/,
+ (3/*HID feature*/ << 8) | report_number,
+ 0,
+ report, offset, length,
+ 1000/*timeout millis*/);
+
+ if (res < 0) {
+ Log.w(TAG, "sendFeatureReport() returned " + res + " on device " + getDeviceName());
+ return -1;
+ }
+
+ if (skipped_report_id) {
+ ++length;
+ }
+ return length;
+ }
+
+ @Override
+ public int sendOutputReport(byte[] report) {
+ int r = mConnection.bulkTransfer(mOutputEndpoint, report, report.length, 1000);
+ if (r != report.length) {
+ Log.w(TAG, "sendOutputReport() returned " + r + " on device " + getDeviceName());
+ }
+ return r;
+ }
+
+ @Override
+ public boolean getFeatureReport(byte[] report) {
+ int res = -1;
+ int offset = 0;
+ int length = report.length;
+ boolean skipped_report_id = false;
+ byte report_number = report[0];
+
+ if (report_number == 0x0) {
+ /* Offset the return buffer by 1, so that the report ID
+ will remain in byte 0. */
+ ++offset;
+ --length;
+ skipped_report_id = true;
+ }
+
+ res = mConnection.controlTransfer(
+ UsbConstants.USB_TYPE_CLASS | 0x01 /*RECIPIENT_INTERFACE*/ | UsbConstants.USB_DIR_IN,
+ 0x01/*HID get_report*/,
+ (3/*HID feature*/ << 8) | report_number,
+ 0,
+ report, offset, length,
+ 1000/*timeout millis*/);
+
+ if (res < 0) {
+ Log.w(TAG, "getFeatureReport() returned " + res + " on device " + getDeviceName());
+ return false;
+ }
+
+ if (skipped_report_id) {
+ ++res;
+ ++length;
+ }
+
+ byte[] data;
+ if (res == length) {
+ data = report;
+ } else {
+ data = Arrays.copyOfRange(report, 0, res);
+ }
+ mManager.HIDDeviceFeatureReport(mDeviceId, data);
+
+ return true;
+ }
+
+ @Override
+ public void close() {
+ mRunning = false;
+ if (mInputThread != null) {
+ while (mInputThread.isAlive()) {
+ mInputThread.interrupt();
+ try {
+ mInputThread.join();
+ } catch (InterruptedException e) {
+ // Keep trying until we're done
+ }
+ }
+ mInputThread = null;
+ }
+ if (mConnection != null) {
+ for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
+ UsbInterface iface = mDevice.getInterface(i);
+ mConnection.releaseInterface(iface);
+ }
+ mConnection.close();
+ mConnection = null;
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ close();
+ mManager = null;
+ }
+
+ @Override
+ public void setFrozen(boolean frozen) {
+ mFrozen = frozen;
+ }
+
+ protected class InputThread extends Thread {
+ @Override
+ public void run() {
+ int packetSize = mInputEndpoint.getMaxPacketSize();
+ byte[] packet = new byte[packetSize];
+ while (mRunning) {
+ int r;
+ try
+ {
+ r = mConnection.bulkTransfer(mInputEndpoint, packet, packetSize, 1000);
+ }
+ catch (Exception e)
+ {
+ Log.v(TAG, "Exception in UsbDeviceConnection bulktransfer: " + e);
+ break;
+ }
+ if (r < 0) {
+ // Could be a timeout or an I/O error
+ }
+ if (r > 0) {
+ byte[] data;
+ if (r == packetSize) {
+ data = packet;
+ } else {
+ data = Arrays.copyOfRange(packet, 0, r);
+ }
+
+ if (!mFrozen) {
+ mManager.HIDDeviceInputReport(mDeviceId, data);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDL.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDL.java
new file mode 100644
index 0000000000..fb7f7319a8
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDL.java
@@ -0,0 +1,84 @@
+package org.libsdl.app;
+
+import android.content.Context;
+
+import java.lang.reflect.*;
+
+/**
+ SDL library initialization
+*/
+public class SDL {
+
+ // This function should be called first and sets up the native code
+ // so it can call into the Java classes
+ public static void setupJNI() {
+ SDLActivity.nativeSetupJNI();
+ SDLAudioManager.nativeSetupJNI();
+ SDLControllerManager.nativeSetupJNI();
+ }
+
+ // This function should be called each time the activity is started
+ public static void initialize() {
+ setContext(null);
+
+ SDLActivity.initialize();
+ SDLAudioManager.initialize();
+ SDLControllerManager.initialize();
+ }
+
+ // This function stores the current activity (SDL or not)
+ public static void setContext(Context context) {
+ mContext = context;
+ }
+
+ public static Context getContext() {
+ return mContext;
+ }
+
+ public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
+
+ if (libraryName == null) {
+ throw new NullPointerException("No library name provided.");
+ }
+
+ try {
+ // Let's see if we have ReLinker available in the project. This is necessary for
+ // some projects that have huge numbers of local libraries bundled, and thus may
+ // trip a bug in Android's native library loader which ReLinker works around. (If
+ // loadLibrary works properly, ReLinker will simply use the normal Android method
+ // internally.)
+ //
+ // To use ReLinker, just add it as a dependency. For more information, see
+ // https://github.com/KeepSafe/ReLinker for ReLinker's repository.
+ //
+ Class relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
+ Class relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
+ Class contextClass = mContext.getClassLoader().loadClass("android.content.Context");
+ Class stringClass = mContext.getClassLoader().loadClass("java.lang.String");
+
+ // Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
+ // they've changed during updates.
+ Method forceMethod = relinkClass.getDeclaredMethod("force");
+ Object relinkInstance = forceMethod.invoke(null);
+ Class relinkInstanceClass = relinkInstance.getClass();
+
+ // Actually load the library!
+ Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
+ loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
+ }
+ catch (final Throwable e) {
+ // Fall back
+ try {
+ System.loadLibrary(libraryName);
+ }
+ catch (final UnsatisfiedLinkError ule) {
+ throw ule;
+ }
+ catch (final SecurityException se) {
+ throw se;
+ }
+ }
+ }
+
+ protected static Context mContext;
+}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java
index e1dc08468d..311b2f1df4 100644
--- a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLActivity.java
@@ -2,40 +2,74 @@
import java.io.IOException;
import java.io.InputStream;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
+import java.util.Hashtable;
import java.lang.reflect.Method;
+import java.lang.Math;
import android.app.*;
import android.content.*;
+import android.content.res.Configuration;
+import android.text.InputType;
import android.view.*;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.widget.AbsoluteLayout;
+import android.widget.RelativeLayout;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.os.*;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.graphics.*;
import android.graphics.drawable.Drawable;
-import android.media.*;
import android.hardware.*;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ApplicationInfo;
/**
SDL Activity
*/
-public class SDLActivity extends Activity {
+public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {
private static final String TAG = "SDL";
- // Keep track of the paused state
- public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
+ public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
+
+ // Cursor types
+ private static final int SDL_SYSTEM_CURSOR_NONE = -1;
+ private static final int SDL_SYSTEM_CURSOR_ARROW = 0;
+ private static final int SDL_SYSTEM_CURSOR_IBEAM = 1;
+ private static final int SDL_SYSTEM_CURSOR_WAIT = 2;
+ private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3;
+ private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4;
+ private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5;
+ private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6;
+ private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7;
+ private static final int SDL_SYSTEM_CURSOR_SIZENS = 8;
+ private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9;
+ private static final int SDL_SYSTEM_CURSOR_NO = 10;
+ private static final int SDL_SYSTEM_CURSOR_HAND = 11;
+
+ protected static final int SDL_ORIENTATION_UNKNOWN = 0;
+ protected static final int SDL_ORIENTATION_LANDSCAPE = 1;
+ protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2;
+ protected static final int SDL_ORIENTATION_PORTRAIT = 3;
+ protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4;
+
+ protected static int mCurrentOrientation;
+
+ // Handle the state of the native layer
+ public enum NativeState {
+ INIT, RESUMED, PAUSED
+ }
+
+ public static NativeState mNextNativeState;
+ public static NativeState mCurrentNativeState;
+
public static boolean mExitCalledFromJava;
/** If shared libraries (e.g. SDL or the native application) could not be loaded. */
@@ -49,14 +83,54 @@ public class SDLActivity extends Activity {
protected static SDLActivity mSingleton;
protected static SDLSurface mSurface;
protected static View mTextEdit;
+ protected static boolean mScreenKeyboardShown;
protected static ViewGroup mLayout;
- protected static SDLJoystickHandler mJoystickHandler;
+ protected static SDLClipboardHandler mClipboardHandler;
+ protected static Hashtable mCursors;
+ protected static int mLastCursorID;
+ protected static SDLGenericMotionListener_API12 mMotionListener;
+ protected static HIDDeviceManager mHIDDeviceManager;
// This is what SDL runs in. It invokes SDL_main(), eventually
protected static Thread mSDLThread;
- // Audio
- protected static AudioTrack mAudioTrack;
+ protected static SDLGenericMotionListener_API12 getMotionListener() {
+ if (mMotionListener == null) {
+ if (Build.VERSION.SDK_INT >= 26) {
+ mMotionListener = new SDLGenericMotionListener_API26();
+ } else
+ if (Build.VERSION.SDK_INT >= 24) {
+ mMotionListener = new SDLGenericMotionListener_API24();
+ } else {
+ mMotionListener = new SDLGenericMotionListener_API12();
+ }
+ }
+
+ return mMotionListener;
+ }
+
+ /**
+ * This method returns the name of the shared object with the application entry point
+ * It can be overridden by derived classes.
+ */
+ protected String getMainSharedObject() {
+ String library;
+ String[] libraries = SDLActivity.mSingleton.getLibraries();
+ if (libraries.length > 0) {
+ library = "lib" + libraries[libraries.length - 1] + ".so";
+ } else {
+ library = "libmain.so";
+ }
+ return getContext().getApplicationInfo().nativeLibraryDir + "/" + library;
+ }
+
+ /**
+ * This method returns the name of the application entry point
+ * It can be overridden by derived classes.
+ */
+ protected String getMainFunction() {
+ return "SDL_main";
+ }
/**
* This method is called by SDL before loading the native shared libraries.
@@ -80,7 +154,7 @@ protected String[] getLibraries() {
// Load the .so
public void loadLibraries() {
for (String lib : getLibraries()) {
- System.loadLibrary(lib);
+ SDL.loadLibrary(lib);
}
}
@@ -101,32 +175,27 @@ public static void initialize() {
mSurface = null;
mTextEdit = null;
mLayout = null;
- mJoystickHandler = null;
+ mClipboardHandler = null;
+ mCursors = new Hashtable();
+ mLastCursorID = 0;
mSDLThread = null;
- mAudioTrack = null;
mExitCalledFromJava = false;
mBrokenLibraries = false;
- mIsPaused = false;
+ mIsResumedCalled = false;
mIsSurfaceReady = false;
mHasFocus = true;
+ mNextNativeState = NativeState.INIT;
+ mCurrentNativeState = NativeState.INIT;
}
// Setup
@Override
protected void onCreate(Bundle savedInstanceState) {
- Log.v("SDL", "Device: " + android.os.Build.DEVICE);
- Log.v("SDL", "Model: " + android.os.Build.MODEL);
- Log.v("SDL", "onCreate():" + mSingleton);
+ Log.v(TAG, "Device: " + Build.DEVICE);
+ Log.v(TAG, "Model: " + Build.MODEL);
+ Log.v(TAG, "onCreate()");
super.onCreate(savedInstanceState);
- SDLActivity.initialize();
- // So we can call stuff from static callbacks
- mSingleton = this;
- }
-
- // We don't do this in onCreate because we unpack and load the app data on a thread
- // and we can't run setup tasks until that thread completes.
- protected void finishLoad() {
// Load shared libraries
String errorMsgBrokenLib = "";
try {
@@ -143,6 +212,7 @@ protected void finishLoad() {
if (mBrokenLibraries)
{
+ mSingleton = this;
AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
+ System.getProperty("line.separator")
@@ -163,52 +233,120 @@ public void onClick(DialogInterface dialog,int id) {
return;
}
- // Set up the surface
- mSurface = new SDLSurface(getApplication());
+ // Set up JNI
+ SDL.setupJNI();
- if(Build.VERSION.SDK_INT >= 12) {
- mJoystickHandler = new SDLJoystickHandler_API12();
- }
- else {
- mJoystickHandler = new SDLJoystickHandler();
+ // Initialize state
+ SDL.initialize();
+
+ // So we can call stuff from static callbacks
+ mSingleton = this;
+ SDL.setContext(this);
+
+ if (Build.VERSION.SDK_INT >= 11) {
+ mClipboardHandler = new SDLClipboardHandler_API11();
+ } else {
+ /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
+ mClipboardHandler = new SDLClipboardHandler_Old();
}
- mLayout = new AbsoluteLayout(this);
+ mHIDDeviceManager = HIDDeviceManager.acquire(this);
+
+ // Set up the surface
+ mSurface = new SDLSurface(getApplication());
+
+ mLayout = new RelativeLayout(this);
mLayout.addView(mSurface);
+ // Get our current screen orientation and pass it down.
+ mCurrentOrientation = SDLActivity.getCurrentOrientation();
+ SDLActivity.onNativeOrientationChanged(mCurrentOrientation);
+
setContentView(mLayout);
+
+ setWindowStyle(false);
+
+ getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);
+
+ // Get filename from "Open with" of another application
+ Intent intent = getIntent();
+ if (intent != null && intent.getData() != null) {
+ String filename = intent.getData().getPath();
+ if (filename != null) {
+ Log.v(TAG, "Got filename: " + filename);
+ SDLActivity.onNativeDropFile(filename);
+ }
+ }
}
// Events
@Override
protected void onPause() {
- Log.v("SDL", "onPause()");
+ Log.v(TAG, "onPause()");
super.onPause();
+ mNextNativeState = NativeState.PAUSED;
+ mIsResumedCalled = false;
if (SDLActivity.mBrokenLibraries) {
return;
}
- SDLActivity.handlePause();
+ if (mHIDDeviceManager != null) {
+ mHIDDeviceManager.setFrozen(true);
+ }
+
+ SDLActivity.handleNativeState();
}
@Override
protected void onResume() {
- Log.v("SDL", "onResume()");
+ Log.v(TAG, "onResume()");
super.onResume();
+ mNextNativeState = NativeState.RESUMED;
+ mIsResumedCalled = true;
if (SDLActivity.mBrokenLibraries) {
return;
}
- SDLActivity.handleResume();
+ if (mHIDDeviceManager != null) {
+ mHIDDeviceManager.setFrozen(false);
+ }
+
+ SDLActivity.handleNativeState();
}
+ public static int getCurrentOrientation() {
+ final Context context = SDLActivity.getContext();
+ final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+
+ int result = SDL_ORIENTATION_UNKNOWN;
+
+ switch (display.getRotation()) {
+ case Surface.ROTATION_0:
+ result = SDL_ORIENTATION_PORTRAIT;
+ break;
+
+ case Surface.ROTATION_90:
+ result = SDL_ORIENTATION_LANDSCAPE;
+ break;
+
+ case Surface.ROTATION_180:
+ result = SDL_ORIENTATION_PORTRAIT_FLIPPED;
+ break;
+
+ case Surface.ROTATION_270:
+ result = SDL_ORIENTATION_LANDSCAPE_FLIPPED;
+ break;
+ }
+
+ return result;
+ }
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
- Log.v("SDL", "onWindowFocusChanged(): " + hasFocus);
+ Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
if (SDLActivity.mBrokenLibraries) {
return;
@@ -216,13 +354,18 @@ public void onWindowFocusChanged(boolean hasFocus) {
SDLActivity.mHasFocus = hasFocus;
if (hasFocus) {
- SDLActivity.handleResume();
+ mNextNativeState = NativeState.RESUMED;
+ SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded();
+ } else {
+ mNextNativeState = NativeState.PAUSED;
}
+
+ SDLActivity.handleNativeState();
}
@Override
public void onLowMemory() {
- Log.v("SDL", "onLowMemory()");
+ Log.v(TAG, "onLowMemory()");
super.onLowMemory();
if (SDLActivity.mBrokenLibraries) {
@@ -234,7 +377,12 @@ public void onLowMemory() {
@Override
protected void onDestroy() {
- Log.v("SDL", "onDestroy()");
+ Log.v(TAG, "onDestroy()");
+
+ if (mHIDDeviceManager != null) {
+ HIDDeviceManager.release(mHIDDeviceManager);
+ mHIDDeviceManager = null;
+ }
if (SDLActivity.mBrokenLibraries) {
super.onDestroy();
@@ -243,6 +391,9 @@ protected void onDestroy() {
return;
}
+ mNextNativeState = NativeState.PAUSED;
+ SDLActivity.handleNativeState();
+
// Send a quit message to the application
SDLActivity.mExitCalledFromJava = true;
SDLActivity.nativeQuit();
@@ -252,19 +403,54 @@ protected void onDestroy() {
try {
SDLActivity.mSDLThread.join();
} catch(Exception e) {
- Log.v("SDL", "Problem stopping thread: " + e);
+ Log.v(TAG, "Problem stopping thread: " + e);
}
SDLActivity.mSDLThread = null;
- //Log.v("SDL", "Finished waiting for SDL thread");
+ //Log.v(TAG, "Finished waiting for SDL thread");
}
super.onDestroy();
+
// Reset everything in case the user re opens the app
SDLActivity.initialize();
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Check if we want to block the back button in case of mouse right click.
+ //
+ // If we do, the normal hardware back button will no longer work and people have to use home,
+ // but the mouse right click will work.
+ //
+ String trapBack = SDLActivity.nativeGetHint("SDL_ANDROID_TRAP_BACK_BUTTON");
+ if ((trapBack != null) && trapBack.equals("1")) {
+ // Exit and let the mouse handler handle this button (if appropriate)
+ return;
+ }
+
+ // Default system back button behavior.
+ super.onBackPressed();
+ }
+
+ // Called by JNI from SDL.
+ public static void manualBackButton() {
+ mSingleton.pressBackButton();
+ }
- // Completely closes application.
- System.exit(0);
+ // Used to get us onto the activity's main thread
+ public void pressBackButton() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ SDLActivity.this.superOnBackPressed();
+ }
+ });
+ }
+
+ // Used to access the system back behavior.
+ public void superOnBackPressed() {
+ super.onBackPressed();
}
@Override
@@ -279,53 +465,77 @@ public boolean dispatchKeyEvent(KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
keyCode == KeyEvent.KEYCODE_CAMERA ||
- keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
- keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
+ keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
+ keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
) {
return false;
}
return super.dispatchKeyEvent(event);
}
- /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed
- * is the first to be called, mIsSurfaceReady should still be set
- * to 'true' during the call to onPause (in a usual scenario).
- */
- public static void handlePause() {
- if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) {
- SDLActivity.mIsPaused = true;
- SDLActivity.nativePause();
- mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false);
+ /* Transition to next state */
+ public static void handleNativeState() {
+
+ if (mNextNativeState == mCurrentNativeState) {
+ // Already in same state, discard.
+ return;
}
- }
- /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready.
- * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume
- * every time we get one of those events, only if it comes after surfaceDestroyed
- */
- public static void handleResume() {
- if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) {
- SDLActivity.mIsPaused = false;
- SDLActivity.nativeResume();
- mSurface.handleResume();
+ // Try a transition to init state
+ if (mNextNativeState == NativeState.INIT) {
+
+ mCurrentNativeState = mNextNativeState;
+ return;
+ }
+
+ // Try a transition to paused state
+ if (mNextNativeState == NativeState.PAUSED) {
+ nativePause();
+ if (mSurface != null)
+ mSurface.handlePause();
+ mCurrentNativeState = mNextNativeState;
+ return;
+ }
+
+ // Try a transition to resumed state
+ if (mNextNativeState == NativeState.RESUMED) {
+ if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
+ if (mSDLThread == null) {
+ // This is the entry point to the C app.
+ // Start up the C app thread and enable sensor input for the first time
+ // FIXME: Why aren't we enabling sensor input at start?
+
+ mSDLThread = new Thread(new SDLMain(), "SDLThread");
+ mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
+ mSDLThread.start();
+ }
+
+ nativeResume();
+ mSurface.handleResume();
+ mCurrentNativeState = mNextNativeState;
+ }
}
}
/* The native thread has finished */
public static void handleNativeExit() {
SDLActivity.mSDLThread = null;
- mSingleton.finish();
+ if (mSingleton != null) {
+ mSingleton.finish();
+ }
}
// Messages from the SDLMain thread
static final int COMMAND_CHANGE_TITLE = 1;
- static final int COMMAND_UNUSED = 2;
+ static final int COMMAND_CHANGE_WINDOW_STYLE = 2;
static final int COMMAND_TEXTEDIT_HIDE = 3;
static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
protected static final int COMMAND_USER = 0x8000;
+ protected static boolean mFullscreenModeActive;
+
/**
* This method is called by SDL if SDL did not handle a message itself.
* This happens if a received message contains an unsupported command.
@@ -346,7 +556,7 @@ protected boolean onUnhandledMessage(int command, Object param) {
protected static class SDLCommandHandler extends Handler {
@Override
public void handleMessage(Message msg) {
- Context context = getContext();
+ Context context = SDL.getContext();
if (context == null) {
Log.e(TAG, "error handling message, getContext() returned null");
return;
@@ -359,22 +569,60 @@ public void handleMessage(Message msg) {
Log.e(TAG, "error handling message, getContext() returned no Activity");
}
break;
+ case COMMAND_CHANGE_WINDOW_STYLE:
+ if (Build.VERSION.SDK_INT < 19) {
+ // This version of Android doesn't support the immersive fullscreen mode
+ break;
+ }
+ if (context instanceof Activity) {
+ Window window = ((Activity) context).getWindow();
+ if (window != null) {
+ if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
+ int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;
+ window.getDecorView().setSystemUiVisibility(flags);
+ window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ SDLActivity.mFullscreenModeActive = true;
+ } else {
+ int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE;
+ window.getDecorView().setSystemUiVisibility(flags);
+ window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ SDLActivity.mFullscreenModeActive = false;
+ }
+ }
+ } else {
+ Log.e(TAG, "error handling message, getContext() returned no Activity");
+ }
+ break;
case COMMAND_TEXTEDIT_HIDE:
if (mTextEdit != null) {
- mTextEdit.setVisibility(View.GONE);
+ // Note: On some devices setting view to GONE creates a flicker in landscape.
+ // Setting the View's sizes to 0 is similar to GONE but without the flicker.
+ // The sizes will be set to useful values when the keyboard is shown again.
+ mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
+
+ mScreenKeyboardShown = false;
}
break;
case COMMAND_SET_KEEP_SCREEN_ON:
{
- Window window = ((Activity) context).getWindow();
- if (window != null) {
- if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- } else {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ if (context instanceof Activity) {
+ Window window = ((Activity) context).getWindow();
+ if (window != null) {
+ if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
}
}
break;
@@ -395,59 +643,216 @@ boolean sendCommand(int command, Object data) {
Message msg = commandHandler.obtainMessage();
msg.arg1 = command;
msg.obj = data;
- return commandHandler.sendMessage(msg);
+ boolean result = commandHandler.sendMessage(msg);
+
+ if ((Build.VERSION.SDK_INT >= 19) && (command == COMMAND_CHANGE_WINDOW_STYLE)) {
+ // Ensure we don't return until the resize has actually happened,
+ // or 500ms have passed.
+
+ boolean bShouldWait = false;
+
+ if (data instanceof Integer) {
+ // Let's figure out if we're already laid out fullscreen or not.
+ Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+ android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics();
+ display.getRealMetrics( realMetrics );
+
+ boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) &&
+ (realMetrics.heightPixels == mSurface.getHeight()));
+
+ if (((Integer)data).intValue() == 1) {
+ // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going
+ // to change size and should wait for surfaceChanged() before we return, so the size
+ // is right back in native code. If we're already laid out fullscreen, though, we're
+ // not going to change size even if we change decor modes, so we shouldn't wait for
+ // surfaceChanged() -- which may not even happen -- and should return immediately.
+ bShouldWait = !bFullscreenLayout;
+ }
+ else {
+ // If we're laid out fullscreen (even if the status bar and nav bar are present),
+ // or are actively in fullscreen, we're going to change size and should wait for
+ // surfaceChanged before we return, so the size is right back in native code.
+ bShouldWait = bFullscreenLayout;
+ }
+ }
+
+ if (bShouldWait) {
+ // We'll wait for the surfaceChanged() method, which will notify us
+ // when called. That way, we know our current size is really the
+ // size we need, instead of grabbing a size that's still got
+ // the navigation and/or status bars before they're hidden.
+ //
+ // We'll wait for up to half a second, because some devices
+ // take a surprisingly long time for the surface resize, but
+ // then we'll just give up and return.
+ //
+ synchronized(SDLActivity.getContext()) {
+ try {
+ SDLActivity.getContext().wait(500);
+ }
+ catch (InterruptedException ie) {
+ ie.printStackTrace();
+ }
+ }
+ }
+ }
+
+ return result;
}
// C functions we call
- public static native int nativeInit(Object arguments);
+ public static native int nativeSetupJNI();
+ public static native int nativeRunMain(String library, String function, Object arguments);
public static native void nativeLowMemory();
public static native void nativeQuit();
public static native void nativePause();
public static native void nativeResume();
- public static native void onNativeResize(int x, int y, int format, float rate);
- public static native int onNativePadDown(int device_id, int keycode);
- public static native int onNativePadUp(int device_id, int keycode);
- public static native void onNativeJoy(int device_id, int axis,
- float value);
- public static native void onNativeHat(int device_id, int hat_id,
- int x, int y);
- public static native void nativeSetEnv(String j_name, String j_value);
+ public static native void onNativeDropFile(String filename);
+ public static native void onNativeResize(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate);
public static native void onNativeKeyDown(int keycode);
public static native void onNativeKeyUp(int keycode);
public static native void onNativeKeyboardFocusLost();
- public static native void onNativeMouse(int button, int action, float x, float y);
+ public static native void onNativeMouse(int button, int action, float x, float y, boolean relative);
public static native void onNativeTouch(int touchDevId, int pointerFingerId,
int action, float x,
float y, float p);
public static native void onNativeAccel(float x, float y, float z);
+ public static native void onNativeClipboardChanged();
public static native void onNativeSurfaceChanged();
public static native void onNativeSurfaceDestroyed();
- public static native void nativeFlipBuffers();
- public static native int nativeAddJoystick(int device_id, String name,
- int is_accelerometer, int nbuttons,
- int naxes, int nhats, int nballs);
- public static native int nativeRemoveJoystick(int device_id);
public static native String nativeGetHint(String name);
+ public static native void nativeSetenv(String name, String value);
+ public static native void onNativeOrientationChanged(int orientation);
/**
* This method is called by SDL using JNI.
*/
- public static void flipBuffers() {
- SDLActivity.nativeFlipBuffers();
+ public static boolean setActivityTitle(String title) {
+ // Called from SDLMain() thread and can't directly affect the view
+ return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
}
/**
* This method is called by SDL using JNI.
*/
- public static boolean setActivityTitle(String title) {
+ public static void setWindowStyle(boolean fullscreen) {
// Called from SDLMain() thread and can't directly affect the view
- return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
+ mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ * This is a static method for JNI convenience, it calls a non-static method
+ * so that is can be overridden
+ */
+ public static void setOrientation(int w, int h, boolean resizable, String hint)
+ {
+ if (mSingleton != null) {
+ mSingleton.setOrientationBis(w, h, resizable, hint);
+ }
+ }
+
+ /**
+ * This can be overridden
+ */
+ public void setOrientationBis(int w, int h, boolean resizable, String hint)
+ {
+ int orientation = -1;
+
+ if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+ } else if (hint.contains("LandscapeRight")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+ } else if (hint.contains("LandscapeLeft")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+ } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ } else if (hint.contains("Portrait")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ } else if (hint.contains("PortraitUpsideDown")) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+ }
+
+ /* no valid hint */
+ if (orientation == -1) {
+ if (resizable) {
+ /* no fixed orientation */
+ } else {
+ if (w > h) {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+ } else {
+ orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ }
+ }
+ }
+
+ Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
+ if (orientation != -1) {
+ mSingleton.setRequestedOrientation(orientation);
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean isScreenKeyboardShown()
+ {
+ if (mTextEdit == null) {
+ return false;
+ }
+
+ if (!mScreenKeyboardShown) {
+ return false;
+ }
+
+ InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ return imm.isAcceptingText();
+
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean supportsRelativeMouse()
+ {
+ // ChromeOS doesn't provide relative mouse motion via the Android 7 APIs
+ if (isChromebook()) {
+ return false;
+ }
+
+ // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under
+ // Android 7 APIs, and simply returns no data under Android 8 APIs.
+ //
+ // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and
+ // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result,
+ // we should stick to relative mode.
+ //
+ if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) {
+ return false;
+ }
+
+ return SDLActivity.getMotionListener().supportsRelativeMouse();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean setRelativeMouseEnabled(boolean enabled)
+ {
+ if (enabled && !supportsRelativeMouse()) {
+ return false;
+ }
+
+ return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled);
}
/**
* This method is called by SDL using JNI.
*/
public static boolean sendMessage(int command, int param) {
+ if (mSingleton == null) {
+ return false;
+ }
return mSingleton.sendCommand(command, Integer.valueOf(param));
}
@@ -455,36 +860,105 @@ public static boolean sendMessage(int command, int param) {
* This method is called by SDL using JNI.
*/
public static Context getContext() {
- return mSingleton;
+ return SDL.getContext();
}
/**
* This method is called by SDL using JNI.
- * @return result of getSystemService(name) but executed on UI thread.
*/
- public Object getSystemServiceFromUiThread(final String name) {
- final Object lock = new Object();
- final Object[] results = new Object[2]; // array for writable variables
- synchronized (lock) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- synchronized (lock) {
- results[0] = getSystemService(name);
- results[1] = Boolean.TRUE;
- lock.notify();
- }
- }
- });
- if (results[1] == null) {
- try {
- lock.wait();
- } catch (InterruptedException ex) {
- ex.printStackTrace();
+ public static boolean isAndroidTV() {
+ UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+ return true;
+ }
+ if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) {
+ return true;
+ }
+ if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean isTablet() {
+ DisplayMetrics metrics = new DisplayMetrics();
+ Activity activity = (Activity)getContext();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+
+ double dWidthInches = metrics.widthPixels / (double)metrics.xdpi;
+ double dHeightInches = metrics.heightPixels / (double)metrics.ydpi;
+
+ double dDiagonal = Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches));
+
+ // If our diagonal size is seven inches or greater, we consider ourselves a tablet.
+ return (dDiagonal >= 7.0);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean isChromebook() {
+ return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean isDeXMode() {
+ if (Build.VERSION.SDK_INT < 24) {
+ return false;
+ }
+ try {
+ final Configuration config = getContext().getResources().getConfiguration();
+ final Class configClass = config.getClass();
+ return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass)
+ == configClass.getField("semDesktopModeEnabled").getInt(config);
+ } catch(Exception ignored) {
+ return false;
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static DisplayMetrics getDisplayDPI() {
+ return getContext().getResources().getDisplayMetrics();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean getManifestEnvironmentVariables() {
+ try {
+ ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
+ Bundle bundle = applicationInfo.metaData;
+ if (bundle == null) {
+ return false;
+ }
+ String prefix = "SDL_ENV.";
+ final int trimLength = prefix.length();
+ for (String key : bundle.keySet()) {
+ if (key.startsWith(prefix)) {
+ String name = key.substring(trimLength);
+ String value = bundle.get(key).toString();
+ nativeSetenv(name, value);
}
}
+ /* environment variables set! */
+ return true;
+ } catch (Exception e) {
+ Log.v("SDL", "exception " + e.toString());
}
- return results[0];
+ return false;
+ }
+
+ // This method is called by SDLControllerManager's API 26 Generic Motion Handler.
+ public static View getContentView()
+ {
+ return mSingleton.mLayout;
}
static class ShowTextInputTask implements Runnable {
@@ -506,11 +980,12 @@ public ShowTextInputTask(int x, int y, int w, int h) {
@Override
public void run() {
- AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams(
- w, h + HEIGHT_PADDING, x, y);
+ RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
+ params.leftMargin = x;
+ params.topMargin = y;
if (mTextEdit == null) {
- mTextEdit = new DummyEdit(getContext());
+ mTextEdit = new DummyEdit(SDL.getContext());
mLayout.addView(mTextEdit, params);
} else {
@@ -520,8 +995,10 @@ public void run() {
mTextEdit.setVisibility(View.VISIBLE);
mTextEdit.requestFocus();
- InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mTextEdit, 0);
+
+ mScreenKeyboardShown = true;
}
}
@@ -533,102 +1010,26 @@ public static boolean showTextInput(int x, int y, int w, int h) {
return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
}
- /**
- * This method is called by SDL using JNI.
- */
- public static Surface getNativeSurface() {
- return SDLActivity.mSurface.getNativeSurface();
- }
-
- // Audio
-
- /**
- * This method is called by SDL using JNI.
- */
- public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
- int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
- int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
- int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
-
- Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
- // Let the user pick a larger buffer if they really want -- but ye
- // gods they probably shouldn't, the minimums are horrifyingly high
- // latency already
- desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
-
- if (mAudioTrack == null) {
- mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
- channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
-
- // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
- // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
- // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
-
- if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
- Log.e("SDL", "Failed during initialization of Audio Track");
- mAudioTrack = null;
- return -1;
- }
-
- mAudioTrack.play();
+ public static boolean isTextInputEvent(KeyEvent event) {
+
+ // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
+ if (Build.VERSION.SDK_INT >= 11) {
+ if (event.isCtrlPressed()) {
+ return false;
+ }
}
- Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
-
- return 0;
+ return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
}
/**
* This method is called by SDL using JNI.
*/
- public static void audioWriteShortBuffer(short[] buffer) {
- for (int i = 0; i < buffer.length; ) {
- int result = mAudioTrack.write(buffer, i, buffer.length - i);
- if (result > 0) {
- i += result;
- } else if (result == 0) {
- try {
- Thread.sleep(1);
- } catch(InterruptedException e) {
- // Nom nom
- }
- } else {
- Log.w("SDL", "SDL audio: error return from write(short)");
- return;
- }
- }
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void audioWriteByteBuffer(byte[] buffer) {
- for (int i = 0; i < buffer.length; ) {
- int result = mAudioTrack.write(buffer, i, buffer.length - i);
- if (result > 0) {
- i += result;
- } else if (result == 0) {
- try {
- Thread.sleep(1);
- } catch(InterruptedException e) {
- // Nom nom
- }
- } else {
- Log.w("SDL", "SDL audio: error return from write(byte)");
- return;
- }
- }
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void audioQuit() {
- if (mAudioTrack != null) {
- mAudioTrack.stop();
- mAudioTrack = null;
+ public static Surface getNativeSurface() {
+ if (SDLActivity.mSurface == null) {
+ return null;
}
+ return SDLActivity.mSurface.getNativeSurface();
}
// Input
@@ -650,50 +1051,47 @@ public static int[] inputGetInputDeviceIds(int sources) {
return Arrays.copyOf(filtered, used);
}
- // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
- public static boolean handleJoystickMotionEvent(MotionEvent event) {
- return mJoystickHandler.handleMotionEvent(event);
- }
-
- /**
- * This method is called by SDL using JNI.
- */
- public static void pollInputDevices() {
- if (SDLActivity.mSDLThread != null) {
- mJoystickHandler.pollInputDevices();
- SDLActivity.mSingleton.keepActive();
- }
- }
-
- /**
- * Trick needed for loading screen
- */
- public void keepActive() {
- }
-
- // APK extension files support
+ // APK expansion files support
/** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
- private Object expansionFile;
+ private static Object expansionFile;
/** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
- private Method expansionFileMethod;
+ private static Method expansionFileMethod;
/**
* This method is called by SDL using JNI.
+ * @return an InputStream on success or null if no expansion file was used.
+ * @throws IOException on errors. Message is set for the SDL error message.
*/
- public InputStream openAPKExtensionInputStream(String fileName) throws IOException {
+ public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
// Get a ZipResourceFile representing a merger of both the main and patch files
if (expansionFile == null) {
- Integer mainVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"));
- Integer patchVersion = Integer.valueOf(nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"));
+ String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
+ if (mainHint == null) {
+ return null; // no expansion use if no main version was set
+ }
+ String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
+ if (patchHint == null) {
+ return null; // no expansion use if no patch version was set
+ }
+ Integer mainVersion;
+ Integer patchVersion;
try {
- // To avoid direct dependency on Google APK extension library that is
+ mainVersion = Integer.valueOf(mainHint);
+ patchVersion = Integer.valueOf(patchHint);
+ } catch (NumberFormatException ex) {
+ ex.printStackTrace();
+ throw new IOException("No valid file versions set for APK expansion files", ex);
+ }
+
+ try {
+ // To avoid direct dependency on Google APK expansion library that is
// not a part of Android SDK we access it using reflection
expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
.getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
- .invoke(null, this, mainVersion, patchVersion);
+ .invoke(null, SDL.getContext(), mainVersion, patchVersion);
expansionFileMethod = expansionFile.getClass()
.getMethod("getInputStream", String.class);
@@ -701,6 +1099,7 @@ public InputStream openAPKExtensionInputStream(String fileName) throws IOExcepti
ex.printStackTrace();
expansionFile = null;
expansionFileMethod = null;
+ throw new IOException("Could not access APK expansion support library", ex);
}
}
@@ -709,12 +1108,14 @@ public InputStream openAPKExtensionInputStream(String fileName) throws IOExcepti
try {
fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
} catch (Exception ex) {
+ // calling "getInputStream" failed
ex.printStackTrace();
- fileStream = null;
+ throw new IOException("Could not open stream from APK expansion file", ex);
}
if (fileStream == null) {
- throw new IOException();
+ // calling "getInputStream" was successful but null was returned
+ throw new IOException("Could not find path in APK expansion file");
}
return fileStream;
@@ -869,7 +1270,7 @@ public void onClick(View v) {
mapping.put(KeyEvent.KEYCODE_ENTER, button);
}
if ((buttonFlags[i] & 0x00000002) != 0) {
- mapping.put(111, button); /* API 11: KeyEvent.KEYCODE_ESCAPE */
+ mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
}
}
button.setText(buttonTexts[i]);
@@ -924,18 +1325,164 @@ public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
return dialog;
}
+
+ private final Runnable rehideSystemUi = new Runnable() {
+ @Override
+ public void run() {
+ int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
+ View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;
+
+ SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags);
+ }
+ };
+
+ public void onSystemUiVisibilityChange(int visibility) {
+ if (SDLActivity.mFullscreenModeActive && (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
+
+ Handler handler = getWindow().getDecorView().getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.
+ handler.postDelayed(rehideSystemUi, 2000);
+ }
+
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean clipboardHasText() {
+ return mClipboardHandler.clipboardHasText();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static String clipboardGetText() {
+ return mClipboardHandler.clipboardGetText();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void clipboardSetText(String string) {
+ mClipboardHandler.clipboardSetText(string);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {
+ Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
+ ++mLastCursorID;
+ // This requires API 24, so use reflection to implement this
+ try {
+ Class PointerIconClass = Class.forName("android.view.PointerIcon");
+ Class[] arg_types = new Class[] { Bitmap.class, float.class, float.class };
+ Method create = PointerIconClass.getMethod("create", arg_types);
+ mCursors.put(mLastCursorID, create.invoke(null, bitmap, hotSpotX, hotSpotY));
+ } catch (Exception e) {
+ return 0;
+ }
+ return mLastCursorID;
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean setCustomCursor(int cursorID) {
+ // This requires API 24, so use reflection to implement this
+ try {
+ Class PointerIconClass = Class.forName("android.view.PointerIcon");
+ Method setPointerIcon = SDLSurface.class.getMethod("setPointerIcon", PointerIconClass);
+ setPointerIcon.invoke(mSurface, mCursors.get(cursorID));
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static boolean setSystemCursor(int cursorID) {
+ int cursor_type = 0; //PointerIcon.TYPE_NULL;
+ switch (cursorID) {
+ case SDL_SYSTEM_CURSOR_ARROW:
+ cursor_type = 1000; //PointerIcon.TYPE_ARROW;
+ break;
+ case SDL_SYSTEM_CURSOR_IBEAM:
+ cursor_type = 1008; //PointerIcon.TYPE_TEXT;
+ break;
+ case SDL_SYSTEM_CURSOR_WAIT:
+ cursor_type = 1004; //PointerIcon.TYPE_WAIT;
+ break;
+ case SDL_SYSTEM_CURSOR_CROSSHAIR:
+ cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR;
+ break;
+ case SDL_SYSTEM_CURSOR_WAITARROW:
+ cursor_type = 1004; //PointerIcon.TYPE_WAIT;
+ break;
+ case SDL_SYSTEM_CURSOR_SIZENWSE:
+ cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
+ break;
+ case SDL_SYSTEM_CURSOR_SIZENESW:
+ cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
+ break;
+ case SDL_SYSTEM_CURSOR_SIZEWE:
+ cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+ break;
+ case SDL_SYSTEM_CURSOR_SIZENS:
+ cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
+ break;
+ case SDL_SYSTEM_CURSOR_SIZEALL:
+ cursor_type = 1020; //PointerIcon.TYPE_GRAB;
+ break;
+ case SDL_SYSTEM_CURSOR_NO:
+ cursor_type = 1012; //PointerIcon.TYPE_NO_DROP;
+ break;
+ case SDL_SYSTEM_CURSOR_HAND:
+ cursor_type = 1002; //PointerIcon.TYPE_HAND;
+ break;
+ }
+ // This requires API 24, so use reflection to implement this
+ try {
+ Class PointerIconClass = Class.forName("android.view.PointerIcon");
+ Class[] arg_types = new Class[] { Context.class, int.class };
+ Method getSystemIcon = PointerIconClass.getMethod("getSystemIcon", arg_types);
+ Method setPointerIcon = SDLSurface.class.getMethod("setPointerIcon", PointerIconClass);
+ setPointerIcon.invoke(mSurface, getSystemIcon.invoke(null, SDL.getContext(), cursor_type));
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
}
/**
- Simple nativeInit() runnable
+ Simple runnable to start the SDL application
*/
class SDLMain implements Runnable {
@Override
public void run() {
// Runs SDL_main()
- SDLActivity.nativeInit(SDLActivity.mSingleton.getArguments());
+ String library = SDLActivity.mSingleton.getMainSharedObject();
+ String function = SDLActivity.mSingleton.getMainFunction();
+ String[] arguments = SDLActivity.mSingleton.getArguments();
+
+ Log.v("SDL", "Running main function " + function + " from library " + library);
+ SDLActivity.nativeRunMain(library, function, arguments);
+
+ Log.v("SDL", "Finished main function");
- //Log.v("SDL", "SDL thread terminated");
+ // Native thread has finished, let's finish the Activity
+ if (!SDLActivity.mExitCalledFromJava) {
+ SDLActivity.handleNativeExit();
+ }
}
}
@@ -970,8 +1517,8 @@ public SDLSurface(Context context) {
mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- if(Build.VERSION.SDK_INT >= 12) {
- setOnGenericMotionListener(new SDLGenericMotionListener_API12());
+ if (Build.VERSION.SDK_INT >= 12) {
+ setOnGenericMotionListener(SDLActivity.getMotionListener());
}
// Some arbitrary defaults to avoid a potential division by zero
@@ -979,6 +1526,10 @@ public SDLSurface(Context context) {
mHeight = 1.0f;
}
+ public void handlePause() {
+ enableSensor(Sensor.TYPE_ACCELEROMETER, false);
+ }
+
public void handleResume() {
setFocusable(true);
setFocusableInTouchMode(true);
@@ -1003,8 +1554,11 @@ public void surfaceCreated(SurfaceHolder holder) {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.v("SDL", "surfaceDestroyed()");
- // Call this *before* setting mIsSurfaceReady to 'false'
- SDLActivity.handlePause();
+
+ // Transition to pause, if needed
+ SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
+ SDLActivity.handleNativeState();
+
SDLActivity.mIsSurfaceReady = false;
SDLActivity.onNativeSurfaceDestroyed();
}
@@ -1015,6 +1569,10 @@ public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
Log.v("SDL", "surfaceChanged()");
+ if (SDLActivity.mSingleton == null) {
+ return;
+ }
+
int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
switch (format) {
case PixelFormat.A_8:
@@ -1062,70 +1620,101 @@ public void surfaceChanged(SurfaceHolder holder,
mWidth = width;
mHeight = height;
- SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate());
- Log.v("SDL", "Window size:" + width + "x"+height);
+ int nDeviceWidth = width;
+ int nDeviceHeight = height;
+ try
+ {
+ if (Build.VERSION.SDK_INT >= 17) {
+ android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics();
+ mDisplay.getRealMetrics( realMetrics );
+ nDeviceWidth = realMetrics.widthPixels;
+ nDeviceHeight = realMetrics.heightPixels;
+ }
+ }
+ catch ( java.lang.Throwable throwable ) {}
- // Set mIsSurfaceReady to 'true' *before* making a call to handleResume
- SDLActivity.mIsSurfaceReady = true;
- SDLActivity.onNativeSurfaceChanged();
+ synchronized(SDLActivity.getContext()) {
+ // In case we're waiting on a size change after going fullscreen, send a notification.
+ SDLActivity.getContext().notifyAll();
+ }
+ Log.v("SDL", "Window size: " + width + "x" + height);
+ Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
+ SDLActivity.onNativeResize(width, height, nDeviceWidth, nDeviceHeight, sdlFormat, mDisplay.getRefreshRate());
- if (SDLActivity.mSDLThread == null) {
- // This is the entry point to the C app.
- // Start up the C app thread and enable sensor input for the first time
+ boolean skip = false;
+ int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
- final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
- enableSensor(Sensor.TYPE_ACCELEROMETER, true);
- sdlThread.start();
+ if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
+ {
+ // Accept any
+ }
+ else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
+ {
+ if (mWidth > mHeight) {
+ skip = true;
+ }
+ } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
+ if (mWidth < mHeight) {
+ skip = true;
+ }
+ }
- // Set up a listener thread to catch when the native thread ends
- SDLActivity.mSDLThread = new Thread(new Runnable(){
- @Override
- public void run(){
- try {
- sdlThread.join();
- }
- catch(Exception e){}
- finally{
- // Native thread has finished
- if (! SDLActivity.mExitCalledFromJava) {
- SDLActivity.handleNativeExit();
- }
- }
- }
- }, "SDLThreadListener");
- SDLActivity.mSDLThread.start();
+ // Special Patch for Square Resolution: Black Berry Passport
+ if (skip) {
+ double min = Math.min(mWidth, mHeight);
+ double max = Math.max(mWidth, mHeight);
+
+ if (max / min < 1.20) {
+ Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
+ skip = false;
+ }
}
- }
- // unused
- @Override
- public void onDraw(Canvas canvas) {}
+ if (skip) {
+ Log.v("SDL", "Skip .. Surface is not ready.");
+ SDLActivity.mIsSurfaceReady = false;
+ return;
+ }
+
+ /* Surface is ready */
+ SDLActivity.mIsSurfaceReady = true;
+ /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
+ SDLActivity.onNativeSurfaceChanged();
+
+ SDLActivity.handleNativeState();
+ }
// Key events
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// Dispatch the different events depending on where they come from
- // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
- // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD
-
- if ( (event.getSource() & InputDevice.SOURCE_GAMEPAD) != 0 ||
- (event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) {
+ // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
+ // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
+ //
+ // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
+ // SOURCE_JOYSTICK, while its key events arrive from the keyboard source
+ // So, retrieve the device itself and check all of its sources
+ if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
+ // Note that we process events with specific key codes here
if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
+ if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
return true;
}
} else if (event.getAction() == KeyEvent.ACTION_UP) {
- if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
+ if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
return true;
}
}
}
- if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
+ if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
//Log.v("SDL", "key down: " + keyCode);
+ if (SDLActivity.isTextInputEvent(event)) {
+ SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);
+ }
SDLActivity.onNativeKeyDown(keyCode);
return true;
}
@@ -1136,6 +1725,20 @@ else if (event.getAction() == KeyEvent.ACTION_UP) {
}
}
+ if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
+ // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
+ // they are ignored here because sending them as mouse input to SDL is messy
+ if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ case KeyEvent.ACTION_UP:
+ // mark the event as handled or it will be handled by system
+ // handling KEYCODE_BACK by system will call onBackPressed()
+ return true;
+ }
+ }
+ }
+
return false;
}
@@ -1152,9 +1755,10 @@ public boolean onTouch(View v, MotionEvent event) {
float x,y,p;
// !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
- if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) {
+ // 12290 = Samsung DeX mode desktop mouse
+ if ((event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == 12290) && SDLActivity.mSeparateMouseAndTouch) {
if (Build.VERSION.SDK_INT < 14) {
- mouseButton = 1; // For Android==12 all mouse buttons are the left button
+ mouseButton = 1; // all mouse buttons are the left button
} else {
try {
mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
@@ -1162,7 +1766,14 @@ public boolean onTouch(View v, MotionEvent event) {
mouseButton = 1; // oh well.
}
}
- SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0));
+
+ // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
+ // if we are. We'll leverage our existing mouse motion listener
+ SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
+ x = motionListener.getEventX(event);
+ y = motionListener.getEventY(event);
+
+ SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
} else {
switch(action) {
case MotionEvent.ACTION_MOVE:
@@ -1171,6 +1782,11 @@ public boolean onTouch(View v, MotionEvent event) {
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
+ if (p > 1.0f) {
+ // may be larger than 1.0f on some devices
+ // see the documentation of getPressure(i)
+ p = 1.0f;
+ }
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
}
break;
@@ -1190,6 +1806,11 @@ public boolean onTouch(View v, MotionEvent event) {
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
+ if (p > 1.0f) {
+ // may be larger than 1.0f on some devices
+ // see the documentation of getPressure(i)
+ p = 1.0f;
+ }
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
break;
@@ -1199,6 +1820,11 @@ public boolean onTouch(View v, MotionEvent event) {
x = event.getX(i) / mWidth;
y = event.getY(i) / mHeight;
p = event.getPressure(i);
+ if (p > 1.0f) {
+ // may be larger than 1.0f on some devices
+ // see the documentation of getPressure(i)
+ p = 1.0f;
+ }
SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
}
break;
@@ -1232,30 +1858,90 @@ public void onAccuracyChanged(Sensor sensor, int accuracy) {
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+
+ // Since we may have an orientation set, we won't receive onConfigurationChanged events.
+ // We thus should check here.
+ int newOrientation = SDLActivity.SDL_ORIENTATION_UNKNOWN;
+
float x, y;
switch (mDisplay.getRotation()) {
case Surface.ROTATION_90:
x = -event.values[1];
y = event.values[0];
+ newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
break;
case Surface.ROTATION_270:
x = event.values[1];
y = -event.values[0];
+ newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
break;
case Surface.ROTATION_180:
x = -event.values[1];
y = -event.values[0];
+ newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
break;
default:
x = event.values[0];
y = event.values[1];
+ newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
break;
}
+
+ if (newOrientation != SDLActivity.mCurrentOrientation) {
+ SDLActivity.mCurrentOrientation = newOrientation;
+ SDLActivity.onNativeOrientationChanged(newOrientation);
+ }
+
SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
y / SensorManager.GRAVITY_EARTH,
- event.values[2] / SensorManager.GRAVITY_EARTH - 1);
+ event.values[2] / SensorManager.GRAVITY_EARTH);
+
+
+ }
+ }
+
+ // Captured pointer events for API 26.
+ public boolean onCapturedPointerEvent(MotionEvent event)
+ {
+ int action = event.getActionMasked();
+
+ float x, y;
+ switch (action) {
+ case MotionEvent.ACTION_SCROLL:
+ x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
+ y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
+ SDLActivity.onNativeMouse(0, action, x, y, false);
+ return true;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_MOVE:
+ x = event.getX(0);
+ y = event.getY(0);
+ SDLActivity.onNativeMouse(0, action, x, y, true);
+ return true;
+
+ case MotionEvent.ACTION_BUTTON_PRESS:
+ case MotionEvent.ACTION_BUTTON_RELEASE:
+
+ // Change our action value to what SDL's code expects.
+ if (action == MotionEvent.ACTION_BUTTON_PRESS) {
+ action = MotionEvent.ACTION_DOWN;
+ }
+ else if (action == MotionEvent.ACTION_BUTTON_RELEASE) {
+ action = MotionEvent.ACTION_UP;
+ }
+
+ x = event.getX(0);
+ y = event.getY(0);
+ int button = event.getButtonState();
+
+ SDLActivity.onNativeMouse(button, action, x, y, true);
+ return true;
}
+
+ return false;
}
+
}
/* This is a fake invisible editor view that receives the input and defines the
@@ -1278,23 +1964,20 @@ public boolean onCheckIsTextEditor() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
-
- // This handles the hardware keyboard input
- if (event.isPrintingKey()) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ /*
+ * This handles the hardware keyboard input
+ */
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (SDLActivity.isTextInputEvent(event)) {
ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
+ return true;
}
- return true;
- }
-
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
SDLActivity.onNativeKeyDown(keyCode);
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
SDLActivity.onNativeKeyUp(keyCode);
return true;
}
-
return false;
}
@@ -1304,7 +1987,7 @@ public boolean onKeyPreIme (int keyCode, KeyEvent event) {
// As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
// FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
// FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
- // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear
+ // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
// FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
// FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
@@ -1319,8 +2002,9 @@ public boolean onKeyPreIme (int keyCode, KeyEvent event) {
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
ic = new SDLInputConnection(this, true);
+ outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
- | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */;
+ | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
return ic;
}
@@ -1335,30 +2019,43 @@ public SDLInputConnection(View targetView, boolean fullEditor) {
@Override
public boolean sendKeyEvent(KeyEvent event) {
+ /*
+ * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
+ * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses
+ * and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys
+ * that still do, we empty this out.
+ */
/*
- * This handles the keycodes from soft keyboard (and IME-translated
- * input from hardkeyboard)
+ * Return DOES still generate a key event, however. So rather than using it as the 'click a button' key
+ * as we do with physical keyboards, let's just use it to hide the keyboard.
*/
- int keyCode = event.getKeyCode();
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (event.isPrintingKey()) {
- commitText(String.valueOf((char) event.getUnicodeChar()), 1);
- }
- SDLActivity.onNativeKeyDown(keyCode);
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- SDLActivity.onNativeKeyUp(keyCode);
- return true;
+ if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
+ String imeHide = SDLActivity.nativeGetHint("SDL_RETURN_KEY_HIDES_IME");
+ if ((imeHide != null) && imeHide.equals("1")) {
+ Context c = SDL.getContext();
+ if (c instanceof SDLActivity) {
+ SDLActivity activity = (SDLActivity)c;
+ activity.sendCommand(SDLActivity.COMMAND_TEXTEDIT_HIDE, null);
+ return true;
+ }
+ }
}
+
+
return super.sendKeyEvent(event);
}
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
- nativeCommitText(text.toString(), newCursorPosition);
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ nativeGenerateScancodeForUnichar(c);
+ }
+
+ SDLInputConnection.nativeCommitText(text.toString(), newCursorPosition);
return super.commitText(text, newCursorPosition);
}
@@ -1371,209 +2068,107 @@ public boolean setComposingText(CharSequence text, int newCursorPosition) {
return super.setComposingText(text, newCursorPosition);
}
- public native void nativeCommitText(String text, int newCursorPosition);
+ public static native void nativeCommitText(String text, int newCursorPosition);
+
+ public native void nativeGenerateScancodeForUnichar(char c);
public native void nativeSetComposingText(String text, int newCursorPosition);
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
- if (beforeLength == 1 && afterLength == 0) {
- // backspace
- return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
- && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
+ // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
+ if (beforeLength > 0 && afterLength == 0) {
+ boolean ret = true;
+ // backspace(s)
+ while (beforeLength-- > 0) {
+ boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
+ && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
+ ret = ret && ret_key;
+ }
+ return ret;
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
}
-/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
-class SDLJoystickHandler {
+interface SDLClipboardHandler {
- /**
- * Handles given MotionEvent.
- * @param event the event to be handled.
- * @return if given event was processed.
- */
- public boolean handleMotionEvent(MotionEvent event) {
- return false;
- }
+ public boolean clipboardHasText();
+ public String clipboardGetText();
+ public void clipboardSetText(String string);
- /**
- * Handles adding and removing of input devices.
- */
- public void pollInputDevices() {
- }
}
-/* Actual joystick functionality available for API >= 12 devices */
-class SDLJoystickHandler_API12 extends SDLJoystickHandler {
- static class SDLJoystick {
- public int device_id;
- public String name;
- public ArrayList axes;
- public ArrayList hats;
- }
- static class RangeComparator implements Comparator {
- @Override
- public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
- return arg0.getAxis() - arg1.getAxis();
- }
- }
+class SDLClipboardHandler_API11 implements
+ SDLClipboardHandler,
+ android.content.ClipboardManager.OnPrimaryClipChangedListener {
- private ArrayList mJoysticks;
+ protected android.content.ClipboardManager mClipMgr;
- public SDLJoystickHandler_API12() {
-
- mJoysticks = new ArrayList();
+ SDLClipboardHandler_API11() {
+ mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ mClipMgr.addPrimaryClipChangedListener(this);
}
@Override
- public void pollInputDevices() {
- int[] deviceIds = InputDevice.getDeviceIds();
- // It helps processing the device ids in reverse order
- // For example, in the case of the XBox 360 wireless dongle,
- // so the first controller seen by SDL matches what the receiver
- // considers to be the first controller
-
- for(int i=deviceIds.length-1; i>-1; i--) {
- SDLJoystick joystick = getJoystick(deviceIds[i]);
- if (joystick == null) {
- joystick = new SDLJoystick();
- InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
- if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
- joystick.device_id = deviceIds[i];
- joystick.name = joystickDevice.getName();
- joystick.axes = new ArrayList();
- joystick.hats = new ArrayList();
-
- List ranges = joystickDevice.getMotionRanges();
- Collections.sort(ranges, new RangeComparator());
- for (InputDevice.MotionRange range : ranges ) {
- if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
- if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
- range.getAxis() == MotionEvent.AXIS_HAT_Y) {
- joystick.hats.add(range);
- }
- else {
- joystick.axes.add(range);
- }
- }
- }
-
- mJoysticks.add(joystick);
- SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1,
- joystick.axes.size(), joystick.hats.size()/2, 0);
- }
- }
- }
-
- /* Check removed devices */
- ArrayList removedDevices = new ArrayList();
- for(int i=0; i < mJoysticks.size(); i++) {
- int device_id = mJoysticks.get(i).device_id;
- int j;
- for (j=0; j < deviceIds.length; j++) {
- if (device_id == deviceIds[j]) break;
- }
- if (j == deviceIds.length) {
- removedDevices.add(Integer.valueOf(device_id));
- }
- }
-
- for(int i=0; i < removedDevices.size(); i++) {
- int device_id = removedDevices.get(i).intValue();
- SDLActivity.nativeRemoveJoystick(device_id);
- for (int j=0; j < mJoysticks.size(); j++) {
- if (mJoysticks.get(j).device_id == device_id) {
- mJoysticks.remove(j);
- break;
- }
- }
- }
+ public boolean clipboardHasText() {
+ return mClipMgr.hasText();
}
- protected SDLJoystick getJoystick(int device_id) {
- for(int i=0; i < mJoysticks.size(); i++) {
- if (mJoysticks.get(i).device_id == device_id) {
- return mJoysticks.get(i);
- }
+ @Override
+ public String clipboardGetText() {
+ CharSequence text;
+ text = mClipMgr.getText();
+ if (text != null) {
+ return text.toString();
}
return null;
}
@Override
- public boolean handleMotionEvent(MotionEvent event) {
- if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
- int actionPointerIndex = event.getActionIndex();
- int action = event.getActionMasked();
- switch(action) {
- case MotionEvent.ACTION_MOVE:
- SDLJoystick joystick = getJoystick(event.getDeviceId());
- if ( joystick != null ) {
- for (int i = 0; i < joystick.axes.size(); i++) {
- InputDevice.MotionRange range = joystick.axes.get(i);
- /* Normalize the value to -1...1 */
- float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
- SDLActivity.onNativeJoy(joystick.device_id, i, value );
- }
- for (int i = 0; i < joystick.hats.size(); i+=2) {
- int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
- int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
- SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY );
- }
- }
- break;
- default:
- break;
- }
- }
- return true;
+ public void clipboardSetText(String string) {
+ mClipMgr.removePrimaryClipChangedListener(this);
+ mClipMgr.setText(string);
+ mClipMgr.addPrimaryClipChangedListener(this);
}
-}
-
-class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
- // Generic Motion (mouse hover, joystick...) events go here
+
@Override
- public boolean onGenericMotion(View v, MotionEvent event) {
- float x, y;
- int mouseButton;
- int action;
-
- switch ( event.getSource() ) {
- case InputDevice.SOURCE_JOYSTICK:
- case InputDevice.SOURCE_GAMEPAD:
- case InputDevice.SOURCE_DPAD:
- SDLActivity.handleJoystickMotionEvent(event);
- return true;
-
- case InputDevice.SOURCE_MOUSE:
- action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_SCROLL:
- x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
- y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
- SDLActivity.onNativeMouse(0, action, x, y);
- return true;
+ public void onPrimaryClipChanged() {
+ SDLActivity.onNativeClipboardChanged();
+ }
- case MotionEvent.ACTION_HOVER_MOVE:
- x = event.getX(0);
- y = event.getY(0);
+}
- SDLActivity.onNativeMouse(0, action, x, y);
- return true;
+class SDLClipboardHandler_Old implements
+ SDLClipboardHandler {
+
+ protected android.text.ClipboardManager mClipMgrOld;
+
+ SDLClipboardHandler_Old() {
+ mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ }
- default:
- break;
- }
+ @Override
+ public boolean clipboardHasText() {
+ return mClipMgrOld.hasText();
+ }
- default:
- break;
- }
+ @Override
+ public String clipboardGetText() {
+ CharSequence text;
+ text = mClipMgrOld.getText();
+ if (text != null) {
+ return text.toString();
+ }
+ return null;
+ }
- // Event was not managed
- return false;
+ @Override
+ public void clipboardSetText(String string) {
+ mClipMgrOld.setText(string);
}
}
+
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLAudioManager.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLAudioManager.java
new file mode 100644
index 0000000000..bed0eb5c3c
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLAudioManager.java
@@ -0,0 +1,368 @@
+package org.libsdl.app;
+
+import android.media.*;
+import android.os.Build;
+import android.util.Log;
+
+public class SDLAudioManager
+{
+ protected static final String TAG = "SDLAudio";
+
+ protected static AudioTrack mAudioTrack;
+ protected static AudioRecord mAudioRecord;
+
+ public static void initialize() {
+ mAudioTrack = null;
+ mAudioRecord = null;
+ }
+
+ // Audio
+
+ protected static String getAudioFormatString(int audioFormat) {
+ switch (audioFormat) {
+ case AudioFormat.ENCODING_PCM_8BIT:
+ return "8-bit";
+ case AudioFormat.ENCODING_PCM_16BIT:
+ return "16-bit";
+ case AudioFormat.ENCODING_PCM_FLOAT:
+ return "float";
+ default:
+ return Integer.toString(audioFormat);
+ }
+ }
+
+ protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
+ int channelConfig;
+ int sampleSize;
+ int frameSize;
+
+ Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
+
+ /* On older devices let's use known good settings */
+ if (Build.VERSION.SDK_INT < 21) {
+ if (desiredChannels > 2) {
+ desiredChannels = 2;
+ }
+ if (sampleRate < 8000) {
+ sampleRate = 8000;
+ } else if (sampleRate > 48000) {
+ sampleRate = 48000;
+ }
+ }
+
+ if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
+ int minSDKVersion = (isCapture ? 23 : 21);
+ if (Build.VERSION.SDK_INT < minSDKVersion) {
+ audioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ }
+ }
+ switch (audioFormat)
+ {
+ case AudioFormat.ENCODING_PCM_8BIT:
+ sampleSize = 1;
+ break;
+ case AudioFormat.ENCODING_PCM_16BIT:
+ sampleSize = 2;
+ break;
+ case AudioFormat.ENCODING_PCM_FLOAT:
+ sampleSize = 4;
+ break;
+ default:
+ Log.v(TAG, "Requested format " + audioFormat + ", getting ENCODING_PCM_16BIT");
+ audioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ sampleSize = 2;
+ break;
+ }
+
+ if (isCapture) {
+ switch (desiredChannels) {
+ case 1:
+ channelConfig = AudioFormat.CHANNEL_IN_MONO;
+ break;
+ case 2:
+ channelConfig = AudioFormat.CHANNEL_IN_STEREO;
+ break;
+ default:
+ Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
+ desiredChannels = 2;
+ channelConfig = AudioFormat.CHANNEL_IN_STEREO;
+ break;
+ }
+ } else {
+ switch (desiredChannels) {
+ case 1:
+ channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+ break;
+ case 2:
+ channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+ break;
+ case 3:
+ channelConfig = AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
+ break;
+ case 4:
+ channelConfig = AudioFormat.CHANNEL_OUT_QUAD;
+ break;
+ case 5:
+ channelConfig = AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
+ break;
+ case 6:
+ channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
+ break;
+ case 7:
+ channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
+ break;
+ case 8:
+ if (Build.VERSION.SDK_INT >= 23) {
+ channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
+ } else {
+ Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
+ desiredChannels = 6;
+ channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
+ }
+ break;
+ default:
+ Log.v(TAG, "Requested " + desiredChannels + " channels, getting stereo");
+ desiredChannels = 2;
+ channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+ break;
+ }
+
+/*
+ Log.v(TAG, "Speaker configuration (and order of channels):");
+
+ if ((channelConfig & 0x00000004) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT");
+ }
+ if ((channelConfig & 0x00000008) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT");
+ }
+ if ((channelConfig & 0x00000010) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_FRONT_CENTER");
+ }
+ if ((channelConfig & 0x00000020) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_LOW_FREQUENCY");
+ }
+ if ((channelConfig & 0x00000040) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_BACK_LEFT");
+ }
+ if ((channelConfig & 0x00000080) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_BACK_RIGHT");
+ }
+ if ((channelConfig & 0x00000100) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
+ }
+ if ((channelConfig & 0x00000200) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
+ }
+ if ((channelConfig & 0x00000400) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_BACK_CENTER");
+ }
+ if ((channelConfig & 0x00000800) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_SIDE_LEFT");
+ }
+ if ((channelConfig & 0x00001000) != 0) {
+ Log.v(TAG, " CHANNEL_OUT_SIDE_RIGHT");
+ }
+*/
+ }
+ frameSize = (sampleSize * desiredChannels);
+
+ // Let the user pick a larger buffer if they really want -- but ye
+ // gods they probably shouldn't, the minimums are horrifyingly high
+ // latency already
+ int minBufferSize;
+ if (isCapture) {
+ minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
+ } else {
+ minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
+ }
+ desiredFrames = Math.max(desiredFrames, (minBufferSize + frameSize - 1) / frameSize);
+
+ int[] results = new int[4];
+
+ if (isCapture) {
+ if (mAudioRecord == null) {
+ mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, sampleRate,
+ channelConfig, audioFormat, desiredFrames * frameSize);
+
+ // see notes about AudioTrack state in audioOpen(), above. Probably also applies here.
+ if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
+ Log.e(TAG, "Failed during initialization of AudioRecord");
+ mAudioRecord.release();
+ mAudioRecord = null;
+ return null;
+ }
+
+ mAudioRecord.startRecording();
+ }
+
+ results[0] = mAudioRecord.getSampleRate();
+ results[1] = mAudioRecord.getAudioFormat();
+ results[2] = mAudioRecord.getChannelCount();
+ results[3] = desiredFrames;
+
+ } else {
+ if (mAudioTrack == null) {
+ mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
+
+ // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
+ // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
+ // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
+ if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
+ /* Try again, with safer values */
+
+ Log.e(TAG, "Failed during initialization of Audio Track");
+ mAudioTrack.release();
+ mAudioTrack = null;
+ return null;
+ }
+
+ mAudioTrack.play();
+ }
+
+ results[0] = mAudioTrack.getSampleRate();
+ results[1] = mAudioTrack.getAudioFormat();
+ results[2] = mAudioTrack.getChannelCount();
+ results[3] = desiredFrames;
+ }
+
+ Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", got " + results[3] + " frames of " + results[2] + " channel " + getAudioFormatString(results[1]) + " audio at " + results[0] + " Hz");
+
+ return results;
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
+ return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void audioWriteFloatBuffer(float[] buffer) {
+ if (mAudioTrack == null) {
+ Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
+ return;
+ }
+
+ for (int i = 0; i < buffer.length;) {
+ int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
+ if (result > 0) {
+ i += result;
+ } else if (result == 0) {
+ try {
+ Thread.sleep(1);
+ } catch(InterruptedException e) {
+ // Nom nom
+ }
+ } else {
+ Log.w(TAG, "SDL audio: error return from write(float)");
+ return;
+ }
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void audioWriteShortBuffer(short[] buffer) {
+ if (mAudioTrack == null) {
+ Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
+ return;
+ }
+
+ for (int i = 0; i < buffer.length;) {
+ int result = mAudioTrack.write(buffer, i, buffer.length - i);
+ if (result > 0) {
+ i += result;
+ } else if (result == 0) {
+ try {
+ Thread.sleep(1);
+ } catch(InterruptedException e) {
+ // Nom nom
+ }
+ } else {
+ Log.w(TAG, "SDL audio: error return from write(short)");
+ return;
+ }
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void audioWriteByteBuffer(byte[] buffer) {
+ if (mAudioTrack == null) {
+ Log.e(TAG, "Attempted to make audio call with uninitialized audio!");
+ return;
+ }
+
+ for (int i = 0; i < buffer.length; ) {
+ int result = mAudioTrack.write(buffer, i, buffer.length - i);
+ if (result > 0) {
+ i += result;
+ } else if (result == 0) {
+ try {
+ Thread.sleep(1);
+ } catch(InterruptedException e) {
+ // Nom nom
+ }
+ } else {
+ Log.w(TAG, "SDL audio: error return from write(byte)");
+ return;
+ }
+ }
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
+ return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
+ }
+
+ /** This method is called by SDL using JNI. */
+ public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
+ return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
+ }
+
+ /** This method is called by SDL using JNI. */
+ public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
+ if (Build.VERSION.SDK_INT < 23) {
+ return mAudioRecord.read(buffer, 0, buffer.length);
+ } else {
+ return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
+ }
+ }
+
+ /** This method is called by SDL using JNI. */
+ public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
+ if (Build.VERSION.SDK_INT < 23) {
+ return mAudioRecord.read(buffer, 0, buffer.length);
+ } else {
+ return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
+ }
+ }
+
+ /** This method is called by SDL using JNI. */
+ public static void audioClose() {
+ if (mAudioTrack != null) {
+ mAudioTrack.stop();
+ mAudioTrack.release();
+ mAudioTrack = null;
+ }
+ }
+
+ /** This method is called by SDL using JNI. */
+ public static void captureClose() {
+ if (mAudioRecord != null) {
+ mAudioRecord.stop();
+ mAudioRecord.release();
+ mAudioRecord = null;
+ }
+ }
+
+ public static native int nativeSetupJNI();
+}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLControllerManager.java b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLControllerManager.java
new file mode 100644
index 0000000000..e60023fa96
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/libsdl/app/SDLControllerManager.java
@@ -0,0 +1,846 @@
+package org.libsdl.app;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import android.content.Context;
+import android.os.*;
+import android.view.*;
+import android.util.Log;
+
+
+public class SDLControllerManager
+{
+
+ public static native int nativeSetupJNI();
+
+ public static native int nativeAddJoystick(int device_id, String name, String desc,
+ int vendor_id, int product_id,
+ boolean is_accelerometer, int button_mask,
+ int naxes, int nhats, int nballs);
+ public static native int nativeRemoveJoystick(int device_id);
+ public static native int nativeAddHaptic(int device_id, String name);
+ public static native int nativeRemoveHaptic(int device_id);
+ public static native int onNativePadDown(int device_id, int keycode);
+ public static native int onNativePadUp(int device_id, int keycode);
+ public static native void onNativeJoy(int device_id, int axis,
+ float value);
+ public static native void onNativeHat(int device_id, int hat_id,
+ int x, int y);
+
+ protected static SDLJoystickHandler mJoystickHandler;
+ protected static SDLHapticHandler mHapticHandler;
+
+ private static final String TAG = "SDLControllerManager";
+
+ public static void initialize() {
+ if (mJoystickHandler == null) {
+ if (Build.VERSION.SDK_INT >= 19) {
+ mJoystickHandler = new SDLJoystickHandler_API19();
+ } else if (Build.VERSION.SDK_INT >= 16) {
+ mJoystickHandler = new SDLJoystickHandler_API16();
+ } else if (Build.VERSION.SDK_INT >= 12) {
+ mJoystickHandler = new SDLJoystickHandler_API12();
+ } else {
+ mJoystickHandler = new SDLJoystickHandler();
+ }
+ }
+
+ if (mHapticHandler == null) {
+ if (Build.VERSION.SDK_INT >= 26) {
+ mHapticHandler = new SDLHapticHandler_API26();
+ } else {
+ mHapticHandler = new SDLHapticHandler();
+ }
+ }
+ }
+
+ // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
+ public static boolean handleJoystickMotionEvent(MotionEvent event) {
+ return mJoystickHandler.handleMotionEvent(event);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void pollInputDevices() {
+ mJoystickHandler.pollInputDevices();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void pollHapticDevices() {
+ mHapticHandler.pollHapticDevices();
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void hapticRun(int device_id, float intensity, int length) {
+ mHapticHandler.run(device_id, intensity, length);
+ }
+
+ /**
+ * This method is called by SDL using JNI.
+ */
+ public static void hapticStop(int device_id)
+ {
+ mHapticHandler.stop(device_id);
+ }
+
+ // Check if a given device is considered a possible SDL joystick
+ public static boolean isDeviceSDLJoystick(int deviceId) {
+ InputDevice device = InputDevice.getDevice(deviceId);
+ // We cannot use InputDevice.isVirtual before API 16, so let's accept
+ // only nonnegative device ids (VIRTUAL_KEYBOARD equals -1)
+ if ((device == null) || (deviceId < 0)) {
+ return false;
+ }
+ int sources = device.getSources();
+
+ /* This is called for every button press, so let's not spam the logs */
+ /**
+ if ((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) {
+ Log.v(TAG, "Input device " + device.getName() + " is a joystick.");
+ }
+ if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) {
+ Log.v(TAG, "Input device " + device.getName() + " is a dpad.");
+ }
+ if ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
+ Log.v(TAG, "Input device " + device.getName() + " is a gamepad.");
+ }
+ **/
+
+ return (((sources & InputDevice.SOURCE_CLASS_JOYSTICK) == InputDevice.SOURCE_CLASS_JOYSTICK) ||
+ ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) ||
+ ((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD)
+ );
+ }
+
+}
+
+/* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
+class SDLJoystickHandler {
+
+ /**
+ * Handles given MotionEvent.
+ * @param event the event to be handled.
+ * @return if given event was processed.
+ */
+ public boolean handleMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Handles adding and removing of input devices.
+ */
+ public void pollInputDevices() {
+ }
+}
+
+/* Actual joystick functionality available for API >= 12 devices */
+class SDLJoystickHandler_API12 extends SDLJoystickHandler {
+
+ static class SDLJoystick {
+ public int device_id;
+ public String name;
+ public String desc;
+ public ArrayList axes;
+ public ArrayList hats;
+ }
+ static class RangeComparator implements Comparator {
+ @Override
+ public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
+ // Some controllers, like the Moga Pro 2, return AXIS_GAS (22) for right trigger and AXIS_BRAKE (23) for left trigger - swap them so they're sorted in the right order for SDL
+ int arg0Axis = arg0.getAxis();
+ int arg1Axis = arg1.getAxis();
+ if (arg0Axis == MotionEvent.AXIS_GAS) {
+ arg0Axis = MotionEvent.AXIS_BRAKE;
+ } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
+ arg0Axis = MotionEvent.AXIS_GAS;
+ }
+ if (arg1Axis == MotionEvent.AXIS_GAS) {
+ arg1Axis = MotionEvent.AXIS_BRAKE;
+ } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
+ arg1Axis = MotionEvent.AXIS_GAS;
+ }
+
+ return arg0Axis - arg1Axis;
+ }
+ }
+
+ private ArrayList mJoysticks;
+
+ public SDLJoystickHandler_API12() {
+
+ mJoysticks = new ArrayList();
+ }
+
+ @Override
+ public void pollInputDevices() {
+ int[] deviceIds = InputDevice.getDeviceIds();
+ for(int i=0; i < deviceIds.length; ++i) {
+ SDLJoystick joystick = getJoystick(deviceIds[i]);
+ if (joystick == null) {
+ joystick = new SDLJoystick();
+ InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
+ if (SDLControllerManager.isDeviceSDLJoystick(deviceIds[i])) {
+ joystick.device_id = deviceIds[i];
+ joystick.name = joystickDevice.getName();
+ joystick.desc = getJoystickDescriptor(joystickDevice);
+ joystick.axes = new ArrayList();
+ joystick.hats = new ArrayList();
+
+ List ranges = joystickDevice.getMotionRanges();
+ Collections.sort(ranges, new RangeComparator());
+ for (InputDevice.MotionRange range : ranges ) {
+ if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
+ range.getAxis() == MotionEvent.AXIS_HAT_Y) {
+ joystick.hats.add(range);
+ }
+ else {
+ joystick.axes.add(range);
+ }
+ }
+ }
+
+ mJoysticks.add(joystick);
+ SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc, getVendorId(joystickDevice), getProductId(joystickDevice), false, getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
+ }
+ }
+ }
+
+ /* Check removed devices */
+ ArrayList removedDevices = new ArrayList();
+ for(int i=0; i < mJoysticks.size(); i++) {
+ int device_id = mJoysticks.get(i).device_id;
+ int j;
+ for (j=0; j < deviceIds.length; j++) {
+ if (device_id == deviceIds[j]) break;
+ }
+ if (j == deviceIds.length) {
+ removedDevices.add(Integer.valueOf(device_id));
+ }
+ }
+
+ for(int i=0; i < removedDevices.size(); i++) {
+ int device_id = removedDevices.get(i).intValue();
+ SDLControllerManager.nativeRemoveJoystick(device_id);
+ for (int j=0; j < mJoysticks.size(); j++) {
+ if (mJoysticks.get(j).device_id == device_id) {
+ mJoysticks.remove(j);
+ break;
+ }
+ }
+ }
+ }
+
+ protected SDLJoystick getJoystick(int device_id) {
+ for(int i=0; i < mJoysticks.size(); i++) {
+ if (mJoysticks.get(i).device_id == device_id) {
+ return mJoysticks.get(i);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean handleMotionEvent(MotionEvent event) {
+ if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
+ int actionPointerIndex = event.getActionIndex();
+ int action = event.getActionMasked();
+ switch(action) {
+ case MotionEvent.ACTION_MOVE:
+ SDLJoystick joystick = getJoystick(event.getDeviceId());
+ if ( joystick != null ) {
+ for (int i = 0; i < joystick.axes.size(); i++) {
+ InputDevice.MotionRange range = joystick.axes.get(i);
+ /* Normalize the value to -1...1 */
+ float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
+ SDLControllerManager.onNativeJoy(joystick.device_id, i, value );
+ }
+ for (int i = 0; i < joystick.hats.size(); i+=2) {
+ int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
+ int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
+ SDLControllerManager.onNativeHat(joystick.device_id, i/2, hatX, hatY );
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return true;
+ }
+
+ public String getJoystickDescriptor(InputDevice joystickDevice) {
+ return joystickDevice.getName();
+ }
+ public int getProductId(InputDevice joystickDevice) {
+ return 0;
+ }
+ public int getVendorId(InputDevice joystickDevice) {
+ return 0;
+ }
+ public int getButtonMask(InputDevice joystickDevice) {
+ return -1;
+ }
+}
+
+class SDLJoystickHandler_API16 extends SDLJoystickHandler_API12 {
+
+ @Override
+ public String getJoystickDescriptor(InputDevice joystickDevice) {
+ String desc = joystickDevice.getDescriptor();
+
+ if (desc != null && !desc.isEmpty()) {
+ return desc;
+ }
+
+ return super.getJoystickDescriptor(joystickDevice);
+ }
+}
+
+class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
+
+ @Override
+ public int getProductId(InputDevice joystickDevice) {
+ return joystickDevice.getProductId();
+ }
+
+ @Override
+ public int getVendorId(InputDevice joystickDevice) {
+ return joystickDevice.getVendorId();
+ }
+
+ @Override
+ public int getButtonMask(InputDevice joystickDevice) {
+ int button_mask = 0;
+ int[] keys = new int[] {
+ KeyEvent.KEYCODE_BUTTON_A,
+ KeyEvent.KEYCODE_BUTTON_B,
+ KeyEvent.KEYCODE_BUTTON_X,
+ KeyEvent.KEYCODE_BUTTON_Y,
+ KeyEvent.KEYCODE_BACK,
+ KeyEvent.KEYCODE_BUTTON_MODE,
+ KeyEvent.KEYCODE_BUTTON_START,
+ KeyEvent.KEYCODE_BUTTON_THUMBL,
+ KeyEvent.KEYCODE_BUTTON_THUMBR,
+ KeyEvent.KEYCODE_BUTTON_L1,
+ KeyEvent.KEYCODE_BUTTON_R1,
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.KEYCODE_BUTTON_SELECT,
+ KeyEvent.KEYCODE_DPAD_CENTER,
+
+ // These don't map into any SDL controller buttons directly
+ KeyEvent.KEYCODE_BUTTON_L2,
+ KeyEvent.KEYCODE_BUTTON_R2,
+ KeyEvent.KEYCODE_BUTTON_C,
+ KeyEvent.KEYCODE_BUTTON_Z,
+ KeyEvent.KEYCODE_BUTTON_1,
+ KeyEvent.KEYCODE_BUTTON_2,
+ KeyEvent.KEYCODE_BUTTON_3,
+ KeyEvent.KEYCODE_BUTTON_4,
+ KeyEvent.KEYCODE_BUTTON_5,
+ KeyEvent.KEYCODE_BUTTON_6,
+ KeyEvent.KEYCODE_BUTTON_7,
+ KeyEvent.KEYCODE_BUTTON_8,
+ KeyEvent.KEYCODE_BUTTON_9,
+ KeyEvent.KEYCODE_BUTTON_10,
+ KeyEvent.KEYCODE_BUTTON_11,
+ KeyEvent.KEYCODE_BUTTON_12,
+ KeyEvent.KEYCODE_BUTTON_13,
+ KeyEvent.KEYCODE_BUTTON_14,
+ KeyEvent.KEYCODE_BUTTON_15,
+ KeyEvent.KEYCODE_BUTTON_16,
+ };
+ int[] masks = new int[] {
+ (1 << 0), // A -> A
+ (1 << 1), // B -> B
+ (1 << 2), // X -> X
+ (1 << 3), // Y -> Y
+ (1 << 4), // BACK -> BACK
+ (1 << 5), // MODE -> GUIDE
+ (1 << 6), // START -> START
+ (1 << 7), // THUMBL -> LEFTSTICK
+ (1 << 8), // THUMBR -> RIGHTSTICK
+ (1 << 9), // L1 -> LEFTSHOULDER
+ (1 << 10), // R1 -> RIGHTSHOULDER
+ (1 << 11), // DPAD_UP -> DPAD_UP
+ (1 << 12), // DPAD_DOWN -> DPAD_DOWN
+ (1 << 13), // DPAD_LEFT -> DPAD_LEFT
+ (1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
+ (1 << 4), // SELECT -> BACK
+ (1 << 0), // DPAD_CENTER -> A
+ (1 << 15), // L2 -> ??
+ (1 << 16), // R2 -> ??
+ (1 << 17), // C -> ??
+ (1 << 18), // Z -> ??
+ (1 << 20), // 1 -> ??
+ (1 << 21), // 2 -> ??
+ (1 << 22), // 3 -> ??
+ (1 << 23), // 4 -> ??
+ (1 << 24), // 5 -> ??
+ (1 << 25), // 6 -> ??
+ (1 << 26), // 7 -> ??
+ (1 << 27), // 8 -> ??
+ (1 << 28), // 9 -> ??
+ (1 << 29), // 10 -> ??
+ (1 << 30), // 11 -> ??
+ (1 << 31), // 12 -> ??
+ // We're out of room...
+ 0xFFFFFFFF, // 13 -> ??
+ 0xFFFFFFFF, // 14 -> ??
+ 0xFFFFFFFF, // 15 -> ??
+ 0xFFFFFFFF, // 16 -> ??
+ };
+ boolean[] has_keys = joystickDevice.hasKeys(keys);
+ for (int i = 0; i < keys.length; ++i) {
+ if (has_keys[i]) {
+ button_mask |= masks[i];
+ }
+ }
+ return button_mask;
+ }
+}
+
+class SDLHapticHandler_API26 extends SDLHapticHandler {
+ @Override
+ public void run(int device_id, float intensity, int length) {
+ SDLHaptic haptic = getHaptic(device_id);
+ if (haptic != null) {
+ Log.d("SDL", "Rtest: Vibe with intensity " + intensity + " for " + length);
+ if (intensity == 0.0f) {
+ stop(device_id);
+ return;
+ }
+
+ int vibeValue = Math.round(intensity * 255);
+
+ if (vibeValue > 255) {
+ vibeValue = 255;
+ }
+ if (vibeValue < 1) {
+ stop(device_id);
+ return;
+ }
+ try {
+ haptic.vib.vibrate(VibrationEffect.createOneShot(length, vibeValue));
+ }
+ catch (Exception e) {
+ // Fall back to the generic method, which uses DEFAULT_AMPLITUDE, but works even if
+ // something went horribly wrong with the Android 8.0 APIs.
+ haptic.vib.vibrate(length);
+ }
+ }
+ }
+}
+
+class SDLHapticHandler {
+
+ class SDLHaptic {
+ public int device_id;
+ public String name;
+ public Vibrator vib;
+ }
+
+ private ArrayList mHaptics;
+
+ public SDLHapticHandler() {
+ mHaptics = new ArrayList();
+ }
+
+ public void run(int device_id, float intensity, int length) {
+ SDLHaptic haptic = getHaptic(device_id);
+ if (haptic != null) {
+ haptic.vib.vibrate(length);
+ }
+ }
+
+ public void stop(int device_id) {
+ SDLHaptic haptic = getHaptic(device_id);
+ if (haptic != null) {
+ haptic.vib.cancel();
+ }
+ }
+
+ public void pollHapticDevices() {
+
+ final int deviceId_VIBRATOR_SERVICE = 999999;
+ boolean hasVibratorService = false;
+
+ int[] deviceIds = InputDevice.getDeviceIds();
+ // It helps processing the device ids in reverse order
+ // For example, in the case of the XBox 360 wireless dongle,
+ // so the first controller seen by SDL matches what the receiver
+ // considers to be the first controller
+
+ if (Build.VERSION.SDK_INT >= 16)
+ {
+ for (int i = deviceIds.length - 1; i > -1; i--) {
+ SDLHaptic haptic = getHaptic(deviceIds[i]);
+ if (haptic == null) {
+ InputDevice device = InputDevice.getDevice(deviceIds[i]);
+ Vibrator vib = device.getVibrator();
+ if (vib.hasVibrator()) {
+ haptic = new SDLHaptic();
+ haptic.device_id = deviceIds[i];
+ haptic.name = device.getName();
+ haptic.vib = vib;
+ mHaptics.add(haptic);
+ SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
+ }
+ }
+ }
+ }
+
+ /* Check VIBRATOR_SERVICE */
+ Vibrator vib = (Vibrator) SDL.getContext().getSystemService(Context.VIBRATOR_SERVICE);
+ if (vib != null) {
+ if (Build.VERSION.SDK_INT >= 11) {
+ hasVibratorService = vib.hasVibrator();
+ } else {
+ hasVibratorService = true;
+ }
+
+ if (hasVibratorService) {
+ SDLHaptic haptic = getHaptic(deviceId_VIBRATOR_SERVICE);
+ if (haptic == null) {
+ haptic = new SDLHaptic();
+ haptic.device_id = deviceId_VIBRATOR_SERVICE;
+ haptic.name = "VIBRATOR_SERVICE";
+ haptic.vib = vib;
+ mHaptics.add(haptic);
+ SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
+ }
+ }
+ }
+
+ /* Check removed devices */
+ ArrayList removedDevices = new ArrayList();
+ for(int i=0; i < mHaptics.size(); i++) {
+ int device_id = mHaptics.get(i).device_id;
+ int j;
+ for (j=0; j < deviceIds.length; j++) {
+ if (device_id == deviceIds[j]) break;
+ }
+
+ if (device_id == deviceId_VIBRATOR_SERVICE && hasVibratorService) {
+ // don't remove the vibrator if it is still present
+ } else if (j == deviceIds.length) {
+ removedDevices.add(device_id);
+ }
+ }
+
+ for(int i=0; i < removedDevices.size(); i++) {
+ int device_id = removedDevices.get(i);
+ SDLControllerManager.nativeRemoveHaptic(device_id);
+ for (int j=0; j < mHaptics.size(); j++) {
+ if (mHaptics.get(j).device_id == device_id) {
+ mHaptics.remove(j);
+ break;
+ }
+ }
+ }
+ }
+
+ protected SDLHaptic getHaptic(int device_id) {
+ for(int i=0; i < mHaptics.size(); i++) {
+ if (mHaptics.get(i).device_id == device_id) {
+ return mHaptics.get(i);
+ }
+ }
+ return null;
+ }
+}
+
+class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
+ // Generic Motion (mouse hover, joystick...) events go here
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent event) {
+ float x, y;
+ int action;
+
+ switch ( event.getSource() ) {
+ case InputDevice.SOURCE_JOYSTICK:
+ case InputDevice.SOURCE_GAMEPAD:
+ case InputDevice.SOURCE_DPAD:
+ return SDLControllerManager.handleJoystickMotionEvent(event);
+
+ case InputDevice.SOURCE_MOUSE:
+ if (!SDLActivity.mSeparateMouseAndTouch) {
+ break;
+ }
+ action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_SCROLL:
+ x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
+ y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
+ SDLActivity.onNativeMouse(0, action, x, y, false);
+ return true;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ x = event.getX(0);
+ y = event.getY(0);
+
+ SDLActivity.onNativeMouse(0, action, x, y, false);
+ return true;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Event was not managed
+ return false;
+ }
+
+ public boolean supportsRelativeMouse() {
+ return false;
+ }
+
+ public boolean inRelativeMode() {
+ return false;
+ }
+
+ public boolean setRelativeMouseEnabled(boolean enabled) {
+ return false;
+ }
+
+ public void reclaimRelativeMouseModeIfNeeded()
+ {
+
+ }
+
+ public float getEventX(MotionEvent event) {
+ return event.getX(0);
+ }
+
+ public float getEventY(MotionEvent event) {
+ return event.getY(0);
+ }
+
+}
+
+class SDLGenericMotionListener_API24 extends SDLGenericMotionListener_API12 {
+ // Generic Motion (mouse hover, joystick...) events go here
+
+ private boolean mRelativeModeEnabled;
+
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent event) {
+ float x, y;
+ int action;
+
+ switch ( event.getSource() ) {
+ case InputDevice.SOURCE_JOYSTICK:
+ case InputDevice.SOURCE_GAMEPAD:
+ case InputDevice.SOURCE_DPAD:
+ return SDLControllerManager.handleJoystickMotionEvent(event);
+
+ case InputDevice.SOURCE_MOUSE:
+ if (!SDLActivity.mSeparateMouseAndTouch) {
+ break;
+ }
+ action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_SCROLL:
+ x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
+ y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
+ SDLActivity.onNativeMouse(0, action, x, y, false);
+ return true;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if (mRelativeModeEnabled) {
+ x = event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
+ y = event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
+ }
+ else {
+ x = event.getX(0);
+ y = event.getY(0);
+ }
+
+ SDLActivity.onNativeMouse(0, action, x, y, mRelativeModeEnabled);
+ return true;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Event was not managed
+ return false;
+ }
+
+ @Override
+ public boolean supportsRelativeMouse() {
+ return true;
+ }
+
+ @Override
+ public boolean inRelativeMode() {
+ return mRelativeModeEnabled;
+ }
+
+ @Override
+ public boolean setRelativeMouseEnabled(boolean enabled) {
+ mRelativeModeEnabled = enabled;
+ return true;
+ }
+
+ @Override
+ public float getEventX(MotionEvent event) {
+ if (mRelativeModeEnabled) {
+ return event.getAxisValue(MotionEvent.AXIS_RELATIVE_X);
+ }
+ else {
+ return event.getX(0);
+ }
+ }
+
+ @Override
+ public float getEventY(MotionEvent event) {
+ if (mRelativeModeEnabled) {
+ return event.getAxisValue(MotionEvent.AXIS_RELATIVE_Y);
+ }
+ else {
+ return event.getY(0);
+ }
+ }
+}
+
+
+class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
+ // Generic Motion (mouse hover, joystick...) events go here
+ private boolean mRelativeModeEnabled;
+
+ @Override
+ public boolean onGenericMotion(View v, MotionEvent event) {
+ float x, y;
+ int action;
+
+ switch ( event.getSource() ) {
+ case InputDevice.SOURCE_JOYSTICK:
+ case InputDevice.SOURCE_GAMEPAD:
+ case InputDevice.SOURCE_DPAD:
+ return SDLControllerManager.handleJoystickMotionEvent(event);
+
+ case InputDevice.SOURCE_MOUSE:
+ case 12290: // DeX desktop mouse cursor is a separate non-standard input type.
+ if (!SDLActivity.mSeparateMouseAndTouch) {
+ break;
+ }
+
+ action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_SCROLL:
+ x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
+ y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
+ SDLActivity.onNativeMouse(0, action, x, y, false);
+ return true;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ x = event.getX(0);
+ y = event.getY(0);
+ SDLActivity.onNativeMouse(0, action, x, y, false);
+ return true;
+
+ default:
+ break;
+ }
+ break;
+
+ case InputDevice.SOURCE_MOUSE_RELATIVE:
+ if (!SDLActivity.mSeparateMouseAndTouch) {
+ break;
+ }
+ action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_SCROLL:
+ x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
+ y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
+ SDLActivity.onNativeMouse(0, action, x, y, false);
+ return true;
+
+ case MotionEvent.ACTION_HOVER_MOVE:
+ x = event.getX(0);
+ y = event.getY(0);
+ SDLActivity.onNativeMouse(0, action, x, y, true);
+ return true;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Event was not managed
+ return false;
+ }
+
+ @Override
+ public boolean supportsRelativeMouse() {
+ return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
+ }
+
+ @Override
+ public boolean inRelativeMode() {
+ return mRelativeModeEnabled;
+ }
+
+ @Override
+ public boolean setRelativeMouseEnabled(boolean enabled) {
+ if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
+ if (enabled) {
+ SDLActivity.getContentView().requestPointerCapture();
+ }
+ else {
+ SDLActivity.getContentView().releasePointerCapture();
+ }
+ mRelativeModeEnabled = enabled;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public void reclaimRelativeMouseModeIfNeeded()
+ {
+ if (mRelativeModeEnabled && !SDLActivity.isDeXMode()) {
+ SDLActivity.getContentView().requestPointerCapture();
+ }
+ }
+
+ @Override
+ public float getEventX(MotionEvent event) {
+ // Relative mouse in capture mode will only have relative for X/Y
+ return event.getX(0);
+ }
+
+ @Override
+ public float getEventY(MotionEvent event) {
+ // Relative mouse in capture mode will only have relative for X/Y
+ return event.getY(0);
+ }
+}
diff --git a/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch b/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch
new file mode 100644
index 0000000000..27b97a7fbb
--- /dev/null
+++ b/pythonforandroid/bootstraps/sdl2/build/src/patches/SDLActivity.java.patch
@@ -0,0 +1,74 @@
+--- a/src/main/java/org/libsdl/app/SDLActivity.java
++++ b/src/main/java/org/libsdl/app/SDLActivity.java
+@@ -196,6 +196,15 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ Log.v(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+
++ SDLActivity.initialize();
++ // So we can call stuff from static callbacks
++ mSingleton = this;
++ }
++
++ // We don't do this in onCreate because we unpack and load the app data on a thread
++ // and we can't run setup tasks until that thread completes.
++ protected void finishLoad() {
++
+ // Load shared libraries
+ String errorMsgBrokenLib = "";
+ try {
+@@ -639,7 +648,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ Handler commandHandler = new SDLCommandHandler();
+
+ // Send a message from the SDLMain thread
+- boolean sendCommand(int command, Object data) {
++ protected boolean sendCommand(int command, Object data) {
+ Message msg = commandHandler.obtainMessage();
+ msg.arg1 = command;
+ msg.obj = data;
+@@ -1051,6 +1061,21 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ return Arrays.copyOf(filtered, used);
+ }
+
++ /**
++ * Calls turnActive() on singleton to keep loading screen active
++ */
++ public static void triggerAppConfirmedActive() {
++ mSingleton.appConfirmedActive();
++ }
++
++ /**
++ * Trick needed for loading screen, overridden by PythonActivity
++ * to keep loading screen active
++ */
++ public void appConfirmedActive() {
++ }
++
++
+ // APK expansion files support
+
+ /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
+@@ -1341,14 +1366,13 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
+ };
+
+ public void onSystemUiVisibilityChange(int visibility) {
+- if (SDLActivity.mFullscreenModeActive && (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
+-
++ // SDL2 BUGFIX (see sdl bug #4424 ) - REMOVE WHEN FIXED IN UPSTREAM !!
++ if (SDLActivity.mFullscreenModeActive && ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0)) {
+ Handler handler = getWindow().getDecorView().getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.
+ handler.postDelayed(rehideSystemUi, 2000);
+ }
+-
+ }
+ }
+
+@@ -1475,6 +1499,7 @@ class SDLMain implements Runnable {
+ String[] arguments = SDLActivity.mSingleton.getArguments();
+
+ Log.v("SDL", "Running main function " + function + " from library " + library);
++ SDLActivity.mSingleton.appConfirmedActive();
+ SDLActivity.nativeRunMain(library, function, arguments);
+
+ Log.v("SDL", "Finished main function");
diff --git a/pythonforandroid/bootstraps/service_only/__init__.py b/pythonforandroid/bootstraps/service_only/__init__.py
index 3b10e8e783..2175f8b61f 100644
--- a/pythonforandroid/bootstraps/service_only/__init__.py
+++ b/pythonforandroid/bootstraps/service_only/__init__.py
@@ -9,7 +9,9 @@ class ServiceOnlyBootstrap(Bootstrap):
name = 'service_only'
- recipe_depends = ['genericndkbuild', ('python2', 'python3', 'python3crystax')]
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'genericndkbuild'})
+ )
def run_distribute(self):
info_main('# Creating Android project from build and {} bootstrap'.format(
diff --git a/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk
index 6a8f1a65a2..0bc42bfb89 100644
--- a/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk
+++ b/pythonforandroid/bootstraps/service_only/build/jni/application/src/Android.mk
@@ -7,13 +7,13 @@ LOCAL_MODULE := main
# Add your application source files here...
LOCAL_SRC_FILES := start.c pyjniusjni.c
-LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../../other_builds/$(MK_PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
+LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
LOCAL_SHARED_LIBRARIES := python_shared
LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
-LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../../other_builds/$(MK_PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
+LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
index 9ca6bd1ec8..3390806041 100644
--- a/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
+++ b/pythonforandroid/bootstraps/service_only/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -74,6 +74,22 @@ public String getAppRoot() {
return app_root;
}
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.pyo|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List entryPoints = new ArrayList();
+ entryPoints.add("main.pyo"); // python 2 compiled files
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
public static void initialize() {
// The static nature of the singleton and Android quirkiness force us to initialize everything here
// Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
@@ -142,16 +158,17 @@ public void onClick(DialogInterface dialog,int id) {
// Set up the Python environment
String app_root_dir = getAppRoot();
String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ String entry_point = getEntryPoint(app_root_dir);
Log.v(TAG, "Setting env vars for start.c and Python to use");
- PythonActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo");
- PythonActivity.nativeSetEnv("ANDROID_ARGUMENT", app_root_dir);
- PythonActivity.nativeSetEnv("ANDROID_APP_PATH", app_root_dir);
- PythonActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory);
- PythonActivity.nativeSetEnv("ANDROID_UNPACK", app_root_dir);
- PythonActivity.nativeSetEnv("PYTHONHOME", app_root_dir);
- PythonActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
- PythonActivity.nativeSetEnv("PYTHONOPTIMIZE", "2");
+ PythonActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ PythonActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ PythonActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ PythonActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
try {
Log.v(TAG, "Access to our meta-data...");
@@ -368,9 +385,10 @@ public static void start_service(String serviceTitle, String serviceDescription,
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
String filesDirectory = argument;
String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
serviceIntent.putExtra("androidPrivate", argument);
serviceIntent.putExtra("androidArgument", app_root_dir);
- serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo");
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
serviceIntent.putExtra("pythonName", "python");
serviceIntent.putExtra("pythonHome", app_root_dir);
serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
@@ -386,7 +404,7 @@ public static void stop_service() {
}
- public static native void nativeSetEnv(String j_name, String j_value);
+ public static native void nativeSetenv(String name, String value);
public static native int nativeInit(Object arguments);
}
diff --git a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
index 137181a7f9..622fbffa02 100644
--- a/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
+++ b/pythonforandroid/bootstraps/service_only/build/templates/Service.tmpl.java
@@ -1,5 +1,7 @@
package {{ args.package }};
+import android.os.Binder;
+import android.os.IBinder;
import android.content.Intent;
import android.content.Context;
import org.kivy.android.PythonService;
@@ -31,7 +33,7 @@ public boolean getStartForeground() {
{% endif %}
public static void start(Context ctx, String pythonServiceArgument) {
- String argument = ctx.getFilesDir().getAbsolutePath();
+ String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
intent.putExtra("androidPrivate", argument);
intent.putExtra("androidArgument", argument);
@@ -45,7 +47,7 @@ public static void start(Context ctx, String pythonServiceArgument) {
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
ctx.startService(intent);
}
-
+
public static void stop(Context ctx) {
Intent intent = new Intent(ctx, Service{{ name|capitalize }}.class);
ctx.stopService(intent);
@@ -58,7 +60,7 @@ public static void stop(Context ctx) {
public IBinder onBind(Intent intent) {
return mBinder;
}
-
+
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
diff --git a/pythonforandroid/bootstraps/webview/__init__.py b/pythonforandroid/bootstraps/webview/__init__.py
index 21d0da5996..c59c158356 100644
--- a/pythonforandroid/bootstraps/webview/__init__.py
+++ b/pythonforandroid/bootstraps/webview/__init__.py
@@ -7,7 +7,9 @@
class WebViewBootstrap(Bootstrap):
name = 'webview'
- recipe_depends = ['genericndkbuild', ('python2', 'python3', 'python3crystax')]
+ recipe_depends = list(
+ set(Bootstrap.recipe_depends).union({'genericndkbuild'})
+ )
def run_distribute(self):
info_main('# Creating Android project from build and {} bootstrap'.format(
diff --git a/pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk
index b1403ec110..20399573c9 100644
--- a/pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk
+++ b/pythonforandroid/bootstraps/webview/build/jni/application/src/Android.mk
@@ -9,13 +9,13 @@ LOCAL_MODULE := main
# Add your application source files here...
LOCAL_SRC_FILES := start.c pyjniusjni.c
-LOCAL_CFLAGS += -I$(LOCAL_PATH)/../../../../../other_builds/$(MK_PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
+LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)
LOCAL_SHARED_LIBRARIES := python_shared
LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)
-LOCAL_LDFLAGS += -L$(LOCAL_PATH)/../../../../../other_builds/$(MK_PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
+LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)
include $(BUILD_SHARED_LIBRARY)
diff --git a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
index a62fc216c9..1a37bc672f 100644
--- a/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
+++ b/pythonforandroid/bootstraps/webview/build/src/main/java/org/kivy/android/PythonActivity.java
@@ -81,6 +81,22 @@ public String getAppRoot() {
return app_root;
}
+ public String getEntryPoint(String search_dir) {
+ /* Get the main file (.pyc|.pyo|.py) depending on if we
+ * have a compiled version or not.
+ */
+ List entryPoints = new ArrayList();
+ entryPoints.add("main.pyo"); // python 2 compiled files
+ entryPoints.add("main.pyc"); // python 3 compiled files
+ for (String value : entryPoints) {
+ File mainFile = new File(search_dir + "/" + value);
+ if (mainFile.exists()) {
+ return value;
+ }
+ }
+ return "main.py";
+ }
+
public static void initialize() {
// The static nature of the singleton and Android quirkyness force us to initialize everything here
// Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
@@ -170,16 +186,17 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
setContentView(mLayout);
String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath();
+ String entry_point = getEntryPoint(app_root_dir);
Log.v(TAG, "Setting env vars for start.c and Python to use");
- PythonActivity.nativeSetEnv("ANDROID_ENTRYPOINT", "main.pyo");
- PythonActivity.nativeSetEnv("ANDROID_ARGUMENT", app_root_dir);
- PythonActivity.nativeSetEnv("ANDROID_APP_PATH", app_root_dir);
- PythonActivity.nativeSetEnv("ANDROID_PRIVATE", mFilesDirectory);
- PythonActivity.nativeSetEnv("ANDROID_UNPACK", app_root_dir);
- PythonActivity.nativeSetEnv("PYTHONHOME", app_root_dir);
- PythonActivity.nativeSetEnv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
- PythonActivity.nativeSetEnv("PYTHONOPTIMIZE", "2");
+ PythonActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point);
+ PythonActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir);
+ PythonActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory);
+ PythonActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONHOME", app_root_dir);
+ PythonActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib");
+ PythonActivity.nativeSetenv("PYTHONOPTIMIZE", "2");
try {
Log.v(TAG, "Access to our meta-data...");
@@ -425,9 +442,10 @@ public static void start_service(String serviceTitle, String serviceDescription,
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
String filesDirectory = argument;
String app_root_dir = PythonActivity.mActivity.getAppRoot();
+ String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service");
serviceIntent.putExtra("androidPrivate", argument);
serviceIntent.putExtra("androidArgument", app_root_dir);
- serviceIntent.putExtra("serviceEntrypoint", "service/main.pyo");
+ serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point);
serviceIntent.putExtra("pythonName", "python");
serviceIntent.putExtra("pythonHome", app_root_dir);
serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib");
@@ -443,7 +461,7 @@ public static void stop_service() {
}
- public static native void nativeSetEnv(String j_name, String j_value);
+ public static native void nativeSetenv(String name, String value);
public static native int nativeInit(Object arguments);
}
diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py
index 23a4392589..734103b20c 100644
--- a/pythonforandroid/build.py
+++ b/pythonforandroid/build.py
@@ -11,14 +11,16 @@
import sh
import subprocess
-from pythonforandroid.util import (ensure_dir, current_directory, BuildInterruptingException)
+from pythonforandroid.util import (
+ current_directory, ensure_dir, get_virtualenv_executable,
+ BuildInterruptingException
+)
from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint)
from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64
from pythonforandroid.recipe import CythonRecipe, Recipe
-
-DEFAULT_ANDROID_API = 15
-
-DEFAULT_NDK_API = 21
+from pythonforandroid.recommendations import (
+ check_ndk_version, check_target_api, check_ndk_api,
+ RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
class Context(object):
@@ -140,19 +142,6 @@ def ndk_api(self):
def ndk_api(self, value):
self._ndk_api = value
- @property
- def ndk_ver(self):
- '''The version of the NDK being used for compilation.'''
- if self._ndk_ver is None:
- raise ValueError('Tried to access ndk_ver but it has not '
- 'been set - this should not happen, something '
- 'went wrong!')
- return self._ndk_ver
-
- @ndk_ver.setter
- def ndk_ver(self, value):
- self._ndk_ver = value
-
@property
def sdk_dir(self):
'''The path to the Android SDK.'''
@@ -183,7 +172,6 @@ def prepare_build_environment(self,
user_sdk_dir,
user_ndk_dir,
user_android_api,
- user_ndk_ver,
user_ndk_api):
'''Checks that build dependencies exist and sets internal variables
for the Android SDK etc.
@@ -237,17 +225,12 @@ def prepare_build_environment(self,
info('Found Android API target in $ANDROIDAPI: {}'.format(android_api))
else:
info('Android API target was not set manually, using '
- 'the default of {}'.format(DEFAULT_ANDROID_API))
- android_api = DEFAULT_ANDROID_API
+ 'the default of {}'.format(RECOMMENDED_TARGET_API))
+ android_api = RECOMMENDED_TARGET_API
android_api = int(android_api)
self.android_api = android_api
- if self.android_api >= 21 and self.archs[0].arch == 'armeabi':
- raise BuildInterruptingException(
- 'Asked to build for armeabi architecture with API '
- '{}, but API 21 or greater does not support armeabi'.format(
- self.android_api),
- instructions='You probably want to build with --arch=armeabi-v7a instead')
+ check_target_api(android_api, self.archs[0].arch)
if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
@@ -306,47 +289,7 @@ def prepare_build_environment(self,
raise BuildInterruptingException('Android NDK dir was not specified')
self.ndk_dir = realpath(ndk_dir)
- # Find the NDK version, and check it against what the NDK dir
- # seems to report
- ndk_ver = None
- if user_ndk_ver:
- ndk_ver = user_ndk_ver
- if ndk_dir is not None:
- info('Got NDK version from from user argument: {}'.format(ndk_ver))
- if ndk_ver is None:
- ndk_ver = environ.get('ANDROIDNDKVER', None)
- if ndk_ver is not None:
- info('Got NDK version from $ANDROIDNDKVER: {}'.format(ndk_ver))
-
- self.ndk = 'google'
-
- try:
- with open(join(ndk_dir, 'RELEASE.TXT')) as fileh:
- reported_ndk_ver = fileh.read().split(' ')[0].strip()
- except IOError:
- pass
- else:
- if reported_ndk_ver.startswith('crystax-ndk-'):
- reported_ndk_ver = reported_ndk_ver[12:]
- self.ndk = 'crystax'
- if ndk_ver is None:
- ndk_ver = reported_ndk_ver
- info(('Got Android NDK version from the NDK dir: {}').format(ndk_ver))
- else:
- if ndk_ver != reported_ndk_ver:
- warning('NDK version was set as {}, but checking '
- 'the NDK dir claims it is {}.'.format(
- ndk_ver, reported_ndk_ver))
- warning('The build will try to continue, but it may '
- 'fail and you should check '
- 'that your setting is correct.')
- warning('If the NDK dir result is correct, you don\'t '
- 'need to manually set the NDK ver.')
- if ndk_ver is None:
- warning('Android NDK version could not be found. This probably'
- 'won\'t cause any problems, but if necessary you can'
- 'set it with `--ndk-version=...`.')
- self.ndk_ver = ndk_ver
+ check_ndk_version(ndk_dir)
ndk_api = None
if user_ndk_api:
@@ -356,29 +299,16 @@ def prepare_build_environment(self,
ndk_api = environ.get('NDKAPI', None)
info('Found Android API target in $NDKAPI')
else:
- ndk_api = min(self.android_api, DEFAULT_NDK_API)
+ ndk_api = min(self.android_api, RECOMMENDED_NDK_API)
warning('NDK API target was not set manually, using '
'the default of {} = min(android-api={}, default ndk-api={})'.format(
- ndk_api, self.android_api, DEFAULT_NDK_API))
+ ndk_api, self.android_api, RECOMMENDED_NDK_API))
ndk_api = int(ndk_api)
self.ndk_api = ndk_api
- if self.ndk_api > self.android_api:
- raise BuildInterruptingException(
- 'Target NDK API is {}, higher than the target Android API {}.'.format(
- self.ndk_api, self.android_api),
- instructions=('The NDK API is a minimum supported API number and must be lower '
- 'than the target Android API'))
-
- info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver))
+ check_ndk_api(ndk_api, self.android_api)
- virtualenv = None
- if virtualenv is None:
- virtualenv = sh.which('virtualenv2')
- if virtualenv is None:
- virtualenv = sh.which('virtualenv-2.7')
- if virtualenv is None:
- virtualenv = sh.which('virtualenv')
+ virtualenv = get_virtualenv_executable()
if virtualenv is None:
raise IOError('Couldn\'t find a virtualenv executable, '
'you must install this to use p4a.')
@@ -483,7 +413,6 @@ def __init__(self):
self._ndk_dir = None
self._android_api = None
self._ndk_api = None
- self._ndk_ver = None
self.ndk = None
self.toolchain_prefix = None
@@ -577,7 +506,7 @@ def has_package(self, name, arch=None):
# Try to look up recipe by name:
try:
recipe = Recipe.get_recipe(name, self)
- except IOError:
+ except ValueError:
pass
else:
name = getattr(recipe, 'site_packages_name', None) or name
@@ -596,7 +525,6 @@ def not_has_package(self, name, arch=None):
def build_recipes(build_order, python_modules, ctx):
# Put recipes in correct build order
- bs = ctx.bootstrap
info_notify("Recipe build order is {}".format(build_order))
if python_modules:
python_modules = sorted(set(python_modules))
@@ -687,14 +615,15 @@ def run_pymodules_install(ctx, modules):
line = '{}\n'.format(module)
fileh.write(line)
+ # Prepare base environment and upgrade pip:
base_env = copy.copy(os.environ)
base_env["PYTHONPATH"] = ctx.get_site_packages_dir()
-
info('Upgrade pip to latest version')
shprint(sh.bash, '-c', (
"source venv/bin/activate && pip install -U pip"
), _env=copy.copy(base_env))
+ # Install Cython in case modules need it to build:
info('Install Cython in case one of the modules needs it to build')
shprint(sh.bash, '-c', (
"venv/bin/pip install Cython"
@@ -717,15 +646,17 @@ def run_pymodules_install(ctx, modules):
'changes / workarounds.')
# Make sure our build package dir is available, and the virtualenv
- # site packages come FIRST (for the proper pip version):
+ # site packages come FIRST (so the proper pip version is used):
env["PYTHONPATH"] += ":" + ctx.get_site_packages_dir()
env["PYTHONPATH"] = os.path.abspath(join(
ctx.build_dir, "venv", "lib",
"python" + ctx.python_recipe.major_minor_version_string,
"site-packages")) + ":" + env["PYTHONPATH"]
+
+ # Do actual install:
shprint(sh.bash, '-c', (
- "source venv/bin/activate && " +
- "pip install -v --target '{0}' --no-deps -r requirements.txt"
+ "venv/bin/pip " +
+ "install -v --target '{0}' --no-deps -r requirements.txt"
).format(ctx.get_site_packages_dir().replace("'", "'\"'\"'")),
_env=copy.copy(env))
diff --git a/pythonforandroid/distribution.py b/pythonforandroid/distribution.py
index 9aa10ee82f..9fa7b4c6b2 100644
--- a/pythonforandroid/distribution.py
+++ b/pythonforandroid/distribution.py
@@ -79,8 +79,6 @@ def get_distribution(cls, ctx, name=None, recipes=[],
existing_dists = Distribution.get_distributions(ctx)
- needs_build = True # whether the dist needs building, will be returned
-
possible_dists = existing_dists
name_match_dist = None
@@ -216,7 +214,9 @@ def save_info(self, dirn):
'bootstrap': self.ctx.bootstrap.name,
'archs': [arch.arch for arch in self.ctx.archs],
'ndk_api': self.ctx.ndk_api,
- 'recipes': self.ctx.recipe_build_order + self.ctx.python_modules},
+ 'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,
+ 'hostpython': self.ctx.hostpython,
+ 'python_version': self.ctx.python_recipe.major_minor_version_string},
fileh)
diff --git a/pythonforandroid/graph.py b/pythonforandroid/graph.py
index 2207957769..2e98e8ccda 100644
--- a/pythonforandroid/graph.py
+++ b/pythonforandroid/graph.py
@@ -1,23 +1,37 @@
from copy import deepcopy
from itertools import product
-from pythonforandroid.logger import (info, warning)
+from pythonforandroid.logger import info
from pythonforandroid.recipe import Recipe
from pythonforandroid.bootstrap import Bootstrap
from pythonforandroid.util import BuildInterruptingException
-class RecipeOrder(dict):
+def fix_deplist(deps):
+ """ Turn a dependency list into lowercase, and make sure all entries
+ that are just a string become a tuple of strings
+ """
+ deps = [
+ ((dep.lower(),)
+ if not isinstance(dep, (list, tuple))
+ else tuple([dep_entry.lower()
+ for dep_entry in dep
+ ]))
+ for dep in deps
+ ]
+ return deps
+
+class RecipeOrder(dict):
def __init__(self, ctx):
self.ctx = ctx
- def conflicts(self, name):
+ def conflicts(self):
for name in self.keys():
try:
recipe = Recipe.get_recipe(name, self.ctx)
- conflicts = recipe.conflicts
- except IOError:
+ conflicts = [dep.lower() for dep in recipe.conflicts]
+ except ValueError:
conflicts = []
if any([c in self for c in conflicts]):
@@ -25,26 +39,59 @@ def conflicts(self, name):
return False
-def recursively_collect_orders(name, ctx, orders=[]):
+def get_dependency_tuple_list_for_recipe(recipe, blacklist=None):
+ """ Get the dependencies of a recipe with filtered out blacklist, and
+ turned into tuples with fix_deplist()
+ """
+ if blacklist is None:
+ blacklist = set()
+ assert(type(blacklist) == set)
+ if recipe.depends is None:
+ dependencies = []
+ else:
+ # Turn all dependencies into tuples so that product will work
+ dependencies = fix_deplist(recipe.depends)
+
+ # Filter out blacklisted items and turn lowercase:
+ dependencies = [
+ tuple(set(deptuple) - blacklist)
+ for deptuple in dependencies
+ if tuple(set(deptuple) - blacklist)
+ ]
+ return dependencies
+
+
+def recursively_collect_orders(
+ name, ctx, all_inputs, orders=None, blacklist=None
+ ):
'''For each possible recipe ordering, try to add the new recipe name
to that order. Recursively do the same thing with all the
dependencies of each recipe.
'''
+ name = name.lower()
+ if orders is None:
+ orders = []
+ if blacklist is None:
+ blacklist = set()
try:
recipe = Recipe.get_recipe(name, ctx)
- if recipe.depends is None:
- dependencies = []
- else:
- # make all dependencies into lists so that product will work
- dependencies = [([dependency] if not isinstance(
- dependency, (list, tuple))
- else dependency) for dependency in recipe.depends]
+ dependencies = get_dependency_tuple_list_for_recipe(
+ recipe, blacklist=blacklist
+ )
+
+ # handle opt_depends: these impose requirements on the build
+ # order only if already present in the list of recipes to build
+ dependencies.extend(fix_deplist(
+ [[d] for d in recipe.get_opt_depends_in_list(all_inputs)
+ if d.lower() not in blacklist]
+ ))
+
if recipe.conflicts is None:
conflicts = []
else:
- conflicts = recipe.conflicts
- except IOError:
+ conflicts = [dep.lower() for dep in recipe.conflicts]
+ except ValueError:
# The recipe does not exist, so we assume it can be installed
# via pip with no extra dependencies
dependencies = []
@@ -56,7 +103,7 @@ def recursively_collect_orders(name, ctx, orders=[]):
if name in order:
new_orders.append(deepcopy(order))
continue
- if order.conflicts(name):
+ if order.conflicts():
continue
if any([conflict in order for conflict in conflicts]):
continue
@@ -68,7 +115,9 @@ def recursively_collect_orders(name, ctx, orders=[]):
dependency_new_orders = [new_order]
for dependency in dependency_set:
dependency_new_orders = recursively_collect_orders(
- dependency, ctx, dependency_new_orders)
+ dependency, ctx, all_inputs, dependency_new_orders,
+ blacklist=blacklist
+ )
new_orders.extend(dependency_new_orders)
@@ -94,22 +143,142 @@ def find_order(graph):
bset.discard(result)
-def get_recipe_order_and_bootstrap(ctx, names, bs=None):
- recipes_to_load = set(names)
+def obvious_conflict_checker(ctx, name_tuples, blacklist=None):
+ """ This is a pre-flight check function that will completely ignore
+ recipe order or choosing an actual value in any of the multiple
+ choice tuples/dependencies, and just do a very basic obvious
+ conflict check.
+ """
+ deps_were_added_by = dict()
+ deps = set()
+ if blacklist is None:
+ blacklist = set()
+
+ # Add dependencies for all recipes:
+ to_be_added = [(name_tuple, None) for name_tuple in name_tuples]
+ while len(to_be_added) > 0:
+ current_to_be_added = list(to_be_added)
+ to_be_added = []
+ for (added_tuple, adding_recipe) in current_to_be_added:
+ assert(type(added_tuple) == tuple)
+ if len(added_tuple) > 1:
+ # No obvious commitment in what to add, don't check it itself
+ # but throw it into deps for later comparing against
+ # (Remember this function only catches obvious issues)
+ deps.add(added_tuple)
+ continue
+
+ name = added_tuple[0]
+ recipe_conflicts = set()
+ recipe_dependencies = []
+ try:
+ # Get recipe to add and who's ultimately adding it:
+ recipe = Recipe.get_recipe(name, ctx)
+ recipe_conflicts = {c.lower() for c in recipe.conflicts}
+ recipe_dependencies = get_dependency_tuple_list_for_recipe(
+ recipe, blacklist=blacklist
+ )
+ except ValueError:
+ pass
+ adder_first_recipe_name = adding_recipe or name
+
+ # Collect the conflicts:
+ triggered_conflicts = []
+ for dep_tuple_list in deps:
+ # See if the new deps conflict with things added before:
+ if set(dep_tuple_list).intersection(
+ recipe_conflicts) == set(dep_tuple_list):
+ triggered_conflicts.append(dep_tuple_list)
+ continue
+
+ # See if what was added before conflicts with the new deps:
+ if len(dep_tuple_list) > 1:
+ # Not an obvious commitment to a specific recipe/dep
+ # to be added, so we won't check.
+ # (remember this function only catches obvious issues)
+ continue
+ try:
+ dep_recipe = Recipe.get_recipe(dep_tuple_list[0], ctx)
+ except ValueError:
+ continue
+ conflicts = [c.lower() for c in dep_recipe.conflicts]
+ if name in conflicts:
+ triggered_conflicts.append(dep_tuple_list)
+
+ # Throw error on conflict:
+ if triggered_conflicts:
+ # Get first conflict and see who added that one:
+ adder_second_recipe_name = "'||'".join(triggered_conflicts[0])
+ second_recipe_original_adder = deps_were_added_by.get(
+ (adder_second_recipe_name,), None
+ )
+ if second_recipe_original_adder:
+ adder_second_recipe_name = second_recipe_original_adder
+
+ # Prompt error:
+ raise BuildInterruptingException(
+ "Conflict detected: '{}'"
+ " inducing dependencies {}, and '{}'"
+ " inducing conflicting dependencies {}".format(
+ adder_first_recipe_name,
+ (recipe.name,),
+ adder_second_recipe_name,
+ triggered_conflicts[0]
+ ))
+
+ # Actually add it to our list:
+ deps.add(added_tuple)
+ deps_were_added_by[added_tuple] = adding_recipe
+
+ # Schedule dependencies to be added
+ to_be_added += [
+ (dep, adder_first_recipe_name or name)
+ for dep in recipe_dependencies
+ if dep not in deps
+ ]
+ # If we came here, then there were no obvious conflicts.
+ return None
+
+
+def get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):
+ # Get set of recipe/dependency names, clean up and add bootstrap deps:
+ names = set(names)
if bs is not None and bs.recipe_depends:
- recipes_to_load = recipes_to_load.union(set(bs.recipe_depends))
+ names = names.union(set(bs.recipe_depends))
+ names = fix_deplist([
+ ([name] if not isinstance(name, (list, tuple)) else name)
+ for name in names
+ ])
+ if blacklist is None:
+ blacklist = set()
+ blacklist = {bitem.lower() for bitem in blacklist}
- possible_orders = []
+ # Remove all values that are in the blacklist:
+ names_before_blacklist = list(names)
+ names = []
+ for name in names_before_blacklist:
+ cleaned_up_tuple = tuple([
+ item for item in name if item not in blacklist
+ ])
+ if cleaned_up_tuple:
+ names.append(cleaned_up_tuple)
+
+ # Do check for obvious conflicts (that would trigger in any order, and
+ # without comitting to any specific choice in a multi-choice tuple of
+ # dependencies):
+ obvious_conflict_checker(ctx, names, blacklist=blacklist)
+ # If we get here, no obvious conflicts!
# get all possible order graphs, as names may include tuples/lists
# of alternative dependencies
- names = [([name] if not isinstance(name, (list, tuple)) else name)
- for name in names]
+ possible_orders = []
for name_set in product(*names):
new_possible_orders = [RecipeOrder(ctx)]
for name in name_set:
new_possible_orders = recursively_collect_orders(
- name, ctx, orders=new_possible_orders)
+ name, ctx, name_set, orders=new_possible_orders,
+ blacklist=blacklist
+ )
possible_orders.extend(new_possible_orders)
# turn each order graph into a linear list if possible
@@ -121,20 +290,16 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
info('Circular dependency found in graph {}, skipping it.'.format(
possible_order))
continue
- except:
- warning('Failed to import recipe named {}; the recipe exists '
- 'but appears broken.'.format(name))
- warning('Exception was:')
- raise
orders.append(list(order))
- # prefer python2 and SDL2 if available
+ # prefer python3 and SDL2 if available
orders = sorted(orders,
- key=lambda order: -('python2' in order) - ('sdl2' in order))
+ key=lambda order: -('python3' in order) - ('sdl2' in order))
if not orders:
raise BuildInterruptingException(
- 'Didn\'t find any valid dependency graphs. This means that some of your '
+ 'Didn\'t find any valid dependency graphs. '
+ 'This means that some of your '
'requirements pull in conflicting dependencies.')
# It would be better to check against possible orders other
@@ -151,8 +316,14 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
if bs is None:
bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx)
+ if bs is None:
+ # Note: don't remove this without thought, causes infinite loop
+ raise BuildInterruptingException(
+ "Could not find any compatible bootstrap!"
+ )
recipes, python_modules, bs = get_recipe_order_and_bootstrap(
- ctx, chosen_order, bs=bs)
+ ctx, chosen_order, bs=bs, blacklist=blacklist
+ )
else:
# check if each requirement has a recipe
recipes = []
@@ -161,7 +332,7 @@ def get_recipe_order_and_bootstrap(ctx, names, bs=None):
try:
recipe = Recipe.get_recipe(name, ctx)
python_modules += recipe.python_depends
- except IOError:
+ except ValueError:
python_modules.append(name)
else:
recipes.append(name)
diff --git a/pythonforandroid/logger.py b/pythonforandroid/logger.py
index f293f0073c..d7914cee38 100644
--- a/pythonforandroid/logger.py
+++ b/pythonforandroid/logger.py
@@ -44,9 +44,9 @@ def format(self, record):
logger = logging.getLogger('p4a')
-if not hasattr(logger, 'touched'): # Necessary as importlib reloads
- # this, which would add a second
- # handler and reset the level
+# Necessary as importlib reloads this,
+# which would add a second handler and reset the level
+if not hasattr(logger, 'touched'):
logger.setLevel(logging.INFO)
logger.touched = True
ch = logging.StreamHandler(stderr)
diff --git a/pythonforandroid/python.py b/pythonforandroid/python.py
old mode 100644
new mode 100755
index 9ff532d89b..afddfe372b
--- a/pythonforandroid/python.py
+++ b/pythonforandroid/python.py
@@ -4,8 +4,10 @@
'''
from os.path import dirname, exists, join
+from multiprocessing import cpu_count
from shutil import copy2
from os import environ
+import subprocess
import glob
import sh
@@ -13,7 +15,7 @@
from pythonforandroid.logger import logger, info, shprint
from pythonforandroid.util import (
current_directory, ensure_dir, walk_valid_filens,
- BuildInterruptingException)
+ BuildInterruptingException, build_platform)
class GuestPythonRecipe(TargetPythonRecipe):
@@ -71,7 +73,7 @@ class GuestPythonRecipe(TargetPythonRecipe):
'''The directories that we want to omit for our python bundle'''
stdlib_filen_blacklist = [
- '*.pyc',
+ '*.py',
'*.exe',
'*.whl',
]
@@ -84,13 +86,23 @@ class GuestPythonRecipe(TargetPythonRecipe):
'''The directories from site packages dir that we don't want to be included
in our python bundle.'''
- site_packages_filen_blacklist = []
+ site_packages_filen_blacklist = [
+ '*.py'
+ ]
'''The file extensions from site packages dir that we don't want to be
included in our python bundle.'''
opt_depends = ['sqlite3', 'libffi', 'openssl']
'''The optional libraries which we would like to get our python linked'''
+ compiled_extension = '.pyc'
+ '''the default extension for compiled python files.
+
+ .. note:: the default extension for compiled python files has been .pyo for
+ python 2.x-3.4 but as of Python 3.5, the .pyo filename extension is no
+ longer used and has been removed in favour of extension .pyc
+ '''
+
def __init__(self, *args, **kwargs):
self._ctx = None
super(GuestPythonRecipe, self).__init__(*args, **kwargs)
@@ -107,12 +119,12 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
toolchain_prefix=self.ctx.toolchain_prefix,
toolchain_version=self.ctx.toolchain_version)
toolchain = join(self.ctx.ndk_dir, 'toolchains',
- toolchain, 'prebuilt', 'linux-x86_64')
+ toolchain, 'prebuilt', build_platform)
env['CC'] = (
'{clang} -target {target} -gcc-toolchain {toolchain}').format(
clang=join(self.ctx.ndk_dir, 'toolchains', 'llvm', 'prebuilt',
- 'linux-x86_64', 'bin', 'clang'),
+ build_platform, 'bin', 'clang'),
target=arch.target,
toolchain=toolchain)
env['AR'] = join(toolchain, 'bin', android_host) + '-ar'
@@ -178,9 +190,13 @@ def add_flags(include_flags, link_dirs, link_libs):
if 'libffi' in self.ctx.recipe_build_order:
info('Activating flags for libffi')
recipe = Recipe.get_recipe('libffi', self.ctx)
+ # In order to force the correct linkage for our libffi library, we
+ # set the following variable to point where is our libffi.pc file,
+ # because the python build system uses pkg-config to configure it.
+ env['PKG_CONFIG_PATH'] = recipe.get_build_dir(arch.arch)
add_flags(' -I' + ' -I'.join(recipe.get_include_dirs(arch)),
- ' -L' + join(recipe.get_build_dir(arch.arch),
- recipe.get_host(arch), '.libs'), ' -lffi')
+ ' -L' + join(recipe.get_build_dir(arch.arch), '.libs'),
+ ' -lffi')
if 'openssl' in self.ctx.recipe_build_order:
info('Activating flags for openssl')
@@ -235,7 +251,7 @@ def build_arch(self, arch):
py_version = self.major_minor_version_string
if self.major_minor_version_string[0] == '3':
py_version += 'm'
- shprint(sh.make, 'all',
+ shprint(sh.make, 'all', '-j', str(cpu_count()),
'INSTSONAME=libpython{version}.so'.format(
version=py_version), _env=env)
@@ -249,26 +265,52 @@ def include_root(self, arch_name):
def link_root(self, arch_name):
return join(self.get_build_dir(arch_name), 'android-build')
+ def compile_python_files(self, dir):
+ '''
+ Compile the python files (recursively) for the python files inside
+ a given folder.
+
+ .. note:: python2 compiles the files into extension .pyo, but in
+ python3, and as of Python 3.5, the .pyo filename extension is no
+ longer used...uses .pyc (https://www.python.org/dev/peps/pep-0488)
+ '''
+ args = [self.ctx.hostpython]
+ if self.ctx.python_recipe.name == 'python3':
+ args += ['-OO', '-m', 'compileall', '-b', '-f', dir]
+ else:
+ args += ['-OO', '-m', 'compileall', '-f', dir]
+ subprocess.call(args)
+
def create_python_bundle(self, dirn, arch):
"""
Create a packaged python bundle in the target directory, by
copying all the modules and standard library to the right
place.
"""
- # Bundle compiled python modules to a folder
- modules_dir = join(dirn, 'modules')
- ensure_dir(modules_dir)
# Todo: find a better way to find the build libs folder
modules_build_dir = join(
self.get_build_dir(arch.arch),
'android-build',
'build',
- 'lib.linux{}-arm-{}'.format(
+ 'lib.linux{}-{}-{}'.format(
'2' if self.version[0] == '2' else '',
+ arch.command_prefix.split('-')[0],
self.major_minor_version_string
))
+
+ # Compile to *.pyc/*.pyo the python modules
+ self.compile_python_files(modules_build_dir)
+ # Compile to *.pyc/*.pyo the standard python library
+ self.compile_python_files(join(self.get_build_dir(arch.arch), 'Lib'))
+ # Compile to *.pyc/*.pyo the other python packages (site-packages)
+ self.compile_python_files(self.ctx.get_python_install_dir())
+
+ # Bundle compiled python modules to a folder
+ modules_dir = join(dirn, 'modules')
+ c_ext = self.compiled_extension
+ ensure_dir(modules_dir)
module_filens = (glob.glob(join(modules_build_dir, '*.so')) +
- glob.glob(join(modules_build_dir, '*.py')))
+ glob.glob(join(modules_build_dir, '*' + c_ext)))
info("Copy {} files into the bundle".format(len(module_filens)))
for filen in module_filens:
info(" - copy {}".format(filen))
@@ -303,7 +345,7 @@ def create_python_bundle(self, dirn, arch):
if self.major_minor_version_string[0] == '3':
python_lib_name += 'm'
shprint(sh.cp, join(python_build_dir, python_lib_name + '.so'),
- 'libs/{}'.format(arch.arch))
+ join(self.ctx.dist_dir, self.ctx.dist_name, 'libs', arch.arch))
info('Renaming .so files to reflect cross-compile')
self.reduce_object_file_names(join(dirn, 'site-packages'))
@@ -379,7 +421,7 @@ def build_arch(self, arch):
shprint(sh.cp, join('Modules', 'Setup.dist'),
join(build_dir, 'Modules', 'Setup'))
- result = shprint(sh.make, '-C', build_dir)
+ shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir)
else:
info('Skipping {name} ({version}) build, as it has already '
'been completed'.format(name=self.name, version=self.version))
diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py
index 6b2c1a4f51..7b5ec9320d 100644
--- a/pythonforandroid/recipe.py
+++ b/pythonforandroid/recipe.py
@@ -12,6 +12,7 @@
import fnmatch
from os import listdir, unlink, environ, mkdir, curdir, walk
from sys import stdout
+import time
try:
from urlparse import urlparse
except ImportError:
@@ -145,7 +146,19 @@ def report_hook(index, blksize, size):
if exists(target):
unlink(target)
- urlretrieve(url, target, report_hook)
+ # Download item with multiple attempts (for bad connections):
+ attempts = 0
+ while True:
+ try:
+ urlretrieve(url, target, report_hook)
+ except OSError as e:
+ attempts += 1
+ if attempts >= 5:
+ raise e
+ stdout.write('Download failed retrying in a second...')
+ time.sleep(1)
+ continue
+ break
return target
elif parsed_url.scheme in ('git', 'git+file', 'git+ssh', 'git+http', 'git+https'):
if isdir(target):
@@ -166,14 +179,18 @@ def report_hook(index, blksize, size):
shprint(sh.git, 'submodule', 'update', '--recursive')
return target
- def apply_patch(self, filename, arch):
+ def apply_patch(self, filename, arch, build_dir=None):
"""
Apply a patch from the current recipe directory into the current
build directory.
+
+ .. versionchanged:: 0.6.0
+ Add ability to apply patch from any dir via kwarg `build_dir`'''
"""
info("Applying patch {}".format(filename))
+ build_dir = build_dir if build_dir else self.get_build_dir(arch)
filename = join(self.get_recipe_dir(), filename)
- shprint(sh.patch, "-t", "-d", self.get_build_dir(arch), "-p1",
+ shprint(sh.patch, "-t", "-d", build_dir, "-p1",
"-i", filename, _tail=10)
def copy_file(self, filename, dest):
@@ -224,6 +241,12 @@ def check_recipe_choices(self):
recipes.append(recipe)
return sorted(recipes)
+ def get_opt_depends_in_list(self, recipes):
+ '''Given a list of recipe names, returns those that are also in
+ self.opt_depends.
+ '''
+ return [recipe for recipe in recipes if recipe in self.opt_depends]
+
def get_build_container_dir(self, arch):
'''Given the arch name, returns the directory where it will be
built.
@@ -376,15 +399,10 @@ def unpack(self, arch):
root_directory = fileh.filelist[0].filename.split('/')[0]
if root_directory != basename(directory_name):
shprint(sh.mv, root_directory, directory_name)
- elif (extraction_filename.endswith('.tar.gz') or
- extraction_filename.endswith('.tgz') or
- extraction_filename.endswith('.tar.bz2') or
- extraction_filename.endswith('.tbz2') or
- extraction_filename.endswith('.tar.xz') or
- extraction_filename.endswith('.txz')):
+ elif extraction_filename.endswith(
+ ('.tar.gz', '.tgz', '.tar.bz2', '.tbz2', '.tar.xz', '.txz')):
sh.tar('xf', extraction_filename)
- root_directory = shprint(
- sh.tar, 'tf', extraction_filename).stdout.decode(
+ root_directory = sh.tar('tf', extraction_filename).stdout.decode(
'utf-8').split('\n')[0].split('/')[0]
if root_directory != directory_name:
shprint(sh.mv, root_directory, directory_name)
@@ -428,8 +446,11 @@ def is_patched(self, arch):
build_dir = self.get_build_dir(arch.arch)
return exists(join(build_dir, '.patched'))
- def apply_patches(self, arch):
- '''Apply any patches for the Recipe.'''
+ def apply_patches(self, arch, build_dir=None):
+ '''Apply any patches for the Recipe.
+
+ .. versionchanged:: 0.6.0
+ Add ability to apply patches from any dir via kwarg `build_dir`'''
if self.patches:
info_main('Applying patches for {}[{}]'
.format(self.name, arch.arch))
@@ -438,6 +459,7 @@ def apply_patches(self, arch):
info_main('{} already patched, skipping'.format(self.name))
return
+ build_dir = build_dir if build_dir else self.get_build_dir(arch.arch)
for patch in self.patches:
if isinstance(patch, (tuple, list)):
patch, patch_check = patch
@@ -446,9 +468,9 @@ def apply_patches(self, arch):
self.apply_patch(
patch.format(version=self.version, arch=arch.arch),
- arch.arch)
+ arch.arch, build_dir=build_dir)
- shprint(sh.touch, join(self.get_build_dir(arch.arch), '.patched'))
+ shprint(sh.touch, join(build_dir, '.patched'))
def should_build(self, arch):
'''Should perform any necessary test and return True only if it needs
@@ -552,6 +574,7 @@ def list_recipes(cls, ctx):
@classmethod
def get_recipe(cls, name, ctx):
'''Returns the Recipe with the given name, if it exists.'''
+ name = name.lower()
if not hasattr(cls, "recipes"):
cls.recipes = {}
if name in cls.recipes:
@@ -559,20 +582,28 @@ def get_recipe(cls, name, ctx):
recipe_file = None
for recipes_dir in cls.recipe_dirs(ctx):
- recipe_file = join(recipes_dir, name, '__init__.py')
- if exists(recipe_file):
+ if not exists(recipes_dir):
+ continue
+ # Find matching folder (may differ in case):
+ for subfolder in listdir(recipes_dir):
+ if subfolder.lower() == name:
+ recipe_file = join(recipes_dir, subfolder, '__init__.py')
+ if exists(recipe_file):
+ name = subfolder # adapt to actual spelling
+ break
+ recipe_file = None
+ if recipe_file is not None:
break
- recipe_file = None
if not recipe_file:
- raise IOError('Recipe does not exist: {}'.format(name))
+ raise ValueError('Recipe does not exist: {}'.format(name))
mod = import_recipe('pythonforandroid.recipes.{}'.format(name), recipe_file)
if len(logger.handlers) > 1:
logger.removeHandler(logger.handlers[1])
recipe = mod.recipe
recipe.ctx = ctx
- cls.recipes[name] = recipe
+ cls.recipes[name.lower()] = recipe
return recipe
@@ -599,9 +630,7 @@ class BootstrapNDKRecipe(Recipe):
:class:`~pythonforandroid.recipe.NDKRecipe`.
To link with python, call the method :meth:`get_recipe_env`
- with the kwarg *with_python=True*. If recipe contains android's mk files
- which should be linked with python, you may want to use the env variables
- MK_PYTHON_INCLUDE_ROOT and MK_PYTHON_LINK_ROOT set in there.
+ with the kwarg *with_python=True*.
'''
dir_name = None # The name of the recipe build folder in the jni dir
@@ -630,16 +659,6 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=False):
self.ctx.python_recipe.major_minor_version_string)
if 'python3' in self.ctx.python_recipe.name:
env['EXTRA_LDLIBS'] += 'm'
-
- # set some env variables that may be needed to build some bootstrap ndk
- # recipes that needs linking with our python via mk files, like
- # recipes: sdl2, genericndkbuild or sdl
- other_builds = join(self.ctx.build_dir, 'other_builds') + '/'
- env['MK_PYTHON_INCLUDE_ROOT'] = \
- self.ctx.python_recipe.include_root(arch.arch)[
- len(other_builds):]
- env['MK_PYTHON_LINK_ROOT'] = \
- self.ctx.python_recipe.link_root(arch.arch)[len(other_builds):]
return env
@@ -754,6 +773,10 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
env['PYTHONNOUSERSITE'] = '1'
+ # Set the LANG, this isn't usually important but is a better default
+ # as it occasionally matters how Python e.g. reads files
+ env['LANG'] = "en_GB.UTF-8"
+
if not self.call_hostpython_via_targetpython:
# sets python headers/linkages...depending on python's recipe
python_name = self.ctx.python_recipe.name
diff --git a/pythonforandroid/recipes/android/__init__.py b/pythonforandroid/recipes/android/__init__.py
index a8f6d2dd0a..6b7374ca9d 100644
--- a/pythonforandroid/recipes/android/__init__.py
+++ b/pythonforandroid/recipes/android/__init__.py
@@ -14,7 +14,8 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
src_filename = 'src'
- depends = [('pygame', 'sdl2', 'genericndkbuild')]
+ depends = [('pygame', 'sdl2', 'genericndkbuild'),
+ 'pyjnius']
config_env = {}
diff --git a/pythonforandroid/recipes/android/src/android/loadingscreen.py b/pythonforandroid/recipes/android/src/android/loadingscreen.py
new file mode 100644
index 0000000000..1dc1b670f5
--- /dev/null
+++ b/pythonforandroid/recipes/android/src/android/loadingscreen.py
@@ -0,0 +1,7 @@
+
+from jnius import autoclass
+
+
+def hide_loading_screen():
+ python_activity = autoclass('org.kivy.android.PythonActivity')
+ python_activity.removeLoadingScreen()
diff --git a/pythonforandroid/recipes/android/src/android/permissions.py b/pythonforandroid/recipes/android/src/android/permissions.py
new file mode 100644
index 0000000000..2b3f516ccf
--- /dev/null
+++ b/pythonforandroid/recipes/android/src/android/permissions.py
@@ -0,0 +1,434 @@
+
+try:
+ from jnius import autoclass
+except ImportError:
+ # To allow importing by build/manifest-creating code without
+ # pyjnius being present:
+ def autoclass(item):
+ raise RuntimeError("pyjnius not available")
+
+
+class Permission:
+ ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
+ ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
+ ACCESS_LOCATION_EXTRA_COMMANDS = (
+ "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"
+ )
+ ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE"
+ ACCESS_NOTIFICATION_POLICY = (
+ "android.permission.ACCESS_NOTIFICATION_POLICY"
+ )
+ ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE"
+ ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"
+ ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"
+ BATTERY_STATS = "android.permission.BATTERY_STATS"
+ BIND_ACCESSIBILITY_SERVICE = (
+ "android.permission.BIND_ACCESSIBILITY_SERVICE"
+ )
+ BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE"
+ BIND_CARRIER_MESSAGING_SERVICE = ( # note: deprecated in api 23+
+ "android.permission.BIND_CARRIER_MESSAGING_SERVICE"
+ )
+ BIND_CARRIER_SERVICES = ( # replaces BIND_CARRIER_MESSAGING_SERVICE
+ "android.permission.BIND_CARRIER_SERVICES"
+ )
+ BIND_CHOOSER_TARGET_SERVICE = (
+ "android.permission.BIND_CHOOSER_TARGET_SERVICE"
+ )
+ BIND_CONDITION_PROVIDER_SERVICE = (
+ "android.permission.BIND_CONDITION_PROVIDER_SERVICE"
+ )
+ BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN"
+ BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE"
+ BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE"
+ BIND_INPUT_METHOD = (
+ "android.permission.BIND_INPUT_METHOD"
+ )
+ BIND_MIDI_DEVICE_SERVICE = (
+ "android.permission.BIND_MIDI_DEVICE_SERVICE"
+ )
+ BIND_NFC_SERVICE = (
+ "android.permission.BIND_NFC_SERVICE"
+ )
+ BIND_NOTIFICATION_LISTENER_SERVICE = (
+ "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+ )
+ BIND_PRINT_SERVICE = (
+ "android.permission.BIND_PRINT_SERVICE"
+ )
+ BIND_QUICK_SETTINGS_TILE = (
+ "android.permission.BIND_QUICK_SETTINGS_TILE"
+ )
+ BIND_REMOTEVIEWS = (
+ "android.permission.BIND_REMOTEVIEWS"
+ )
+ BIND_SCREENING_SERVICE = (
+ "android.permission.BIND_SCREENING_SERVICE"
+ )
+ BIND_TELECOM_CONNECTION_SERVICE = (
+ "android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+ )
+ BIND_TEXT_SERVICE = (
+ "android.permission.BIND_TEXT_SERVICE"
+ )
+ BIND_TV_INPUT = (
+ "android.permission.BIND_TV_INPUT"
+ )
+ BIND_VISUAL_VOICEMAIL_SERVICE = (
+ "android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"
+ )
+ BIND_VOICE_INTERACTION = (
+ "android.permission.BIND_VOICE_INTERACTION"
+ )
+ BIND_VPN_SERVICE = (
+ "android.permission.BIND_VPN_SERVICE"
+ )
+ BIND_VR_LISTENER_SERVICE = (
+ "android.permission.BIND_VR_LISTENER_SERVICE"
+ )
+ BIND_WALLPAPER = (
+ "android.permission.BIND_WALLPAPER"
+ )
+ BLUETOOTH = (
+ "android.permission.BLUETOOTH"
+ )
+ BLUETOOTH_ADMIN = (
+ "android.permission.BLUETOOTH_ADMIN"
+ )
+ BODY_SENSORS = (
+ "android.permission.BODY_SENSORS"
+ )
+ BROADCAST_PACKAGE_REMOVED = (
+ "android.permission.BROADCAST_PACKAGE_REMOVED"
+ )
+ BROADCAST_STICKY = (
+ "android.permission.BROADCAST_STICKY"
+ )
+ CALL_PHONE = (
+ "android.permission.CALL_PHONE"
+ )
+ CALL_PRIVILEGED = (
+ "android.permission.CALL_PRIVILEGED"
+ )
+ CAMERA = (
+ "android.permission.CAMERA"
+ )
+ CAPTURE_AUDIO_OUTPUT = (
+ "android.permission.CAPTURE_AUDIO_OUTPUT"
+ )
+ CAPTURE_SECURE_VIDEO_OUTPUT = (
+ "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT"
+ )
+ CAPTURE_VIDEO_OUTPUT = (
+ "android.permission.CAPTURE_VIDEO_OUTPUT"
+ )
+ CHANGE_COMPONENT_ENABLED_STATE = (
+ "android.permission.CHANGE_COMPONENT_ENABLED_STATE"
+ )
+ CHANGE_CONFIGURATION = (
+ "android.permission.CHANGE_CONFIGURATION"
+ )
+ CHANGE_NETWORK_STATE = (
+ "android.permission.CHANGE_NETWORK_STATE"
+ )
+ CHANGE_WIFI_MULTICAST_STATE = (
+ "android.permission.CHANGE_WIFI_MULTICAST_STATE"
+ )
+ CHANGE_WIFI_STATE = (
+ "android.permission.CHANGE_WIFI_STATE"
+ )
+ CLEAR_APP_CACHE = (
+ "android.permission.CLEAR_APP_CACHE"
+ )
+ CONTROL_LOCATION_UPDATES = (
+ "android.permission.CONTROL_LOCATION_UPDATES"
+ )
+ DELETE_CACHE_FILES = (
+ "android.permission.DELETE_CACHE_FILES"
+ )
+ DELETE_PACKAGES = (
+ "android.permission.DELETE_PACKAGES"
+ )
+ DIAGNOSTIC = (
+ "android.permission.DIAGNOSTIC"
+ )
+ DISABLE_KEYGUARD = (
+ "android.permission.DISABLE_KEYGUARD"
+ )
+ DUMP = (
+ "android.permission.DUMP"
+ )
+ EXPAND_STATUS_BAR = (
+ "android.permission.EXPAND_STATUS_BAR"
+ )
+ FACTORY_TEST = (
+ "android.permission.FACTORY_TEST"
+ )
+ FOREGROUND_SERVICE = (
+ "android.permission.FOREGROUND_SERVICE"
+ )
+ GET_ACCOUNTS = (
+ "android.permission.GET_ACCOUNTS"
+ )
+ GET_ACCOUNTS_PRIVILEGED = (
+ "android.permission.GET_ACCOUNTS_PRIVILEGED"
+ )
+ GET_PACKAGE_SIZE = (
+ "android.permission.GET_PACKAGE_SIZE"
+ )
+ GET_TASKS = (
+ "android.permission.GET_TASKS"
+ )
+ GLOBAL_SEARCH = (
+ "android.permission.GLOBAL_SEARCH"
+ )
+ INSTALL_LOCATION_PROVIDER = (
+ "android.permission.INSTALL_LOCATION_PROVIDER"
+ )
+ INSTALL_PACKAGES = (
+ "android.permission.INSTALL_PACKAGES"
+ )
+ INSTALL_SHORTCUT = (
+ "com.android.launcher.permission.INSTALL_SHORTCUT"
+ )
+ INSTANT_APP_FOREGROUND_SERVICE = (
+ "android.permission.INSTANT_APP_FOREGROUND_SERVICE"
+ )
+ INTERNET = (
+ "android.permission.INTERNET"
+ )
+ KILL_BACKGROUND_PROCESSES = (
+ "android.permission.KILL_BACKGROUND_PROCESSES"
+ )
+ LOCATION_HARDWARE = (
+ "android.permission.LOCATION_HARDWARE"
+ )
+ MANAGE_DOCUMENTS = (
+ "android.permission.MANAGE_DOCUMENTS"
+ )
+ MANAGE_OWN_CALLS = (
+ "android.permission.MANAGE_OWN_CALLS"
+ )
+ MASTER_CLEAR = (
+ "android.permission.MASTER_CLEAR"
+ )
+ MEDIA_CONTENT_CONTROL = (
+ "android.permission.MEDIA_CONTENT_CONTROL"
+ )
+ MODIFY_AUDIO_SETTINGS = (
+ "android.permission.MODIFY_AUDIO_SETTINGS"
+ )
+ MODIFY_PHONE_STATE = (
+ "android.permission.MODIFY_PHONE_STATE"
+ )
+ MOUNT_FORMAT_FILESYSTEMS = (
+ "android.permission.MOUNT_FORMAT_FILESYSTEMS"
+ )
+ MOUNT_UNMOUNT_FILESYSTEMS = (
+ "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
+ )
+ NFC = (
+ "android.permission.NFC"
+ )
+ NFC_TRANSACTION_EVENT = (
+ "android.permission.NFC_TRANSACTION_EVENT"
+ )
+ PACKAGE_USAGE_STATS = (
+ "android.permission.PACKAGE_USAGE_STATS"
+ )
+ PERSISTENT_ACTIVITY = (
+ "android.permission.PERSISTENT_ACTIVITY"
+ )
+ PROCESS_OUTGOING_CALLS = (
+ "android.permission.PROCESS_OUTGOING_CALLS"
+ )
+ READ_CALENDAR = (
+ "android.permission.READ_CALENDAR"
+ )
+ READ_CALL_LOG = (
+ "android.permission.READ_CALL_LOG"
+ )
+ READ_CONTACTS = (
+ "android.permission.READ_CONTACTS"
+ )
+ READ_EXTERNAL_STORAGE = (
+ "android.permission.READ_EXTERNAL_STORAGE"
+ )
+ READ_FRAME_BUFFER = (
+ "android.permission.READ_FRAME_BUFFER"
+ )
+ READ_INPUT_STATE = (
+ "android.permission.READ_INPUT_STATE"
+ )
+ READ_LOGS = (
+ "android.permission.READ_LOGS"
+ )
+ READ_PHONE_NUMBERS = (
+ "android.permission.READ_PHONE_NUMBERS"
+ )
+ READ_PHONE_STATE = (
+ "android.permission.READ_PHONE_STATE"
+ )
+ READ_SMS = (
+ "android.permission.READ_SMS"
+ )
+ READ_SYNC_SETTINGS = (
+ "android.permission.READ_SYNC_SETTINGS"
+ )
+ READ_SYNC_STATS = (
+ "android.permission.READ_SYNC_STATS"
+ )
+ READ_VOICEMAIL = (
+ "com.android.voicemail.permission.READ_VOICEMAIL"
+ )
+ REBOOT = (
+ "android.permission.REBOOT"
+ )
+ RECEIVE_BOOT_COMPLETED = (
+ "android.permission.RECEIVE_BOOT_COMPLETED"
+ )
+ RECEIVE_MMS = (
+ "android.permission.RECEIVE_MMS"
+ )
+ RECEIVE_SMS = (
+ "android.permission.RECEIVE_SMS"
+ )
+ RECEIVE_WAP_PUSH = (
+ "android.permission.RECEIVE_WAP_PUSH"
+ )
+ RECORD_AUDIO = (
+ "android.permission.RECORD_AUDIO"
+ )
+ REORDER_TASKS = (
+ "android.permission.REORDER_TASKS"
+ )
+ REQUEST_COMPANION_RUN_IN_BACKGROUND = (
+ "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"
+ )
+ REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = (
+ "android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND"
+ )
+ REQUEST_DELETE_PACKAGES = (
+ "android.permission.REQUEST_DELETE_PACKAGES"
+ )
+ REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = (
+ "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"
+ )
+ REQUEST_INSTALL_PACKAGES = (
+ "android.permission.REQUEST_INSTALL_PACKAGES"
+ )
+ RESTART_PACKAGES = (
+ "android.permission.RESTART_PACKAGES"
+ )
+ SEND_RESPOND_VIA_MESSAGE = (
+ "android.permission.SEND_RESPOND_VIA_MESSAGE"
+ )
+ SEND_SMS = (
+ "android.permission.SEND_SMS"
+ )
+ SET_ALARM = (
+ "com.android.alarm.permission.SET_ALARM"
+ )
+ SET_ALWAYS_FINISH = (
+ "android.permission.SET_ALWAYS_FINISH"
+ )
+ SET_ANIMATION_SCALE = (
+ "android.permission.SET_ANIMATION_SCALE"
+ )
+ SET_DEBUG_APP = (
+ "android.permission.SET_DEBUG_APP"
+ )
+ SET_PREFERRED_APPLICATIONS = (
+ "android.permission.SET_PREFERRED_APPLICATIONS"
+ )
+ SET_PROCESS_LIMIT = (
+ "android.permission.SET_PROCESS_LIMIT"
+ )
+ SET_TIME = (
+ "android.permission.SET_TIME"
+ )
+ SET_TIME_ZONE = (
+ "android.permission.SET_TIME_ZONE"
+ )
+ SET_WALLPAPER = (
+ "android.permission.SET_WALLPAPER"
+ )
+ SET_WALLPAPER_HINTS = (
+ "android.permission.SET_WALLPAPER_HINTS"
+ )
+ SIGNAL_PERSISTENT_PROCESSES = (
+ "android.permission.SIGNAL_PERSISTENT_PROCESSES"
+ )
+ STATUS_BAR = (
+ "android.permission.STATUS_BAR"
+ )
+ SYSTEM_ALERT_WINDOW = (
+ "android.permission.SYSTEM_ALERT_WINDOW"
+ )
+ TRANSMIT_IR = (
+ "android.permission.TRANSMIT_IR"
+ )
+ UNINSTALL_SHORTCUT = (
+ "com.android.launcher.permission.UNINSTALL_SHORTCUT"
+ )
+ UPDATE_DEVICE_STATS = (
+ "android.permission.UPDATE_DEVICE_STATS"
+ )
+ USE_BIOMETRIC = (
+ "android.permission.USE_BIOMETRIC"
+ )
+ USE_FINGERPRINT = (
+ "android.permission.USE_FINGERPRINT"
+ )
+ USE_SIP = (
+ "android.permission.USE_SIP"
+ )
+ VIBRATE = (
+ "android.permission.VIBRATE"
+ )
+ WAKE_LOCK = (
+ "android.permission.WAKE_LOCK"
+ )
+ WRITE_APN_SETTINGS = (
+ "android.permission.WRITE_APN_SETTINGS"
+ )
+ WRITE_CALENDAR = (
+ "android.permission.WRITE_CALENDAR"
+ )
+ WRITE_CALL_LOG = (
+ "android.permission.WRITE_CALL_LOG"
+ )
+ WRITE_CONTACTS = (
+ "android.permission.WRITE_CONTACTS"
+ )
+ WRITE_EXTERNAL_STORAGE = (
+ "android.permission.WRITE_EXTERNAL_STORAGE"
+ )
+ WRITE_GSERVICES = (
+ "android.permission.WRITE_GSERVICES"
+ )
+ WRITE_SECURE_SETTINGS = (
+ "android.permission.WRITE_SECURE_SETTINGS"
+ )
+ WRITE_SETTINGS = (
+ "android.permission.WRITE_SETTINGS"
+ )
+ WRITE_SYNC_SETTINGS = (
+ "android.permission.WRITE_SYNC_SETTINGS"
+ )
+ WRITE_VOICEMAIL = (
+ "com.android.voicemail.permission.WRITE_VOICEMAIL"
+ )
+
+
+def request_permission(permission):
+ python_activity = autoclass('org.kivy.android.PythonActivity')
+ python_activity.requestNewPermission(permission + "")
+
+
+def check_permission(permission):
+ python_activity = autoclass('org.kivy.android.PythonActivity')
+ result = bool(python_activity.checkCurrentPermission(
+ permission + ""
+ ))
+ return result
diff --git a/pythonforandroid/recipes/android/src/android/runnable.py b/pythonforandroid/recipes/android/src/android/runnable.py
index 12c86a1ba6..8d2d1161d1 100644
--- a/pythonforandroid/recipes/android/src/android/runnable.py
+++ b/pythonforandroid/recipes/android/src/android/runnable.py
@@ -33,7 +33,7 @@ def __call__(self, *args, **kwargs):
def run(self):
try:
self.func(*self.args, **self.kwargs)
- except:
+ except: # noqa E722
import traceback
traceback.print_exc()
diff --git a/pythonforandroid/recipes/apsw/__init__.py b/pythonforandroid/recipes/apsw/__init__.py
index 68e745961e..6098e4b98c 100644
--- a/pythonforandroid/recipes/apsw/__init__.py
+++ b/pythonforandroid/recipes/apsw/__init__.py
@@ -6,7 +6,7 @@
class ApswRecipe(PythonRecipe):
version = '3.15.0-r1'
url = 'https://github.com/rogerbinns/apsw/archive/{version}.tar.gz'
- depends = ['sqlite3', 'hostpython2', 'python2', 'setuptools']
+ depends = ['sqlite3', ('python2', 'python3'), 'setuptools']
call_hostpython_via_targetpython = False
site_packages_name = 'apsw'
@@ -24,14 +24,10 @@ def build_arch(self, arch):
def get_recipe_env(self, arch):
env = super(ApswRecipe, self).get_recipe_env(arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \
- ' -I' + self.get_recipe('sqlite3', self.ctx).get_build_dir(arch.arch)
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
- ' -lpython2.7' + \
- ' -lsqlite3'
+ sqlite_recipe = self.get_recipe('sqlite3', self.ctx)
+ env['CFLAGS'] += ' -I' + sqlite_recipe.get_build_dir(arch.arch)
+ env['LDFLAGS'] += ' -L' + sqlite_recipe.get_lib_dir(arch)
+ env['LIBS'] = env.get('LIBS', '') + ' -lsqlite3'
return env
diff --git a/pythonforandroid/recipes/atom/__init__.py b/pythonforandroid/recipes/atom/__init__.py
index 854911372e..51923d5487 100644
--- a/pythonforandroid/recipes/atom/__init__.py
+++ b/pythonforandroid/recipes/atom/__init__.py
@@ -5,7 +5,7 @@ class AtomRecipe(CppCompiledComponentsPythonRecipe):
site_packages_name = 'atom'
version = '0.3.10'
url = 'https://github.com/nucleic/atom/archive/master.zip'
- depends = ['python2', 'setuptools']
+ depends = ['setuptools']
recipe = AtomRecipe()
diff --git a/pythonforandroid/recipes/babel/__init__.py b/pythonforandroid/recipes/babel/__init__.py
index dc1fad86f1..fc17f8e4b0 100644
--- a/pythonforandroid/recipes/babel/__init__.py
+++ b/pythonforandroid/recipes/babel/__init__.py
@@ -6,7 +6,7 @@ class BabelRecipe(PythonRecipe):
version = '2.2.0'
url = 'https://pypi.python.org/packages/source/B/Babel/Babel-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools', 'pytz']
+ depends = ['setuptools', 'pytz']
call_hostpython_via_targetpython = False
install_in_hostpython = True
diff --git a/pythonforandroid/recipes/boost/__init__.py b/pythonforandroid/recipes/boost/__init__.py
index 89109c3f7d..53d9388877 100644
--- a/pythonforandroid/recipes/boost/__init__.py
+++ b/pythonforandroid/recipes/boost/__init__.py
@@ -1,5 +1,6 @@
from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
from os.path import join, exists
+from os import environ
import sh
"""
@@ -9,11 +10,36 @@
class BoostRecipe(Recipe):
- version = '1.60.0'
- # Don't forget to change the URL when changing the version
- url = 'http://downloads.sourceforge.net/project/boost/boost/{version}/boost_1_60_0.tar.bz2'
- depends = ['python2']
- patches = ['disable-so-version.patch', 'use-android-libs.patch']
+ # Todo: make recipe compatible with all p4a architectures
+ '''
+ .. note:: This recipe can be built only against API 21+ and arch armeabi-v7a
+
+ .. versionchanged:: 0.6.0
+ Rewrote recipe to support clang's build. The following changes has
+ been made:
+
+ - Bumped version number to 1.68.0
+ - Better version handling for url
+ - Added python 3 compatibility
+ - Default compiler for ndk's toolchain set to clang
+ - Python version will be detected via user-config.jam
+ - Changed stl's lib from ``gnustl_shared`` to ``c++_shared``
+ '''
+ version = '1.68.0'
+ url = 'http://downloads.sourceforge.net/project/boost/' \
+ 'boost/{version}/boost_{version_underscore}.tar.bz2'
+ depends = [('python2', 'python3')]
+ patches = ['disable-so-version.patch',
+ 'use-android-libs.patch',
+ 'fix-android-issues.patch']
+
+ @property
+ def versioned_url(self):
+ if self.url is None:
+ return None
+ return self.url.format(
+ version=self.version,
+ version_underscore=self.version.replace('.', '_'))
def should_build(self, arch):
return not exists(join(self.get_build_dir(arch.arch), 'b2'))
@@ -28,9 +54,11 @@ def prebuild_arch(self, arch):
shprint(bash, join(self.ctx.ndk_dir, 'build/tools/make-standalone-toolchain.sh'),
'--arch=' + env['ARCH'],
'--platform=android-' + str(self.ctx.android_api),
- '--toolchain=' + env['CROSSHOST'] + '-' + env['TOOLCHAIN_VERSION'],
+ '--toolchain=' + env['CROSSHOST'] + '-' + self.ctx.toolchain_version + ':-llvm',
+ '--use-llvm',
+ '--stl=libc++',
'--install-dir=' + env['CROSSHOME']
- )
+ )
# Set custom configuration
shutil.copyfile(join(self.get_recipe_dir(), 'user-config.jam'),
join(env['BOOST_BUILD_PATH'], 'user-config.jam'))
@@ -38,31 +66,38 @@ def prebuild_arch(self, arch):
def build_arch(self, arch):
super(BoostRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
+ env['PYTHON_HOST'] = self.ctx.hostpython
with current_directory(self.get_build_dir(arch.arch)):
# Compile Boost.Build engine with this custom toolchain
bash = sh.Command('bash')
- shprint(bash, 'bootstrap.sh',
- '--with-python=' + join(env['PYTHON_ROOT'], 'bin/python.host'),
- '--with-python-version=2.7',
- '--with-python-root=' + env['PYTHON_ROOT']
- ) # Do not pass env
+ shprint(bash, 'bootstrap.sh') # Do not pass env
# Install app stl
- shutil.copyfile(join(env['CROSSHOME'], env['CROSSHOST'], 'lib/libgnustl_shared.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libgnustl_shared.so'))
+ shutil.copyfile(
+ join(self.ctx.ndk_dir, 'sources/cxx-stl/llvm-libc++/libs/'
+ 'armeabi-v7a/libc++_shared.so'),
+ join(self.ctx.get_libs_dir(arch.arch), 'libc++_shared.so'))
def select_build_arch(self, arch):
return arch.arch.replace('eabi-v7a', '').replace('eabi', '')
def get_recipe_env(self, arch):
- env = super(BoostRecipe, self).get_recipe_env(arch)
+ # We don't use the normal env because we
+ # are building with a standalone toolchain
+ env = environ.copy()
+
env['BOOST_BUILD_PATH'] = self.get_build_dir(arch.arch) # find user-config.jam
env['BOOST_ROOT'] = env['BOOST_BUILD_PATH'] # find boost source
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+
+ env['PYTHON_ROOT'] = self.ctx.python_recipe.link_root(arch.arch)
+ env['PYTHON_INCLUDE'] = self.ctx.python_recipe.include_root(arch.arch)
+ env['PYTHON_MAJOR_MINOR'] = self.ctx.python_recipe.version[:3]
+ env['PYTHON_LINK_VERSION'] = self.ctx.python_recipe.major_minor_version_string
+ if 'python3' in self.ctx.python_recipe.name:
+ env['PYTHON_LINK_VERSION'] += 'm'
+
env['ARCH'] = self.select_build_arch(arch)
- env['ANDROIDAPI'] = str(self.ctx.android_api)
env['CROSSHOST'] = env['ARCH'] + '-linux-androideabi'
env['CROSSHOME'] = join(env['BOOST_ROOT'], 'standalone-' + env['ARCH'] + '-toolchain')
- env['TOOLCHAIN_PREFIX'] = join(env['CROSSHOME'], 'bin', env['CROSSHOST'])
return env
diff --git a/pythonforandroid/recipes/boost/fix-android-issues.patch b/pythonforandroid/recipes/boost/fix-android-issues.patch
new file mode 100644
index 0000000000..54134800a1
--- /dev/null
+++ b/pythonforandroid/recipes/boost/fix-android-issues.patch
@@ -0,0 +1,68 @@
+diff -u -r boost_1_68_0.orig/boost/config/user.hpp boost_1_68_0/boost/config/user.hpp
+--- boost_1_68_0.orig/boost/config/user.hpp 2018-08-01 22:50:46.000000000 +0200
++++ boost_1_68_0/boost/config/user.hpp 2018-08-27 15:43:38.000000000 +0200
+@@ -13,6 +13,12 @@
+ // configuration policy:
+ //
+
++// Android defines
++// There is problem with std::atomic on android (and some other platforms).
++// See this link for more info:
++// https://code.google.com/p/android/issues/detail?id=42735#makechanges
++#define BOOST_ASIO_DISABLE_STD_ATOMIC 1
++
+ // define this to locate a compiler config file:
+ // #define BOOST_COMPILER_CONFIG
+
+diff -u -r boost_1_68_0.orig/boost/asio/detail/config.hpp boost_1_68_0/boost/asio/detail/config.hpp
+--- boost_1_68_0.orig/boost/asio/detail/config.hpp 2018-08-01 22:50:46.000000000 +0200
++++ boost_1_68_0/boost/asio/detail/config.hpp 2018-09-19 12:39:56.000000000 +0200
+@@ -804,7 +804,11 @@
+ # if defined(__clang__)
+ # if (__cplusplus >= 201402)
+ # if __has_include()
+-# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
++# if __clang_major__ >= 7
++# undef BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW
++# else
++# define BOOST_ASIO_HAS_STD_EXPERIMENTAL_STRING_VIEW 1
++# endif // __clang_major__ >= 7
+ # endif // __has_include()
+ # endif // (__cplusplus >= 201402)
+ # endif // defined(__clang__)
+diff -u -r boost_1_68_0.orig/boost/system/error_code.hpp boost_1_68_0/boost/system/error_code.hpp
+--- boost_1_68_0.orig/boost/system/error_code.hpp 2018-08-01 22:50:53.000000000 +0200
++++ boost_1_68_0/boost/system/error_code.hpp 2018-08-27 15:44:29.000000000 +0200
+@@ -17,6 +17,7 @@
+ #include
+ #include
+ #include
++#include
+ #include
+ #include
+ #include
+diff -u -r boost_1_68_0.orig/libs/filesystem/src/operations.cpp boost_1_68_0/libs/filesystem/src/operations.cpp
+--- boost_1_68_0.orig/libs/filesystem/src/operations.cpp 2018-08-01 22:50:47.000000000 +0200
++++ boost_1_68_0/libs/filesystem/src/operations.cpp 2018-08-27 15:47:15.000000000 +0200
+@@ -232,6 +232,21 @@
+
+ # if defined(BOOST_POSIX_API)
+
++# if defined(__ANDROID__)
++# define truncate libboost_truncate_wrapper
++// truncate() is present in Android libc only starting from ABI 21, so here's a simple wrapper
++static int libboost_truncate_wrapper(const char *path, off_t length)
++{
++ int fd = open(path, O_WRONLY);
++ if (fd == -1) {
++ return -1;
++ }
++ int status = ftruncate(fd, length);
++ close(fd);
++ return status;
++}
++# endif
++
+ typedef int err_t;
+
+ // POSIX uses a 0 return to indicate success
diff --git a/pythonforandroid/recipes/boost/user-config.jam b/pythonforandroid/recipes/boost/user-config.jam
index 72643d8a06..e50b50afea 100644
--- a/pythonforandroid/recipes/boost/user-config.jam
+++ b/pythonforandroid/recipes/boost/user-config.jam
@@ -1,28 +1,61 @@
import os ;
-local ANDROIDNDK = [ os.environ ANDROIDNDK ] ;
-local ANDROIDAPI = [ os.environ ANDROIDAPI ] ;
-local TOOLCHAIN_VERSION = [ os.environ TOOLCHAIN_VERSION ] ;
-local TOOLCHAIN_PREFIX = [ os.environ TOOLCHAIN_PREFIX ] ;
local ARCH = [ os.environ ARCH ] ;
+local CROSSHOME = [ os.environ CROSSHOME ] ;
+local PYTHON_HOST = [ os.environ PYTHON_HOST ] ;
local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
+local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;
+local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;
+local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;
-using gcc : $(ARCH) : $(TOOLCHAIN_PREFIX)-g++ :
+using clang : $(ARCH) : $(CROSSHOME)/bin/arm-linux-androideabi-clang++ :
+$(CROSSHOME)/bin/arm-linux-androideabi-ar
+$(CROSSHOME)/sysroot
$(ARCH)
-$(TOOLCHAIN_PREFIX)-ar
--DBOOST_SP_USE_PTHREADS
--DBOOST_AC_USE_PTHREADS
--DBOOST_SP_USE_PTHREADS
--DBOOST_AC_USE_PTHREADS
--frtti
--fexceptions
--I$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)/usr/include
--I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/include
--I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)/include
--I$(PYTHON_ROOT)/include/python2.7
---sysroot=$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)
--L$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)
--L$(PYTHON_ROOT)/lib
--lgnustl_shared
--lpython2.7
+-fexceptions
+-frtti
+-fpic
+-ffunction-sections
+-funwind-tables
+-march=armv7-a
+-msoft-float
+-mfpu=neon
+-mthumb
+-march=armv7-a
+-Wl,--fix-cortex-a8
+-Os
+-fomit-frame-pointer
+-fno-strict-aliasing
+-DANDROID
+-D__ANDROID__
+-DANDROID_TOOLCHAIN=clang
+-DANDROID_ABI=armv7-a
+-DANDROID_STL=c++_shared
+-DBOOST_ALL_NO_LIB
+#-DNDEBUG
+-O2
+-g
+-fvisibility=hidden
+-fvisibility-inlines-hidden
+-fdata-sections
+-D__arm__
+-D_REENTRANT
+-D_GLIBCXX__PTHREADS
+-Wno-long-long
+-Wno-missing-field-initializers
+-Wno-unused-variable
+-Wl,-z,relro
+-Wl,-z,now
+-lc++_shared
+-L$(PYTHON_ROOT)
+-lpython$(PYTHON_LINK_VERSION)
+-Wl,-O1
+-Wl,-Bsymbolic-functions
;
+
+using python : $(PYTHON_MAJOR_MINOR)
+ : $(PYTHON_host)
+ : $(PYTHON_ROOT) $(PYTHON_INCLUDE)
+ : $(PYTHON_ROOT)/libpython$(PYTHON_LINK_VERSION).so
+ : #BOOST_ALL_DYN_LINK
+;
\ No newline at end of file
diff --git a/pythonforandroid/recipes/cdecimal/__init__.py b/pythonforandroid/recipes/cdecimal/__init__.py
index 6c4740bd3c..94929c7807 100644
--- a/pythonforandroid/recipes/cdecimal/__init__.py
+++ b/pythonforandroid/recipes/cdecimal/__init__.py
@@ -7,7 +7,7 @@ class CdecimalRecipe(CompiledComponentsPythonRecipe):
version = '2.3'
url = 'http://www.bytereef.org/software/mpdecimal/releases/cdecimal-{version}.tar.gz'
- depends = ['python2']
+ depends = []
patches = ['locale.patch',
'cross-compile.patch']
diff --git a/pythonforandroid/recipes/coverage/__init__.py b/pythonforandroid/recipes/coverage/__init__.py
index d88ac6a652..95f08f1f4a 100644
--- a/pythonforandroid/recipes/coverage/__init__.py
+++ b/pythonforandroid/recipes/coverage/__init__.py
@@ -7,7 +7,7 @@ class CoverageRecipe(PythonRecipe):
url = 'https://pypi.python.org/packages/2d/10/6136c8e10644c16906edf4d9f7c782c0f2e7ed47ff2f41f067384e432088/coverage-{version}.tar.gz'
- depends = ['hostpython2', 'setuptools']
+ depends = [('hostpython2', 'hostpython3'), 'setuptools']
patches = ['fallback-utf8.patch']
diff --git a/pythonforandroid/recipes/cryptography/__init__.py b/pythonforandroid/recipes/cryptography/__init__.py
index 868ca74d14..c5b3e9eb49 100644
--- a/pythonforandroid/recipes/cryptography/__init__.py
+++ b/pythonforandroid/recipes/cryptography/__init__.py
@@ -3,7 +3,7 @@
class CryptographyRecipe(CompiledComponentsPythonRecipe):
name = 'cryptography'
- version = '2.4.2'
+ version = '2.5'
url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz'
depends = ['openssl', 'idna', 'asn1crypto', 'six', 'setuptools',
'enum34', 'ipaddress', 'cffi']
diff --git a/pythonforandroid/recipes/cymunk/__init__.py b/pythonforandroid/recipes/cymunk/__init__.py
index d070a44dad..96d4169710 100644
--- a/pythonforandroid/recipes/cymunk/__init__.py
+++ b/pythonforandroid/recipes/cymunk/__init__.py
@@ -6,7 +6,7 @@ class CymunkRecipe(CythonRecipe):
url = 'https://github.com/tito/cymunk/archive/{version}.zip'
name = 'cymunk'
- depends = [('python2', 'python3crystax')]
+ depends = [('python2', 'python3crystax', 'python3')]
recipe = CymunkRecipe()
diff --git a/pythonforandroid/recipes/dateutil/__init__.py b/pythonforandroid/recipes/dateutil/__init__.py
index ace3c638a2..3367f8d145 100644
--- a/pythonforandroid/recipes/dateutil/__init__.py
+++ b/pythonforandroid/recipes/dateutil/__init__.py
@@ -6,7 +6,7 @@ class DateutilRecipe(PythonRecipe):
version = '2.6.0'
url = 'https://pypi.python.org/packages/51/fc/39a3fbde6864942e8bb24c93663734b74e281b984d1b8c4f95d64b0c21f6/python-dateutil-2.6.0.tar.gz'
- depends = ['python2', "setuptools"]
+ depends = ["setuptools"]
call_hostpython_via_targetpython = False
install_in_hostpython = True
diff --git a/pythonforandroid/recipes/decorator/__init__.py b/pythonforandroid/recipes/decorator/__init__.py
index e97840c190..e1001dd6f3 100644
--- a/pythonforandroid/recipes/decorator/__init__.py
+++ b/pythonforandroid/recipes/decorator/__init__.py
@@ -5,7 +5,7 @@ class DecoratorPyRecipe(PythonRecipe):
version = '4.2.1'
url = 'https://pypi.python.org/packages/source/d/decorator/decorator-{version}.tar.gz'
url = 'https://github.com/micheles/decorator/archive/{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
+ depends = ['setuptools']
site_packages_name = 'decorator'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/doubleratchet/__init__.py b/pythonforandroid/recipes/doubleratchet/__init__.py
index f5d13d0a5f..8e93044bf1 100644
--- a/pythonforandroid/recipes/doubleratchet/__init__.py
+++ b/pythonforandroid/recipes/doubleratchet/__init__.py
@@ -6,7 +6,6 @@ class DoubleRatchetRecipe(PythonRecipe):
version = '0.4.0'
url = 'https://pypi.python.org/packages/source/D/DoubleRatchet/DoubleRatchet-{version}.tar.gz'
depends = [
- ('python2', 'python3crystax'),
'setuptools',
'cryptography',
]
diff --git a/pythonforandroid/recipes/enaml/__init__.py b/pythonforandroid/recipes/enaml/__init__.py
index 81738e73aa..d2335206c8 100644
--- a/pythonforandroid/recipes/enaml/__init__.py
+++ b/pythonforandroid/recipes/enaml/__init__.py
@@ -4,9 +4,9 @@
class EnamlRecipe(CppCompiledComponentsPythonRecipe):
site_packages_name = 'enaml'
version = '0.9.8'
- url = 'https://github.com/nucleic/enaml/archive/master.zip'
+ url = 'https://github.com/nucleic/enaml/archive/{version}.zip'
patches = ['0001-Update-setup.py.patch'] # Remove PyQt dependency
- depends = ['python2', 'setuptools', 'atom', 'kiwisolver']
+ depends = ['setuptools', 'atom', 'kiwisolver']
recipe = EnamlRecipe()
diff --git a/pythonforandroid/recipes/ethash/__init__.py b/pythonforandroid/recipes/ethash/__init__.py
index 403513d89d..b65e10ad38 100644
--- a/pythonforandroid/recipes/ethash/__init__.py
+++ b/pythonforandroid/recipes/ethash/__init__.py
@@ -5,7 +5,7 @@ class EthashRecipe(PythonRecipe):
url = 'https://github.com/ethereum/ethash/archive/master.zip'
- depends = ['python2', 'setuptools']
+ depends = ['setuptools']
recipe = EthashRecipe()
diff --git a/pythonforandroid/recipes/evdev/__init__.py b/pythonforandroid/recipes/evdev/__init__.py
index b4921dd76e..afd542e2a0 100644
--- a/pythonforandroid/recipes/evdev/__init__.py
+++ b/pythonforandroid/recipes/evdev/__init__.py
@@ -6,7 +6,7 @@ class EvdevRecipe(CompiledComponentsPythonRecipe):
version = 'v0.4.7'
url = 'https://github.com/gvalkov/python-evdev/archive/{version}.zip'
- depends = [('python2', 'python3crystax')]
+ depends = []
build_cmd = 'build'
diff --git a/pythonforandroid/recipes/ffmpeg/__init__.py b/pythonforandroid/recipes/ffmpeg/__init__.py
index 818722c3f7..f8e3ec141f 100644
--- a/pythonforandroid/recipes/ffmpeg/__init__.py
+++ b/pythonforandroid/recipes/ffmpeg/__init__.py
@@ -4,7 +4,7 @@
class FFMpegRecipe(Recipe):
- version = '3.4.1'
+ version = '3.4.5'
url = 'http://ffmpeg.org/releases/ffmpeg-{version}.tar.bz2'
depends = ['sdl2'] # Need this to build correct recipe order
opts_depends = ['openssl', 'ffpyplayer_codecs']
diff --git a/pythonforandroid/recipes/gevent-websocket/__init__.py b/pythonforandroid/recipes/gevent-websocket/__init__.py
index 8820a10b3b..598ca130c7 100644
--- a/pythonforandroid/recipes/gevent-websocket/__init__.py
+++ b/pythonforandroid/recipes/gevent-websocket/__init__.py
@@ -4,7 +4,7 @@
class GeventWebsocketRecipe(PythonRecipe):
version = '0.9.5'
url = 'https://pypi.python.org/packages/source/g/gevent-websocket/gevent-websocket-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
+ depends = ['setuptools']
site_packages_name = 'geventwebsocket'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/gevent/__init__.py b/pythonforandroid/recipes/gevent/__init__.py
index efe08d45f4..5933fb3364 100644
--- a/pythonforandroid/recipes/gevent/__init__.py
+++ b/pythonforandroid/recipes/gevent/__init__.py
@@ -1,27 +1,31 @@
-import os
-from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+import re
+from pythonforandroid.logger import info
+from pythonforandroid.recipe import CythonRecipe
-class GeventRecipe(CompiledComponentsPythonRecipe):
- version = '1.1.1'
+class GeventRecipe(CythonRecipe):
+ version = '1.4.0'
url = 'https://pypi.python.org/packages/source/g/gevent/gevent-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'greenlet']
- patches = ["gevent.patch"]
+ depends = ['librt', 'greenlet']
+ patches = ["cross_compiling.patch"]
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ """
+ - Moves all -I -D from CFLAGS to CPPFLAGS environment.
+ - Moves all -l from LDFLAGS to LIBS environment.
+ - Fixes linker name (use cross compiler) and flags (appends LIBS)
+ """
env = super(GeventRecipe, self).get_recipe_env(arch, with_flags_in_cc)
- # sets linker to use the correct gcc (cross compiler)
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
# CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS
- env['CPPFLAGS'] = env['CFLAGS'] + ' -I{}/sources/python/3.5/include/python/'.format(self.ctx.ndk_dir)
- env['CFLAGS'] = ''
+ regex = re.compile(r'(?:\s|^)-[DI][\S]+')
+ env['CPPFLAGS'] = ''.join(re.findall(regex, env['CFLAGS'])).strip()
+ env['CFLAGS'] = re.sub(regex, '', env['CFLAGS'])
+ info('Moved "{}" from CFLAGS to CPPFLAGS.'.format(env['CPPFLAGS']))
# LDFLAGS may only be used to specify linker flags, for libraries use LIBS
- env['LDFLAGS'] = env['LDFLAGS'].replace('-lm', '').replace('-lcrystax', '')
- env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch))
- env['LIBS'] = ' -lm'
- if self.ctx.ndk == 'crystax':
- env['LIBS'] += ' -lcrystax -lpython{}m'.format(self.ctx.python_recipe.version[0:3])
- env['LDSHARED'] += env['LIBS']
+ regex = re.compile(r'(?:\s|^)-l[\w\.]+')
+ env['LIBS'] = ''.join(re.findall(regex, env['LDFLAGS'])).strip()
+ env['LDFLAGS'] = re.sub(regex, '', env['LDFLAGS'])
+ info('Moved "{}" from LDFLAGS to LIBS.'.format(env['LIBS']))
return env
diff --git a/pythonforandroid/recipes/gevent/cross_compiling.patch b/pythonforandroid/recipes/gevent/cross_compiling.patch
new file mode 100644
index 0000000000..01e55d8c00
--- /dev/null
+++ b/pythonforandroid/recipes/gevent/cross_compiling.patch
@@ -0,0 +1,26 @@
+diff --git a/_setupares.py b/_setupares.py
+index dd184de6..bb16bebe 100644
+--- a/_setupares.py
++++ b/_setupares.py
+@@ -43,7 +43,7 @@ else:
+ ares_configure_command = ' '.join([
+ "(cd ", quoted_dep_abspath('c-ares'),
+ " && if [ -r ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ",
+- " && sh ./configure --disable-dependency-tracking " + _m32 + "CONFIG_COMMANDS= ",
++ " && sh ./configure --host={} --disable-dependency-tracking ".format(os.environ['TOOLCHAIN_PREFIX']) + _m32 + "CONFIG_COMMANDS= ",
+ " && cp ares_config.h ares_build.h \"$OLDPWD\" ",
+ " && cat ares_build.h ",
+ " && if [ -r ares_build.h.orig ]; then mv ares_build.h.orig ares_build.h; fi)",
+diff --git a/_setuplibev.py b/_setuplibev.py
+index 2a5841bf..b6433c94 100644
+--- a/_setuplibev.py
++++ b/_setuplibev.py
+@@ -31,7 +31,7 @@ LIBEV_EMBED = should_embed('libev')
+ # and the PyPy branch will clean it up.
+ libev_configure_command = ' '.join([
+ "(cd ", quoted_dep_abspath('libev'),
+- " && sh ./configure ",
++ " && sh ./configure --host={} ".format(os.environ['TOOLCHAIN_PREFIX']),
+ " && cp config.h \"$OLDPWD\"",
+ ")",
+ '> configure-output.txt'
diff --git a/pythonforandroid/recipes/gevent/gevent.patch b/pythonforandroid/recipes/gevent/gevent.patch
deleted file mode 100644
index 72c77cece2..0000000000
--- a/pythonforandroid/recipes/gevent/gevent.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-diff -Naur gevent-1.1.1/setup.py gevent-1.1.1_diff/setup.py
---- gevent-1.1.1/setup.py 2016-04-04 17:27:33.000000000 +0200
-+++ gevent-1.1.1_diff/setup.py 2016-05-10 10:10:39.145881610 +0200
-@@ -96,7 +96,7 @@
- # and the PyPy branch will clean it up.
- libev_configure_command = ' '.join([
- "(cd ", _quoted_abspath('libev/'),
-- " && /bin/sh ./configure ",
-+ " && /bin/sh ./configure --host={}".format(os.environ['TOOLCHAIN_PREFIX']),
- " && cp config.h \"$OLDPWD\"",
- ")",
- '> configure-output.txt'
-@@ -112,7 +112,7 @@
- # Use -r, not -e, for support of old solaris. See https://github.com/gevent/gevent/issues/777
- ares_configure_command = ' '.join(["(cd ", _quoted_abspath('c-ares/'),
- " && if [ -r ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ",
-- " && /bin/sh ./configure " + _m32 + "CONFIG_COMMANDS= CONFIG_FILES= ",
-+ " && /bin/sh ./configure --host={} ".format(os.environ['TOOLCHAIN_PREFIX']) + "CONFIG_COMMANDS= CONFIG_FILES= ",
- " && cp ares_config.h ares_build.h \"$OLDPWD\" ",
- " && mv ares_build.h.orig ares_build.h)",
- "> configure-output.txt"])
diff --git a/pythonforandroid/recipes/greenlet/__init__.py b/pythonforandroid/recipes/greenlet/__init__.py
index 80564cd284..3f2043d57d 100644
--- a/pythonforandroid/recipes/greenlet/__init__.py
+++ b/pythonforandroid/recipes/greenlet/__init__.py
@@ -1,10 +1,11 @@
-from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.recipe import CompiledComponentsPythonRecipe
-class GreenletRecipe(PythonRecipe):
- version = '0.4.9'
+class GreenletRecipe(CompiledComponentsPythonRecipe):
+ version = '0.4.15'
url = 'https://pypi.python.org/packages/source/g/greenlet/greenlet-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
recipe = GreenletRecipe()
diff --git a/pythonforandroid/recipes/groestlcoin_hash/__init__.py b/pythonforandroid/recipes/groestlcoin_hash/__init__.py
index 6eb7333cda..43e7ab3686 100644
--- a/pythonforandroid/recipes/groestlcoin_hash/__init__.py
+++ b/pythonforandroid/recipes/groestlcoin_hash/__init__.py
@@ -4,7 +4,7 @@
class GroestlcoinHashRecipe(CythonRecipe):
version = '1.0.1'
url = 'https://github.com/Groestlcoin/groestlcoin-hash-python/archive/{version}.tar.gz'
- depends = ['python3crystax']
+ depends = []
call_hostpython_via_targetpython = True
cythonize = False
diff --git a/pythonforandroid/recipes/icu/__init__.py b/pythonforandroid/recipes/icu/__init__.py
index 56914ff1a3..4bb2de0c99 100644
--- a/pythonforandroid/recipes/icu/__init__.py
+++ b/pythonforandroid/recipes/icu/__init__.py
@@ -11,7 +11,7 @@ class ICURecipe(NDKRecipe):
version = '57.1'
url = 'http://download.icu-project.org/files/icu4c/57.1/icu4c-57_1-src.tgz'
- depends = [('python2', 'python3crystax'), 'hostpython2'] # installs in python
+ depends = [('hostpython2', 'hostpython3')] # installs in python
generated_libraries = [
'libicui18n.so', 'libicuuc.so', 'libicudata.so', 'libicule.so']
diff --git a/pythonforandroid/recipes/ifaddrs/__init__.py b/pythonforandroid/recipes/ifaddrs/__init__.py
index 5d6d4a0b9e..47c0008fa5 100644
--- a/pythonforandroid/recipes/ifaddrs/__init__.py
+++ b/pythonforandroid/recipes/ifaddrs/__init__.py
@@ -8,20 +8,14 @@
class IFAddrRecipe(CompiledComponentsPythonRecipe):
- version = 'master'
- url = 'git+https://github.com/morristech/android-ifaddrs.git'
- depends = [('hostpython2', 'hostpython3'), ('python2', 'python3crystax')]
+ version = '8f9a87c'
+ url = 'https://github.com/morristech/android-ifaddrs/archive/{version}.zip'
+ depends = [('hostpython2', 'hostpython3')]
call_hostpython_via_targetpython = False
site_packages_name = 'ifaddrs'
generated_libraries = ['libifaddrs.so']
- def should_build(self, arch):
- """It's faster to build than check"""
- return not (
- exists(join(self.ctx.libs_dir, arch.arch, 'libifaddrs.so'))
- and exists(join(self.ctx.get_python_install_dir(), 'lib' "libifaddrs.so")))
-
def prebuild_arch(self, arch):
"""Make the build and target directories"""
path = self.get_build_dir(arch.arch)
@@ -39,30 +33,22 @@ def build_arch(self, arch):
if not exists(path):
info("creating {}".format(path))
shprint(sh.mkdir, '-p', path)
- cli = env['CC'].split()
- cc = sh.Command(cli[0])
+ cli = env['CC'].split()[0]
+ # makes sure first CC command is the compiler rather than ccache, refs:
+ # https://github.com/kivy/python-for-android/issues/1398
+ if 'ccache' in cli:
+ cli = env['CC'].split()[1]
+ cc = sh.Command(cli)
with current_directory(self.get_build_dir(arch.arch)):
cflags = env['CFLAGS'].split()
cflags.extend(['-I.', '-c', '-l.', 'ifaddrs.c', '-I.'])
shprint(cc, *cflags, _env=env)
-
cflags = env['CFLAGS'].split()
cflags.extend(['-shared', '-I.', 'ifaddrs.o', '-o', 'libifaddrs.so'])
cflags.extend(env['LDFLAGS'].split())
shprint(cc, *cflags, _env=env)
-
shprint(sh.cp, 'libifaddrs.so', self.ctx.get_libs_dir(arch.arch))
- shprint(sh.cp, "libifaddrs.so", join(self.ctx.get_python_install_dir(), 'lib'))
- # drop header in to the Python include directory
- python_version = self.ctx.python_recipe.version[0:3]
- shprint(sh.cp, "ifaddrs.h",
- join(
- self.ctx.get_python_install_dir(),
- 'include/python{}'.format(python_version))
- )
- include_path = join(self.ctx.python_recipe.get_build_dir(arch.arch), 'Include')
- shprint(sh.cp, "ifaddrs.h", include_path)
recipe = IFAddrRecipe()
diff --git a/pythonforandroid/recipes/kivent_core/__init__.py b/pythonforandroid/recipes/kivent_core/__init__.py
deleted file mode 100644
index f04152a6db..0000000000
--- a/pythonforandroid/recipes/kivent_core/__init__.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from pythonforandroid.recipe import CythonRecipe
-from os.path import join
-
-
-class KiventCoreRecipe(CythonRecipe):
- version = 'master'
- url = 'https://github.com/kivy/kivent/archive/{version}.zip'
- name = 'kivent_core'
-
- depends = ['kivy']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventCoreRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- env['CYTHONPATH'] = self.get_recipe(
- 'kivy', self.ctx).get_build_dir(arch.arch)
- return env
-
- def get_build_dir(self, arch, sub=False):
- builddir = super(KiventCoreRecipe, self).get_build_dir(arch)
- if sub or self.subbuilddir:
- return join(builddir, 'modules', 'core')
- else:
- return builddir
-
- def build_arch(self, arch):
- self.subbuilddir = True
- super(KiventCoreRecipe, self).build_arch(arch)
- self.subbuilddir = False
-
-
-recipe = KiventCoreRecipe()
diff --git a/pythonforandroid/recipes/kivent_cymunk/__init__.py b/pythonforandroid/recipes/kivent_cymunk/__init__.py
deleted file mode 100644
index 20031f16c2..0000000000
--- a/pythonforandroid/recipes/kivent_cymunk/__init__.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from pythonforandroid.recipe import CythonRecipe
-from os.path import join
-
-
-class KiventCymunkRecipe(CythonRecipe):
- name = 'kivent_cymunk'
-
- depends = ['kivent_core', 'cymunk']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventCymunkRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- cymunk = self.get_recipe('cymunk', self.ctx).get_build_dir(arch.arch)
- env['PYTHONPATH'] = join(cymunk, 'cymunk', 'python')
- kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch)
- kivent = self.get_recipe('kivent_core',
- self.ctx).get_build_dir(arch.arch, sub=True)
- env['CYTHONPATH'] = ':'.join((kivy, cymunk, kivent))
- return env
-
- def prepare_build_dir(self, arch):
- '''No need to prepare, we'll use kivent_core'''
- return
-
- def get_build_dir(self, arch):
- builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch)
- return join(builddir, 'modules', 'cymunk')
-
-
-recipe = KiventCymunkRecipe()
diff --git a/pythonforandroid/recipes/kivent_particles/__init__.py b/pythonforandroid/recipes/kivent_particles/__init__.py
deleted file mode 100644
index 0ec8efeb5a..0000000000
--- a/pythonforandroid/recipes/kivent_particles/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from pythonforandroid.recipe import CythonRecipe
-from os.path import join
-
-
-class KiventParticlesRecipe(CythonRecipe):
- name = 'kivent_particles'
-
- depends = ['kivent_core']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventParticlesRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch)
- kivent = self.get_recipe('kivent_core',
- self.ctx).get_build_dir(arch.arch, sub=True)
- env['CYTHONPATH'] = ':'.join((kivy, kivent))
- return env
-
- def prepare_build_dir(self, arch):
- '''No need to prepare, we'll use kivent_core'''
- return
-
- def get_build_dir(self, arch):
- builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch)
- return join(builddir, 'modules', 'particles')
-
-
-recipe = KiventParticlesRecipe()
diff --git a/pythonforandroid/recipes/kivent_polygen/__init__.py b/pythonforandroid/recipes/kivent_polygen/__init__.py
deleted file mode 100644
index 96f14f40f8..0000000000
--- a/pythonforandroid/recipes/kivent_polygen/__init__.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from pythonforandroid.recipe import CythonRecipe
-from os.path import join
-
-
-class KiventPolygenRecipe(CythonRecipe):
- name = 'kivent_polygen'
-
- depends = ['kivent_core']
-
- subbuilddir = False
-
- def get_recipe_env(self, arch, with_flags_in_cc=True):
- env = super(KiventPolygenRecipe, self).get_recipe_env(
- arch, with_flags_in_cc=with_flags_in_cc)
- kivy = self.get_recipe('kivy', self.ctx).get_build_dir(arch.arch)
- kivent = self.get_recipe('kivent_core',
- self.ctx).get_build_dir(arch.arch, sub=True)
- env['CYTHONPATH'] = ':'.join((kivy, kivent))
- return env
-
- def prepare_build_dir(self, arch):
- '''No need to prepare, we'll use kivent_core'''
- return
-
- def get_build_dir(self, arch):
- builddir = self.get_recipe('kivent_core', self.ctx).get_build_dir(arch)
- return join(builddir, 'modules', 'polygen')
-
-
-recipe = KiventPolygenRecipe()
diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py
index 70a5f5e69c..19c3a303f8 100644
--- a/pythonforandroid/recipes/kivy/__init__.py
+++ b/pythonforandroid/recipes/kivy/__init__.py
@@ -6,7 +6,8 @@
class KivyRecipe(CythonRecipe):
- version = '1.10.1'
+ # post kivy==1.10.1, `fixes SDL2 image loading (jpg)`
+ version = 'a95d67f'
url = 'https://github.com/kivy/kivy/archive/{version}.zip'
name = 'kivy'
diff --git a/pythonforandroid/recipes/libffi/__init__.py b/pythonforandroid/recipes/libffi/__init__.py
index 62c452c10f..c1fd4a40c4 100644
--- a/pythonforandroid/recipes/libffi/__init__.py
+++ b/pythonforandroid/recipes/libffi/__init__.py
@@ -1,7 +1,7 @@
from os.path import exists, join
from pythonforandroid.recipe import Recipe
from pythonforandroid.logger import info, shprint
-from pythonforandroid.util import current_directory
+from pythonforandroid.util import current_directory, ensure_dir
from glob import glob
import sh
@@ -17,22 +17,11 @@ class LibffiRecipe(Recipe):
version = '3.2.1'
url = 'https://github.com/libffi/libffi/archive/v{version}.tar.gz'
- patches = ['remove-version-info.patch']
-
- def get_host(self, arch):
- with current_directory(self.get_build_dir(arch.arch)):
- host = None
- with open('Makefile') as f:
- for line in f:
- if line.startswith('host = '):
- host = line.strip()[7:]
- break
-
- if not host or not exists(host):
- raise RuntimeError('failed to find build output! ({})'
- .format(host))
-
- return host
+ patches = ['remove-version-info.patch',
+ # This patch below is already included into libffi's master
+ # branch and included in the pre-release 3.3rc0...so we should
+ # remove this when we update the version number for libffi
+ 'fix-includedir.patch']
def should_build(self, arch):
return not exists(join(self.ctx.get_libs_dir(arch.arch), 'libffi.so'))
@@ -45,7 +34,8 @@ def build_arch(self, arch):
shprint(sh.Command('autoreconf'), '-vif', _env=env)
shprint(sh.Command('./configure'),
'--host=' + arch.command_prefix,
- '--prefix=' + self.ctx.get_python_install_dir(),
+ '--prefix=' + self.get_build_dir(arch.arch),
+ '--disable-builddir',
'--enable-shared', _env=env)
# '--with-sysroot={}'.format(self.ctx.ndk_platform),
# '--target={}'.format(arch.toolchain_prefix),
@@ -62,7 +52,7 @@ def build_arch(self, arch):
info("make libffi.la failed as expected")
cc = sh.Command(env['CC'].split()[0])
cflags = env['CC'].split()[1:]
- host_build = join(self.get_build_dir(arch.arch), self.get_host(arch))
+ host_build = self.get_build_dir(arch.arch)
arch_flags = ''
if '-march=' in env['CFLAGS']:
@@ -87,12 +77,13 @@ def build_arch(self, arch):
with current_directory(host_build):
shprint(cc, *cflags, _env=env)
- shprint(sh.cp, '-t', self.ctx.get_libs_dir(arch.arch),
- join(host_build, '.libs', 'libffi.so'))
+ ensure_dir(self.ctx.get_libs_dir(arch.arch))
+ shprint(sh.cp,
+ join(host_build, '.libs', 'libffi.so'),
+ self.ctx.get_libs_dir(arch.arch))
def get_include_dirs(self, arch):
- return [join(self.get_build_dir(arch.arch), self.get_host(arch),
- 'include')]
+ return [join(self.get_build_dir(arch.arch), 'include')]
recipe = LibffiRecipe()
diff --git a/pythonforandroid/recipes/libffi/fix-includedir.patch b/pythonforandroid/recipes/libffi/fix-includedir.patch
new file mode 100644
index 0000000000..0dc35c70ca
--- /dev/null
+++ b/pythonforandroid/recipes/libffi/fix-includedir.patch
@@ -0,0 +1,34 @@
+From 982b89c01aca99c7bc229914fc1521f96930919b Mon Sep 17 00:00:00 2001
+From: Yen Chi Hsuan
+Date: Sun, 13 Nov 2016 19:17:19 +0800
+Subject: [PATCH] Install public headers in the standard path
+
+---
+ include/Makefile.am | 3 +--
+ libffi.pc.in | 2 +-
+ 2 files changed, 2 insertions(+), 3 deletions(-)
+
+diff --git a/include/Makefile.am b/include/Makefile.am
+index bb241e88..c59df9fb 100644
+--- a/include/Makefile.am
++++ b/include/Makefile.am
+@@ -6,5 +6,4 @@ DISTCLEANFILES=ffitarget.h
+ noinst_HEADERS=ffi_common.h ffi_cfi.h
+ EXTRA_DIST=ffi.h.in
+
+-includesdir = $(libdir)/@PACKAGE_NAME@-@PACKAGE_VERSION@/include
+-nodist_includes_HEADERS = ffi.h ffitarget.h
++nodist_include_HEADERS = ffi.h ffitarget.h
+diff --git a/libffi.pc.in b/libffi.pc.in
+index edf6fde5..6fad83b4 100644
+--- a/libffi.pc.in
++++ b/libffi.pc.in
+@@ -2,7 +2,7 @@ prefix=@prefix@
+ exec_prefix=@exec_prefix@
+ libdir=@libdir@
+ toolexeclibdir=@toolexeclibdir@
+-includedir=${libdir}/@PACKAGE_NAME@-@PACKAGE_VERSION@/include
++includedir=@includedir@
+
+ Name: @PACKAGE_NAME@
+ Description: Library supporting Foreign Function Interfaces
diff --git a/pythonforandroid/recipes/libgeos/__init__.py b/pythonforandroid/recipes/libgeos/__init__.py
index e89e3d5714..30786f8ea4 100644
--- a/pythonforandroid/recipes/libgeos/__init__.py
+++ b/pythonforandroid/recipes/libgeos/__init__.py
@@ -8,7 +8,7 @@ class LibgeosRecipe(Recipe):
version = '3.5'
# url = 'http://download.osgeo.org/geos/geos-{version}.tar.bz2'
url = 'https://github.com/libgeos/libgeos/archive/svn-{version}.zip'
- depends = ['python2']
+ depends = []
def should_build(self, arch):
super(LibgeosRecipe, self).should_build(arch)
diff --git a/pythonforandroid/recipes/libglob/__init__.py b/pythonforandroid/recipes/libglob/__init__.py
index aa5925883f..37fa94859d 100644
--- a/pythonforandroid/recipes/libglob/__init__.py
+++ b/pythonforandroid/recipes/libglob/__init__.py
@@ -21,7 +21,7 @@ class LibGlobRecipe(CompiledComponentsPythonRecipe):
# and pushed in via patch
name = 'libglob'
- depends = [('hostpython2', 'hostpython3'), ('python2', 'python3crystax')]
+ depends = [('hostpython2', 'hostpython3')]
patches = ['glob.patch']
def should_build(self, arch):
diff --git a/pythonforandroid/recipes/libnacl/__init__.py b/pythonforandroid/recipes/libnacl/__init__.py
index 6ebdc5ad82..3fc5da82f0 100644
--- a/pythonforandroid/recipes/libnacl/__init__.py
+++ b/pythonforandroid/recipes/libnacl/__init__.py
@@ -4,7 +4,7 @@
class LibNaClRecipe(PythonRecipe):
version = '1.4.4'
url = 'https://github.com/saltstack/libnacl/archive/v{version}.tar.gz'
- depends = ['hostpython2', 'setuptools', 'libsodium']
+ depends = [('hostpython2', 'hostpython3'), 'setuptools', 'libsodium']
site_packages_name = 'libnacl'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/libpq/__init__.py b/pythonforandroid/recipes/libpq/__init__.py
index dcfb541870..45c296a2a6 100644
--- a/pythonforandroid/recipes/libpq/__init__.py
+++ b/pythonforandroid/recipes/libpq/__init__.py
@@ -6,7 +6,7 @@
class LibpqRecipe(Recipe):
version = '9.5.3'
url = 'http://ftp.postgresql.org/pub/source/v{version}/postgresql-{version}.tar.bz2'
- depends = [('python2', 'python3crystax')]
+ depends = []
def should_build(self, arch):
return not os.path.isfile('{}/libpq.a'.format(self.ctx.get_libs_dir(arch.arch)))
diff --git a/pythonforandroid/recipes/librt/__init__.py b/pythonforandroid/recipes/librt/__init__.py
new file mode 100644
index 0000000000..9eb56b3b18
--- /dev/null
+++ b/pythonforandroid/recipes/librt/__init__.py
@@ -0,0 +1,55 @@
+from os import makedirs, remove
+from os.path import exists, join
+import sh
+
+from pythonforandroid.recipe import Recipe
+from pythonforandroid.logger import shprint
+
+
+class LibRt(Recipe):
+ '''
+ This is a dumb recipe. We may need this because some recipes inserted some
+ flags `-lrt` without our control, case of:
+
+ - :class:`~pythonforandroid.recipes.gevent.GeventRecipe`
+ - :class:`~pythonforandroid.recipes.lxml.LXMLRecipe`
+
+ .. note:: the librt doesn't exist in android but it is integrated into
+ libc, so we create a symbolic link which we will remove when our build
+ finishes'''
+
+ @property
+ def libc_path(self):
+ return join(self.ctx.ndk_platform, 'usr', 'lib', 'libc')
+
+ def build_arch(self, arch):
+ # Create a temporary folder to add to link path with a fake librt.so:
+ fake_librt_temp_folder = join(
+ self.get_build_dir(arch.arch),
+ "p4a-librt-recipe-tempdir"
+ )
+ if not exists(fake_librt_temp_folder):
+ makedirs(fake_librt_temp_folder)
+
+ # Set symlinks, and make sure to update them on every build run:
+ if exists(join(fake_librt_temp_folder, "librt.so")):
+ remove(join(fake_librt_temp_folder, "librt.so"))
+ shprint(sh.ln, '-sf',
+ self.libc_path + '.so',
+ join(fake_librt_temp_folder, "librt.so"),
+ )
+ if exists(join(fake_librt_temp_folder, "librt.a")):
+ remove(join(fake_librt_temp_folder, "librt.a"))
+ shprint(sh.ln, '-sf',
+ self.libc_path + '.a',
+ join(fake_librt_temp_folder, "librt.a"),
+ )
+
+ # Add folder as -L link option for all recipes if not done yet:
+ if fake_librt_temp_folder not in arch.extra_global_link_paths:
+ arch.extra_global_link_paths.append(
+ fake_librt_temp_folder
+ )
+
+
+recipe = LibRt()
diff --git a/pythonforandroid/recipes/libsodium/__init__.py b/pythonforandroid/recipes/libsodium/__init__.py
index ca7a1f367b..9911e36baa 100644
--- a/pythonforandroid/recipes/libsodium/__init__.py
+++ b/pythonforandroid/recipes/libsodium/__init__.py
@@ -6,7 +6,7 @@
class LibsodiumRecipe(Recipe):
version = '1.0.16'
url = 'https://github.com/jedisct1/libsodium/releases/download/{version}/libsodium-{version}.tar.gz'
- depends = ['python2']
+ depends = []
patches = ['size_max_fix.patch']
def should_build(self, arch):
diff --git a/pythonforandroid/recipes/libtorrent/__init__.py b/pythonforandroid/recipes/libtorrent/__init__.py
index f358f5166d..c73bb02962 100644
--- a/pythonforandroid/recipes/libtorrent/__init__.py
+++ b/pythonforandroid/recipes/libtorrent/__init__.py
@@ -1,5 +1,7 @@
from pythonforandroid.toolchain import Recipe, shprint, shutil, current_directory
-from os.path import join
+from multiprocessing import cpu_count
+from os.path import join, basename
+from os import listdir, walk
import sh
# This recipe builds libtorrent with Python bindings
@@ -7,71 +9,128 @@
# which is all provided by the boost recipe
+def get_lib_from(search_directory, lib_extension='.so'):
+ '''Scan directories recursively until find any file with the given
+ extension. The default extension to search is ``.so``.'''
+ for root, dirs, files in walk(search_directory):
+ for file in files:
+ if file.endswith(lib_extension):
+ print('get_lib_from: {}\n\t- {}'.format(
+ search_directory, join(root, file)))
+ return join(root, file)
+ return None
+
+
class LibtorrentRecipe(Recipe):
- version = '1.0.9'
- # Don't forget to change the URL when changing the version
- url = 'https://github.com/arvidn/libtorrent/archive/libtorrent-1_0_9.tar.gz'
- depends = ['boost', 'python2']
+ # Todo: make recipe compatible with all p4a architectures
+ '''
+ .. note:: This recipe can be built only against API 21+ and arch armeabi-v7a
+
+ .. versionchanged:: 0.6.0
+ Rewrote recipe to support clang's build and boost 1.68. The following
+ changes has been made:
+
+ - Bumped version number to 1.2.0
+ - added python 3 compatibility
+ - new system to detect/copy generated libraries
+ '''
+ version = '1_2_0'
+ url = 'https://github.com/arvidn/libtorrent/archive/libtorrent_{version}.tar.gz'
+
+ depends = ['boost']
opt_depends = ['openssl']
- patches = ['disable-so-version.patch', 'use-soname-python.patch', 'setup-lib-name.patch']
+ patches = ['disable-so-version.patch',
+ 'use-soname-python.patch',
+ 'setup-lib-name.patch']
+
+ # libtorrent.so is not included because is not a system library
+ generated_libraries = [
+ 'boost_system', 'boost_python{py_version}', 'torrent_rasterbar']
def should_build(self, arch):
- return not (
- self.has_libs(arch, 'libboost_python.so', 'libboost_system.so', 'libtorrent_rasterbar.so')
- and self.ctx.has_package('libtorrent', arch.arch))
+ python_version = self.ctx.python_recipe.version[:3].replace('.', '')
+ libs = ['lib' + lib_name.format(py_version=python_version) +
+ '.so' for lib_name in self.generated_libraries]
+ return not (self.has_libs(arch, *libs) and
+ self.ctx.has_package('libtorrent', arch.arch))
def prebuild_arch(self, arch):
super(LibtorrentRecipe, self).prebuild_arch(arch)
if 'openssl' in recipe.ctx.recipe_build_order:
# Patch boost user-config.jam to use openssl
- self.get_recipe('boost', self.ctx).apply_patch(join(self.get_recipe_dir(), 'user-config-openssl.patch'), arch.arch)
+ self.get_recipe('boost', self.ctx).apply_patch(
+ join(self.get_recipe_dir(), 'user-config-openssl.patch'), arch.arch)
def build_arch(self, arch):
super(LibtorrentRecipe, self).build_arch(arch)
env = self.get_recipe_env(arch)
- with current_directory(join(self.get_build_dir(arch.arch), 'bindings/python')):
- # Compile libtorrent with boost libraries and python bindings
+ env['PYTHON_HOST'] = self.ctx.hostpython
+
+ # Define build variables
+ build_dir = self.get_build_dir(arch.arch)
+ ctx_libs_dir = self.ctx.get_libs_dir(arch.arch)
+ encryption = 'openssl' if 'openssl' in recipe.ctx.recipe_build_order else 'built-in'
+ build_args = [
+ '-q',
+ # '-a', # force build, useful to debug the build
+ '-j' + str(cpu_count()),
+ '--debug-configuration', # so we know if our python is detected
+ # '--deprecated-functions=off',
+ 'toolset=clang-arm',
+ 'abi=aapcs',
+ 'binary-format=elf',
+ 'cxxflags=-std=c++11',
+ 'target-os=android',
+ 'threading=multi',
+ 'link=shared',
+ 'boost-link=shared',
+ 'libtorrent-link=shared',
+ 'runtime-link=shared',
+ 'encryption={}'.format('on' if encryption == 'openssl' else 'off'),
+ 'crypto=' + encryption
+ ]
+ crypto_folder = 'encryption-off'
+ if encryption == 'openssl':
+ crypto_folder = 'crypto-openssl'
+ build_args.extend(['openssl-lib=' + env['OPENSSL_BUILD_PATH'],
+ 'openssl-include=' + env['OPENSSL_INCLUDE']
+ ])
+ build_args.append('release')
+
+ # Compile libtorrent with boost libraries and python bindings
+ with current_directory(join(build_dir, 'bindings/python')):
b2 = sh.Command(join(env['BOOST_ROOT'], 'b2'))
- shprint(b2,
- '-q',
- '-j5',
- 'toolset=gcc-' + env['ARCH'],
- 'target-os=android',
- 'threading=multi',
- 'link=shared',
- 'boost-link=shared',
- 'boost=source',
- 'encryption=openssl' if 'openssl' in recipe.ctx.recipe_build_order else '',
- '--prefix=' + env['CROSSHOME'],
- 'release', _env=env)
- # Common build directories
- build_subdirs = 'gcc-arm/release/boost-link-shared/boost-source'
- if 'openssl' in recipe.ctx.recipe_build_order:
- build_subdirs += '/encryption-openssl'
- build_subdirs += '/libtorrent-python-pic-on/target-os-android/threading-multi/visibility-hidden'
- # Copy the shared libraries into the libs folder
- shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/python/build', build_subdirs, 'libboost_python.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libboost_python.so'))
- shutil.copyfile(join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/system/build', build_subdirs, 'libboost_system.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libboost_system.so'))
- if 'openssl' in recipe.ctx.recipe_build_order:
- shutil.copyfile(
- join(env['BOOST_BUILD_PATH'], 'bin.v2/libs/date_time/build', build_subdirs, 'libboost_date_time.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libboost_date_time.so'))
- shutil.copyfile(
- join(self.get_build_dir(arch.arch), 'bin', build_subdirs, 'libtorrent_rasterbar.so'),
- join(self.ctx.get_libs_dir(arch.arch), 'libtorrent_rasterbar.so'))
- shutil.copyfile(
- join(self.get_build_dir(arch.arch), 'bindings/python/bin', build_subdirs, 'libtorrent.so'),
- join(self.ctx.get_site_packages_dir(arch.arch), 'libtorrent.so'))
+ shprint(b2, *build_args, _env=env)
+
+ # Copy only the boost shared libraries into the libs folder. Because
+ # boost build two boost_python libraries, we force to search the lib
+ # into the corresponding build path.
+ b2_build_dir = 'build/clang-linux-arm/release/{encryption}/' \
+ 'lt-visibility-hidden/'.format(encryption=crypto_folder)
+ boost_libs_dir = join(env['BOOST_BUILD_PATH'], 'bin.v2/libs')
+ for boost_lib in listdir(boost_libs_dir):
+ lib_path = get_lib_from(join(boost_libs_dir, boost_lib, b2_build_dir))
+ if lib_path:
+ lib_name = basename(lib_path)
+ shutil.copyfile(lib_path, join(ctx_libs_dir, lib_name))
+
+ # Copy libtorrent shared libraries into the right places
+ system_libtorrent = get_lib_from(join(build_dir, 'bin'))
+ if system_libtorrent:
+ shutil.copyfile(system_libtorrent,
+ join(ctx_libs_dir, 'libtorrent_rasterbar.so'))
+
+ python_libtorrent = get_lib_from(join(build_dir, 'bindings/python/bin'))
+ shutil.copyfile(python_libtorrent,
+ join(self.ctx.get_site_packages_dir(arch.arch), 'libtorrent.so'))
def get_recipe_env(self, arch):
- env = super(LibtorrentRecipe, self).get_recipe_env(arch)
- # Copy environment from boost recipe
- env.update(self.get_recipe('boost', self.ctx).get_recipe_env(arch))
+ # Use environment from boost recipe, cause we use b2 tool from boost
+ env = self.get_recipe('boost', self.ctx).get_recipe_env(arch)
if 'openssl' in recipe.ctx.recipe_build_order:
r = self.get_recipe('openssl', self.ctx)
env['OPENSSL_BUILD_PATH'] = r.get_build_dir(arch.arch)
+ env['OPENSSL_INCLUDE'] = join(r.get_build_dir(arch.arch), 'include')
env['OPENSSL_VERSION'] = r.version
return env
diff --git a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch b/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
index ec3985af16..183705c839 100644
--- a/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
+++ b/pythonforandroid/recipes/libtorrent/setup-lib-name.patch
@@ -1,20 +1,20 @@
---- libtorrent/bindings/python/setup.py 2016-02-28 08:28:49.000000000 +0100
-+++ patch/bindings/python/setup.py 2016-07-12 12:03:05.256455888 +0200
-@@ -97,7 +97,7 @@
- source_list = os.listdir(os.path.join(os.path.dirname(__file__), "src"))
- source_list = [os.path.join("src", s) for s in source_list if s.endswith(".cpp")]
-
-- ext = [Extension('libtorrent',
-+ ext = [Extension('libtorrent_rasterbar',
- sources = source_list,
- language='c++',
- include_dirs = parse_cmd(extra_cmd, '-I'),
-@@ -107,7 +107,7 @@
- + target_specific(),
- libraries = ['torrent-rasterbar'] + parse_cmd(extra_cmd, '-l'))]
-
--setup(name = 'python-libtorrent',
-+setup(name = 'libtorrent',
- version = '1.0.9',
- author = 'Arvid Norberg',
- author_email = 'arvid@libtorrent.org',
+--- libtorrent/bindings/python/setup.py.orig 2018-11-26 22:21:48.772142135 +0100
++++ libtorrent/bindings/python/setup.py 2018-11-26 22:23:23.092141235 +0100
+@@ -167,7 +167,7 @@
+ extra_compile = flags.parse(extra_cmd)
+
+ ext = [Extension(
+- 'libtorrent',
++ 'libtorrent_rasterbar',
+ sources=sorted(source_list),
+ language='c++',
+ include_dirs=flags.include_dirs,
+@@ -178,7 +178,7 @@
+ ]
+
+ setup(
+- name='python-libtorrent',
++ name='libtorrent',
+ version='1.2.0',
+ author='Arvid Norberg',
+ author_email='arvid@libtorrent.org',
diff --git a/pythonforandroid/recipes/libtorrent/use-soname-python.patch b/pythonforandroid/recipes/libtorrent/use-soname-python.patch
index f78553d269..1456220712 100644
--- a/pythonforandroid/recipes/libtorrent/use-soname-python.patch
+++ b/pythonforandroid/recipes/libtorrent/use-soname-python.patch
@@ -1,11 +1,11 @@
---- libtorrent/bindings/python/Jamfile 2016-01-17 23:52:45.000000000 +0100
-+++ libtorrent-patch/bindings/python/Jamfile 2016-02-09 17:11:44.261578000 +0100
-@@ -35,7 +35,7 @@
-
- if ( gcc in $(properties) )
- {
-- result += -Wl,-Bsymbolic ;
-+ result += -Wl,-soname=libtorrent.so,-Bsymbolic ;
- }
- }
+--- libtorrent/bindings/python/Jamfile.orig 2018-12-07 16:46:50.851838981 +0100
++++ libtorrent/bindings/python/Jamfile 2018-12-07 16:49:09.099837663 +0100
+@@ -113,7 +113,7 @@
+
+ if ( gcc in $(properties) )
+ {
+- result += -Wl,-Bsymbolic ;
++ result += -Wl,-soname=libtorrent.so,-Bsymbolic ;
+ }
+ }
diff --git a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch b/pythonforandroid/recipes/libtorrent/user-config-openssl.patch
index eea9b3f648..6a54071f43 100644
--- a/pythonforandroid/recipes/libtorrent/user-config-openssl.patch
+++ b/pythonforandroid/recipes/libtorrent/user-config-openssl.patch
@@ -1,25 +1,21 @@
---- boost/user-config.jam 2016-03-02 14:31:41.280414820 +0100
-+++ boost-patch/user-config.jam 2016-03-02 14:32:08.904384741 +0100
-@@ -6,6 +6,7 @@
- local TOOLCHAIN_PREFIX = [ os.environ TOOLCHAIN_PREFIX ] ;
- local ARCH = [ os.environ ARCH ] ;
- local PYTHON_ROOT = [ os.environ PYTHON_ROOT ] ;
+--- boost/user-config.jam.orig 2018-12-07 14:16:45.911924859 +0100
++++ boost/user-config.jam 2018-12-07 14:20:16.243922853 +0100
+@@ -9,6 +9,8 @@
+ local PYTHON_INCLUDE = [ os.environ PYTHON_INCLUDE ] ;
+ local PYTHON_LINK_VERSION = [ os.environ PYTHON_LINK_VERSION ] ;
+ local PYTHON_MAJOR_MINOR = [ os.environ PYTHON_MAJOR_MINOR ] ;
+local OPENSSL_BUILD_PATH = [ os.environ OPENSSL_BUILD_PATH ] ;
++local OPENSSL_VERSION = [ os.environ OPENSSL_VERSION ] ;
- using gcc : $(ARCH) : $(TOOLCHAIN_PREFIX)-g++ :
- $(ARCH)
-@@ -20,9 +21,14 @@
- -I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/include
- -I$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)/include
- -I$(PYTHON_ROOT)/include/python2.7
-+-I$(OPENSSL_BUILD_PATH)/include
-+-I$(OPENSSL_BUILD_PATH)/include/openssl
- --sysroot=$(ANDROIDNDK)/platforms/android-$(ANDROIDAPI)/arch-$(ARCH)
- -L$(ANDROIDNDK)/sources/cxx-stl/gnu-libstdc++/$(TOOLCHAIN_VERSION)/libs/$(ARCH)
- -L$(PYTHON_ROOT)/lib
+ #using clang : $(ARCH) : $(ANDROID_BINARIES_PATH)/clang++ :
+ #$(ANDROID_BINARIES_PATH)/llvm-ar
+@@ -56,6 +58,9 @@
+ -Wl,-z,relro
+ -Wl,-z,now
+ -lc++_shared
+-L$(OPENSSL_BUILD_PATH)
- -lgnustl_shared
- -lpython2.7
+-lcrypto$(OPENSSL_VERSION)
+-lssl$(OPENSSL_VERSION)
- ;
+ -L$(PYTHON_ROOT)
+ -lpython$(PYTHON_LINK_VERSION)
+ -Wl,-O1
diff --git a/pythonforandroid/recipes/libtribler/__init__.py b/pythonforandroid/recipes/libtribler/__init__.py
index 6c64ecd145..1abb696892 100644
--- a/pythonforandroid/recipes/libtribler/__init__.py
+++ b/pythonforandroid/recipes/libtribler/__init__.py
@@ -14,7 +14,7 @@ class LibTriblerRecipe(PythonRecipe):
url = 'git+https://github.com/Tribler/tribler.git'
depends = ['apsw', 'cryptography', 'ffmpeg', 'libsodium', 'libtorrent', 'm2crypto',
- 'netifaces', 'openssl', 'pil', 'pycrypto', 'pyleveldb', 'python2', 'twisted',
+ 'netifaces', 'openssl', 'pil', 'pycrypto', 'pyleveldb', 'twisted',
]
python_depends = ['chardet', 'cherrypy', 'configobj', 'decorator', 'feedparser',
diff --git a/pythonforandroid/recipes/libzbar/__init__.py b/pythonforandroid/recipes/libzbar/__init__.py
index b9de0c828b..43ae34cc9d 100644
--- a/pythonforandroid/recipes/libzbar/__init__.py
+++ b/pythonforandroid/recipes/libzbar/__init__.py
@@ -11,22 +11,20 @@ class LibZBarRecipe(Recipe):
url = 'https://github.com/ZBar/ZBar/archive/{version}.zip'
- depends = ['hostpython2', 'python2', 'libiconv']
+ depends = ['libiconv']
patches = ["werror.patch"]
def should_build(self, arch):
return not os.path.exists(
- os.path.join(self.ctx.get_libs_dir(arch.arch), 'libzbar.so'))
+ os.path.join(self.ctx.get_libs_dir(arch.arch), 'libzbar.so'))
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
env = super(LibZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc)
libiconv = self.get_recipe('libiconv', self.ctx)
libiconv_dir = libiconv.get_build_dir(arch.arch)
env['CFLAGS'] += ' -I' + os.path.join(libiconv_dir, 'include')
- env['LDSHARED'] = env['CC'] + \
- ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- env['LDFLAGS'] += " -landroid -liconv"
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -liconv'
return env
def build_arch(self, arch):
diff --git a/pythonforandroid/recipes/libzmq/__init__.py b/pythonforandroid/recipes/libzmq/__init__.py
index 0dea2052ed..b33f3ac6cf 100644
--- a/pythonforandroid/recipes/libzmq/__init__.py
+++ b/pythonforandroid/recipes/libzmq/__init__.py
@@ -7,7 +7,7 @@
class LibZMQRecipe(Recipe):
version = '4.1.4'
url = 'http://download.zeromq.org/zeromq-{version}.tar.gz'
- depends = ['python2']
+ depends = []
def should_build(self, arch):
super(LibZMQRecipe, self).should_build(arch)
diff --git a/pythonforandroid/recipes/lxml/__init__.py b/pythonforandroid/recipes/lxml/__init__.py
index 2b782ef059..6d4b91c25b 100644
--- a/pythonforandroid/recipes/lxml/__init__.py
+++ b/pythonforandroid/recipes/lxml/__init__.py
@@ -1,14 +1,12 @@
from pythonforandroid.recipe import Recipe, CompiledComponentsPythonRecipe
-from pythonforandroid.logger import shprint
from os.path import exists, join
from os import uname
-import sh
class LXMLRecipe(CompiledComponentsPythonRecipe):
version = '4.2.5'
url = 'https://pypi.python.org/packages/source/l/lxml/lxml-{version}.tar.gz' # noqa
- depends = ['libxml2', 'libxslt', 'setuptools']
+ depends = ['librt', 'libxml2', 'libxslt', 'setuptools']
name = 'lxml'
call_hostpython_via_targetpython = False # Due to setuptools
@@ -25,21 +23,6 @@ def should_build(self, arch):
return not all([exists(join(build_dir, lib)) for lib in py_libs])
- def build_compiled_components(self, arch):
- # Hack to make it link properly to librt, inserted automatically by the
- # installer (Note: the librt doesn't exist in android but it is
- # integrated into libc, so we create a symbolic link which we will
- # remove when our build finishes)
- link_c = join(self.ctx.ndk_platform, 'usr', 'lib', 'libc')
- link_rt = join(self.ctx.ndk_platform, 'usr', 'lib', 'librt')
- shprint(sh.ln, '-sf', link_c + '.so', link_rt + '.so')
- shprint(sh.ln, '-sf', link_c + '.a', link_rt + '.a')
-
- super(LXMLRecipe, self).build_compiled_components(arch)
-
- shprint(sh.rm, '-r', link_rt + '.so')
- shprint(sh.rm, '-r', link_rt + '.a')
-
def get_recipe_env(self, arch):
env = super(LXMLRecipe, self).get_recipe_env(arch)
diff --git a/pythonforandroid/recipes/m2crypto/__init__.py b/pythonforandroid/recipes/m2crypto/__init__.py
index 399cdbeb48..653eeca85c 100644
--- a/pythonforandroid/recipes/m2crypto/__init__.py
+++ b/pythonforandroid/recipes/m2crypto/__init__.py
@@ -6,10 +6,9 @@
class M2CryptoRecipe(CompiledComponentsPythonRecipe):
- version = '0.24.0'
+ version = '0.30.1'
url = 'https://pypi.python.org/packages/source/M/M2Crypto/M2Crypto-{version}.tar.gz'
- # md5sum = '89557730e245294a6cab06de8ad4fb42'
- depends = ['openssl', 'hostpython2', 'python2', 'setuptools']
+ depends = ['openssl', 'setuptools']
site_packages_name = 'M2Crypto'
call_hostpython_via_targetpython = False
@@ -35,8 +34,6 @@ def build_compiled_components(self, arch):
def get_recipe_env(self, arch):
env = super(M2CryptoRecipe, self).get_recipe_env(arch)
env['OPENSSL_BUILD_PATH'] = self.get_recipe('openssl', self.ctx).get_build_dir(arch.arch)
- # Set linker to use the correct gcc
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
return env
diff --git a/pythonforandroid/recipes/msgpack-python/__init__.py b/pythonforandroid/recipes/msgpack-python/__init__.py
index f005c338c3..cdd024b922 100644
--- a/pythonforandroid/recipes/msgpack-python/__init__.py
+++ b/pythonforandroid/recipes/msgpack-python/__init__.py
@@ -4,7 +4,7 @@
class MsgPackRecipe(CythonRecipe):
version = '0.4.7'
url = 'https://pypi.python.org/packages/source/m/msgpack-python/msgpack-python-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "setuptools"]
+ depends = ["setuptools"]
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/mysqldb/__init__.py b/pythonforandroid/recipes/mysqldb/__init__.py
index f04d56683c..f08458567b 100644
--- a/pythonforandroid/recipes/mysqldb/__init__.py
+++ b/pythonforandroid/recipes/mysqldb/__init__.py
@@ -8,7 +8,7 @@ class MysqldbRecipe(CompiledComponentsPythonRecipe):
url = 'https://pypi.python.org/packages/source/M/MySQL-python/MySQL-python-{version}.zip'
site_packages_name = 'MySQLdb'
- depends = ['python2', 'setuptools', 'libmysqlclient']
+ depends = ['setuptools', 'libmysqlclient']
patches = ['override-mysql-config.patch',
'disable-zip.patch']
diff --git a/pythonforandroid/recipes/netifaces/__init__.py b/pythonforandroid/recipes/netifaces/__init__.py
index 6c9361a80b..8ad1382025 100644
--- a/pythonforandroid/recipes/netifaces/__init__.py
+++ b/pythonforandroid/recipes/netifaces/__init__.py
@@ -3,7 +3,7 @@
class NetifacesRecipe(CompiledComponentsPythonRecipe):
- version = '0.10.7'
+ version = '0.10.9'
url = 'https://files.pythonhosted.org/packages/source/n/netifaces/netifaces-{version}.tar.gz'
diff --git a/pythonforandroid/recipes/numpy/__init__.py b/pythonforandroid/recipes/numpy/__init__.py
index 1357689c36..6b6e6b3907 100644
--- a/pythonforandroid/recipes/numpy/__init__.py
+++ b/pythonforandroid/recipes/numpy/__init__.py
@@ -1,4 +1,5 @@
from pythonforandroid.recipe import CompiledComponentsPythonRecipe
+from multiprocessing import cpu_count
from os.path import join
@@ -7,7 +8,6 @@ class NumpyRecipe(CompiledComponentsPythonRecipe):
version = '1.15.1'
url = 'https://pypi.python.org/packages/source/n/numpy/numpy-{version}.zip'
site_packages_name = 'numpy'
-
depends = [('python2', 'python3', 'python3crystax')]
patches = [
@@ -18,6 +18,16 @@ class NumpyRecipe(CompiledComponentsPythonRecipe):
join('patches', 'python-fixes.patch')
]
+ def build_compiled_components(self, arch):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ super(NumpyRecipe, self).build_compiled_components(arch)
+ self.setup_extra_args = []
+
+ def rebuild_compiled_components(self, arch, env):
+ self.setup_extra_args = ['-j', str(cpu_count())]
+ super(NumpyRecipe, self).rebuild_compiled_components(arch, env)
+ self.setup_extra_args = []
+
def get_recipe_env(self, arch):
env = super(NumpyRecipe, self).get_recipe_env(arch)
@@ -44,8 +54,5 @@ def get_recipe_env(self, arch):
env['LD'] += flags + ' -shared'
return env
- def prebuild_arch(self, arch):
- super(NumpyRecipe, self).prebuild_arch(arch)
-
recipe = NumpyRecipe()
diff --git a/pythonforandroid/recipes/omemo-backend-signal/__init__.py b/pythonforandroid/recipes/omemo-backend-signal/__init__.py
index ba01eec530..c87034ce7d 100644
--- a/pythonforandroid/recipes/omemo-backend-signal/__init__.py
+++ b/pythonforandroid/recipes/omemo-backend-signal/__init__.py
@@ -7,7 +7,6 @@ class OmemoBackendSignalRecipe(PythonRecipe):
url = 'https://pypi.python.org/packages/source/o/omemo-backend-signal/omemo-backend-signal-{version}.tar.gz'
site_packages_name = 'omemo-backend-signal'
depends = [
- ('python2', 'python3crystax'),
'setuptools',
'protobuf_cpp',
'x3dh',
diff --git a/pythonforandroid/recipes/omemo/__init__.py b/pythonforandroid/recipes/omemo/__init__.py
index 425ffd0d10..a940105a59 100644
--- a/pythonforandroid/recipes/omemo/__init__.py
+++ b/pythonforandroid/recipes/omemo/__init__.py
@@ -7,7 +7,6 @@ class OmemoRecipe(PythonRecipe):
url = 'https://pypi.python.org/packages/source/O/OMEMO/OMEMO-{version}.tar.gz'
site_packages_name = 'omemo'
depends = [
- ('python2', 'python3crystax'),
'setuptools',
'x3dh',
'cryptography',
diff --git a/pythonforandroid/recipes/opencv/__init__.py b/pythonforandroid/recipes/opencv/__init__.py
index 0e51450991..6932bc225c 100644
--- a/pythonforandroid/recipes/opencv/__init__.py
+++ b/pythonforandroid/recipes/opencv/__init__.py
@@ -1,4 +1,4 @@
-import os
+from os.path import join
import sh
from pythonforandroid.recipe import NDKRecipe
from pythonforandroid.toolchain import (
@@ -9,45 +9,127 @@
class OpenCVRecipe(NDKRecipe):
- version = '2.4.10.1'
- url = 'https://github.com/Itseez/opencv/archive/{version}.zip'
- # md5sum = '2ddfa98e867e6611254040df841186dc'
+ '''
+ .. versionchanged:: 0.7.1
+ rewrote recipe to support the python bindings (cv2.so) and enable the
+ build of most of the libraries of the opencv's package, so we can
+ process images, videos, objects, photos...
+ '''
+ version = '4.0.1'
+ url = 'https://github.com/opencv/opencv/archive/{version}.zip'
depends = ['numpy']
- patches = ['patches/p4a_build-2.4.10.1.patch']
- generated_libraries = ['cv2.so']
+ patches = ['patches/p4a_build.patch']
+ generated_libraries = [
+ 'libopencv_features2d.so',
+ 'libopencv_imgproc.so',
+ 'libopencv_stitching.so',
+ 'libopencv_calib3d.so',
+ 'libopencv_flann.so',
+ 'libopencv_ml.so',
+ 'libopencv_videoio.so',
+ 'libopencv_core.so',
+ 'libopencv_highgui.so',
+ 'libopencv_objdetect.so',
+ 'libopencv_video.so',
+ 'libopencv_dnn.so',
+ 'libopencv_imgcodecs.so',
+ 'libopencv_photo.so'
+ ]
- def prebuild_arch(self, arch):
- self.apply_patches(arch)
+ def get_lib_dir(self, arch):
+ return join(self.get_build_dir(arch.arch), 'build', 'lib', arch.arch)
def get_recipe_env(self, arch):
env = super(OpenCVRecipe, self).get_recipe_env(arch)
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
env['ANDROID_NDK'] = self.ctx.ndk_dir
env['ANDROID_SDK'] = self.ctx.sdk_dir
- env['SITEPACKAGES_PATH'] = self.ctx.get_site_packages_dir()
return env
def build_arch(self, arch):
- with current_directory(self.get_build_dir(arch.arch)):
+ build_dir = join(self.get_build_dir(arch.arch), 'build')
+ shprint(sh.mkdir, '-p', build_dir)
+ with current_directory(build_dir):
env = self.get_recipe_env(arch)
- cvsrc = self.get_build_dir(arch.arch)
- lib_dir = os.path.join(self.ctx.get_python_install_dir(), "lib")
+
+ python_major = self.ctx.python_recipe.version[0]
+ python_include_root = self.ctx.python_recipe.include_root(arch.arch)
+ python_site_packages = self.ctx.get_site_packages_dir()
+ python_link_root = self.ctx.python_recipe.link_root(arch.arch)
+ python_link_version = self.ctx.python_recipe.major_minor_version_string
+ if 'python3' in self.ctx.python_recipe.name:
+ python_link_version += 'm'
+ python_library = join(python_link_root,
+ 'libpython{}.so'.format(python_link_version))
+ python_include_numpy = join(python_site_packages,
+ 'numpy', 'core', 'include')
shprint(sh.cmake,
- '-DP4A=ON', '-DANDROID_ABI={}'.format(arch.arch),
- '-DCMAKE_TOOLCHAIN_FILE={}/platforms/android/android.toolchain.cmake'.format(cvsrc),
- '-DPYTHON_INCLUDE_PATH={}/include/python2.7'.format(env['PYTHON_ROOT']),
- '-DPYTHON_LIBRARY={}/lib/libpython2.7.so'.format(env['PYTHON_ROOT']),
- '-DPYTHON_NUMPY_INCLUDE_DIR={}/numpy/core/include'.format(env['SITEPACKAGES_PATH']),
+ '-DP4A=ON',
+ '-DANDROID_ABI={}'.format(arch.arch),
+ '-DANDROID_STANDALONE_TOOLCHAIN={}'.format(self.ctx.ndk_dir),
+ '-DANDROID_NATIVE_API_LEVEL={}'.format(self.ctx.ndk_api),
'-DANDROID_EXECUTABLE={}/tools/android'.format(env['ANDROID_SDK']),
- '-DBUILD_TESTS=OFF', '-DBUILD_PERF_TESTS=OFF',
- '-DBUILD_EXAMPLES=OFF', '-DBUILD_ANDROID_EXAMPLES=OFF',
- '-DPYTHON_PACKAGES_PATH={}'.format(env['SITEPACKAGES_PATH']),
- cvsrc,
+
+ '-DCMAKE_TOOLCHAIN_FILE={}'.format(
+ join(self.ctx.ndk_dir, 'build', 'cmake',
+ 'android.toolchain.cmake')),
+ # Make the linkage with our python library, otherwise we
+ # will get dlopen error when trying to import cv2's module.
+ '-DCMAKE_SHARED_LINKER_FLAGS=-L{path} -lpython{version}'.format(
+ path=python_link_root,
+ version=python_link_version),
+
+ '-DBUILD_WITH_STANDALONE_TOOLCHAIN=ON',
+ # Force to build as shared libraries the cv2's dependant
+ # libs or we will not be able to link with our python
+ '-DBUILD_SHARED_LIBS=ON',
+ '-DBUILD_STATIC_LIBS=OFF',
+
+ # Disable some opencv's features
+ '-DBUILD_opencv_java=OFF',
+ '-DBUILD_opencv_java_bindings_generator=OFF',
+ # '-DBUILD_opencv_highgui=OFF',
+ # '-DBUILD_opencv_imgproc=OFF',
+ # '-DBUILD_opencv_flann=OFF',
+ '-DBUILD_TESTS=OFF',
+ '-DBUILD_PERF_TESTS=OFF',
+ '-DENABLE_TESTING=OFF',
+ '-DBUILD_EXAMPLES=OFF',
+ '-DBUILD_ANDROID_EXAMPLES=OFF',
+
+ # Force to only build our version of python
+ '-DBUILD_OPENCV_PYTHON{major}=ON'.format(major=python_major),
+ '-DBUILD_OPENCV_PYTHON{major}=OFF'.format(
+ major='2' if python_major == '3' else '3'),
+
+ # Force to install the `cv2.so` library directly into
+ # python's site packages (otherwise the cv2's loader fails
+ # on finding the cv2.so library)
+ '-DOPENCV_SKIP_PYTHON_LOADER=ON',
+ '-DOPENCV_PYTHON{major}_INSTALL_PATH={site_packages}'.format(
+ major=python_major, site_packages=python_site_packages),
+
+ # Define python's paths for: exe, lib, includes, numpy...
+ '-DPYTHON_DEFAULT_EXECUTABLE={}'.format(self.ctx.hostpython),
+ '-DPYTHON{major}_EXECUTABLE={host_python}'.format(
+ major=python_major, host_python=self.ctx.hostpython),
+ '-DPYTHON{major}_INCLUDE_PATH={include_path}'.format(
+ major=python_major, include_path=python_include_root),
+ '-DPYTHON{major}_LIBRARIES={python_lib}'.format(
+ major=python_major, python_lib=python_library),
+ '-DPYTHON{major}_NUMPY_INCLUDE_DIRS={numpy_include}'.format(
+ major=python_major, numpy_include=python_include_numpy),
+ '-DPYTHON{major}_PACKAGES_PATH={site_packages}'.format(
+ major=python_major, site_packages=python_site_packages),
+
+ self.get_build_dir(arch.arch),
_env=env)
- shprint(sh.make, '-j', str(cpu_count()), 'opencv_python')
+ shprint(sh.make, '-j' + str(cpu_count()), 'opencv_python' + python_major)
+ # Install python bindings (cv2.so)
shprint(sh.cmake, '-DCOMPONENT=python', '-P', './cmake_install.cmake')
- sh.cp('-a', sh.glob('./lib/{}/lib*.so'.format(arch.arch)), lib_dir)
+ # Copy third party shared libs that we need in our final apk
+ sh.cp('-a', sh.glob('./lib/{}/lib*.so'.format(arch.arch)),
+ self.ctx.get_libs_dir(arch.arch))
recipe = OpenCVRecipe()
diff --git a/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch b/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch
deleted file mode 100644
index a7a60aa3b3..0000000000
--- a/pythonforandroid/recipes/opencv/patches/p4a_build-2.4.10.1.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-diff --git a/cmake/OpenCVDetectPython.cmake b/cmake/OpenCVDetectPython.cmake
-index 31c2c1e..c890917 100644
---- a/cmake/OpenCVDetectPython.cmake
-+++ b/cmake/OpenCVDetectPython.cmake
-@@ -36,7 +36,7 @@ if(PYTHON_EXECUTABLE)
- unset(PYTHON_VERSION_FULL)
- endif()
-
-- if(NOT ANDROID AND NOT IOS)
-+ if(P4A OR NOT ANDROID AND NOT IOS)
- ocv_check_environment_variables(PYTHON_LIBRARY PYTHON_INCLUDE_DIR)
- if(CMAKE_CROSSCOMPILING)
- find_host_package(PythonLibs ${PYTHON_VERSION_MAJOR_MINOR})
-@@ -51,7 +51,7 @@ if(PYTHON_EXECUTABLE)
- endif()
- endif()
-
-- if(NOT ANDROID AND NOT IOS)
-+ if(P4A OR NOT ANDROID AND NOT IOS)
- if(CMAKE_HOST_UNIX)
- execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import *; print get_python_lib()"
- RESULT_VARIABLE PYTHON_CVPY_PROCESS
-@@ -117,7 +117,7 @@ if(PYTHON_EXECUTABLE)
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- endif()
- endif()
-- endif(NOT ANDROID AND NOT IOS)
-+ endif(P4A OR NOT ANDROID AND NOT IOS)
-
- if(BUILD_DOCS)
- find_host_program(SPHINX_BUILD sphinx-build)
-diff --git a/modules/python/CMakeLists.txt b/modules/python/CMakeLists.txt
-index 3c0f2fd..7ba234a 100644
---- a/modules/python/CMakeLists.txt
-+++ b/modules/python/CMakeLists.txt
-@@ -5,7 +5,7 @@
- if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
- ocv_module_disable(python)
- endif()
--if(ANDROID OR IOS OR NOT PYTHONLIBS_FOUND OR NOT PYTHON_USE_NUMPY)
-+if(ANDROID AND NOT P4A OR IOS OR NOT PYTHONLIBS_FOUND OR NOT PYTHON_USE_NUMPY)
- ocv_module_disable(python)
- endif()
-
-diff --git a/modules/androidcamera/src/camera_activity.cpp b/modules/androidcamera/src/camera_activity.cpp
-index 84db3e1..4222526 100644
---- a/modules/androidcamera/src/camera_activity.cpp
-+++ b/modules/androidcamera/src/camera_activity.cpp
-@@ -7,6 +7,7 @@
- #include
- #include
- #include
-+#include
- #include
- #include "camera_activity.hpp"
- #include "camera_wrapper.h"
-@@ -342,6 +343,8 @@ std::string CameraWrapperConnector::getPathLibFolder()
-
- char* pathEnd = strrchr(pathBegin, '/');
- pathEnd[1] = 0;
-+ pathBegin = realpath((std::string(pathBegin)+"../../../../lib").c_str(), lineBuf);
-+ pathBegin = strcat(pathBegin, "/");
-
- LOGD("Libraries folder found: %s", pathBegin);
-
-
diff --git a/pythonforandroid/recipes/opencv/patches/p4a_build.patch b/pythonforandroid/recipes/opencv/patches/p4a_build.patch
new file mode 100644
index 0000000000..fd60c01d38
--- /dev/null
+++ b/pythonforandroid/recipes/opencv/patches/p4a_build.patch
@@ -0,0 +1,33 @@
+This patch allow that the opencv's build command correctly detects our version
+of python, so we can successfully build the python bindings (cv2.so)
+--- opencv-4.0.1/cmake/OpenCVDetectPython.cmake.orig 2018-12-22 08:03:30.000000000 +0100
++++ opencv-4.0.1/cmake/OpenCVDetectPython.cmake 2019-01-31 11:33:10.896502978 +0100
+@@ -175,7 +175,7 @@ if(NOT ${found})
+ endif()
+ endif()
+
+- if(NOT ANDROID AND NOT IOS)
++ if(P4A OR NOT ANDROID AND NOT IOS)
+ if(CMAKE_HOST_UNIX)
+ execute_process(COMMAND ${_executable} -c "from distutils.sysconfig import *; print(get_python_lib())"
+ RESULT_VARIABLE _cvpy_process
+@@ -244,7 +244,7 @@ if(NOT ${found})
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ endif()
+ endif()
+- endif(NOT ANDROID AND NOT IOS)
++ endif(P4A OR NOT ANDROID AND NOT IOS)
+ endif()
+
+ # Export return values
+--- opencv-4.0.1/modules/python/CMakeLists.txt.orig 2018-12-22 08:03:30.000000000 +0100
++++ opencv-4.0.1/modules/python/CMakeLists.txt 2019-01-31 11:47:17.100494908 +0100
+@@ -3,7 +3,7 @@
+ # ----------------------------------------------------------------------------
+ if(DEFINED OPENCV_INITIAL_PASS) # OpenCV build
+
+-if(ANDROID OR APPLE_FRAMEWORK OR WINRT)
++if(ANDROID AND NOT P4A OR APPLE_FRAMEWORK OR WINRT)
+ ocv_module_disable_(python2)
+ ocv_module_disable_(python3)
+ return()
diff --git a/pythonforandroid/recipes/pil/__init__.py b/pythonforandroid/recipes/pil/__init__.py
index 1b99dabfb4..f3ad2f42ef 100644
--- a/pythonforandroid/recipes/pil/__init__.py
+++ b/pythonforandroid/recipes/pil/__init__.py
@@ -8,7 +8,7 @@ class PILRecipe(CompiledComponentsPythonRecipe):
name = 'pil'
version = '1.1.7'
url = 'http://effbot.org/downloads/Imaging-{version}.tar.gz'
- depends = [('python2', 'python2legacy'), 'png', 'jpeg', 'setuptools']
+ depends = ['png', 'jpeg', 'setuptools']
opt_depends = ['freetype']
site_packages_name = 'PIL'
diff --git a/pythonforandroid/recipes/preppy/__init__.py b/pythonforandroid/recipes/preppy/__init__.py
index 495a58dd2c..40afd681ba 100644
--- a/pythonforandroid/recipes/preppy/__init__.py
+++ b/pythonforandroid/recipes/preppy/__init__.py
@@ -4,7 +4,7 @@
class PreppyRecipe(PythonRecipe):
version = '27b7085'
url = 'https://bitbucket.org/rptlab/preppy/get/{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
patches = ['fix-setup.patch']
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/protobuf_cpp/__init__.py b/pythonforandroid/recipes/protobuf_cpp/__init__.py
index 6cfa674b57..d82fcf7206 100644
--- a/pythonforandroid/recipes/protobuf_cpp/__init__.py
+++ b/pythonforandroid/recipes/protobuf_cpp/__init__.py
@@ -1,7 +1,7 @@
from pythonforandroid.recipe import PythonRecipe
from pythonforandroid.logger import shprint, info_notify
from pythonforandroid.util import current_directory, shutil
-from os.path import exists, join, dirname
+from os.path import exists, join
import sh
from multiprocessing import cpu_count
from pythonforandroid.toolchain import info
@@ -11,7 +11,7 @@
class ProtobufCppRecipe(PythonRecipe):
name = 'protobuf_cpp'
- version = '3.5.1'
+ version = '3.6.1'
url = 'https://github.com/google/protobuf/releases/download/v{version}/protobuf-python-{version}.tar.gz'
call_hostpython_via_targetpython = False
depends = ['cffi', 'setuptools']
@@ -20,6 +20,12 @@ class ProtobufCppRecipe(PythonRecipe):
def prebuild_arch(self, arch):
super(ProtobufCppRecipe, self).prebuild_arch(arch)
+
+ patch_mark = join(self.get_build_dir(arch.arch), '.protobuf-patched')
+ if self.ctx.python_recipe.name == 'python3' and not exists(patch_mark):
+ self.apply_patch('fix-python3-compatibility.patch', arch.arch)
+ shprint(sh.touch, patch_mark)
+
# During building, host needs to transpile .proto files to .py
# ideally with the same version as protobuf runtime, or with an older one.
# Because protoc is compiled for target (i.e. Android), we need an other binary
@@ -100,34 +106,18 @@ def install_python_package(self, arch):
with current_directory(join(self.get_build_dir(arch.arch), 'python')):
hostpython = sh.Command(self.hostpython_location)
- if self.ctx.python_recipe.from_crystax:
- hpenv = env.copy()
- shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=.',
- '--cpp_implementation',
- _env=hpenv, *self.setup_extra_args)
- else:
- hppath = join(dirname(self.hostpython_location), 'Lib',
- 'site-packages')
- hpenv = env.copy()
- if 'PYTHONPATH' in hpenv:
- hpenv['PYTHONPATH'] = ':'.join([hppath] +
- hpenv['PYTHONPATH'].split(':'))
- else:
- hpenv['PYTHONPATH'] = hppath
- shprint(hostpython, 'setup.py', 'install', '-O2',
- '--root={}'.format(self.ctx.get_python_install_dir()),
- '--install-lib=lib/python2.7/site-packages',
- '--cpp_implementation',
- _env=hpenv, *self.setup_extra_args)
+ hpenv = env.copy()
+ shprint(hostpython, 'setup.py', 'install', '-O2',
+ '--root={}'.format(self.ctx.get_python_install_dir()),
+ '--install-lib=.',
+ '--cpp_implementation',
+ _env=hpenv, *self.setup_extra_args)
def get_recipe_env(self, arch):
env = super(ProtobufCppRecipe, self).get_recipe_env(arch)
if self.protoc_dir is not None:
# we need protoc with binary for host platform
env['PROTOC'] = join(self.protoc_dir, 'bin', 'protoc')
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
env['TARGET_OS'] = 'OS_ANDROID_CROSSCOMPILE'
env['CFLAGS'] += (
' -I' + self.ctx.ndk_dir + '/platforms/android-' +
@@ -136,17 +126,17 @@ def get_recipe_env(self, arch):
' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' +
self.ctx.toolchain_version + '/include' +
' -I' + self.ctx.ndk_dir + '/sources/cxx-stl/gnu-libstdc++/' +
- self.ctx.toolchain_version + '/libs/' + arch.arch + '/include' +
- ' -I' + env['PYTHON_ROOT'] + '/include/python2.7')
+ self.ctx.toolchain_version + '/libs/' + arch.arch + '/include')
+ env['CFLAGS'] += ' -std=gnu++11'
env['CXXFLAGS'] = env['CFLAGS']
env['CXXFLAGS'] += ' -frtti'
env['CXXFLAGS'] += ' -fexceptions'
env['LDFLAGS'] += (
' -L' + self.ctx.ndk_dir +
'/sources/cxx-stl/gnu-libstdc++/' + self.ctx.toolchain_version +
- '/libs/' + arch.arch + ' -lgnustl_shared -lpython2.7 -landroid -llog')
+ '/libs/' + arch.arch)
+ env['LIBS'] = env.get('LIBS', '') + ' -lgnustl_shared -landroid -llog'
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
return env
diff --git a/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch b/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch
new file mode 100644
index 0000000000..e77debaa61
--- /dev/null
+++ b/pythonforandroid/recipes/protobuf_cpp/fix-python3-compatibility.patch
@@ -0,0 +1,91 @@
+From 539bc017a62f91bdf7c547b58948cb5a2f59d918 Mon Sep 17 00:00:00 2001
+From: Ben Webb
+Date: Thu, 12 Jul 2018 10:58:10 -0700
+Subject: [PATCH] Add Python 3.7 compatibility (#4862)
+
+Compilation of Python wrappers fails with Python 3.7 because
+the Python folks changed their C API such that
+PyUnicode_AsUTF8AndSize() now returns a const char* rather
+than a char*. Add a patch to work around. Relates #4086.
+---
+ python/google/protobuf/pyext/descriptor.cc | 2 +-
+ python/google/protobuf/pyext/descriptor_containers.cc | 2 +-
+ python/google/protobuf/pyext/descriptor_pool.cc | 2 +-
+ python/google/protobuf/pyext/extension_dict.cc | 2 +-
+ python/google/protobuf/pyext/message.cc | 4 ++--
+ 5 files changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/python/google/protobuf/pyext/descriptor.cc b/python/google/protobuf/pyext/descriptor.cc
+index 8af0cb1289..19a1c38a62 100644
+--- a/python/google/protobuf/pyext/descriptor.cc
++++ b/python/google/protobuf/pyext/descriptor.cc
+@@ -56,7 +56,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/descriptor_containers.cc b/python/google/protobuf/pyext/descriptor_containers.cc
+index bc007f7efa..0153664f50 100644
+--- a/python/google/protobuf/pyext/descriptor_containers.cc
++++ b/python/google/protobuf/pyext/descriptor_containers.cc
+@@ -66,7 +66,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/descriptor_pool.cc b/python/google/protobuf/pyext/descriptor_pool.cc
+index 95882aeb35..962accc6e9 100644
+--- a/python/google/protobuf/pyext/descriptor_pool.cc
++++ b/python/google/protobuf/pyext/descriptor_pool.cc
+@@ -48,7 +48,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/extension_dict.cc b/python/google/protobuf/pyext/extension_dict.cc
+index 018b5c2c49..174c5470c2 100644
+--- a/python/google/protobuf/pyext/extension_dict.cc
++++ b/python/google/protobuf/pyext/extension_dict.cc
+@@ -53,7 +53,7 @@
+ #endif
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+
+diff --git a/python/google/protobuf/pyext/message.cc b/python/google/protobuf/pyext/message.cc
+index 5893533adf..31094b7e10 100644
+--- a/python/google/protobuf/pyext/message.cc
++++ b/python/google/protobuf/pyext/message.cc
+@@ -79,7 +79,7 @@
+ (PyUnicode_Check(ob)? PyUnicode_AsUTF8(ob): PyBytes_AsString(ob))
+ #define PyString_AsStringAndSize(ob, charpp, sizep) \
+ (PyUnicode_Check(ob)? \
+- ((*(charpp) = PyUnicode_AsUTF8AndSize(ob, (sizep))) == NULL? -1: 0): \
++ ((*(charpp) = const_cast(PyUnicode_AsUTF8AndSize(ob, (sizep)))) == NULL? -1: 0): \
+ PyBytes_AsStringAndSize(ob, (charpp), (sizep)))
+ #endif
+ #endif
+@@ -1529,7 +1529,7 @@ PyObject* HasField(CMessage* self, PyObject* arg) {
+ return NULL;
+ }
+ #else
+- field_name = PyUnicode_AsUTF8AndSize(arg, &size);
++ field_name = const_cast(PyUnicode_AsUTF8AndSize(arg, &size));
+ if (!field_name) {
+ return NULL;
+ }
diff --git a/pythonforandroid/recipes/psycopg2/__init__.py b/pythonforandroid/recipes/psycopg2/__init__.py
index f26f79109d..75a07374e5 100644
--- a/pythonforandroid/recipes/psycopg2/__init__.py
+++ b/pythonforandroid/recipes/psycopg2/__init__.py
@@ -4,10 +4,14 @@
class Psycopg2Recipe(PythonRecipe):
+ """
+ Requires `libpq-dev` system dependency e.g. for `pg_config` binary.
+ """
version = 'latest'
url = 'http://initd.org/psycopg/tarballs/psycopg2-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'libpq']
+ depends = ['libpq']
site_packages_name = 'psycopg2'
+ call_hostpython_via_targetpython = False
def prebuild_arch(self, arch):
libdir = self.ctx.get_libs_dir(arch.arch)
diff --git a/pythonforandroid/recipes/pyaml/__init__.py b/pythonforandroid/recipes/pyaml/__init__.py
index ee24eb8f9c..8440175707 100644
--- a/pythonforandroid/recipes/pyaml/__init__.py
+++ b/pythonforandroid/recipes/pyaml/__init__.py
@@ -4,7 +4,7 @@
class PyamlRecipe(PythonRecipe):
version = "15.8.2"
url = 'https://pypi.python.org/packages/source/p/pyaml/pyaml-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "setuptools"]
+ depends = ["setuptools"]
site_packages_name = 'yaml'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pyasn1/__init__.py b/pythonforandroid/recipes/pyasn1/__init__.py
index 64b007d8ce..dec21e8010 100644
--- a/pythonforandroid/recipes/pyasn1/__init__.py
+++ b/pythonforandroid/recipes/pyasn1/__init__.py
@@ -5,7 +5,7 @@
class PyASN1Recipe(PythonRecipe):
version = '0.1.8'
url = 'https://pypi.python.org/packages/source/p/pyasn1/pyasn1-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
recipe = PyASN1Recipe()
diff --git a/pythonforandroid/recipes/pycryptodome/__init__.py b/pythonforandroid/recipes/pycryptodome/__init__.py
index 43e28fc72f..9418600a29 100644
--- a/pythonforandroid/recipes/pycryptodome/__init__.py
+++ b/pythonforandroid/recipes/pycryptodome/__init__.py
@@ -2,15 +2,9 @@
class PycryptodomeRecipe(PythonRecipe):
- version = '3.4.6'
+ version = '3.6.3'
url = 'https://github.com/Legrandin/pycryptodome/archive/v{version}.tar.gz'
depends = ['setuptools', 'cffi']
- def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(PycryptodomeRecipe, self).get_recipe_env(arch, with_flags_in_cc)
- # sets linker to use the correct gcc (cross compiler)
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- return env
-
recipe = PycryptodomeRecipe()
diff --git a/pythonforandroid/recipes/pyethereum/__init__.py b/pythonforandroid/recipes/pyethereum/__init__.py
index f08c073308..d18ad8ea0f 100644
--- a/pythonforandroid/recipes/pyethereum/__init__.py
+++ b/pythonforandroid/recipes/pyethereum/__init__.py
@@ -6,7 +6,7 @@ class PyethereumRecipe(PythonRecipe):
url = 'https://github.com/ethereum/pyethereum/archive/{version}.tar.gz'
depends = [
- 'python2', 'setuptools', 'pycryptodome', 'pysha3', 'ethash', 'scrypt'
+ 'setuptools', 'pycryptodome', 'pysha3', 'ethash', 'scrypt'
]
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pygame/__init__.py b/pythonforandroid/recipes/pygame/__init__.py
index 82fac2eac5..981fa445fb 100644
--- a/pythonforandroid/recipes/pygame/__init__.py
+++ b/pythonforandroid/recipes/pygame/__init__.py
@@ -68,7 +68,6 @@ def build_arch(self, arch):
shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
env['STRIP'], '{}', ';')
- python_install_path = join(self.ctx.build_dir, 'python-install')
warning('Should remove pygame tests etc. here, but skipping for now')
diff --git a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py b/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py
index f6682d2b3a..f18dd5bd26 100644
--- a/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py
+++ b/pythonforandroid/recipes/pygame_bootstrap_components/__init__.py
@@ -9,9 +9,10 @@ class PygameJNIComponentsRecipe(BootstrapNDKRecipe):
version = 'master'
url = 'https://github.com/kivy/p4a-pygame-bootstrap-components/archive/{version}.zip'
dir_name = 'bootstrap_components'
+ patches = ['jpeg-ndk15-plus.patch']
def prebuild_arch(self, arch):
- super(PygameJNIComponentsRecipe, self).postbuild_arch(arch)
+ super(PygameJNIComponentsRecipe, self).prebuild_arch(arch)
info('Unpacking pygame bootstrap JNI dir components')
with current_directory(self.get_build_container_dir(arch)):
@@ -25,11 +26,9 @@ def prebuild_arch(self, arch):
info('Unpacking was successful, deleting original container dir')
shprint(sh.rm, '-rf', self.get_build_dir(arch))
- info('Applying jpeg assembler patch')
- ndk_15_plus_patch = join(self.get_recipe_dir(), 'jpeg-ndk15-plus.patch')
- shprint(sh.patch, '-t', '-d',
- join(self.get_build_container_dir(arch), 'jpeg'), '-p1',
- '-i', ndk_15_plus_patch, _tail=10)
+ def apply_patches(self, arch, build_dir=None):
+ super(PygameJNIComponentsRecipe, self).apply_patches(
+ arch, build_dir=self.get_build_container_dir(arch.arch))
recipe = PygameJNIComponentsRecipe()
diff --git a/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch b/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch
index e12942223b..9992084fc4 100644
--- a/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch
+++ b/pythonforandroid/recipes/pygame_bootstrap_components/jpeg-ndk15-plus.patch
@@ -2,8 +2,8 @@ The distributed jpeg has troubles to be build with newer ndks, starting from
the introduction of the `unified headers` (ndk > 15). This patch allow us to
build the distributed `external jpeg` in sdl package, got the solution in here:
https://github.com/oNaiPs/droidVncServer/issues/53
---- jpeg/Android.mk.orig 2015-06-21 15:14:54.000000000 +0200
-+++ jpeg/Android.mk 2019-01-14 10:57:06.384806168 +0100
+--- jni/jpeg/Android.mk.orig 2015-06-21 15:14:54.000000000 +0200
++++ jni/jpeg/Android.mk 2019-01-14 10:57:06.384806168 +0100
@@ -20,7 +20,7 @@
endif
@@ -13,8 +13,8 @@ https://github.com/oNaiPs/droidVncServer/issues/53
ifeq ($(strip $(ANDROID_JPEG_NO_ASSEMBLER)),true)
LOCAL_SRC_FILES += jidctint.c jidctfst.c
- --- jpeg/jidctfst.S.orig 2019-01-14 11:00:38.000000000 +0100
-+++ jpeg/jidctfst.S 2019-01-14 11:00:56.844803970 +0100
+--- jni/jpeg/jidctfst.S.orig 2019-01-14 11:00:38.000000000 +0100
++++ jni/jpeg/jidctfst.S 2019-01-14 11:00:56.844803970 +0100
@@ -63,7 +63,7 @@
diff --git a/pythonforandroid/recipes/pyicu/__init__.py b/pythonforandroid/recipes/pyicu/__init__.py
index 65a7d69fe0..98ec7b7979 100644
--- a/pythonforandroid/recipes/pyicu/__init__.py
+++ b/pythonforandroid/recipes/pyicu/__init__.py
@@ -8,7 +8,7 @@
class PyICURecipe(CompiledComponentsPythonRecipe):
version = '1.9.2'
url = 'https://pypi.python.org/packages/source/P/PyICU/PyICU-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "icu"]
+ depends = ["icu"]
patches = ['locale.patch', 'icu.patch']
def get_recipe_env(self, arch):
diff --git a/pythonforandroid/recipes/pyleveldb/__init__.py b/pythonforandroid/recipes/pyleveldb/__init__.py
index f0913b0662..65f17cebe5 100644
--- a/pythonforandroid/recipes/pyleveldb/__init__.py
+++ b/pythonforandroid/recipes/pyleveldb/__init__.py
@@ -6,13 +6,12 @@
class PyLevelDBRecipe(CompiledComponentsPythonRecipe):
version = '0.193'
url = 'https://pypi.python.org/packages/source/l/leveldb/leveldb-{version}.tar.gz'
- depends = ['snappy', 'leveldb', 'hostpython2', 'python2', 'setuptools']
+ depends = ['snappy', 'leveldb', ('hostpython2', 'hostpython3'), 'setuptools']
patches = ['bindings-only.patch']
call_hostpython_via_targetpython = False # Due to setuptools
site_packages_name = 'leveldb'
def build_arch(self, arch):
- env = self.get_recipe_env(arch)
with current_directory(self.get_build_dir(arch.arch)):
# Remove source in this pypi package
sh.rm('-rf', 'leveldb', 'leveldb.egg-info', 'snappy')
diff --git a/pythonforandroid/recipes/pymunk/__init__.py b/pythonforandroid/recipes/pymunk/__init__.py
index 7aeb366c05..b72b85b09b 100644
--- a/pythonforandroid/recipes/pymunk/__init__.py
+++ b/pythonforandroid/recipes/pymunk/__init__.py
@@ -4,8 +4,8 @@
class PymunkRecipe(CompiledComponentsPythonRecipe):
name = "pymunk"
- version = '5.2.0'
- url = 'https://pypi.python.org/packages/5e/bd/e67edcffdee3d0a1e3ebf0050bb9746a61d616f5502ceedddf0f7fd0a896/pymunk-5.2.0.zip'
+ version = '5.3.2'
+ url = 'https://pypi.python.org/packages/source/p/pymunk/pymunk-{version}.zip'
depends = ['cffi', 'setuptools']
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pynacl/__init__.py b/pythonforandroid/recipes/pynacl/__init__.py
index bde45a6b81..eb9ca2df50 100644
--- a/pythonforandroid/recipes/pynacl/__init__.py
+++ b/pythonforandroid/recipes/pynacl/__init__.py
@@ -7,7 +7,7 @@ class PyNaCLRecipe(CompiledComponentsPythonRecipe):
version = '1.3.0'
url = 'https://pypi.python.org/packages/source/P/PyNaCl/PyNaCl-{version}.tar.gz'
- depends = ['hostpython2', 'python2', 'six', 'setuptools', 'cffi', 'libsodium']
+ depends = [('hostpython2', 'hostpython3'), 'six', 'setuptools', 'cffi', 'libsodium']
call_hostpython_via_targetpython = False
def get_recipe_env(self, arch):
diff --git a/pythonforandroid/recipes/pyogg/__init__.py b/pythonforandroid/recipes/pyogg/__init__.py
index 340785f1db..70ea435c7b 100644
--- a/pythonforandroid/recipes/pyogg/__init__.py
+++ b/pythonforandroid/recipes/pyogg/__init__.py
@@ -5,7 +5,7 @@
class PyOggRecipe(PythonRecipe):
version = '0.6.4a1'
url = 'https://files.pythonhosted.org/packages/source/p/pyogg/PyOgg-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'libogg', 'libvorbis', 'setuptools']
+ depends = ['libogg', 'libvorbis', 'setuptools']
patches = [join('patches', 'fix-find-lib.patch')]
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pyopenal/__init__.py b/pythonforandroid/recipes/pyopenal/__init__.py
index 2c2dac45c8..c42cd09652 100644
--- a/pythonforandroid/recipes/pyopenal/__init__.py
+++ b/pythonforandroid/recipes/pyopenal/__init__.py
@@ -5,7 +5,7 @@
class PyOpenALRecipe(PythonRecipe):
version = '0.7.3a1'
url = 'https://files.pythonhosted.org/packages/source/p/pyopenal/PyOpenAL-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'openal', 'numpy', 'setuptools']
+ depends = ['openal', 'numpy', 'setuptools']
patches = [join('patches', 'fix-find-lib.patch')]
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pyopenssl/__init__.py b/pythonforandroid/recipes/pyopenssl/__init__.py
index 8e43753176..65be3080ef 100644
--- a/pythonforandroid/recipes/pyopenssl/__init__.py
+++ b/pythonforandroid/recipes/pyopenssl/__init__.py
@@ -5,7 +5,7 @@
class PyOpenSSLRecipe(PythonRecipe):
version = '16.0.0'
url = 'https://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'openssl', 'setuptools']
+ depends = ['openssl', 'setuptools']
site_packages_name = 'OpenSSL'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pyproj/__init__.py b/pythonforandroid/recipes/pyproj/__init__.py
index e8d8800191..71b272d136 100644
--- a/pythonforandroid/recipes/pyproj/__init__.py
+++ b/pythonforandroid/recipes/pyproj/__init__.py
@@ -4,7 +4,7 @@
class PyProjRecipe(CythonRecipe):
version = '1.9.5.1'
url = 'https://github.com/jswhit/pyproj/archive/master.zip'
- depends = ['python2', 'setuptools']
+ depends = ['setuptools']
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/pyrxp/__init__.py b/pythonforandroid/recipes/pyrxp/__init__.py
index b0796ac643..09b1804a83 100644
--- a/pythonforandroid/recipes/pyrxp/__init__.py
+++ b/pythonforandroid/recipes/pyrxp/__init__.py
@@ -4,7 +4,7 @@
class PyRXPURecipe(CompiledComponentsPythonRecipe):
version = '2a02cecc87b9'
url = 'https://bitbucket.org/rptlab/pyrxp/get/{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
patches = []
diff --git a/pythonforandroid/recipes/pysha3/__init__.py b/pythonforandroid/recipes/pysha3/__init__.py
index df002fd147..35cfff84a8 100644
--- a/pythonforandroid/recipes/pysha3/__init__.py
+++ b/pythonforandroid/recipes/pysha3/__init__.py
@@ -6,14 +6,16 @@
class Pysha3Recipe(PythonRecipe):
version = '1.0.2'
url = 'https://github.com/tiran/pysha3/archive/{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
+ depends = ['setuptools']
+ call_hostpython_via_targetpython = False
def get_recipe_env(self, arch=None, with_flags_in_cc=True):
env = super(Pysha3Recipe, self).get_recipe_env(arch, with_flags_in_cc)
- # sets linker to use the correct gcc (cross compiler)
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
# CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS
- env['CPPFLAGS'] = env['CFLAGS'] + ' -I{}/sources/python/3.5/include/python/'.format(self.ctx.ndk_dir)
+ env['CPPFLAGS'] = env['CFLAGS']
+ if self.ctx.ndk == 'crystax':
+ env['CPPFLAGS'] += ' -I{}/sources/python/{}/include/python/'.format(
+ self.ctx.ndk_dir, self.ctx.python_recipe.version[0:3])
env['CFLAGS'] = ''
# LDFLAGS may only be used to specify linker flags, for libraries use LIBS
env['LDFLAGS'] = env['LDFLAGS'].replace('-lm', '').replace('-lcrystax', '')
diff --git a/pythonforandroid/recipes/python2/__init__.py b/pythonforandroid/recipes/python2/__init__.py
index 064c6609f5..beba2b65a0 100644
--- a/pythonforandroid/recipes/python2/__init__.py
+++ b/pythonforandroid/recipes/python2/__init__.py
@@ -47,6 +47,8 @@ class Python2Recipe(GuestPythonRecipe):
'--prefix={prefix}',
'--exec-prefix={exec_prefix}')
+ compiled_extension = '.pyo'
+
def prebuild_arch(self, arch):
super(Python2Recipe, self).prebuild_arch(arch)
patch_mark = join(self.get_build_dir(arch.arch), '.openssl-patched')
@@ -56,6 +58,11 @@ def prebuild_arch(self, arch):
def set_libs_flags(self, env, arch):
env = super(Python2Recipe, self).set_libs_flags(env, arch)
+ if 'libffi' in self.ctx.recipe_build_order:
+ # For python2 we need to tell configure that we want to use our
+ # compiled libffi, this step is not necessary for python3.
+ self.configure_args += ('--with-system-ffi',)
+
if 'openssl' in self.ctx.recipe_build_order:
recipe = Recipe.get_recipe('openssl', self.ctx)
openssl_build = recipe.get_build_dir(arch.arch)
diff --git a/pythonforandroid/recipes/python3/__init__.py b/pythonforandroid/recipes/python3/__init__.py
index bf29a23537..e7a9e5403f 100644
--- a/pythonforandroid/recipes/python3/__init__.py
+++ b/pythonforandroid/recipes/python3/__init__.py
@@ -21,7 +21,9 @@ class Python3Recipe(GuestPythonRecipe):
url = 'https://www.python.org/ftp/python/{version}/Python-{version}.tgz'
name = 'python3'
- depends = ['hostpython3']
+ patches = ["patches/fix-ctypes-util-find-library.patch"]
+
+ depends = ['hostpython3', 'sqlite3', 'openssl', 'libffi']
conflicts = ['python3crystax', 'python2', 'python2legacy']
configure_args = (
diff --git a/pythonforandroid/recipes/python3/patches/fix-ctypes-util-find-library.patch b/pythonforandroid/recipes/python3/patches/fix-ctypes-util-find-library.patch
new file mode 100644
index 0000000000..ac75c83919
--- /dev/null
+++ b/pythonforandroid/recipes/python3/patches/fix-ctypes-util-find-library.patch
@@ -0,0 +1,23 @@
+diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
+--- a/Lib/ctypes/util.py
++++ b/Lib/ctypes/util.py
+@@ -67,4 +67,19 @@
+ return fname
+ return None
+
++# This patch overrides the find_library to look in the right places on
++# Android
++if True:
++ def find_library(name):
++ # Check the user app libs and system libraries directory:
++ app_root = os.path.normpath(os.path.abspath('../../'))
++ lib_search_dirs = [os.path.join(app_root, 'lib'), "/system/lib"]
++ for lib_dir in lib_search_dirs:
++ for filename in os.listdir(lib_dir):
++ if filename.endswith('.so') and (
++ filename.startswith("lib" + name + ".") or
++ filename.startswith(name + ".")):
++ return os.path.join(lib_dir, filename)
++ return None
++
+ elif os.name == "posix" and sys.platform == "darwin":
diff --git a/pythonforandroid/recipes/pytz/__init__.py b/pythonforandroid/recipes/pytz/__init__.py
index 6d45cc6f0e..12133bc98e 100644
--- a/pythonforandroid/recipes/pytz/__init__.py
+++ b/pythonforandroid/recipes/pytz/__init__.py
@@ -6,7 +6,7 @@ class PytzRecipe(PythonRecipe):
version = '2015.7'
url = 'https://pypi.python.org/packages/source/p/pytz/pytz-{version}.tar.bz2'
- depends = [('python2', 'python3crystax')]
+ depends = []
call_hostpython_via_targetpython = False
install_in_hostpython = True
diff --git a/pythonforandroid/recipes/pyusb/__init__.py b/pythonforandroid/recipes/pyusb/__init__.py
index 3ef7a88073..0a0fbc72b4 100644
--- a/pythonforandroid/recipes/pyusb/__init__.py
+++ b/pythonforandroid/recipes/pyusb/__init__.py
@@ -5,7 +5,7 @@ class PyusbRecipe(PythonRecipe):
name = 'pyusb'
version = '1.0.0b1'
url = 'https://pypi.python.org/packages/source/p/pyusb/pyusb-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
site_packages_name = 'usb'
patches = ['fix-android.patch']
diff --git a/pythonforandroid/recipes/pyyaml/__init__.py b/pythonforandroid/recipes/pyyaml/__init__.py
index 4ad827964e..fcd15d329f 100644
--- a/pythonforandroid/recipes/pyyaml/__init__.py
+++ b/pythonforandroid/recipes/pyyaml/__init__.py
@@ -4,7 +4,7 @@
class PyYamlRecipe(PythonRecipe):
version = "3.12"
url = 'http://pyyaml.org/download/pyyaml/PyYAML-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), "setuptools"]
+ depends = ["setuptools"]
site_packages_name = 'pyyaml'
diff --git a/pythonforandroid/recipes/pyzbar/__init__.py b/pythonforandroid/recipes/pyzbar/__init__.py
new file mode 100644
index 0000000000..ccfcd9b2ea
--- /dev/null
+++ b/pythonforandroid/recipes/pyzbar/__init__.py
@@ -0,0 +1,26 @@
+from os.path import join
+from pythonforandroid.recipe import PythonRecipe
+
+
+class PyZBarRecipe(PythonRecipe):
+
+ version = '0.1.7'
+
+ url = 'https://github.com/NaturalHistoryMuseum/pyzbar/archive/v{version}.tar.gz' # noqa
+
+ call_hostpython_via_targetpython = False
+
+ depends = ['setuptools', 'libzbar']
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super(PyZBarRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ libzbar = self.get_recipe('libzbar', self.ctx)
+ libzbar_dir = libzbar.get_build_dir(arch.arch)
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+ env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
+ env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
+ return env
+
+
+recipe = PyZBarRecipe()
diff --git a/pythonforandroid/recipes/pyzmq/__init__.py b/pythonforandroid/recipes/pyzmq/__init__.py
index c4a79ffa82..5f9614ddb8 100644
--- a/pythonforandroid/recipes/pyzmq/__init__.py
+++ b/pythonforandroid/recipes/pyzmq/__init__.py
@@ -13,7 +13,7 @@ class PyZMQRecipe(CythonRecipe):
version = 'master'
url = 'https://github.com/zeromq/pyzmq/archive/{version}.zip'
site_packages_name = 'zmq'
- depends = ['python2', 'libzmq']
+ depends = ['libzmq']
cython_args = ['-Izmq/utils',
'-Izmq/backend/cython',
'-Izmq/devices']
diff --git a/pythonforandroid/recipes/regex/__init__.py b/pythonforandroid/recipes/regex/__init__.py
index 13f8c5c086..9533905303 100644
--- a/pythonforandroid/recipes/regex/__init__.py
+++ b/pythonforandroid/recipes/regex/__init__.py
@@ -6,7 +6,7 @@ class RegexRecipe(CompiledComponentsPythonRecipe):
version = '2017.07.28'
url = 'https://pypi.python.org/packages/d1/23/5fa829706ee1d4452552eb32e0bfc1039553e01f50a8754c6f7152e85c1b/regex-{version}.tar.gz'
- depends = ['python2', 'setuptools']
+ depends = ['setuptools']
recipe = RegexRecipe()
diff --git a/pythonforandroid/recipes/reportlab/__init__.py b/pythonforandroid/recipes/reportlab/__init__.py
index da07e58156..d5e8001e46 100644
--- a/pythonforandroid/recipes/reportlab/__init__.py
+++ b/pythonforandroid/recipes/reportlab/__init__.py
@@ -9,6 +9,7 @@ class ReportLabRecipe(CompiledComponentsPythonRecipe):
version = 'c088826211ca'
url = 'https://bitbucket.org/rptlab/reportlab/get/{version}.tar.gz'
depends = ['freetype']
+ call_hostpython_via_targetpython = False
def prebuild_arch(self, arch):
if not self.is_patched(arch):
diff --git a/pythonforandroid/recipes/scrypt/__init__.py b/pythonforandroid/recipes/scrypt/__init__.py
index 17a4ef5bc0..26b8048a05 100644
--- a/pythonforandroid/recipes/scrypt/__init__.py
+++ b/pythonforandroid/recipes/scrypt/__init__.py
@@ -1,4 +1,3 @@
-import os
from pythonforandroid.recipe import CythonRecipe
@@ -6,7 +5,7 @@ class ScryptRecipe(CythonRecipe):
version = '0.8.6'
url = 'https://bitbucket.org/mhallin/py-scrypt/get/v{version}.zip'
- depends = [('python2', 'python3crystax'), 'setuptools', 'openssl']
+ depends = ['setuptools', 'openssl']
call_hostpython_via_targetpython = False
patches = ["remove_librt.patch"]
@@ -15,23 +14,12 @@ def get_recipe_env(self, arch, with_flags_in_cc=True):
Adds openssl recipe to include and library path.
"""
env = super(ScryptRecipe, self).get_recipe_env(arch, with_flags_in_cc)
- openssl_build_dir = self.get_recipe(
- 'openssl', self.ctx).get_build_dir(arch.arch)
- env['CFLAGS'] += ' -I{}'.format(os.path.join(openssl_build_dir, 'include'))
- env['LDFLAGS'] += ' -L{}'.format(
- self.ctx.get_libs_dir(arch.arch) +
- '-L{}'.format(self.ctx.libs_dir)) + ' -L{}'.format(
- openssl_build_dir)
- # required additional library and path for Crystax
- if self.ctx.ndk == 'crystax':
- # only keeps major.minor (discards patch)
- python_version = self.ctx.python_recipe.version[0:3]
- ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
- env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
- env['LDFLAGS'] += ' -lpython{}m'.format(python_version)
- # until `pythonforandroid/archs.py` gets merged upstream:
- # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
- env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python)
+ openssl_recipe = self.get_recipe('openssl', self.ctx)
+ env['CFLAGS'] += openssl_recipe.include_flags(arch)
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.get_libs_dir(arch.arch))
+ env['LDFLAGS'] += ' -L{}'.format(self.ctx.libs_dir)
+ env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch)
+ env['LIBS'] = env.get('LIBS', '') + openssl_recipe.link_libs_flags()
return env
diff --git a/pythonforandroid/recipes/sdl2/__init__.py b/pythonforandroid/recipes/sdl2/__init__.py
index 48b7515c0e..bbfadc2c04 100644
--- a/pythonforandroid/recipes/sdl2/__init__.py
+++ b/pythonforandroid/recipes/sdl2/__init__.py
@@ -4,17 +4,15 @@
class LibSDL2Recipe(BootstrapNDKRecipe):
- version = "2.0.4"
+ version = "2.0.9"
url = "https://www.libsdl.org/release/SDL2-{version}.tar.gz"
- md5sum = '44fc4a023349933e7f5d7a582f7b886e'
+ md5sum = 'f2ecfba915c54f7200f504d8b48a5dfe'
dir_name = 'SDL'
depends = ['sdl2_image', 'sdl2_mixer', 'sdl2_ttf']
conflicts = ['sdl', 'pygame', 'pygame_bootstrap_components']
- patches = ['add_nativeSetEnv.patch']
-
def get_recipe_env(self, arch=None, with_flags_in_cc=True, with_python=True):
env = super(LibSDL2Recipe, self).get_recipe_env(
arch=arch, with_flags_in_cc=with_flags_in_cc, with_python=with_python)
diff --git a/pythonforandroid/recipes/sdl2/add_nativeSetEnv.patch b/pythonforandroid/recipes/sdl2/add_nativeSetEnv.patch
deleted file mode 100644
index 2262f1690f..0000000000
--- a/pythonforandroid/recipes/sdl2/add_nativeSetEnv.patch
+++ /dev/null
@@ -1,22 +0,0 @@
---- orig/src/core/android/SDL_android.c 2016-01-02 20:56:31.000000000 +0100
-+++ patch/src/core/android/SDL_android.c 2016-04-15 22:21:13.985708267 +0200
-@@ -188,6 +188,19 @@
- Android_OnHat(device_id, hat_id, x, y);
- }
-
-+/* Patched in env var setter for python-for-android */
-+JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_nativeSetEnv(
-+ JNIEnv* env, jclass jcls,
-+ jstring j_name, jstring j_value)
-+{
-+ jboolean iscopy;
-+ const char *name = (*env)->GetStringUTFChars(env, j_name, &iscopy);
-+ const char *value = (*env)->GetStringUTFChars(env, j_value, &iscopy);
-+ setenv(name, value, 1);
-+ (*env)->ReleaseStringUTFChars(env, j_name, name);
-+ (*env)->ReleaseStringUTFChars(env, j_value, value);
-+}
-+
-
- JNIEXPORT jint JNICALL Java_org_libsdl_app_SDLActivity_nativeAddJoystick(
- JNIEnv* env, jclass jcls,
diff --git a/pythonforandroid/recipes/sdl2_image/__init__.py b/pythonforandroid/recipes/sdl2_image/__init__.py
index d611899b42..9ecfc388d0 100644
--- a/pythonforandroid/recipes/sdl2_image/__init__.py
+++ b/pythonforandroid/recipes/sdl2_image/__init__.py
@@ -3,14 +3,14 @@
class LibSDL2Image(BootstrapNDKRecipe):
- version = '2.0.1'
+ version = '2.0.4'
url = 'https://www.libsdl.org/projects/SDL_image/release/SDL2_image-{version}.tar.gz'
dir_name = 'SDL2_image'
patches = ['toggle_jpg_png_webp.patch',
('disable_jpg.patch', is_arch('x86')),
'extra_cflags.patch',
- 'fix_with_ndk_15_plus.patch']
+ ]
recipe = LibSDL2Image()
diff --git a/pythonforandroid/recipes/sdl2_image/extra_cflags.patch b/pythonforandroid/recipes/sdl2_image/extra_cflags.patch
index f8f26b73e2..c2b875bbe4 100644
--- a/pythonforandroid/recipes/sdl2_image/extra_cflags.patch
+++ b/pythonforandroid/recipes/sdl2_image/extra_cflags.patch
@@ -1,11 +1,11 @@
---- orig/Android.mk 2016-01-03 06:52:28.000000000 +0100
-+++ patch/Android.mk 2016-04-15 21:03:18.547379710 +0200
-@@ -25,7 +25,7 @@
- LOCAL_C_INCLUDES := $(LOCAL_PATH)
+--- SDL2_image-2.0.4/Android.mk.orig 2018-10-31 15:58:52.000000000 +0100
++++ SDL2_image-2.0.4/Android.mk 2019-02-07 21:57:29.552365123 +0100
+@@ -61,6 +61,8 @@ LOCAL_SRC_FILES := \
+
LOCAL_CFLAGS := -DLOAD_BMP -DLOAD_GIF -DLOAD_LBM -DLOAD_PCX -DLOAD_PNM \
- -DLOAD_TGA -DLOAD_XCF -DLOAD_XPM -DLOAD_XV
--LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays
-+LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays $(EXTRA_CFLAGS)
-
- LOCAL_SRC_FILES := $(notdir $(filter-out %/showimage.c, $(wildcard $(LOCAL_PATH)/*.c)))
-
+ -DLOAD_SVG -DLOAD_TGA -DLOAD_XCF -DLOAD_XPM -DLOAD_XV
++LOCAL_CFLAGS += $(EXTRA_CFLAGS)
++
+ LOCAL_LDLIBS :=
+ LOCAL_STATIC_LIBRARIES :=
+ LOCAL_SHARED_LIBRARIES := SDL2
diff --git a/pythonforandroid/recipes/sdl2_image/fix_with_ndk_15_plus.patch b/pythonforandroid/recipes/sdl2_image/fix_with_ndk_15_plus.patch
deleted file mode 100644
index a6d42b8dc4..0000000000
--- a/pythonforandroid/recipes/sdl2_image/fix_with_ndk_15_plus.patch
+++ /dev/null
@@ -1,50 +0,0 @@
-diff --git a/Android.mk b/Android.mk
-index 97a96c7..2e724c0 100644
---- a/Android.mk
-+++ b/Android.mk
-@@ -79,6 +79,7 @@ ifeq ($(SUPPORT_JPG),true)
- $(JPG_LIBRARY_PATH)/jfdctfst.c \
- $(JPG_LIBRARY_PATH)/jfdctint.c \
- $(JPG_LIBRARY_PATH)/jidctflt.c \
-+ $(JPG_LIBRARY_PATH)/jidctfst.c \
- $(JPG_LIBRARY_PATH)/jidctint.c \
- $(JPG_LIBRARY_PATH)/jquant1.c \
- $(JPG_LIBRARY_PATH)/jquant2.c \
-@@ -86,12 +87,6 @@ ifeq ($(SUPPORT_JPG),true)
- $(JPG_LIBRARY_PATH)/jmemmgr.c \
- $(JPG_LIBRARY_PATH)/jmem-android.c
-
-- # assembler support is available for arm
-- ifeq ($(TARGET_ARCH),arm)
-- LOCAL_SRC_FILES += $(JPG_LIBRARY_PATH)/jidctfst.S
-- else
-- LOCAL_SRC_FILES += $(JPG_LIBRARY_PATH)/jidctfst.c
-- endif
- endif
-
- ifeq ($(SUPPORT_PNG),true)
-diff --git a/external/jpeg-9/Android.mk b/external/jpeg-9/Android.mk
-index a5edbde..77f139c 100644
---- a/external/jpeg-9/Android.mk
-+++ b/external/jpeg-9/Android.mk
-@@ -14,20 +14,6 @@ LOCAL_SRC_FILES := \
- jquant2.c jutils.c jmemmgr.c \
- jmem-android.c
-
--# the assembler is only for the ARM version, don't break the Linux sim
--ifneq ($(TARGET_ARCH),arm)
--ANDROID_JPEG_NO_ASSEMBLER := true
--endif
--
--# temp fix until we understand why this broke cnn.com
--#ANDROID_JPEG_NO_ASSEMBLER := true
--
--ifeq ($(strip $(ANDROID_JPEG_NO_ASSEMBLER)),true)
--LOCAL_SRC_FILES += jidctint.c jidctfst.c
--else
--LOCAL_SRC_FILES += jidctint.c jidctfst.S
--endif
--
- LOCAL_CFLAGS += -DAVOID_TABLES
- LOCAL_CFLAGS += -O3 -fstrict-aliasing -fprefetch-loop-arrays
- #LOCAL_CFLAGS += -march=armv6j
diff --git a/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch b/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch
index 320d1abf03..f7dce8ac62 100644
--- a/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch
+++ b/pythonforandroid/recipes/sdl2_image/toggle_jpg_png_webp.patch
@@ -1,25 +1,25 @@
---- orig/Android.mk 2016-01-03 06:52:28.000000000 +0100
-+++ patch/Android.mk 2016-04-15 21:14:23.906688966 +0200
-@@ -6,19 +6,19 @@
-
+--- SDL2_image-2.0.4/Android.mk.orig 2018-10-31 15:58:52.000000000 +0100
++++ SDL2_image-2.0.4/Android.mk 2019-02-07 23:51:51.740299680 +0100
+@@ -3,18 +3,18 @@ SDL_IMAGE_LOCAL_PATH := $(call my-dir)
+
# Enable this if you want to support loading JPEG images
# The library path should be a relative path to this directory.
-SUPPORT_JPG ?= true
+SUPPORT_JPG := true
- JPG_LIBRARY_PATH := external/jpeg-9
-
+ JPG_LIBRARY_PATH := external/jpeg-9b
+
# Enable this if you want to support loading PNG images
# The library path should be a relative path to this directory.
-SUPPORT_PNG ?= true
+SUPPORT_PNG := true
- PNG_LIBRARY_PATH := external/libpng-1.6.2
-
+ PNG_LIBRARY_PATH := external/libpng-1.6.32
+
# Enable this if you want to support loading WebP images
# The library path should be a relative path to this directory.
- #
- # IMPORTANT: In order to enable this must have a symlink in your jni directory to external/libwebp-0.3.0.
--SUPPORT_WEBP ?= false
-+SUPPORT_WEBP := false
- WEBP_LIBRARY_PATH := external/libwebp-0.3.0
-
-
+-SUPPORT_WEBP ?= true
+-WEBP_LIBRARY_PATH := external/libwebp-0.6.0
++SUPPORT_WEBP := true
++WEBP_LIBRARY_PATH := external/libwebp-1.0.0
+
+
+ # Build the library
diff --git a/pythonforandroid/recipes/secp256k1/__init__.py b/pythonforandroid/recipes/secp256k1/__init__.py
index d07cea4343..889803100f 100644
--- a/pythonforandroid/recipes/secp256k1/__init__.py
+++ b/pythonforandroid/recipes/secp256k1/__init__.py
@@ -1,45 +1,29 @@
import os
-from pythonforandroid.recipe import PythonRecipe
+from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe
-class Secp256k1Recipe(PythonRecipe):
+class Secp256k1Recipe(CppCompiledComponentsPythonRecipe):
- url = 'https://github.com/ludbb/secp256k1-py/archive/master.zip'
+ version = '0.13.2.4'
+ url = 'https://github.com/ludbb/secp256k1-py/archive/{version}.tar.gz'
call_hostpython_via_targetpython = False
depends = [
- 'openssl', ('hostpython2', 'hostpython3crystax'),
- ('python2', 'python3crystax'), 'setuptools',
+ 'openssl', ('hostpython3', 'hostpython2', 'hostpython3crystax'),
+ ('python2', 'python3', 'python3crystax'), 'setuptools',
'libffi', 'cffi', 'libsecp256k1']
patches = [
"cross_compile.patch", "drop_setup_requires.patch",
"pkg-config.patch", "find_lib.patch", "no-download.patch"]
- def get_recipe_env(self, arch=None, with_flags_in_cc=True):
- env = super(Secp256k1Recipe, self).get_recipe_env(arch, with_flags_in_cc)
- # sets linker to use the correct gcc (cross compiler)
- env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
+ def get_recipe_env(self, arch=None):
+ env = super(Secp256k1Recipe, self).get_recipe_env(arch)
libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx)
libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch)
- env['LDFLAGS'] += ' -L{}'.format(libsecp256k1_dir)
- env['CFLAGS'] = ' -I' + os.path.join(libsecp256k1_dir, 'include')
- # only keeps major.minor (discards patch)
- python_version = self.ctx.python_recipe.version[0:3]
- # required additional library and path for Crystax
- if self.ctx.ndk == 'crystax':
- ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version)
- env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch))
- env['LDFLAGS'] += ' -lpython{}m'.format(python_version)
- # until `pythonforandroid/archs.py` gets merged upstream:
- # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6
- env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python)
- else:
- env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python{}'.format(python_version)
- env['LDFLAGS'] += " -lpython{}".format(python_version)
- env['LDFLAGS'] += " -lsecp256k1"
+ env['CFLAGS'] += ' -I' + os.path.join(libsecp256k1_dir, 'include')
+ env['LDFLAGS'] += ' -L{} -lsecp256k1'.format(libsecp256k1_dir)
return env
diff --git a/pythonforandroid/recipes/shapely/__init__.py b/pythonforandroid/recipes/shapely/__init__.py
index 05e260846e..e0b093766b 100644
--- a/pythonforandroid/recipes/shapely/__init__.py
+++ b/pythonforandroid/recipes/shapely/__init__.py
@@ -4,7 +4,7 @@
class ShapelyRecipe(CythonRecipe):
version = '1.5'
url = 'https://github.com/Toblerity/Shapely/archive/master.zip'
- depends = ['python2', 'setuptools', 'libgeos']
+ depends = ['setuptools', 'libgeos']
call_hostpython_via_targetpython = False
patches = ['setup.patch'] # Patch to force setup to fail when C extention fails to build
diff --git a/pythonforandroid/recipes/simple-crypt/__init__.py b/pythonforandroid/recipes/simple-crypt/__init__.py
index 0c2781ee4d..94c5f510b8 100644
--- a/pythonforandroid/recipes/simple-crypt/__init__.py
+++ b/pythonforandroid/recipes/simple-crypt/__init__.py
@@ -4,7 +4,7 @@
class SimpleCryptRecipe(PythonRecipe):
version = '4.1.7'
url = 'https://pypi.python.org/packages/source/s/simple-crypt/simple-crypt-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'pycrypto']
+ depends = ['pycrypto']
site_packages_name = 'simplecrypt'
diff --git a/pythonforandroid/recipes/sqlalchemy/__init__.py b/pythonforandroid/recipes/sqlalchemy/__init__.py
index 809d45a3dc..974667af2c 100644
--- a/pythonforandroid/recipes/sqlalchemy/__init__.py
+++ b/pythonforandroid/recipes/sqlalchemy/__init__.py
@@ -6,7 +6,7 @@ class SQLAlchemyRecipe(CompiledComponentsPythonRecipe):
version = '1.0.9'
url = 'https://pypi.python.org/packages/source/S/SQLAlchemy/SQLAlchemy-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'setuptools']
+ depends = ['setuptools']
patches = ['zipsafe.patch']
diff --git a/pythonforandroid/recipes/storm/__init__.py b/pythonforandroid/recipes/storm/__init__.py
index 117e73361d..6b64465998 100644
--- a/pythonforandroid/recipes/storm/__init__.py
+++ b/pythonforandroid/recipes/storm/__init__.py
@@ -5,7 +5,7 @@
class StormRecipe(PythonRecipe):
version = '0.20'
url = 'https://launchpad.net/storm/trunk/{version}/+download/storm-{version}.tar.bz2'
- depends = [('python2', 'python3crystax')]
+ depends = []
site_packages_name = 'storm'
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/sympy/__init__.py b/pythonforandroid/recipes/sympy/__init__.py
index 473c4332da..8684a95e06 100644
--- a/pythonforandroid/recipes/sympy/__init__.py
+++ b/pythonforandroid/recipes/sympy/__init__.py
@@ -6,7 +6,7 @@ class SympyRecipe(PythonRecipe):
version = '1.1.1'
url = 'https://github.com/sympy/sympy/releases/download/sympy-{version}/sympy-{version}.tar.gz'
- depends = [('python2', 'python3crystax'), 'mpmath']
+ depends = ['mpmath']
call_hostpython_via_targetpython = True
diff --git a/pythonforandroid/recipes/ujson/__init__.py b/pythonforandroid/recipes/ujson/__init__.py
index 57bc69f711..421e4d927c 100644
--- a/pythonforandroid/recipes/ujson/__init__.py
+++ b/pythonforandroid/recipes/ujson/__init__.py
@@ -4,7 +4,7 @@
class UJsonRecipe(CompiledComponentsPythonRecipe):
version = '1.35'
url = 'https://pypi.python.org/packages/source/u/ujson/ujson-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
recipe = UJsonRecipe()
diff --git a/pythonforandroid/recipes/vispy/__init__.py b/pythonforandroid/recipes/vispy/__init__.py
index fc41046bef..7ea046b3b3 100644
--- a/pythonforandroid/recipes/vispy/__init__.py
+++ b/pythonforandroid/recipes/vispy/__init__.py
@@ -4,7 +4,7 @@
class VispyRecipe(PythonRecipe):
version = '0.4.0'
url = 'https://github.com/vispy/vispy/archive/v{version}.tar.gz'
- depends = ['python2', 'numpy', 'pysdl2']
+ depends = ['numpy', 'pysdl2']
patches = ['disable_freetype.patch',
'disable_font_triage.patch',
'use_es2.patch',
diff --git a/pythonforandroid/recipes/websocket-client/__init__.py b/pythonforandroid/recipes/websocket-client/__init__.py
index ac44aecea9..8a3b8add87 100644
--- a/pythonforandroid/recipes/websocket-client/__init__.py
+++ b/pythonforandroid/recipes/websocket-client/__init__.py
@@ -21,7 +21,7 @@ class WebSocketClient(Recipe):
# patches = ['websocket.patch'] # Paths relative to the recipe dir
- depends = ['python2', 'android', 'pyjnius', 'cryptography', 'pyasn1', 'pyopenssl']
+ depends = ['android', 'pyjnius', 'cryptography', 'pyasn1', 'pyopenssl']
recipe = WebSocketClient()
diff --git a/pythonforandroid/recipes/wsaccel/__init__.py b/pythonforandroid/recipes/wsaccel/__init__.py
index dd27caace8..7bfc3465db 100644
--- a/pythonforandroid/recipes/wsaccel/__init__.py
+++ b/pythonforandroid/recipes/wsaccel/__init__.py
@@ -4,7 +4,7 @@
class WSAccellRecipe(CythonRecipe):
version = '0.6.2'
url = 'https://pypi.python.org/packages/source/w/wsaccel/wsaccel-{version}.tar.gz'
- depends = [('python2', 'python3crystax')]
+ depends = []
call_hostpython_via_targetpython = False
diff --git a/pythonforandroid/recipes/x3dh/__init__.py b/pythonforandroid/recipes/x3dh/__init__.py
index 2990ac58dd..134bf2991e 100644
--- a/pythonforandroid/recipes/x3dh/__init__.py
+++ b/pythonforandroid/recipes/x3dh/__init__.py
@@ -7,7 +7,6 @@ class X3DHRecipe(PythonRecipe):
url = 'https://pypi.python.org/packages/source/X/X3DH/X3DH-{version}.tar.gz'
site_packages_name = 'x3dh'
depends = [
- ('python2', 'python3crystax'),
'setuptools',
'cryptography',
'xeddsa',
diff --git a/pythonforandroid/recipes/xeddsa/__init__.py b/pythonforandroid/recipes/xeddsa/__init__.py
index bec7ba2d68..eb0e2aeb0c 100644
--- a/pythonforandroid/recipes/xeddsa/__init__.py
+++ b/pythonforandroid/recipes/xeddsa/__init__.py
@@ -9,7 +9,6 @@ class XedDSARecipe(CythonRecipe):
version = '0.4.4'
url = 'https://pypi.python.org/packages/source/X/XEdDSA/XEdDSA-{version}.tar.gz'
depends = [
- ('python2', 'python3crystax'),
'setuptools',
'cffi',
'pynacl',
diff --git a/pythonforandroid/recipes/zbar/__init__.py b/pythonforandroid/recipes/zbar/__init__.py
index 6f604e3759..62aa85bbe7 100644
--- a/pythonforandroid/recipes/zbar/__init__.py
+++ b/pythonforandroid/recipes/zbar/__init__.py
@@ -1,4 +1,4 @@
-import os
+from os.path import join
from pythonforandroid.recipe import PythonRecipe
@@ -15,7 +15,7 @@ class ZBarRecipe(PythonRecipe):
call_hostpython_via_targetpython = False
- depends = ['hostpython2', 'python2', 'setuptools', 'libzbar']
+ depends = ['setuptools', 'libzbar']
patches = ["zbar-0.10-python-crash.patch"]
@@ -24,13 +24,9 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
libzbar = self.get_recipe('libzbar', self.ctx)
libzbar_dir = libzbar.get_build_dir(arch.arch)
env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
- env['CFLAGS'] += ' -I' + os.path.join(libzbar_dir, 'include')
- env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
- # TODO
- env['LDSHARED'] = env['CC'] + \
- ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
- # TODO: hardcoded Python version
- env['LDFLAGS'] += " -landroid -lpython2.7 -lzbar"
+ env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
+ env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
return env
diff --git a/pythonforandroid/recipes/zbarlight/__init__.py b/pythonforandroid/recipes/zbarlight/__init__.py
new file mode 100644
index 0000000000..966c7fb241
--- /dev/null
+++ b/pythonforandroid/recipes/zbarlight/__init__.py
@@ -0,0 +1,26 @@
+from os.path import join
+from pythonforandroid.recipe import PythonRecipe
+
+
+class ZBarLightRecipe(PythonRecipe):
+
+ version = '2.1'
+
+ url = 'https://github.com/Polyconseil/zbarlight/archive/{version}.tar.gz' # noqa
+
+ call_hostpython_via_targetpython = False
+
+ depends = ['setuptools', 'libzbar']
+
+ def get_recipe_env(self, arch=None, with_flags_in_cc=True):
+ env = super(ZBarLightRecipe, self).get_recipe_env(arch, with_flags_in_cc)
+ libzbar = self.get_recipe('libzbar', self.ctx)
+ libzbar_dir = libzbar.get_build_dir(arch.arch)
+ env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
+ env['CFLAGS'] += ' -I' + join(libzbar_dir, 'include')
+ env['LDFLAGS'] += ' -L' + join(libzbar_dir, 'zbar', '.libs')
+ env['LIBS'] = env.get('LIBS', '') + ' -landroid -lzbar'
+ return env
+
+
+recipe = ZBarLightRecipe()
diff --git a/pythonforandroid/recipes/zeroconf/__init__.py b/pythonforandroid/recipes/zeroconf/__init__.py
index 6ee14a19f5..5ca57084a1 100644
--- a/pythonforandroid/recipes/zeroconf/__init__.py
+++ b/pythonforandroid/recipes/zeroconf/__init__.py
@@ -1,24 +1,12 @@
from pythonforandroid.recipe import PythonRecipe
-from os.path import join
class ZeroconfRecipe(PythonRecipe):
name = 'zeroconf'
version = '0.17.4'
url = 'https://pypi.python.org/packages/source/z/zeroconf/zeroconf-{version}.tar.gz'
- depends = ['python2', 'netifaces', 'enum34', 'six']
-
- def get_recipe_env(self, arch=None):
- env = super(ZeroconfRecipe, self).get_recipe_env(arch)
-
- # TODO: fix hardcoded path
- # This is required to prevent issue with _io.so import.
- hostpython = self.get_recipe('hostpython2', self.ctx)
- env['PYTHONPATH'] = (
- join(hostpython.get_build_dir(arch.arch), 'build',
- 'lib.linux-x86_64-2.7') + ':' + env.get('PYTHONPATH', '')
- )
- return env
+ depends = ['setuptools', 'enum34', 'six']
+ call_hostpython_via_targetpython = False
recipe = ZeroconfRecipe()
diff --git a/pythonforandroid/recipes/zope_interface/__init__.py b/pythonforandroid/recipes/zope_interface/__init__.py
index e1e43ed261..b1fb0bd122 100644
--- a/pythonforandroid/recipes/zope_interface/__init__.py
+++ b/pythonforandroid/recipes/zope_interface/__init__.py
@@ -1,5 +1,6 @@
from pythonforandroid.recipe import PythonRecipe
from pythonforandroid.toolchain import current_directory
+from os.path import join
import sh
@@ -9,10 +10,18 @@ class ZopeInterfaceRecipe(PythonRecipe):
version = '4.1.3'
url = 'https://pypi.python.org/packages/source/z/zope.interface/zope.interface-{version}.tar.gz'
site_packages_name = 'zope.interface'
-
- depends = [('python2', 'python3crystax')]
+ depends = ['setuptools']
patches = ['no_tests.patch']
+ def build_arch(self, arch):
+ super(ZopeInterfaceRecipe, self).build_arch(arch)
+ # The zope.interface module lacks of the __init__.py file in one of his
+ # folders (once is installed), that leads into an ImportError.
+ # Here we intentionally apply a patch to solve that, so, in case that
+ # this is solved in the future an error will be triggered
+ zope_install = join(self.ctx.get_site_packages_dir(arch.arch), 'zope')
+ self.apply_patch('fix-init.patch', arch.arch, build_dir=zope_install)
+
def prebuild_arch(self, arch):
super(ZopeInterfaceRecipe, self).prebuild_arch(arch)
with current_directory(self.get_build_dir(arch.arch)):
diff --git a/pythonforandroid/recipes/zope_interface/fix-init.patch b/pythonforandroid/recipes/zope_interface/fix-init.patch
new file mode 100644
index 0000000000..b618eb5314
--- /dev/null
+++ b/pythonforandroid/recipes/zope_interface/fix-init.patch
@@ -0,0 +1,9 @@
+The zope.interface module lacks of the __init__.py file in `zope` folder
+(once is installed), this patch creates that missing file. This seems to be
+caused during the installation process because that file exists in source
+files.
+diff -Naurp zope.orig/__init__.py zope/__init__.py
+--- zope.orig/__init__.py 1970-01-01 01:00:00.000000000 +0100
++++ zope/__init__.py 2019-02-05 11:29:22.666757227 +0100
+@@ -0,0 +1 @@
++
diff --git a/pythonforandroid/recommendations.py b/pythonforandroid/recommendations.py
new file mode 100644
index 0000000000..fd2fd3a8be
--- /dev/null
+++ b/pythonforandroid/recommendations.py
@@ -0,0 +1,107 @@
+"""Simple functions for checking dependency versions."""
+
+from distutils.version import LooseVersion
+from os.path import join
+from pythonforandroid.logger import info, warning
+from pythonforandroid.util import BuildInterruptingException
+
+# We only check the NDK major version
+MIN_NDK_VERSION = 17
+MAX_NDK_VERSION = 17
+
+RECOMMENDED_NDK_VERSION = '17c'
+OLD_NDK_MESSAGE = 'Older NDKs may not be compatible with all p4a features.'
+NEW_NDK_MESSAGE = 'Newer NDKs may not be fully supported by p4a.'
+
+
+def check_ndk_version(ndk_dir):
+ # Check the NDK version against what is currently recommended
+ version = read_ndk_version(ndk_dir)
+
+ if version is None:
+ return # if we failed to read the version, just don't worry about it
+
+ major_version = version.version[0]
+
+ info('Found NDK revision {}'.format(version))
+
+ if major_version < MIN_NDK_VERSION:
+ warning('Minimum recommended NDK version is {}'.format(
+ RECOMMENDED_NDK_VERSION))
+ warning(OLD_NDK_MESSAGE)
+ elif major_version > MAX_NDK_VERSION:
+ warning('Maximum recommended NDK version is {}'.format(
+ RECOMMENDED_NDK_VERSION))
+ warning(NEW_NDK_MESSAGE)
+
+
+def read_ndk_version(ndk_dir):
+ """Read the NDK version from the NDK dir, if possible"""
+ try:
+ with open(join(ndk_dir, 'source.properties')) as fileh:
+ ndk_data = fileh.read()
+ except IOError:
+ info('Could not determine NDK version, no source.properties '
+ 'in the NDK dir')
+ return
+
+ for line in ndk_data.split('\n'):
+ if line.startswith('Pkg.Revision'):
+ break
+ else:
+ info('Could not parse $NDK_DIR/source.properties, not checking '
+ 'NDK version')
+ return
+
+ # Line should have the form "Pkg.Revision = ..."
+ ndk_version = LooseVersion(line.split('=')[-1].strip())
+
+ return ndk_version
+
+
+MIN_TARGET_API = 26
+
+# highest version tested to work fine with SDL2
+# should be a good default for other bootstraps too
+RECOMMENDED_TARGET_API = 27
+
+ARMEABI_MAX_TARGET_API = 21
+OLD_API_MESSAGE = (
+ 'Target APIs lower than 26 are no longer supported on Google Play, '
+ 'and are not recommended. Note that the Target API can be higher than '
+ 'your device Android version, and should usually be as high as possible.')
+
+
+def check_target_api(api, arch):
+ """Warn if the user's target API is less than the current minimum
+ recommendation
+ """
+
+ if api >= ARMEABI_MAX_TARGET_API and arch == 'armeabi':
+ raise BuildInterruptingException(
+ 'Asked to build for armeabi architecture with API '
+ '{}, but API {} or greater does not support armeabi'.format(
+ api, ARMEABI_MAX_TARGET_API),
+ instructions='You probably want to build with --arch=armeabi-v7a instead')
+
+ if api < MIN_TARGET_API:
+ warning('Target API {} < {}'.format(api, MIN_TARGET_API))
+ warning(OLD_API_MESSAGE)
+
+
+MIN_NDK_API = 21
+RECOMMENDED_NDK_API = 21
+OLD_NDK_API_MESSAGE = ('NDK API less than {} is not supported'.format(MIN_NDK_API))
+
+
+def check_ndk_api(ndk_api, android_api):
+ """Warn if the user's NDK is too high or low."""
+ if ndk_api > android_api:
+ raise BuildInterruptingException(
+ 'Target NDK API is {}, higher than the target Android API {}.'.format(
+ ndk_api, android_api),
+ instructions=('The NDK API is a minimum supported API number and must be lower '
+ 'than the target Android API'))
+
+ if ndk_api < MIN_NDK_API:
+ warning(OLD_NDK_API_MESSAGE)
diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py
index 7c62320fea..153bba1523 100644
--- a/pythonforandroid/toolchain.py
+++ b/pythonforandroid/toolchain.py
@@ -7,8 +7,10 @@
"""
from __future__ import print_function
+from os import environ
from pythonforandroid import __version__
-from pythonforandroid.build import DEFAULT_NDK_API, DEFAULT_ANDROID_API
+from pythonforandroid.recommendations import (
+ RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
from pythonforandroid.util import BuildInterruptingException, handle_build_exception
@@ -139,7 +141,6 @@ def wrapper_func(self, args):
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
user_ndk_dir=self.ndk_dir,
user_android_api=self.android_api,
- user_ndk_ver=self.ndk_version,
user_ndk_api=self.ndk_api)
dist = self._dist
if dist.needs_build:
@@ -170,16 +171,24 @@ def build_dist_from_args(ctx, dist, args):
"""Parses out any bootstrap related arguments, and uses them to build
a dist."""
bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)
- build_order, python_modules, bs \
- = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs)
+ blacklist = getattr(args, "blacklist_requirements", "").split(",")
+ if len(blacklist) == 1 and blacklist[0] == "":
+ blacklist = []
+ build_order, python_modules, bs = (
+ get_recipe_order_and_bootstrap(
+ ctx, dist.recipes, bs,
+ blacklist=blacklist
+ ))
ctx.recipe_build_order = build_order
ctx.python_modules = python_modules
info('The selected bootstrap is {}'.format(bs.name))
info_main('# Creating dist with {} bootstrap'.format(bs.name))
bs.distribution = dist
- info_notify('Dist will have name {} and recipes ({})'.format(
+ info_notify('Dist will have name {} and requirements ({})'.format(
dist.name, ', '.join(dist.recipes)))
+ info('Dist contains the following requirements as recipes: {}'.format(
+ ctx.recipe_build_order))
info('Dist will also contain modules ({}) installed from pip'.format(
', '.join(ctx.python_modules)))
@@ -258,16 +267,16 @@ def __init__(self):
default=0,
type=int,
help=('The Android API level to build against defaults to {} if '
- 'not specified.').format(DEFAULT_ANDROID_API))
+ 'not specified.').format(RECOMMENDED_TARGET_API))
generic_parser.add_argument(
- '--ndk-version', '--ndk_version', dest='ndk_version', default='',
- help=('The version of the Android NDK. This is optional: '
- 'we try to work it out automatically from the ndk_dir.'))
+ '--ndk-version', '--ndk_version', dest='ndk_version', default=None,
+ help=('DEPRECATED: the NDK version is now found automatically or '
+ 'not at all.'))
generic_parser.add_argument(
'--ndk-api', type=int, default=None,
help=('The Android API level to compile against. This should be your '
'*minimal supported* API, not normally the same as your --android-api. '
- 'Defaults to min(ANDROID_API, {}) if not specified.').format(DEFAULT_NDK_API))
+ 'Defaults to min(ANDROID_API, {}) if not specified.').format(RECOMMENDED_NDK_API))
generic_parser.add_argument(
'--symlink-java-src', '--symlink_java_src',
action='store_true',
@@ -300,6 +309,13 @@ def __init__(self):
'Python modules'),
default='')
+ generic_parser.add_argument(
+ '--blacklist-requirements',
+ help=('Blacklist an internal recipe from use. Allows '
+ 'disabling Python 3 core modules to save size'),
+ dest="blacklist_requirements",
+ default='')
+
generic_parser.add_argument(
'--bootstrap',
help='The bootstrap to build with. Leave unset to choose '
@@ -360,6 +376,11 @@ def add_parser(subparsers, *args, **kwargs):
kwargs.pop('aliases')
return subparsers.add_parser(*args, **kwargs)
+ add_parser(
+ subparsers,
+ 'recommendations',
+ parents=[generic_parser],
+ help='List recommended p4a dependencies')
parser_recipes = add_parser(
subparsers,
'recipes',
@@ -442,7 +463,6 @@ def add_parser(subparsers, *args, **kwargs):
help='Symlink the dist instead of copying')
parser_apk = add_parser(
-
subparsers,
'apk', help='Build an APK',
parents=[generic_parser])
@@ -464,20 +484,20 @@ def add_parser(subparsers, *args, **kwargs):
'--signkeypw', dest='signkeypw', action='store', default=None,
help='Password for key alias')
- parser_create = add_parser(
+ add_parser(
subparsers,
'create', help='Compile a set of requirements into a dist',
parents=[generic_parser])
- parser_archs = add_parser(
+ add_parser(
subparsers,
'archs', help='List the available target architectures',
parents=[generic_parser])
- parser_distributions = add_parser(
+ add_parser(
subparsers,
'distributions', aliases=['dists'],
help='List the currently available (compiled) dists',
parents=[generic_parser])
- parser_delete_dist = add_parser(
+ add_parser(
subparsers,
'delete_dist', aliases=['delete-dist'], help='Delete a compiled dist',
parents=[generic_parser])
@@ -490,15 +510,15 @@ def add_parser(subparsers, *args, **kwargs):
parser_sdk_tools.add_argument(
'tool', help='The binary tool name to run')
- parser_adb = add_parser(
+ add_parser(
subparsers,
'adb', help='Run adb from the given SDK',
parents=[generic_parser])
- parser_logcat = add_parser(
+ add_parser(
subparsers,
'logcat', help='Run logcat from the given SDK',
parents=[generic_parser])
- parser_build_status = add_parser(
+ add_parser(
subparsers,
'build_status', aliases=['build-status'],
help='Print some debug information about current built components',
@@ -521,9 +541,11 @@ def add_parser(subparsers, *args, **kwargs):
if args.debug:
logger.setLevel(logging.DEBUG)
- # strip version from requirements, and put them in environ
+ # Process requirements and put version in environ
if hasattr(args, 'requirements'):
requirements = []
+
+ # Parse --requirements argument list:
for requirement in split_argument_list(args.requirements):
if "==" in requirement:
requirement, version = requirement.split(u"==", 1)
@@ -533,13 +555,14 @@ def add_parser(subparsers, *args, **kwargs):
requirements.append(requirement)
args.requirements = u",".join(requirements)
+ self.warn_on_deprecated_args(args)
+
self.ctx = Context()
self.storage_dir = args.storage_dir
self.ctx.setup_dirs(self.storage_dir)
self.sdk_dir = args.sdk_dir
self.ndk_dir = args.ndk_dir
self.android_api = args.android_api
- self.ndk_version = args.ndk_version
self.ndk_api = args.ndk_api
self.ctx.symlink_java_src = args.symlink_java_src
self.ctx.java_build_tool = args.java_build_tool
@@ -552,6 +575,19 @@ def add_parser(subparsers, *args, **kwargs):
# Each subparser corresponds to a method
getattr(self, args.subparser_name.replace('-', '_'))(args)
+ def warn_on_deprecated_args(self, args):
+ """
+ Print warning messages for any deprecated arguments that were passed.
+ """
+
+ # NDK version is now determined automatically
+ if args.ndk_version is not None:
+ warning('--ndk-version is deprecated and no longer necessary, '
+ 'the value you passed is ignored')
+ if 'ANDROIDNDKVER' in environ:
+ warning('$ANDROIDNDKVER is deprecated and no longer necessary, '
+ 'the value you set is ignored')
+
def hook(self, name):
if not self.args.hook:
return
@@ -594,7 +630,7 @@ def recipes(self, args):
for name in sorted(Recipe.list_recipes(ctx)):
try:
recipe = Recipe.get_recipe(name, ctx)
- except IOError:
+ except (IOError, ValueError):
warning('Recipe "{}" could not be loaded'.format(name))
except SyntaxError:
import traceback
@@ -772,7 +808,7 @@ def apk(self, args):
if len(argx) > 1:
unknown_args[i] = '='.join(
(argx[0], realpath(expanduser(argx[1]))))
- else:
+ elif i + 1 < len(unknown_args):
unknown_args[i+1] = realpath(expanduser(unknown_args[i+1]))
env = os.environ.copy()
@@ -959,7 +995,6 @@ def sdk_tools(self, args):
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
user_ndk_dir=self.ndk_dir,
user_android_api=self.android_api,
- user_ndk_ver=self.ndk_version,
user_ndk_api=self.ndk_api)
android = sh.Command(join(ctx.sdk_dir, 'tools', args.tool))
output = android(
@@ -987,7 +1022,6 @@ def _adb(self, commands):
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
user_ndk_dir=self.ndk_dir,
user_android_api=self.android_api,
- user_ndk_ver=self.ndk_version,
user_ndk_api=self.ndk_api)
if platform in ('win32', 'cygwin'):
adb = sh.Command(join(ctx.sdk_dir, 'platform-tools', 'adb.exe'))
diff --git a/pythonforandroid/util.py b/pythonforandroid/util.py
index 4c83338a52..9c007c2142 100644
--- a/pythonforandroid/util.py
+++ b/pythonforandroid/util.py
@@ -1,8 +1,9 @@
import contextlib
from os.path import exists, join
-from os import getcwd, chdir, makedirs, walk
+from os import getcwd, chdir, makedirs, walk, uname
import io
import json
+import sh
import shutil
import sys
from fnmatch import fnmatch
@@ -24,6 +25,13 @@ class WgetDownloader(FancyURLopener):
urlretrieve = WgetDownloader().retrieve
+build_platform = '{system}-{machine}'.format(
+ system=uname()[0], machine=uname()[-1]).lower()
+"""the build platform in the format `system-machine`. We use
+this string to define the right build system when compiling some recipes or
+to get the right path for clang compiler"""
+
+
@contextlib.contextmanager
def current_directory(new_dir):
cur_dir = getcwd()
@@ -127,6 +135,17 @@ def is_exe(fpath):
return None
+def get_virtualenv_executable():
+ virtualenv = None
+ if virtualenv is None:
+ virtualenv = sh.which('virtualenv2')
+ if virtualenv is None:
+ virtualenv = sh.which('virtualenv-2.7')
+ if virtualenv is None:
+ virtualenv = sh.which('virtualenv')
+ return virtualenv
+
+
def walk_valid_filens(base_dir, invalid_dir_names, invalid_file_patterns):
"""Recursively walks all the files and directories in ``dirn``,
ignoring directories that match any pattern in ``invalid_dirns``
diff --git a/setup.py b/setup.py
index 74117ffd6d..558dcb2f27 100644
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,11 @@
-from setuptools import setup, find_packages
+import glob
+from io import open # for open(..,encoding=...) parameter in python 2
from os import walk
from os.path import join, dirname, sep
import os
-import glob
import re
+from setuptools import setup, find_packages
# NOTE: All package data should also be set in MANIFEST.in
@@ -44,7 +45,7 @@ def recursively_include(results, directory, patterns):
recursively_include(package_data, 'pythonforandroid/bootstraps',
['*.properties', '*.xml', '*.java', '*.tmpl', '*.txt', '*.png',
'*.mk', '*.c', '*.h', '*.py', '*.sh', '*.jpg', '*.aidl',
- '*.gradle', '.gitkeep', 'gradlew*', '*.jar', ])
+ '*.gradle', '.gitkeep', 'gradlew*', '*.jar', "*.patch", ])
recursively_include(package_data, 'pythonforandroid/bootstraps',
['sdl-config', ])
recursively_include(package_data, 'pythonforandroid/bootstraps/webview',
@@ -52,13 +53,19 @@ def recursively_include(results, directory, patterns):
recursively_include(package_data, 'pythonforandroid',
['liblink', 'biglink', 'liblink.sh'])
-with open(join(dirname(__file__), 'README.rst')) as fileh:
+with open(join(dirname(__file__), 'README.md'),
+ encoding="utf-8",
+ errors="replace",
+ ) as fileh:
long_description = fileh.read()
init_filen = join(dirname(__file__), 'pythonforandroid', '__init__.py')
version = None
try:
- with open(init_filen) as fileh:
+ with open(init_filen,
+ encoding="utf-8",
+ errors="replace"
+ ) as fileh:
lines = fileh.readlines()
except IOError:
pass
diff --git a/testapps/on_device_unit_tests/buildozer.spec b/testapps/on_device_unit_tests/buildozer.spec
new file mode 100644
index 0000000000..b313bad059
--- /dev/null
+++ b/testapps/on_device_unit_tests/buildozer.spec
@@ -0,0 +1,282 @@
+[app]
+
+# (str) Title of your application
+title = p4a unit tests
+
+# (str) Package name
+package.name = p4aunittests
+
+# (str) Package domain (needed for android/ios packaging)
+package.domain = org.kivy
+
+# (str) Source code where the main.py live
+source.dir = test_app
+
+# (list) Source files to include (let empty to include all the files)
+source.include_exts = py,png,jpg,kv,atlas
+
+# (list) List of inclusions using pattern matching
+#source.include_patterns = assets/*,images/*.png
+
+# (list) Source files to exclude (let empty to not exclude anything)
+#source.exclude_exts = spec
+
+# (list) List of directory to exclude (let empty to not exclude anything)
+#source.exclude_dirs = tests, bin
+
+# (list) List of exclusions using pattern matching
+#source.exclude_patterns = license,images/*/*.jpg
+
+# (str) Application versioning (method 1)
+version = 0.1
+
+# (str) Application versioning (method 2)
+# version.regex = __version__ = ['"](.*)['"]
+# version.filename = %(source.dir)s/main.py
+
+# (list) Application requirements
+# comma separated e.g. requirements = sqlite3,kivy
+requirements = python3,kivy,openssl,numpy,sqlite3
+
+# (str) Custom source folders for requirements
+# Sets custom source for any requirements with recipes
+# requirements.source.kivy = ../../kivy
+
+# (list) Garden requirements
+#garden_requirements =
+
+# (str) Presplash of the application
+#presplash.filename = %(source.dir)s/data/presplash.png
+
+# (str) Icon of the application
+#icon.filename = %(source.dir)s/data/icon.png
+
+# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
+orientation = portrait
+
+# (list) List of service to declare
+#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
+
+#
+# OSX Specific
+#
+
+#
+# author = © Copyright Info
+
+# change the major version of python used by the app
+osx.python_version = 3
+
+# Kivy version to use
+osx.kivy_version = 1.9.1
+
+#
+# Android specific
+#
+
+# (bool) Indicate if the application should be fullscreen or not
+fullscreen = 0
+
+# (string) Presplash background color (for new android toolchain)
+# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
+# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
+# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
+# olive, purple, silver, teal.
+#android.presplash_color = #FFFFFF
+
+# (list) Permissions
+#android.permissions = INTERNET
+
+# (int) Target Android API, should be as high as possible.
+#android.api = 27
+
+# (int) Minimum API your APK will support.
+#android.minapi = 21
+
+# (int) Android SDK version to use
+#android.sdk = 20
+
+# (str) Android NDK version to use
+#android.ndk = 17c
+
+# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
+#android.ndk_api = 21
+
+# (bool) Use --private data storage (True) or --dir public storage (False)
+#android.private_storage = True
+
+# (str) Android NDK directory (if empty, it will be automatically downloaded.)
+#android.ndk_path =
+
+# (str) Android SDK directory (if empty, it will be automatically downloaded.)
+#android.sdk_path =
+
+# (str) ANT directory (if empty, it will be automatically downloaded.)
+#android.ant_path =
+
+# (bool) If True, then skip trying to update the Android sdk
+# This can be useful to avoid excess Internet downloads or save time
+# when an update is due and you just want to test/build your package
+# android.skip_update = False
+
+# (str) Android entry point, default is ok for Kivy-based app
+#android.entrypoint = org.renpy.android.PythonActivity
+
+# (list) Pattern to whitelist for the whole project
+android.whitelist = unittest/*
+
+# (str) Path to a custom whitelist file
+#android.whitelist_src =
+
+# (str) Path to a custom blacklist file
+#android.blacklist_src =
+
+# (list) List of Java .jar files to add to the libs so that pyjnius can access
+# their classes. Don't add jars that you do not need, since extra jars can slow
+# down the build process. Allows wildcards matching, for example:
+# OUYA-ODK/libs/*.jar
+#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
+
+# (list) List of Java files to add to the android project (can be java or a
+# directory containing the files)
+#android.add_src =
+
+# (list) Android AAR archives to add (currently works only with sdl2_gradle
+# bootstrap)
+#android.add_aars =
+
+# (list) Gradle dependencies to add (currently works only with sdl2_gradle
+# bootstrap)
+#android.gradle_dependencies =
+
+# (list) Java classes to add as activities to the manifest.
+#android.add_activites = com.example.ExampleActivity
+
+# (str) python-for-android branch to use, defaults to master
+p4a.branch = master
+
+# (str) OUYA Console category. Should be one of GAME or APP
+# If you leave this blank, OUYA support will not be enabled
+#android.ouya.category = GAME
+
+# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
+#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
+
+# (str) XML file to include as an intent filters in tag
+#android.manifest.intent_filters =
+
+# (str) launchMode to set for the main activity
+#android.manifest.launch_mode = standard
+
+# (list) Android additional libraries to copy into libs/armeabi
+#android.add_libs_armeabi = libs/android/*.so
+#android.add_libs_armeabi_v7a = libs/android-v7/*.so
+#android.add_libs_x86 = libs/android-x86/*.so
+#android.add_libs_mips = libs/android-mips/*.so
+
+# (bool) Indicate whether the screen should stay on
+# Don't forget to add the WAKE_LOCK permission if you set this to True
+#android.wakelock = False
+
+# (list) Android application meta-data to set (key=value format)
+#android.meta_data =
+
+# (list) Android library project to add (will be added in the
+# project.properties automatically.)
+#android.library_references =
+
+# (str) Android logcat filters to use
+#android.logcat_filters = *:S python:D
+
+# (bool) Copy library instead of making a libpymodules.so
+#android.copy_libs = 1
+
+# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86
+android.arch = armeabi-v7a
+
+#
+# Python for android (p4a) specific
+#
+
+# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
+#p4a.source_dir =
+
+# (str) The directory in which python-for-android should look for your own build recipes (if any)
+#p4a.local_recipes =
+
+# (str) Filename to the hook for p4a
+#p4a.hook =
+
+# (str) Bootstrap to use for android builds
+# p4a.bootstrap = sdl2
+
+# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
+#p4a.port =
+
+
+#
+# iOS specific
+#
+
+# (str) Path to a custom kivy-ios folder
+#ios.kivy_ios_dir = ../kivy-ios
+
+# (str) Name of the certificate to use for signing the debug version
+# Get a list of available identities: buildozer ios list_identities
+#ios.codesign.debug = "iPhone Developer: ()"
+
+# (str) Name of the certificate to use for signing the release version
+#ios.codesign.release = %(ios.codesign.debug)s
+
+
+[buildozer]
+
+# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
+log_level = 2
+
+# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
+warn_on_root = 1
+
+# (str) Path to build artifact storage, absolute or relative to spec file
+# build_dir = ./.buildozer
+
+# (str) Path to build output (i.e. .apk, .ipa) storage
+# bin_dir = ./bin
+
+# -----------------------------------------------------------------------------
+# List as sections
+#
+# You can define all the "list" as [section:key].
+# Each line will be considered as a option to the list.
+# Let's take [app] / source.exclude_patterns.
+# Instead of doing:
+#
+#[app]
+#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
+#
+# This can be translated into:
+#
+#[app:source.exclude_patterns]
+#license
+#data/audio/*.wav
+#data/images/original/*
+#
+
+
+# -----------------------------------------------------------------------------
+# Profiles
+#
+# You can extend section / key with a profile
+# For example, you want to deploy a demo version of your application without
+# HD content. You could first change the title to add "(demo)" in the name
+# and extend the excluded directories to remove the HD content.
+#
+#[app@demo]
+#title = My Application (demo)
+#
+#[app:source.exclude_patterns@demo]
+#images/hd/*
+#
+# Then, invoke the command line with the "demo" profile:
+#
+#buildozer --profile demo android debug
diff --git a/testapps/on_device_unit_tests/test_app/main.py b/testapps/on_device_unit_tests/test_app/main.py
new file mode 100644
index 0000000000..e0cd9c527d
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/main.py
@@ -0,0 +1,45 @@
+import sys
+if sys.version_info.major < 3:
+ print(('Running under Python {} but these tests '
+ 'require Python 3+').format(sys.version_info.major))
+
+import unittest
+import importlib
+
+print('Imported unittest')
+
+
+class PythonTestMixIn(object):
+
+ module_import = None
+
+ def test_import_module(self):
+ """Test importing the specified Python module name. This import test
+ is common to all Python modules, it does not test any further
+ functionality.
+ """
+ self.assertIsNotNone(
+ self.module_import,
+ 'module_import is not set (was default None)')
+
+ importlib.import_module(self.module_import)
+
+ def test_run_module(self):
+ """Import the specified module and do something with it as a minimal
+ check that it actually works.
+
+ This test fails by default, it must be overridden by every
+ child test class.
+ """
+
+ self.fail('This test must be overridden by {}'.format(self))
+
+print('Defined test case')
+
+import sys
+sys.path.append('./')
+from tests import test_requirements
+suite = unittest.TestLoader().loadTestsFromModule(test_requirements)
+unittest.TextTestRunner().run(suite)
+
+print('Ran tests')
diff --git a/doc/source/old_toolchain/_static/.empty b/testapps/on_device_unit_tests/test_app/tests/__init__.py
similarity index 100%
rename from doc/source/old_toolchain/_static/.empty
rename to testapps/on_device_unit_tests/test_app/tests/__init__.py
diff --git a/testapps/on_device_unit_tests/test_app/tests/test_requirements.py b/testapps/on_device_unit_tests/test_app/tests/test_requirements.py
new file mode 100644
index 0000000000..625a99e5db
--- /dev/null
+++ b/testapps/on_device_unit_tests/test_app/tests/test_requirements.py
@@ -0,0 +1,50 @@
+
+from main import PythonTestMixIn
+from unittest import TestCase
+
+
+class NumpyTestCase(PythonTestMixIn, TestCase):
+ module_import = 'numpy'
+
+ def test_run_module(self):
+ import numpy as np
+
+ arr = np.random.random((3, 3))
+ det = np.linalg.det(arr)
+
+
+class OpensslTestCase(PythonTestMixIn, TestCase):
+ module_import = '_ssl'
+
+ def test_run_module(self):
+ import ssl
+
+ ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ ctx.options &= ~ssl.OP_NO_SSLv3
+
+
+class SqliteTestCase(PythonTestMixIn, TestCase):
+ module_import = 'sqlite3'
+
+ def test_run_module(self):
+ import sqlite3
+
+ conn = sqlite3.connect('example.db')
+ conn.cursor()
+
+
+class KivyTestCase(PythonTestMixIn, TestCase):
+ module_import = 'kivy'
+
+ def test_run_module(self):
+ # This import has side effects, if it works then it's an
+ # indication that Kivy is okay
+ from kivy.core.window import Window
+
+
+class PyjniusTestCase(PythonTestMixIn, TestCase):
+ module_import = 'jnius'
+
+ def test_run_module(self):
+ from jnius import autoclass
+ autoclass('org.kivy.android.PythonActivity')
diff --git a/testapps/setup_keyboard.py b/testapps/setup_keyboard.py
index 38fa78726f..026847764d 100644
--- a/testapps/setup_keyboard.py
+++ b/testapps/setup_keyboard.py
@@ -3,8 +3,10 @@
from setuptools import find_packages
options = {'apk': {'debug': None,
- 'requirements': 'sdl2,pyjnius,kivy,python2',
- 'android-api': 19,
+ 'requirements': 'sdl2,pyjnius,kivy,python3',
+ 'blacklist-requirements': 'openssl,sqlite3',
+ 'android-api': 27,
+ 'ndk-api': 21,
'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
'dist-name': 'bdisttest',
'ndk-version': '10.3.2',
diff --git a/testapps/setup_testapp_flask.py b/testapps/setup_testapp_flask.py
index 1722b6d6c4..3302e8595c 100644
--- a/testapps/setup_testapp_flask.py
+++ b/testapps/setup_testapp_flask.py
@@ -3,7 +3,8 @@
from setuptools import find_packages
options = {'apk': {'debug': None,
- 'requirements': 'python2,flask,pyjnius',
+ 'requirements': 'python3,flask,pyjnius',
+ 'blacklist-requirements': 'openssl,sqlite3',
'android-api': 27,
'ndk-api': 21,
'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
diff --git a/testapps/setup_testapp_python3.py b/testapps/setup_testapp_python3.py
index 40ed20d231..bf83b06c33 100644
--- a/testapps/setup_testapp_python3.py
+++ b/testapps/setup_testapp_python3.py
@@ -2,7 +2,8 @@
from distutils.core import setup
from setuptools import find_packages
-options = {'apk': {'requirements': 'libffi,sdl2,numpy,pyjnius,kivy,python3',
+options = {'apk': {'requirements': 'sdl2,numpy,pyjnius,kivy,python3',
+ 'blacklist-requirements': 'openssl,sqlite3',
'android-api': 27,
'ndk-api': 21,
'dist-name': 'bdisttest_python3_googlendk',
diff --git a/testapps/setup_testapp_python3_sqlite_openssl.py b/testapps/setup_testapp_python3_sqlite_openssl.py
index 19e6b91fef..0f7485d132 100644
--- a/testapps/setup_testapp_python3_sqlite_openssl.py
+++ b/testapps/setup_testapp_python3_sqlite_openssl.py
@@ -2,8 +2,7 @@
from distutils.core import setup
from setuptools import find_packages
-options = {'apk': {'requirements': 'libffi,openssl,sqlite3,requests,peewee,'
- 'sdl2,pyjnius,kivy,python3',
+options = {'apk': {'requirements': 'requests,peewee,sdl2,pyjnius,kivy,python3',
'android-api': 27,
'ndk-api': 21,
'dist-name': 'bdisttest_python3_sqlite_openssl_googlendk',
diff --git a/testapps/setup_testapp_python_encryption.py b/testapps/setup_testapp_python_encryption.py
new file mode 100644
index 0000000000..2a468ade86
--- /dev/null
+++ b/testapps/setup_testapp_python_encryption.py
@@ -0,0 +1,30 @@
+
+from distutils.core import setup
+from setuptools import find_packages
+
+options = {'apk': {'requirements': 'sdl2,pyjnius,kivy,python3,cryptography,'
+ 'pycrypto,scrypt,m2crypto,pysha3,'
+ 'pycryptodome,libtorrent',
+ 'blacklist-requirements': 'sqlite3',
+ 'android-api': 27,
+ 'ndk-api': 21,
+ 'dist-name': 'bdisttest_encryption',
+ 'ndk-version': '10.3.2',
+ 'arch': 'armeabi-v7a',
+ 'permissions': ['INTERNET', 'VIBRATE'],
+ }}
+
+package_data = {'': ['*.py',
+ '*.png']
+ }
+
+setup(
+ name='testapp_encryption',
+ version='1.0',
+ description='p4a setup.py test',
+ author='Pol Canelles',
+ author_email='canellestudi@gmail.com',
+ packages=find_packages(),
+ options=options,
+ package_data={'testapp_encryption': ['*.py', '*.png']}
+)
diff --git a/testapps/setup_testapp_service.py b/testapps/setup_testapp_service.py
index 0ae059e49d..b246f108d9 100644
--- a/testapps/setup_testapp_service.py
+++ b/testapps/setup_testapp_service.py
@@ -3,7 +3,8 @@
from setuptools import find_packages
options = {'apk': {'debug': None,
- 'requirements': 'python2,genericndkbuild',
+ 'requirements': 'python3,genericndkbuild,pyjnius',
+ 'blacklist-requirements': 'openssl,sqlite3',
'android-api': 27,
'ndk-api': 21,
'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
@@ -12,6 +13,7 @@
'bootstrap': 'service_only',
'permissions': ['INTERNET', 'VIBRATE'],
'arch': 'armeabi-v7a',
+ 'service': 'time:p4atime.py',
}}
package_data = {'': ['*.py']}
@@ -21,7 +23,7 @@
setup(
name='testapp_service',
- version='1.0',
+ version='1.1',
description='p4a service testapp',
author='Alexander Taylor',
author_email='alexanderjohntaylor@gmail.com',
diff --git a/testapps/setup_vispy.py b/testapps/setup_vispy.py
index 99e2a5563b..a0863d0a1c 100644
--- a/testapps/setup_vispy.py
+++ b/testapps/setup_vispy.py
@@ -3,8 +3,10 @@
from setuptools import find_packages
options = {'apk': {'debug': None,
- 'requirements': 'vispy',
- 'android-api': 19,
+ 'requirements': 'python3,vispy',
+ 'blacklist-requirements': 'openssl,sqlite3',
+ 'android-api': 27,
+ 'ndk-api': 21,
'ndk-dir': '/home/asandy/android/crystax-ndk-10.3.2',
'dist-name': 'bdisttest',
'ndk-version': '10.3.2',
diff --git a/testapps/testapp_encryption/colours.png b/testapps/testapp_encryption/colours.png
new file mode 100644
index 0000000000..30b685e32b
Binary files /dev/null and b/testapps/testapp_encryption/colours.png differ
diff --git a/testapps/testapp_encryption/main.py b/testapps/testapp_encryption/main.py
new file mode 100644
index 0000000000..b3289e857a
--- /dev/null
+++ b/testapps/testapp_encryption/main.py
@@ -0,0 +1,345 @@
+print('main.py was successfully called')
+
+import os
+
+print('imported os')
+
+print('this dir is', os.path.abspath(os.curdir))
+
+print('contents of this dir', os.listdir('./'))
+
+import sys
+
+print('pythonpath is', sys.path)
+
+import kivy
+
+print('imported kivy')
+print('file is', kivy.__file__)
+
+from kivy.app import App
+
+from kivy.lang import Builder
+from kivy.properties import StringProperty
+
+from kivy.uix.popup import Popup
+from kivy.clock import Clock
+
+print('Imported kivy')
+from kivy.utils import platform
+
+print('platform is', platform)
+
+# Test cryptography
+try:
+ from cryptography.fernet import Fernet
+
+ key = Fernet.generate_key()
+ f = Fernet(key)
+ cryptography_encrypted = f.encrypt(
+ b'A really secret message. Not for prying eyes.')
+ cryptography_decrypted = f.decrypt(cryptography_encrypted)
+except Exception as e1:
+ print('**************************')
+ print('Error on cryptography operations:\n{}'.format(e1))
+ print('**************************')
+ cryptography_encrypted = 'Error'
+ cryptography_decrypted = 'Error'
+
+# Test pycrypto
+crypto_hash_message = 'A secret message'
+try:
+ from Crypto.Hash import SHA256
+
+ hash = SHA256.new()
+ hash.update(crypto_hash_message)
+ crypto_hash_hexdigest = hash.hexdigest()
+except Exception as e2:
+ print('**************************')
+ print('Error on Crypto operations:\n{}'.format(e2))
+ print('**************************')
+ crypto_hash_hexdigest = 'Error'
+
+# Test scrypt
+try:
+ from scrypt import *
+
+ status_import_scrypt = 'Success'
+except ImportError as e3:
+ print('**************************')
+ print('Unable to import scrypt:\n{}'.format(e3))
+ print('**************************')
+ status_import_scrypt = 'Error'
+
+# Test M2Crypto
+try:
+ from M2Crypto import *
+
+ status_import_m2crypto = 'Success'
+except ImportError as e5:
+ print('**************************')
+ print('Unable to import M2Crypto:\n{}'.format(e5))
+ print('**************************\n')
+ status_import_m2crypto = 'Error'
+
+# Test pysha3
+try:
+ import sha3
+
+ print('Ok imported pysha3, testing some basic operations...')
+ k = sha3.keccak_512()
+ k.update(b"data")
+ print('Test pysha3 operation (keccak_512): {}'.format(k.hexdigest()))
+ status_import_pysha3 = 'Success'
+except ImportError as e6:
+ print('**************************')
+ print('Unable to import/operate with pysha3:\n{}'.format(e6))
+ print('**************************')
+ status_import_pysha3 = 'Error'
+
+# Test pycryptodome
+try:
+ from Crypto.PublicKey import RSA
+
+ print('Ok imported pycryptodome, testing some basic operations...')
+ secret_code = "Unguessable"
+ key = RSA.generate(2048)
+ encrypted_key = key.export_key(passphrase=secret_code, pkcs=8,
+ protection="scryptAndAES128-CBC")
+ print('\t -> Testing key for secret code "Unguessable": {}'.format(
+ encrypted_key))
+
+ file_out = open("rsa_key.bin", "wb")
+ file_out.write(encrypted_key)
+ print('\t -> Testing key write: {}'.format(
+ 'ok' if os.path.exists(file_out) else 'fail'))
+
+ print('\t -> Testing Public key:'.format(key.publickey().export_key()))
+ status_import_pycryptodome = 'Success (import and doing simple operations)'
+except ImportError as e6:
+ print('**************************')
+ print('Unable to import/operate with pycryptodome:\n{}'.format(e6))
+ print('**************************')
+ status_import_pycryptodome = 'Error'
+
+# Test libtorrent
+try:
+ import libtorrent as lt
+
+ print('Imported libtorrent version {}'.format(lt.version))
+ status_import_libtorrent = 'Success (version is: {})'.format(lt.version)
+except Exception as e4:
+ print('**************************')
+ print('Unable to import libtorrent:\n{}'.format(e4))
+ print('**************************')
+ status_import_libtorrent = 'Error'
+
+kv = '''
+#:import Metrics kivy.metrics.Metrics
+#:import sys sys
+
+:
+ size_hint_y: None
+ height: dp(60)
+
+:
+ orientation: 'vertical'
+ size_hint_y: None
+ height: self.minimum_height
+ test_module: ''
+ test_result: ''
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ markup: True
+ text: '[b]*** TEST {} MODULE ***[/b]'.format(self.parent.test_module)
+ halign: 'center'
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ markup: True
+ text:
+ 'Import {}: [color=a0a0a0]{}[/color]'.format(
+ self.parent.test_module, self.parent.test_result)
+ halign: 'left'
+ Widget:
+ size_hint_y: None
+ height: 20
+
+
+ScrollView:
+ GridLayout:
+ cols: 1
+ size_hint_y: None
+ height: self.minimum_height
+ FixedSizeButton:
+ text: 'test pyjnius'
+ on_press: app.test_pyjnius()
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ markup: True
+ text: '[b]*** TEST CRYPTOGRAPHY MODULE ***[/b]'
+ halign: 'center'
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ markup: True
+ text:
+ 'Cryptography decrypted:\\n[color=a0a0a0]%s[/color]\\n' \\
+ 'Cryptography encrypted:\\n[color=a0a0a0]%s[/color]' % (
+ app.cryptography_decrypted, app.cryptography_encrypted)
+ halign: 'left'
+ Widget:
+ size_hint_y: None
+ height: 20
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ markup: True
+ text: '[b]*** TEST CRYPTO MODULE ***[/b]'
+ halign: 'center'
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ markup: True
+ text:
+ 'Crypto message: \\n[color=a0a0a0]%s[/color]\\n'\\
+ 'Crypto hex: \\n[color=a0a0a0]%s[/color]' % (
+ app.crypto_hash_message, app.crypto_hash_hexdigest)
+ halign: 'left'
+ Widget:
+ size_hint_y: None
+ height: 20
+ TestImport:
+ test_module: 'scrypt'
+ test_result: app.status_import_scrypt
+ TestImport:
+ test_module: 'm2crypto'
+ test_result: app.status_import_m2crypto
+ TestImport:
+ test_module: 'pysha3'
+ test_result: app.status_import_pysha3
+ TestImport:
+ test_module: 'pycryptodome'
+ test_result: app.status_import_pycryptodome
+ TestImport:
+ test_module: 'libtorrent'
+ test_result: app.status_import_libtorrent
+ Image:
+ keep_ratio: False
+ allow_stretch: True
+ source: 'colours.png'
+ size_hint_y: None
+ height: dp(100)
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ font_size: 100
+ text_size: self.size[0], None
+ markup: True
+ text: '[b]Kivy[/b] on [b]SDL2[/b] on [b]Android[/b]!'
+ halign: 'center'
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ text_size: self.size[0], None
+ markup: True
+ text: sys.version
+ halign: 'center'
+ padding_y: dp(10)
+ Widget:
+ size_hint_y: None
+ height: 20
+ Label:
+ height: self.texture_size[1]
+ size_hint_y: None
+ font_size: 50
+ text_size: self.size[0], None
+ markup: True
+ text:
+ 'dpi: [color=a0a0a0]%s[/color]\\n'\\
+ 'density: [color=a0a0a0]%s[/color]\\n'\\
+ 'fontscale: [color=a0a0a0]%s[/color]' % (
+ Metrics.dpi, Metrics.density, Metrics.fontscale)
+ halign: 'center'
+ FixedSizeButton:
+ text: 'test ctypes'
+ on_press: app.test_ctypes()
+ Widget:
+ size_hint_y: None
+ height: 1000
+ on_touch_down: print('touched at', args[-1].pos)
+
+:
+ title: 'Error'
+ size_hint: 0.75, 0.75
+ Label:
+ text: root.error_text
+'''
+
+
+class ErrorPopup(Popup):
+ error_text = StringProperty('')
+
+
+def raise_error(error):
+ print('ERROR:', error)
+ ErrorPopup(error_text=error).open()
+
+
+class TestApp(App):
+ cryptography_encrypted = cryptography_encrypted
+ cryptography_decrypted = cryptography_decrypted
+ crypto_hash_message = crypto_hash_message
+ crypto_hash_hexdigest = crypto_hash_hexdigest
+ status_import_scrypt = status_import_scrypt
+ status_import_m2crypto = status_import_m2crypto
+ status_import_pysha3 = status_import_pysha3
+ status_import_pycryptodome = status_import_pycryptodome
+ status_import_libtorrent = status_import_libtorrent
+
+ def build(self):
+ root = Builder.load_string(kv)
+ Clock.schedule_interval(self.print_something, 2)
+ # Clock.schedule_interval(self.test_pyjnius, 5)
+ print('testing metrics')
+ from kivy.metrics import Metrics
+ print('dpi is', Metrics.dpi)
+ print('density is', Metrics.density)
+ print('fontscale is', Metrics.fontscale)
+ return root
+
+ def print_something(self, *args):
+ print('App print tick', Clock.get_boottime())
+
+ def on_pause(self):
+ return True
+
+ def test_pyjnius(self, *args):
+ try:
+ from jnius import autoclass
+ except ImportError:
+ raise_error('Could not import pyjnius')
+ return
+
+ print('Attempting to vibrate with pyjnius')
+ python_activity = autoclass('org.kivy.android.PythonActivity')
+ activity = python_activity.mActivity
+ intent = autoclass('android.content.Intent')
+ context = autoclass('android.content.Context')
+ vibrator = activity.getSystemService(context.VIBRATOR_SERVICE)
+
+ vibrator.vibrate(1000)
+
+ def test_ctypes(self, *args):
+ import ctypes
+
+
+TestApp().run()
diff --git a/testapps/testapp_keyboard/main.py b/testapps/testapp_keyboard/main.py
index 03e66aa3e1..cb76b7af99 100644
--- a/testapps/testapp_keyboard/main.py
+++ b/testapps/testapp_keyboard/main.py
@@ -2,23 +2,29 @@
import os
print('imported os')
+import sys
+print('imported sys')
from kivy import platform
if platform == 'android':
- print('contents of ./lib/python2.7/site-packages/ etc.')
- print(os.listdir('./lib'))
- print(os.listdir('./lib/python2.7'))
- print(os.listdir('./lib/python2.7/site-packages'))
+ site_dir_path = './_python_bundle/site-packages'
+ if not os.path.exists(site_dir_path):
+ print('warning: site-packages dir not found: ' + site_dir_path)
+ else:
+ print('contents of ' + site_dir_path)
+ print(os.listdir(site_dir_path))
print('this dir is', os.path.abspath(os.curdir))
print('contents of this dir', os.listdir('./'))
- with open('./lib/python2.7/site-packages/kivy/app.pyo', 'rb') as fileh:
- print('app.pyo size is', len(fileh.read()))
+ if (os.path.exists(site_dir_path) and
+ os.path.exists(site_dir_path + '/kivy/app.pyo')
+ ):
+ with open(site_dir_path + '/kivy/app.pyo', 'rb') as fileh:
+ print('app.pyo size is', len(fileh.read()))
-import sys
print('pythonpath is', sys.path)
import kivy
diff --git a/testapps/testapp_service/main.py b/testapps/testapp_service/main.py
index 29ccb477bb..dc781cd3bd 100644
--- a/testapps/testapp_service/main.py
+++ b/testapps/testapp_service/main.py
@@ -1,4 +1,4 @@
-print('main.py was successfully called')
+print('Service Test App main.py was successfully called')
import sys
print('python version is: ', sys.version)
@@ -11,17 +11,9 @@
print(i, sqrt(i))
print('Just printing stuff apparently worked, trying a simple service')
-import datetime, threading, time
-next_call = time.time()
-
-
-def service_timer():
- global next_call
- print('P4a datetime service: {}'.format(datetime.datetime.now()))
- next_call = next_call + 1
- threading.Timer(next_call - time.time(), service_timer).start()
-
-
-print('Starting the service timer...')
-service_timer()
+from jnius import autoclass
+service = autoclass('org.test.testapp_service.ServiceTime')
+mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
+argument = 'test argument ok'
+service.start(mActivity, argument)
diff --git a/testapps/testapp_service/p4atime.py b/testapps/testapp_service/p4atime.py
new file mode 100644
index 0000000000..75a1d76e0f
--- /dev/null
+++ b/testapps/testapp_service/p4atime.py
@@ -0,0 +1,19 @@
+import datetime
+import threading
+import time
+from os import environ
+argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
+print('p4atime.py was successfully called with argument: "{}"'.format(argument))
+
+next_call = time.time()
+
+
+def service_timer():
+ global next_call
+ print('P4a datetime service: {}'.format(datetime.datetime.now()))
+ next_call = next_call + 1
+ threading.Timer(next_call - time.time(), service_timer).start()
+
+
+print('Starting the service timer...')
+service_timer()
diff --git a/tests/recipes/test_gevent.py b/tests/recipes/test_gevent.py
new file mode 100644
index 0000000000..8548ffa64a
--- /dev/null
+++ b/tests/recipes/test_gevent.py
@@ -0,0 +1,73 @@
+import unittest
+from mock import patch
+from pythonforandroid.archs import ArchARMv7_a
+from pythonforandroid.build import Context
+from pythonforandroid.recipe import Recipe
+
+
+class TestGeventRecipe(unittest.TestCase):
+
+ def setUp(self):
+ """
+ Setups recipe and context.
+ """
+ self.context = Context()
+ self.context.ndk_api = 21
+ self.context.android_api = 27
+ self.arch = ArchARMv7_a(self.context)
+ self.recipe = Recipe.get_recipe('gevent', self.context)
+
+ def test_get_recipe_env(self):
+ """
+ Makes sure `get_recipe_env()` sets compilation flags properly.
+ """
+ mocked_cflags = (
+ '-DANDROID -fomit-frame-pointer -D__ANDROID_API__=27 -mandroid '
+ '-isystem /path/to/isystem '
+ '-I/path/to/include1 '
+ '-isysroot /path/to/sysroot '
+ '-I/path/to/include2 '
+ '-march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb '
+ '-I/path/to/python3-libffi-openssl/include'
+ )
+ mocked_ldflags = (
+ ' --sysroot /path/to/sysroot '
+ '-lm '
+ '-L/path/to/library1 '
+ '-L/path/to/library2 '
+ '-lpython3.7m '
+ # checks the regex doesn't parse `python3-libffi-openssl` as a `-libffi`
+ '-L/path/to/python3-libffi-openssl/library3 '
+ )
+ mocked_env = {
+ 'CFLAGS': mocked_cflags,
+ 'LDFLAGS': mocked_ldflags,
+ }
+ with patch('pythonforandroid.recipe.CythonRecipe.get_recipe_env') as m_get_recipe_env:
+ m_get_recipe_env.return_value = mocked_env
+ env = self.recipe.get_recipe_env()
+ expected_cflags = (
+ ' -fomit-frame-pointer -mandroid -isystem /path/to/isystem'
+ ' -isysroot /path/to/sysroot'
+ ' -march=armv7-a -mfloat-abi=softfp -mfpu=vfp -mthumb'
+ )
+ expected_cppflags = (
+ '-DANDROID -D__ANDROID_API__=27 '
+ '-I/path/to/include1 '
+ '-I/path/to/include2 '
+ '-I/path/to/python3-libffi-openssl/include'
+ )
+ expected_ldflags = (
+ ' --sysroot /path/to/sysroot'
+ ' -L/path/to/library1'
+ ' -L/path/to/library2'
+ ' -L/path/to/python3-libffi-openssl/library3 '
+ )
+ expected_libs = '-lm -lpython3.7m'
+ expected_env = {
+ 'CFLAGS': expected_cflags,
+ 'CPPFLAGS': expected_cppflags,
+ 'LDFLAGS': expected_ldflags,
+ 'LIBS': expected_libs,
+ }
+ self.assertEqual(expected_env, env)
diff --git a/tests/test_graph.py b/tests/test_graph.py
index 96cda76d3f..e113c3bb3b 100644
--- a/tests/test_graph.py
+++ b/tests/test_graph.py
@@ -1,24 +1,75 @@
from pythonforandroid.build import Context
-from pythonforandroid.graph import get_recipe_order_and_bootstrap
+from pythonforandroid.graph import (
+ fix_deplist, get_dependency_tuple_list_for_recipe,
+ get_recipe_order_and_bootstrap, obvious_conflict_checker,
+)
from pythonforandroid.bootstrap import Bootstrap
+from pythonforandroid.recipe import Recipe
from pythonforandroid.util import BuildInterruptingException
from itertools import product
+import mock
import pytest
-
ctx = Context()
name_sets = [['python2'],
['kivy']]
bootstraps = [None,
- Bootstrap.get_bootstrap('pygame', ctx),
Bootstrap.get_bootstrap('sdl2', ctx)]
valid_combinations = list(product(name_sets, bootstraps))
valid_combinations.extend(
[(['python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx)),
- (['kivy', 'python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx))])
-invalid_combinations = [[['python2', 'python3crystax'], None]]
+ (['kivy', 'python3crystax'], Bootstrap.get_bootstrap('sdl2', ctx)),
+ (['flask'], Bootstrap.get_bootstrap('webview', ctx)),
+ (['pysdl2'], None), # auto-detect bootstrap! important corner case
+ ]
+)
+invalid_combinations = [
+ [['python2', 'python3crystax'], None],
+ [['pysdl2', 'genericndkbuild'], None],
+]
+invalid_combinations_simple = list(invalid_combinations)
+# NOTE !! keep in mind when setting invalid_combinations_simple:
+#
+# This is used to test obvious_conflict_checker(), which only
+# catches CERTAIN conflicts:
+#
+# This must be a list of conflicts where the conflict is ONLY in
+# non-tuple/non-ambiguous dependencies, e.g.:
+#
+# dependencies_1st = ["python2", "pillow"]
+# dependencies_2nd = ["python3crystax", "pillow"]
+#
+# This however won't work:
+#
+# dependencies_1st = [("python2", "python3"), "pillow"]
+# dependencies_2nd = [("python2legacy", "python3crystax"), "pillow"]
+#
+# (This is simply because the conflict checker doesn't resolve this to
+# keep the code ismple enough)
+
+
+def get_fake_recipe(name, depends=None, conflicts=None):
+ recipe = mock.Mock()
+ recipe.name = name
+ recipe.get_opt_depends_in_list = lambda: []
+ recipe.get_dir_name = lambda: name
+ recipe.depends = list(depends or [])
+ recipe.conflicts = list(conflicts or [])
+ return recipe
+
+
+def register_fake_recipes_for_test(monkeypatch, recipe_list):
+ _orig_get_recipe = Recipe.get_recipe
+
+ def mock_get_recipe(name, ctx):
+ for recipe in recipe_list:
+ if recipe.name == name:
+ return recipe
+ return _orig_get_recipe(name, ctx)
+ # Note: staticmethod() needed for python ONLY, don't ask me why:
+ monkeypatch.setattr(Recipe, 'get_recipe', staticmethod(mock_get_recipe))
@pytest.mark.parametrize('names,bootstrap', valid_combinations)
@@ -30,18 +81,151 @@ def test_valid_recipe_order_and_bootstrap(names, bootstrap):
def test_invalid_recipe_order_and_bootstrap(names, bootstrap):
with pytest.raises(BuildInterruptingException) as e_info:
get_recipe_order_and_bootstrap(ctx, names, bootstrap)
- assert e_info.value.message == (
- "Didn't find any valid dependency graphs. "
- "This means that some of your requirements pull in conflicting dependencies."
+ assert "conflict" in e_info.value.message.lower()
+
+
+def test_blacklist():
+ # First, get order without blacklist:
+ build_order, python_modules, bs = get_recipe_order_and_bootstrap(
+ ctx, ["python3", "kivy"], None
+ )
+ # Now, obtain again with blacklist:
+ build_order_2, python_modules_2, bs_2 = get_recipe_order_and_bootstrap(
+ ctx, ["python3", "kivy"], None, blacklist=["libffi"]
+ )
+ assert "libffi" not in build_order_2
+ assert set(build_order_2).union({"libffi"}) == set(build_order)
+
+ # Check that we get a conflict when using webview and kivy combined:
+ wbootstrap = Bootstrap.get_bootstrap('webview', ctx)
+ with pytest.raises(BuildInterruptingException) as e_info:
+ get_recipe_order_and_bootstrap(ctx, ["flask", "kivy"], wbootstrap)
+ assert "conflict" in e_info.value.message.lower()
+
+ # We should no longer get a conflict blacklisting sdl2 and pygame:
+ get_recipe_order_and_bootstrap(
+ ctx, ["flask", "kivy"], wbootstrap, blacklist=["sdl2", "pygame"]
)
+def test_get_dependency_tuple_list_for_recipe(monkeypatch):
+ r = get_fake_recipe("recipe1", depends=[
+ "libffi",
+ ("libffi", "Pillow")
+ ])
+ dep_list = get_dependency_tuple_list_for_recipe(
+ r, blacklist={"libffi"}
+ )
+ assert(dep_list == [("pillow",)])
+
+
+@pytest.mark.parametrize('names,bootstrap', valid_combinations)
+def test_valid_obvious_conflict_checker(names, bootstrap):
+ # Note: obvious_conflict_checker is stricter on input
+ # (needs fix_deplist) than get_recipe_order_and_bootstrap!
+ obvious_conflict_checker(ctx, fix_deplist(names))
+
+
+@pytest.mark.parametrize('names,bootstrap',
+ invalid_combinations_simple # see above for why this
+ ) # is a separate list
+def test_invalid_obvious_conflict_checker(names, bootstrap):
+ # Note: obvious_conflict_checker is stricter on input
+ # (needs fix_deplist) than get_recipe_order_and_bootstrap!
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(ctx, fix_deplist(names))
+ assert "conflict" in e_info.value.message.lower()
+
+
+def test_misc_obvious_conflict_checker(monkeypatch):
+ # Check that the assert about wrong input data is hit:
+ with pytest.raises(AssertionError) as e_info:
+ obvious_conflict_checker(
+ ctx,
+ ["this_is_invalid"]
+ # (invalid because it isn't properly nested as tuple)
+ )
+
+ # Test that non-recipe dependencies work in overall:
+ obvious_conflict_checker(
+ ctx, fix_deplist(["python3", "notarecipelibrary"])
+ )
+
+ # Test that a conflict with a non-recipe dependency works:
+ # This is currently not used, so we need a custom test recipe:
+ # To get that, we simply modify one!
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("recipe1", conflicts=[("fakelib")]),
+ ])
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(ctx, fix_deplist(["recipe1", "fakelib"]))
+ assert "conflict" in e_info.value.message.lower()
+
+ # Test a case where a recipe pulls in a conditional tuple
+ # of additional dependencies. This is e.g. done for ('python3',
+ # 'python2', ...) but most recipes don't depend on this anymore,
+ # so we need to add a manual test for this case:
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("recipe1", depends=[("libffi", "Pillow")]),
+ ])
+ obvious_conflict_checker(ctx, fix_deplist(["recipe1"]))
+
+
+def test_indirectconflict_obvious_conflict_checker(monkeypatch):
+ # Test a case where there's an indirect conflict, which also
+ # makes sure the error message correctly blames the OUTER recipes
+ # as original conflict source:
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("outerrecipe1", depends=["innerrecipe1"]),
+ get_fake_recipe("outerrecipe2", depends=["innerrecipe2"]),
+ get_fake_recipe("innerrecipe1"),
+ get_fake_recipe("innerrecipe2", conflicts=["innerrecipe1"]),
+ ])
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(
+ ctx,
+ fix_deplist(["outerrecipe1", "outerrecipe2"])
+ )
+ assert ("conflict" in e_info.value.message.lower() and
+ "outerrecipe1" in e_info.value.message.lower() and
+ "outerrecipe2" in e_info.value.message.lower())
+
+
+def test_multichoice_obvious_conflict_checker(monkeypatch):
+ # Test a case where there's a conflict with a multi-choice tuple:
+ with monkeypatch.context() as m:
+ register_fake_recipes_for_test(m, [
+ get_fake_recipe("recipe1", conflicts=["lib1", "lib2"]),
+ get_fake_recipe("recipe2", depends=[("lib1", "lib2")]),
+ ])
+ with pytest.raises(BuildInterruptingException) as e_info:
+ obvious_conflict_checker(
+ ctx,
+ fix_deplist([("lib1", "lib2"), "recipe1"])
+ )
+ assert "conflict" in e_info.value.message.lower()
+
+
def test_bootstrap_dependency_addition():
build_order, python_modules, bs = get_recipe_order_and_bootstrap(
ctx, ['kivy'], None)
assert (('hostpython2' in build_order) or ('hostpython3' in build_order))
+def test_graph_deplist_transformation():
+ test_pairs = [
+ (["Pillow", ('python2', 'python3')],
+ [('pillow',), ('python2', 'python3')]),
+ (["Pillow", ('python2',)],
+ [('pillow',), ('python2',)]),
+ ]
+ for (before_list, after_list) in test_pairs:
+ assert fix_deplist(before_list) == after_list
+
+
def test_bootstrap_dependency_addition2():
build_order, python_modules, bs = get_recipe_order_and_bootstrap(
ctx, ['kivy', 'python2'], None)
diff --git a/tests/test_logger.py b/tests/test_logger.py
new file mode 100644
index 0000000000..8212b4596f
--- /dev/null
+++ b/tests/test_logger.py
@@ -0,0 +1,18 @@
+import unittest
+from mock import MagicMock
+from pythonforandroid import logger
+
+
+class TestShprint(unittest.TestCase):
+
+ def test_unicode_encode(self):
+ """
+ Makes sure `shprint()` can handle unicode command output.
+ Running the test with PYTHONIOENCODING=ASCII env would fail, refs:
+ https://github.com/kivy/python-for-android/issues/1654
+ """
+ expected_command_output = ["foo\xa0bar"]
+ command = MagicMock()
+ command.return_value = expected_command_output
+ output = logger.shprint(command, 'a1', k1='k1')
+ self.assertEqual(output, expected_command_output)
diff --git a/tests/test_recipe.py b/tests/test_recipe.py
new file mode 100644
index 0000000000..d46a6e9ef8
--- /dev/null
+++ b/tests/test_recipe.py
@@ -0,0 +1,43 @@
+import types
+import unittest
+from pythonforandroid.build import Context
+from pythonforandroid.recipe import Recipe
+
+
+class TestRecipe(unittest.TestCase):
+
+ def test_recipe_dirs(self):
+ """
+ Trivial `recipe_dirs()` test.
+ Makes sure the list is not empty and has the root directory.
+ """
+ ctx = Context()
+ recipes_dir = Recipe.recipe_dirs(ctx)
+ # by default only the root dir `recipes` directory
+ self.assertEqual(len(recipes_dir), 1)
+ self.assertTrue(recipes_dir[0].startswith(ctx.root_dir))
+
+ def test_list_recipes(self):
+ """
+ Trivial test verifying list_recipes returns a generator with some recipes.
+ """
+ ctx = Context()
+ recipes = Recipe.list_recipes(ctx)
+ self.assertTrue(isinstance(recipes, types.GeneratorType))
+ recipes = list(recipes)
+ self.assertIn('python3', recipes)
+
+ def test_get_recipe(self):
+ """
+ Makes sure `get_recipe()` returns a `Recipe` object when possible.
+ """
+ ctx = Context()
+ recipe_name = 'python3'
+ recipe = Recipe.get_recipe(recipe_name, ctx)
+ self.assertTrue(isinstance(recipe, Recipe))
+ self.assertEqual(recipe.name, recipe_name)
+ recipe_name = 'does_not_exist'
+ with self.assertRaises(ValueError) as e:
+ Recipe.get_recipe(recipe_name, ctx)
+ self.assertEqual(
+ e.exception.args[0], 'Recipe does not exist: {}'.format(recipe_name))
diff --git a/tox.ini b/tox.ini
index 00ac584626..74cdc3940b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,6 @@
[tox]
envlist = pep8,py27,py3
+basepython = python3
[testenv]
deps =
@@ -17,6 +18,6 @@ commands = flake8 pythonforandroid/ tests/ ci/
ignore =
E123, E124, E126,
E226,
- E402, E501, E722,
- F812, F841, W503,
+ E402, E501,
+ W503,
W504