'''
Android target, based on python-for-android project
'''

import sys
if sys.platform == 'win32':
    raise NotImplementedError('Windows platform not yet working for Android')

from platform import uname
WSL = 'microsoft' in uname()[2].lower()

ANDROID_API = '31'
ANDROID_MINAPI = '21'
APACHE_ANT_VERSION = '1.9.4'

# This constant should *not* be updated, it is used only in the case
# that python-for-android cannot provide a recommendation, which in
# turn only happens if the python-for-android is old and probably
# doesn't support any newer NDK.
DEFAULT_ANDROID_NDK_VERSION = '17c'

import traceback
import os
import io
import re
import ast
from sys import platform, executable
from buildozer import BuildozerException, USE_COLOR
from buildozer.target import Target
from os import environ
from os.path import exists, join, realpath, expanduser, basename, relpath
from platform import architecture
from shutil import copyfile, rmtree, which
import shlex
import pexpect
from glob import glob
from time import sleep

from buildozer.libs.version import parse
from distutils.version import LooseVersion

# buildozer.spec tokens that used to exist but are now ignored
DEPRECATED_TOKENS = (('app', 'android.sdk'), )

# Default SDK tag to download. This is not a configurable option
# because it doesn't seem to matter much, it is normally correct to
# download once then update all the components as buildozer already
# does.
DEFAULT_SDK_TAG = '6514223'

DEFAULT_ARCHS = ['arm64-v8a', 'armeabi-v7a']

MSG_P4A_RECOMMENDED_NDK_ERROR = (
    "WARNING: Unable to find recommended Android NDK for current "
    "installation of python-for-android, defaulting to the default "
    "version r{android_ndk}".format(android_ndk=DEFAULT_ANDROID_NDK_VERSION)
)


class TargetAndroid(Target):
    targetname = 'android'
    p4a_directory_name = "python-for-android"
    p4a_fork = 'kivy'
    p4a_branch = 'master'
    p4a_commit = 'HEAD'
    p4a_recommended_ndk_version = None
    extra_p4a_args = ''

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.buildozer.config.has_option(
            "app", "android.arch"
        ) and not self.buildozer.config.has_option("app", "android.archs"):
            self.buildozer.error("`android.archs` not detected, instead `android.arch` is present.")
            self.buildozer.error("`android.arch` will be removed and ignored in future.")
            self.buildozer.error("If you're seeing this error, please migrate to `android.archs`.")
            self._archs = self.buildozer.config.getlist(
                'app', 'android.arch', DEFAULT_ARCHS)
        else:
            self._archs = self.buildozer.config.getlist(
                'app', 'android.archs', DEFAULT_ARCHS)
        self._build_dir = join(
            self.buildozer.platform_dir, 'build-{}'.format(self.archs_snake))
        executable = sys.executable or 'python'
        self._p4a_cmd = [executable, "-m", "pythonforandroid.toolchain"]
        self._p4a_bootstrap = self.buildozer.config.getdefault(
            'app', 'p4a.bootstrap', 'sdl2')
        color = 'always' if USE_COLOR else 'never'
        self.extra_p4a_args = [f"--color={color}", f"--storage-dir={self._build_dir}"]

        # minapi should match ndk-api, so can use the same default if
        # nothing is specified
        ndk_api = self.buildozer.config.getdefault(
            'app', 'android.ndk_api', self.android_minapi)
        self.extra_p4a_args.append(f"--ndk-api={ndk_api}")

        hook = self.buildozer.config.getdefault("app", "p4a.hook", None)
        if hook is not None:
            self.extra_p4a_args.append(f"--hook={realpath(expanduser(hook))}")
        port = self.buildozer.config.getdefault('app', 'p4a.port', None)
        if port is not None:
            self.extra_p4a_args.append(f"--port={port}")

        setup_py = self.buildozer.config.getdefault('app', 'p4a.setup_py', False)
        if setup_py:
            self.extra_p4a_args.append("--use-setup-py")
        else:
            self.extra_p4a_args.append("--ignore-setup-py")

        activity_class_name = self.buildozer.config.getdefault(
            'app', 'android.activity_class_name', 'org.kivy.android.PythonActivity')
        if activity_class_name != 'org.kivy.android.PythonActivity':
            self.extra_p4a_args.append(f"--activity-class-name={activity_class_name}")

        if self.buildozer.log_level >= 2:
            self.extra_p4a_args.append("--debug")

        user_extra_p4a_args = self.buildozer.config.getdefault('app', 'p4a.extra_args', "")
        self.extra_p4a_args.extend(shlex.split(user_extra_p4a_args))

        self.warn_on_deprecated_tokens()

    def warn_on_deprecated_tokens(self):
        for section, token in DEPRECATED_TOKENS:
            value = self.buildozer.config.getdefault(section, token, None)
            if value is not None:
                error = ('WARNING: Config token {} {} is deprecated and ignored, '
                         'but you set value {}').format(section, token, value)
                self.buildozer.error(error)

    def _p4a(self, cmd, **kwargs):
        kwargs.setdefault('cwd', self.p4a_dir)
        return self.buildozer.cmd([*self._p4a_cmd, *cmd, *self.extra_p4a_args], **kwargs)

    @property
    def p4a_dir(self):
        """The directory where python-for-android is/will be installed."""

        # Default p4a dir
        p4a_dir = join(self.buildozer.platform_dir, self.p4a_directory_name)

        # Possibly overridden by user setting
        system_p4a_dir = self.buildozer.config.getdefault('app', 'p4a.source_dir')
        if system_p4a_dir:
            p4a_dir = expanduser(system_p4a_dir)

        return p4a_dir

    @property
    def p4a_recommended_android_ndk(self):
        """
        Return the p4a's recommended android's NDK version, depending on the
        p4a version used for our buildozer build. In case that we don't find
        it, we will return the buildozer's recommended one, defined by global
        variable `DEFAULT_ANDROID_NDK_VERSION`.
        """
        # make sure to read p4a version only the first time
        if self.p4a_recommended_ndk_version is not None:
            return self.p4a_recommended_ndk_version

        # check p4a's recommendation file, and in case that exists find the
        # recommended android's NDK version, otherwise return buildozer's one
        ndk_version = DEFAULT_ANDROID_NDK_VERSION
        rec_file = join(self.p4a_dir, "pythonforandroid", "recommendations.py")
        if not os.path.isfile(rec_file):
            self.buildozer.error(MSG_P4A_RECOMMENDED_NDK_ERROR)
            return ndk_version

        for line in open(rec_file, "r"):
            if line.startswith("RECOMMENDED_NDK_VERSION ="):
                ndk_version = line.replace(
                    "RECOMMENDED_NDK_VERSION =", "")
                # clean version of unwanted characters
                for i in {"'", '"', "\n", " "}:
                    ndk_version = ndk_version.replace(i, "")
                self.buildozer.info(
                    "Recommended android's NDK version by p4a is: {}".format(
                        ndk_version
                    )
                )
                self.p4a_recommended_ndk_version = ndk_version
                break
        return ndk_version

    def _sdkmanager(self, *args, **kwargs):
        """Call the sdkmanager in our Android SDK with the given arguments."""
        # Use the android-sdk dir as cwd by default
        android_sdk_dir = self.android_sdk_dir
        kwargs['cwd'] = kwargs.get('cwd', android_sdk_dir)
        command = [self.sdkmanager_path, f"--sdk_root={android_sdk_dir}", *args]

        if kwargs.pop('return_child', False):
            return self.buildozer.cmd_expect(command, **kwargs)
        else:
            kwargs['get_stdout'] = kwargs.get('get_stdout', True)
            return self.buildozer.cmd(command, **kwargs)

    @property
    def android_ndk_version(self):
        return self.buildozer.config.getdefault('app', 'android.ndk',
                                                self.p4a_recommended_android_ndk)

    @property
    def android_api(self):
        return self.buildozer.config.getdefault('app', 'android.api',
                                                ANDROID_API)

    @property
    def android_minapi(self):
        return self.buildozer.config.getdefault('app', 'android.minapi',
                                                ANDROID_MINAPI)

    @property
    def android_sdk_dir(self):
        directory = expanduser(self.buildozer.config.getdefault(
            'app', 'android.sdk_path', ''))
        if directory:
            return realpath(directory)
        return join(self.buildozer.global_platform_dir,
                    'android-sdk')

    @property
    def android_ndk_dir(self):
        directory = expanduser(self.buildozer.config.getdefault(
            'app', 'android.ndk_path', ''))
        if directory:
            return realpath(directory)
        version = self.buildozer.config.getdefault('app', 'android.ndk',
                                                   self.android_ndk_version)
        return join(self.buildozer.global_platform_dir,
                    'android-ndk-r{0}'.format(version))

    @property
    def apache_ant_dir(self):
        directory = expanduser(self.buildozer.config.getdefault(
            'app', 'android.ant_path', ''))
        if directory:
            return realpath(directory)
        version = self.buildozer.config.getdefault('app', 'android.ant',
                                                   APACHE_ANT_VERSION)
        return join(self.buildozer.global_platform_dir,
                    'apache-ant-{0}'.format(version))

    @property
    def sdkmanager_path(self):
        sdkmanager_path = join(
            self.android_sdk_dir, 'tools', 'bin', 'sdkmanager')
        if not os.path.isfile(sdkmanager_path):
            raise BuildozerException(
                ('sdkmanager path "{}" does not exist, sdkmanager is not'
                 'installed'.format(sdkmanager_path)))
        return sdkmanager_path

    @property
    def archs_snake(self):
        return "_".join(self._archs)

    def check_requirements(self):
        if platform in ('win32', 'cygwin'):
            try:
                self._set_win32_java_home()
            except:
                traceback.print_exc()
            self.adb_executable = join(self.android_sdk_dir, 'platform-tools',
                                'adb.exe')
            self.javac_cmd = self._locate_java('javac.exe')
            self.keytool_cmd = self._locate_java('keytool.exe')
        # darwin, linux, freebsd
        else:
            self.adb_executable = join(self.android_sdk_dir, 'platform-tools', 'adb')
            self.javac_cmd = self._locate_java('javac')
            self.keytool_cmd = self._locate_java('keytool')

            # Check for C header <zlib.h>.
            is_debian_like = which("dpkg") is not None
            if is_debian_like and \
                    not self.buildozer.file_exists('/usr/include/zlib.h'):
                raise BuildozerException(
                    'zlib headers must be installed, '
                    'run: sudo apt-get install zlib1g-dev')

            # Override the OS which `sdkmanager` should download the packages for.
            # This enables download and use of Linux binaries on FreeBSD.
            if platform.startswith('freebsd'):
                os.environ['REPO_OS_OVERRIDE'] = 'linux'

        # Adb arguments:
        adb_args = self.buildozer.config.getdefault(
            "app", "android.adb_args", "")
        self.adb_args = shlex.split(adb_args)

        # Need to add internally installed ant to path for external tools
        # like adb to use
        path = [join(self.apache_ant_dir, 'bin')]
        if 'PATH' in self.buildozer.environ:
            path.append(self.buildozer.environ['PATH'])
        else:
            path.append(os.environ['PATH'])
        self.buildozer.environ['PATH'] = ':'.join(path)
        checkbin = self.buildozer.checkbin
        checkbin('Git (git)', 'git')
        checkbin('Cython (cython)', 'cython')
        checkbin('Java compiler (javac)', self.javac_cmd)
        checkbin('Java keytool (keytool)', self.keytool_cmd)

    def _p4a_have_aab_support(self):
        returncode = self._p4a(["aab", "-h"], break_on_error=False)[2]
        if returncode == 0:
            return True
        else:
            return False

    def _set_win32_java_home(self):
        if 'JAVA_HOME' in self.buildozer.environ:
            return
        import _winreg
        with _winreg.OpenKey(
                _winreg.HKEY_LOCAL_MACHINE,
                r"SOFTWARE\JavaSoft\Java Development Kit") as jdk:  # @UndefinedVariable
            current_version, _type = _winreg.QueryValueEx(
                jdk, "CurrentVersion")  # @UndefinedVariable
            with _winreg.OpenKey(jdk, current_version) as cv:  # @UndefinedVariable
                java_home, _type = _winreg.QueryValueEx(
                    cv, "JavaHome")  # @UndefinedVariable
            self.buildozer.environ['JAVA_HOME'] = java_home

    def _locate_java(self, s):
        '''If JAVA_HOME is in the environ, return $JAVA_HOME/bin/s. Otherwise,
        return s.
        '''
        if 'JAVA_HOME' in self.buildozer.environ:
            return join(self.buildozer.environ['JAVA_HOME'], 'bin', s)
        else:
            return s

    def _install_apache_ant(self):
        ant_dir = self.apache_ant_dir
        if self.buildozer.file_exists(ant_dir):
            self.buildozer.info('Apache ANT found at {0}'.format(ant_dir))
            return ant_dir

        if not os.path.exists(ant_dir):
            os.makedirs(ant_dir)

        self.buildozer.info('Android ANT is missing, downloading')
        archive = 'apache-ant-{0}-bin.tar.gz'.format(APACHE_ANT_VERSION)
        url = 'https://archive.apache.org/dist/ant/binaries/'
        self.buildozer.download(url,
                                archive,
                                cwd=ant_dir)
        self.buildozer.file_extract(archive,
                                    cwd=ant_dir)
        self.buildozer.info('Apache ANT installation done.')
        return ant_dir

    def _install_android_sdk(self):
        sdk_dir = self.android_sdk_dir
        if self.buildozer.file_exists(sdk_dir):
            self.buildozer.info('Android SDK found at {0}'.format(sdk_dir))
            return sdk_dir

        self.buildozer.info('Android SDK is missing, downloading')
        if platform in ('win32', 'cygwin'):
            archive = 'commandlinetools-win-{}_latest.zip'.format(DEFAULT_SDK_TAG)
        elif platform in ('darwin', ):
            archive = 'commandlinetools-mac-{}_latest.zip'.format(DEFAULT_SDK_TAG)
        elif platform.startswith('linux') or platform.startswith('freebsd'):
            archive = 'commandlinetools-linux-{}_latest.zip'.format(DEFAULT_SDK_TAG)
        else:
            raise SystemError('Unsupported platform: {0}'.format(platform))

        if not os.path.exists(sdk_dir):
            os.makedirs(sdk_dir)

        url = 'https://dl.google.com/android/repository/'
        self.buildozer.download(url,
                                archive,
                                cwd=sdk_dir)

        self.buildozer.info('Unpacking Android SDK')
        self.buildozer.file_extract(archive,
                                    cwd=sdk_dir)

        self.buildozer.info('Android SDK tools base installation done.')

        return sdk_dir

    def _install_android_ndk(self):
        ndk_dir = self.android_ndk_dir
        if self.buildozer.file_exists(ndk_dir):
            self.buildozer.info('Android NDK found at {0}'.format(ndk_dir))
            return ndk_dir

        import re
        _version = int(re.search(r'(\d+)', self.android_ndk_version).group(1))

        self.buildozer.info('Android NDK is missing, downloading')
        # Welcome to the NDK URL hell!
        # a list of all NDK URLs up to level 14 can be found here:
        #  https://gist.github.com/roscopecoltran/43861414fbf341adac3b6fa05e7fad08
        # it seems that from level 11 on the naming schema is consistent
        # from 10e on the URLs can be looked up at
        # https://developer.android.com/ndk/downloads/older_releases

        is_darwin = platform == 'darwin'
        is_linux = platform.startswith('linux')
        is_freebsd = platform.startswith('freebsd')

        if platform in ('win32', 'cygwin'):
            # Checking of 32/64 bits at Windows from: https://stackoverflow.com/a/1405971/798575
            import struct
            archive = 'android-ndk-r{0}-windows-{1}.zip'
            is_64 = (8 * struct.calcsize("P") == 64)
        elif is_darwin or is_linux or is_freebsd:
            _platform = 'linux' if (is_linux or is_freebsd) else 'darwin'
            if self.android_ndk_version in ['10c', '10d', '10e']:
                ext = 'bin'
            elif _version <= 10:
                ext = 'tar.bz2'
            else:
                ext = 'zip'
            archive = 'android-ndk-r{0}-' + _platform + '{1}.' + ext
            is_64 = ('64' in os.uname()[4])
        else:
            raise SystemError('Unsupported platform: {}'.format(platform))

        architecture = 'x86_64' if is_64 else 'x86'
        architecture = '' if _version >= 23 else f'-{architecture}'
        unpacked = 'android-ndk-r{0}'
        archive = archive.format(self.android_ndk_version, architecture)
        unpacked = unpacked.format(self.android_ndk_version)

        if _version >= 11:
            url = 'https://dl.google.com/android/repository/'
        else:
            url = 'https://dl.google.com/android/ndk/'

        self.buildozer.download(url,
                                archive,
                                cwd=self.buildozer.global_platform_dir)

        self.buildozer.info('Unpacking Android NDK')
        self.buildozer.file_extract(archive,
                                    cwd=self.buildozer.global_platform_dir)
        self.buildozer.file_rename(unpacked,
                                   ndk_dir,
                                   cwd=self.buildozer.global_platform_dir)
        self.buildozer.info('Android NDK installation done.')
        return ndk_dir

    def _android_list_build_tools_versions(self):
        available_packages = self._sdkmanager('--list')

        lines = available_packages[0].split('\n')

        build_tools_versions = []

        for line in lines:
            if not line.strip().startswith('build-tools;'):
                continue
            package_name = line.strip().split(' ')[0]
            assert package_name.count(';') == 1, (
                'could not parse package "{}"'.format(package_name))
            version = package_name.split(';')[1]

            build_tools_versions.append(parse(version))

        return build_tools_versions

    def _android_update_sdk(self, *sdkmanager_commands):
        """Update the tools and package-tools if possible"""
        auto_accept_license = self.buildozer.config.getbooldefault(
            'app', 'android.accept_sdk_license', False)

        kwargs = {}
        if auto_accept_license:
            kwargs["return_child"] = True
        else:
            kwargs['show_output'] = True

        ret_child = self._sdkmanager(*sdkmanager_commands, **kwargs)

        if auto_accept_license:
            while ret_child.isalive():
                pexp_match = ret_child.expect(
                    ["(y/N)", pexpect.EOF, pexpect.TIMEOUT], timeout=300
                )
                if pexp_match == 0:
                    ret_child.sendline("y")

    def _read_version_subdir(self, *args):
        versions = []
        if not os.path.exists(join(*args)):
            self.buildozer.debug('build-tools folder not found {}'.format(join(
                *args)))
            return parse("0")
        for v in os.listdir(join(*args)):
            try:
                versions.append(parse(v))
            except:
                pass
        if not versions:
            self.buildozer.error(
                'Unable to find the latest version for {}'.format(join(*args)))
            return parse("0")
        return max(versions)

    def _find_latest_package(self, packages, key):
        package_versions = []
        for p in packages:
            if not p.startswith(key):
                continue
            version_string = p.split(key)[-1]
            version = parse(version_string)
            package_versions.append(version)
        if not package_versions:
            return
        return max(package_versions)

    def _install_android_packages(self):

        # if any of theses value change into the buildozer.spec, retry the
        # update
        cache_key = 'android:sdk_installation'
        cache_value = [
            self.android_api, self.android_minapi, self.android_ndk_version,
            self.android_sdk_dir, self.android_ndk_dir
        ]
        if self.buildozer.state.get(cache_key, None) == cache_value:
            return True

        # 1. update the platform-tools package if needed

        skip_upd = self.buildozer.config.getbooldefault(
            'app', 'android.skip_update', False)

        if not skip_upd:
            self.buildozer.info('Installing/updating SDK platform tools if necessary')

            # just calling sdkmanager with the items will install them if necessary
            self._android_update_sdk('platform-tools')
            self._android_update_sdk('--update')
        else:
            self.buildozer.info('Skipping Android SDK update due to spec file setting')
            self.buildozer.info('Note: this also prevents installing missing '
                                'SDK components')

        # 2. install the latest build tool
        self.buildozer.info('Updating SDK build tools if necessary')
        installed_v_build_tools = self._read_version_subdir(self.android_sdk_dir,
                                                  'build-tools')
        available_v_build_tools = self._android_list_build_tools_versions()
        if not available_v_build_tools:
            self.buildozer.error('Did not find any build tools available to download')

        latest_v_build_tools = sorted(available_v_build_tools)[-1]
        if latest_v_build_tools > installed_v_build_tools:
            if not skip_upd:
                self._android_update_sdk(f"build-tools;{latest_v_build_tools}")
                installed_v_build_tools = latest_v_build_tools
            else:
                self.buildozer.info(
                    'Skipping update to build tools {} due to spec setting'.format(
                        latest_v_build_tools))

        # 2. check aidl can be run
        self._check_aidl(installed_v_build_tools)

        # 3. finally, install the android for the current api
        self.buildozer.info('Downloading platform api target if necessary')
        android_platform = join(self.android_sdk_dir, 'platforms', 'android-{}'.format(self.android_api))
        if not self.buildozer.file_exists(android_platform):
            if not skip_upd:
                self._sdkmanager(f"platforms;android-{self.android_api}")
            else:
                self.buildozer.info(
                    'Skipping install API {} platform tools due to spec setting'.format(
                        self.android_api))

        self.buildozer.info('Android packages installation done.')

        self.buildozer.state[cache_key] = cache_value
        self.buildozer.state.sync()

    def _check_aidl(self, v_build_tools):
        self.buildozer.debug('Check that aidl can be executed')
        v_build_tools = self._read_version_subdir(self.android_sdk_dir,
                                                  'build-tools')
        aidl_cmd = join(self.android_sdk_dir, 'build-tools',
                        str(v_build_tools), 'aidl')
        self.buildozer.checkbin('Aidl', aidl_cmd)
        _, _, returncode = self.buildozer.cmd(aidl_cmd,
                                              break_on_error=False,
                                              show_output=False)
        if returncode != 1:
            self.buildozer.error('Aidl cannot be executed')
            if architecture()[0] == '64bit':
                self.buildozer.error('')
                self.buildozer.error(
                    'You might have missed to install 32bits libs')
                self.buildozer.error(
                    'Check https://buildozer.readthedocs.org/en/latest/installation.html')
                self.buildozer.error('')
            else:
                self.buildozer.error('')
                self.buildozer.error(
                    'In case of a bug report, please add a full log with log_level = 2')
                self.buildozer.error('')
            raise BuildozerException()

    def install_platform(self):
        self._install_p4a()
        self._install_apache_ant()
        self._install_android_sdk()
        self._install_android_ndk()
        self._install_android_packages()

        # ultimate configuration check.
        # some of our configuration cannot be check without platform.
        self.check_configuration_tokens()
        if not self._p4a_have_aab_support():
            self.buildozer.error(
                "This buildozer version requires a python-for-android version with AAB (Android App Bundle) support. "
                "Please update your pinned version accordingly."
            )
            raise BuildozerException()

        self.buildozer.environ.update({
            'PACKAGES_PATH': self.buildozer.global_packages_dir,
            'ANDROIDSDK': self.android_sdk_dir,
            'ANDROIDNDK': self.android_ndk_dir,
            'ANDROIDAPI': self.android_api,
            'ANDROIDMINAPI': self.android_minapi,
        })

    def _install_p4a(self):
        cmd = self.buildozer.cmd
        p4a_fork = self.buildozer.config.getdefault(
            'app', 'p4a.fork', self.p4a_fork
        )
        p4a_url = self.buildozer.config.getdefault(
            'app', 'p4a.url', f'https://github.com/{p4a_fork}/python-for-android.git'
        )
        p4a_branch = self.buildozer.config.getdefault(
            'app', 'p4a.branch', self.p4a_branch
        )
        p4a_commit = self.buildozer.config.getdefault(
            'app', 'p4a.commit', self.p4a_commit
        )

        p4a_dir = self.p4a_dir
        system_p4a_dir = self.buildozer.config.getdefault('app',
                                                          'p4a.source_dir')
        if system_p4a_dir:
            # Don't install anything, just check that the dir does exist
            if not self.buildozer.file_exists(p4a_dir):
                self.buildozer.error(
                    'Path for p4a.source_dir does not exist')
                self.buildozer.error('')
                raise BuildozerException()
        else:
            # check that url/branch has not been changed
            if self.buildozer.file_exists(p4a_dir):
                cur_url = cmd(
                    ["git", "config", "--get", "remote.origin.url"],
                    get_stdout=True,
                    cwd=p4a_dir,
                )[0].strip()
                cur_branch = cmd(
                    ["git", "branch", "-vv"], get_stdout=True, cwd=p4a_dir
                )[0].split()[1]
                if any([cur_url != p4a_url, cur_branch != p4a_branch]):
                    self.buildozer.info(
                        f"Detected old url/branch ({cur_url}/{cur_branch}), deleting..."
                    )
                    rmtree(p4a_dir)

            if not self.buildozer.file_exists(p4a_dir):
                cmd(
                    [
                        "git",
                        "clone",
                        "-b",
                        p4a_branch,
                        "--single-branch",
                        p4a_url,
                        self.p4a_directory_name,
                    ],
                    cwd=self.buildozer.platform_dir,
                )
            elif self.platform_update:
                cmd(["git", "clean", "-dxf"], cwd=p4a_dir)
                current_branch = cmd(["git", "rev-parse", "--abbrev-ref", "HEAD"],
                                     get_stdout=True, cwd=p4a_dir)[0].strip()
                if current_branch == p4a_branch:
                    cmd(["git", "pull"], cwd=p4a_dir)
                else:
                    cmd(["git", "fetch", "--tags", "origin", "{0}:{0}".format(p4a_branch)],
                        cwd=p4a_dir)
                    cmd(["git", "checkout", p4a_branch], cwd=p4a_dir)
            if p4a_commit != 'HEAD':
                cmd(["git", "reset", "--hard", p4a_commit], cwd=p4a_dir)

        # also install dependencies (currently, only setup.py knows about it)
        # let's extract them.
        try:
            with open(join(self.p4a_dir, "setup.py")) as fd:
                setup = fd.read()
                deps = re.findall(r"^\s*install_reqs = (\[[^\]]*\])", setup, re.DOTALL | re.MULTILINE)[0]
                deps = ast.literal_eval(deps)
        except IOError:
            self.buildozer.error('Failed to read python-for-android setup.py at {}'.format(
                join(self.p4a_dir, 'setup.py')))
            sys.exit(1)

        # in virtualenv or conda env
        options = ["--user"]
        if "VIRTUAL_ENV" in os.environ or "CONDA_PREFIX" in os.environ:
            options = []
        cmd([executable, "-m", "pip", "install", "-q", *options, *deps])

    def compile_platform(self):
        app_requirements = self.buildozer.config.getlist(
            'app', 'requirements', '')
        dist_name = self.buildozer.config.get('app', 'package.name')
        local_recipes = self.get_local_recipes_dir()
        requirements = ','.join(app_requirements)
        options = []

        source_dirs = {
            'P4A_{}_DIR'.format(name[20:]): realpath(expanduser(value))
            for name, value in self.buildozer.config.items('app')
            if name.startswith('requirements.source.')
        }
        if source_dirs:
            self.buildozer.environ.update(source_dirs)
            self.buildozer.info('Using custom source dirs:\n    {}'.format(
                '\n    '.join(['{} = {}'.format(k, v)
                               for k, v in source_dirs.items()])))

        if self.buildozer.config.getbooldefault('app', 'android.copy_libs', True):
            options.append("--copy-libs")
        # support for recipes in a local directory within the project
        if local_recipes:
            options.append('--local-recipes')
            options.append(local_recipes)

        p4a_create = ["create", f"--dist_name={dist_name}", f"--bootstrap={self._p4a_bootstrap}", f"--requirements={requirements}"]

        for arch in self._archs:
            p4a_create.append(f"--arch={arch}")

        p4a_create.extend(options)

        self._p4a(p4a_create, get_stdout=True)[0]

    def get_available_packages(self):
        return True

    def get_dist_dir(self, dist_name):
        """Find the dist dir with the given name if one
        already exists, otherwise return a new dist_dir name.
        """

        # If the expected dist name does exist, simply use that
        expected_dist_dir = join(self._build_dir, 'dists', dist_name)
        if exists(expected_dist_dir):
            return expected_dist_dir

        # If no directory has been found yet, our dist probably
        # doesn't exist yet, so use the expected name
        return expected_dist_dir

    def get_local_recipes_dir(self):
        local_recipes = self.buildozer.config.getdefault('app', 'p4a.local_recipes')
        return realpath(expanduser(local_recipes)) if local_recipes else None

    def execute_build_package(self, build_cmd):
        # wrapper from previous old_toolchain to new toolchain
        dist_name = self.buildozer.config.get('app', 'package.name')
        local_recipes = self.get_local_recipes_dir()
        cmd = [self.artifact_format, "--bootstrap", self._p4a_bootstrap, "--dist_name", dist_name]
        for args in build_cmd:
            option, values = args[0], args[1:]
            if option == "debug":
                continue
            elif option == "release":
                cmd.append("--release")
                if self.check_p4a_sign_env(True):
                    cmd.append("--sign")
                continue
            if option == "--window":
                cmd.append("--window")
            elif option == "--sdk":
                cmd.append("--android_api")
                cmd.extend(values)
            else:
                cmd.extend(args)

        # support for presplash background color
        presplash_color = self.buildozer.config.getdefault('app', 'android.presplash_color', None)
        if presplash_color:
            cmd.append('--presplash-color')
            cmd.append("{}".format(presplash_color))

        # support for services
        services = self.buildozer.config.getlist('app', 'services', [])
        for service in services:
            cmd.append("--service")
            cmd.append(service)

        # support for copy-libs
        if self.buildozer.config.getbooldefault('app', 'android.copy_libs', True):
            cmd.append("--copy-libs")

        # support for recipes in a local directory within the project
        if local_recipes:
            cmd.append('--local-recipes')
            cmd.append(local_recipes)

        # support for blacklist/whitelist filename
        whitelist_src = self.buildozer.config.getdefault('app', 'android.whitelist_src', None)
        blacklist_src = self.buildozer.config.getdefault('app', 'android.blacklist_src', None)
        if whitelist_src:
            cmd.append('--whitelist')
            cmd.append(realpath(expanduser(whitelist_src)))
        if blacklist_src:
            cmd.append('--blacklist')
            cmd.append(realpath(expanduser(blacklist_src)))

        # support for java directory
        javadirs = self.buildozer.config.getlist('app', 'android.add_src', [])
        for javadir in javadirs:
            cmd.append('--add-source')
            cmd.append(realpath(expanduser(javadir)))

        # support for aars
        aars = self.buildozer.config.getlist('app', 'android.add_aars', [])
        for aar in aars:
            cmd.append('--add-aar')
            cmd.append(realpath(expanduser(aar)))

        # support for assets folder
        assets = self.buildozer.config.getlist('app', 'android.add_assets', [])
        for asset in assets:
            cmd.append('--add-asset')
            if ':' in asset:
                asset_src, asset_dest = asset.split(":")
            else:
                asset_src = asset
                asset_dest = asset
            cmd.append(realpath(expanduser(asset_src)) + ':' + asset_dest)

        # support for res folder
        resources = self.buildozer.config.getlist('app', 'android.add_resources', [])
        for resource in resources:
            cmd.append('--add-resource')
            if ':' in resource:
                resource_src, resource_dest = resource.split(":")
            else:
                resource_src = resource
                resource_dest = ""
            cmd.append(realpath(expanduser(resource_src)) + ':' + resource_dest)

        # support for uses-lib
        uses_library = self.buildozer.config.getlist(
            'app', 'android.uses_library', '')
        for lib in uses_library:
            cmd.append('--uses-library={}'.format(lib))

        # support for activity-class-name
        activity_class_name = self.buildozer.config.getdefault(
            'app', 'android.activity_class_name', 'org.kivy.android.PythonActivity')
        if activity_class_name != 'org.kivy.android.PythonActivity':
            cmd.append('--activity-class-name={}'.format(activity_class_name))

        # support for service-class-name
        service_class_name = self.buildozer.config.getdefault(
            'app', 'android.service_class_name', 'org.kivy.android.PythonService')
        if service_class_name != 'org.kivy.android.PythonService':
            cmd.append('--service-class-name={}'.format(service_class_name))

        # support for extra-manifest-xml
        extra_manifest_xml = self.buildozer.config.getdefault(
            'app', 'android.extra_manifest_xml', '')
        if extra_manifest_xml:
            cmd.append('--extra-manifest-xml="{}"'.format(open(extra_manifest_xml, 'rt').read()))

        # support for extra-manifest-application-arguments
        extra_manifest_application_arguments = self.buildozer.config.getdefault(
            'app', 'android.extra_manifest_application_arguments', '')
        if extra_manifest_application_arguments:
            args_body = open(extra_manifest_application_arguments, 'rt').read().replace('"', '\\"').replace('\n', ' ').replace('\t', ' ')
            cmd.append('--extra-manifest-application-arguments="{}"'.format(args_body))

        # support for gradle dependencies
        gradle_dependencies = self.buildozer.config.getlist('app', 'android.gradle_dependencies', [])
        for gradle_dependency in gradle_dependencies:
            cmd.append('--depend')
            cmd.append(gradle_dependency)

        # support for manifestPlaceholders
        manifest_placeholders = self.buildozer.config.getdefault('app', 'android.manifest_placeholders', None)
        if manifest_placeholders:
            cmd.append('--manifest-placeholders')
            cmd.append("{}".format(manifest_placeholders))

        # support disabling of byte compile for .py files
        no_byte_compile = self.buildozer.config.getdefault('app', 'android.no-byte-compile-python', False)
        if no_byte_compile:
            cmd.append('--no-byte-compile-python')

        for arch in self._archs:
            cmd.append('--arch')
            cmd.append(arch)

        self._p4a(cmd)

    def get_release_mode(self):
        # aab, also if unsigned is named as *-release
        if self.check_p4a_sign_env() or self.artifact_format in ["aab", "aar"]:
            return "release"
        return "release-unsigned"

    def check_p4a_sign_env(self, error=False):
        keys = ["KEYALIAS", "KEYSTORE_PASSWD", "KEYSTORE", "KEYALIAS_PASSWD"]
        check = True
        for key in keys:
            key = "P4A_RELEASE_{}".format(key)
            if key not in os.environ:
                if error:
                    self.buildozer.error(
                        ("Asking for release but {} is missing"
                         "--sign will not be passed").format(key))
                check = False
        return check

    def cmd_run(self, *args):
        entrypoint = self.buildozer.config.getdefault(
            'app', 'android.entrypoint')
        if not entrypoint:
            self.buildozer.config.set('app', 'android.entrypoint', 'org.kivy.android.PythonActivity')

        super().cmd_run(*args)

        entrypoint = self.buildozer.config.getdefault(
            'app', 'android.entrypoint', 'org.kivy.android.PythonActivity')

        package = self._get_package()

        # push on the device
        for serial in self.serials:
            self.buildozer.environ['ANDROID_SERIAL'] = serial
            self.buildozer.info('Run on {}'.format(serial))
            self.buildozer.cmd(
                [
                    self.adb_executable,
                    *self.adb_args,
                    "shell",
                    "am",
                    "start",
                    "-n",
                    f"{package}/{entrypoint}",
                    "-a",
                    entrypoint,
                ],
                cwd=self.buildozer.global_platform_dir,
            )
        self.buildozer.environ.pop('ANDROID_SERIAL', None)

        while True:
            if self._get_pid():
                break
            sleep(.1)
            self.buildozer.info('Waiting for application to start.')

        self.buildozer.info('Application started.')

    def cmd_p4a(self, *args):
        '''
        Run p4a commands. Args must come after --, or
        use --alias to make an alias
        '''
        self.check_requirements()
        self.install_platform()
        args = args[0]
        if args and args[0] == '--alias':
            print('To set up p4a in this shell session, execute:')
            print('    alias p4a=$(buildozer {} p4a --alias 2>&1 >/dev/null)'
                  .format(self.targetname))
            sys.stderr.write('PYTHONPATH={} {}\n'.format(self.p4a_dir, self._p4a_cmd))
        else:
            self._p4a(args)

    def cmd_clean(self, *args):
        '''
        Clean the build and distribution
        '''
        self._p4a(["clean_builds"])
        self._p4a(["clean_dists"])

    def _get_package(self):
        config = self.buildozer.config
        package_domain = config.getdefault('app', 'package.domain', '')
        package = config.get('app', 'package.name')
        if package_domain:
            package = package_domain + '.' + package
        return package.lower()

    def _generate_whitelist(self, dist_dir):
        p4a_whitelist = self.buildozer.config.getlist(
            'app', 'android.whitelist') or []
        whitelist_fn = join(dist_dir, 'whitelist.txt')
        with open(whitelist_fn, 'w') as fd:
            for wl in p4a_whitelist:
                fd.write(wl + '\n')

    def build_package(self):
        dist_name = self.buildozer.config.get('app', 'package.name')
        dist_dir = self.get_dist_dir(dist_name)
        config = self.buildozer.config
        package = self._get_package()
        version = self.buildozer.get_version()

        # add extra libs/armeabi files in dist/default/libs/armeabi
        # (same for armeabi-v7a, arm64-v8a, x86, mips)
        for config_key, lib_dir in (
                ('android.add_libs_armeabi', 'armeabi'),
                ('android.add_libs_armeabi_v7a', 'armeabi-v7a'),
                ('android.add_libs_arm64_v8a', 'arm64-v8a'),
                ('android.add_libs_x86', 'x86'),
                ('android.add_libs_mips', 'mips')):

            patterns = config.getlist('app', config_key, [])
            if not patterns:
                continue
            if lib_dir not in self._archs:
                continue

            self.buildozer.debug('Search and copy libs for {}'.format(lib_dir))
            for fn in self.buildozer.file_matches(patterns):
                self.buildozer.file_copy(
                    join(self.buildozer.root_dir, fn),
                    join(dist_dir, 'libs', lib_dir, basename(fn)))

        # update the project.properties libraries references
        self._update_libraries_references(dist_dir)

        # generate the whitelist if needed
        self._generate_whitelist(dist_dir)

        # build the app
        build_cmd = [
            ("--name", config.get('app', 'title')),
            ("--version", version),
            ("--package", package),
            ("--minsdk", config.getdefault('app', 'android.minapi',
                                           self.android_minapi)),
            ("--ndk-api", config.getdefault('app', 'android.minapi',
                                            self.android_minapi)),
        ]
        is_private_storage = config.getbooldefault(
            'app', 'android.private_storage', True)
        if is_private_storage:
            build_cmd += [("--private", self.buildozer.app_dir)]
        else:
            build_cmd += [("--dir", self.buildozer.app_dir)]

        # add permissions
        permissions = config.getlist('app', 'android.permissions', [])
        for permission in permissions:
            build_cmd += [("--permission", permission)]

        # add features
        features = config.getlist('app', 'android.features', [])
        for feature in features:
            build_cmd += [("--feature", feature)]

        # add res_xml
        xmlfiles = config.getlist('app', 'android.res_xml', [])
        for xmlfile in xmlfiles:
            build_cmd += [("--res_xml", join(self.buildozer.root_dir,
                                                    xmlfile))]

        # android.entrypoint
        entrypoint = config.getdefault('app', 'android.entrypoint', 'org.kivy.android.PythonActivity')
        build_cmd += [('--android-entrypoint', entrypoint)]

        # android.apptheme
        apptheme = config.getdefault('app', 'android.apptheme', '@android:style/Theme.NoTitleBar')
        build_cmd += [('--android-apptheme', apptheme)]

        # android.compile_options
        compile_options = config.getlist('app', 'android.add_compile_options', [])
        for option in compile_options:
            build_cmd += [('--add-compile-option', option)]

        # android.add_gradle_repositories
        repos = config.getlist('app', 'android.add_gradle_repositories', [])
        for repo in repos:
            build_cmd += [('--add-gradle-repository', repo)]

        # android packaging options
        pkgoptions = config.getlist('app', 'android.add_packaging_options', [])
        for pkgoption in pkgoptions:
            build_cmd += [('--add-packaging-option', pkgoption)]

        # meta-data
        meta_datas = config.getlistvalues('app', 'android.meta_data', [])
        for meta in meta_datas:
            key, value = meta.split('=', 1)
            meta = '{}={}'.format(key.strip(), value.strip())
            build_cmd += [("--meta-data", meta)]

        # add extra Java jar files
        add_jars = config.getlist('app', 'android.add_jars', [])
        for pattern in add_jars:
            pattern = join(self.buildozer.root_dir, pattern)
            matches = glob(expanduser(pattern.strip()))
            if matches:
                for jar in matches:
                    build_cmd += [("--add-jar", jar)]
            else:
                raise SystemError('Failed to find jar file: {}'.format(
                    pattern))

        # add Java activity
        add_activities = config.getlist('app', 'android.add_activities', [])
        for activity in add_activities:
            build_cmd += [("--add-activity", activity)]

        # add presplash, lottie animation or static
        presplash = config.getdefault('app', 'android.presplash_lottie', '')
        if presplash:
            build_cmd += [("--presplash-lottie", join(self.buildozer.root_dir,
                                                      presplash))]
        else:
            presplash = config.getdefault('app', 'presplash.filename', '')
            if presplash:
                build_cmd += [("--presplash", join(self.buildozer.root_dir,
                                                   presplash))]

        # add icon
        icon = config.getdefault('app', 'icon.filename', '')
        if icon:
            build_cmd += [("--icon", join(self.buildozer.root_dir, icon))]
        icon_fg = config.getdefault('app', 'icon.adaptive_foreground.filename', '')
        icon_bg = config.getdefault('app', 'icon.adaptive_background.filename', '')
        if icon_fg and icon_bg:
            build_cmd += [("--icon-fg", join(self.buildozer.root_dir, icon_fg))]
            build_cmd += [("--icon-bg", join(self.buildozer.root_dir, icon_bg))]

        # OUYA Console support
        ouya_category = config.getdefault('app', 'android.ouya.category',
                                          '').upper()
        if ouya_category:
            if ouya_category not in ('GAME', 'APP'):
                raise SystemError(
                    'Invalid android.ouya.category: "{}" must be one of GAME or APP'.format(
                        ouya_category))
            # add icon
            ouya_icon = config.getdefault('app', 'android.ouya.icon.filename',
                                          '')
            build_cmd += [("--ouya-category", ouya_category)]
            build_cmd += [("--ouya-icon", join(self.buildozer.root_dir,
                                               ouya_icon))]

        if config.getdefault('app', 'p4a.bootstrap', 'sdl2') != 'service_only':
            # add orientation
            orientation = config.getlist('app', 'orientation', ['landscape'])
            for orient in orientation:
                build_cmd += [("--orientation", orient)]

            # fullscreen ?
            fullscreen = config.getbooldefault('app', 'fullscreen', True)
            if not fullscreen:
                build_cmd += [("--window", )]

        # wakelock ?
        wakelock = config.getbooldefault('app', 'android.wakelock', False)
        if wakelock:
            build_cmd += [("--wakelock", )]

        # AndroidX ?
        enable_androidx = config.getbooldefault('app',
                                                'android.enable_androidx',
                                                self.android_api > "28")
        if enable_androidx:
            build_cmd += [("--enable-androidx", )]

        # intent filters
        intent_filters = config.getdefault(
            'app', 'android.manifest.intent_filters', '')
        if intent_filters:
            build_cmd += [("--intent-filters", join(self.buildozer.root_dir,
                                                    intent_filters))]

        # activity launch mode
        launch_mode = config.getdefault(
            'app', 'android.manifest.launch_mode', '')
        if launch_mode:
            build_cmd += [("--activity-launch-mode", launch_mode)]

        # screenOrientation
        manifest_orientation = config.getdefault(
            'app', 'android.manifest.orientation', '')
        if manifest_orientation:
            build_cmd += [("--manifest-orientation", manifest_orientation)]

        # numeric version
        numeric_version = config.getdefault('app', 'android.numeric_version')
        if numeric_version:
            build_cmd += [("--numeric-version", numeric_version)]

        # android.allow_backup
        allow_backup = config.getbooldefault('app', 'android.allow_backup', True)
        if not allow_backup:
            build_cmd += [('--allow-backup', 'false')]

        # android.backup_rules
        backup_rules = config.getdefault('app', 'android.backup_rules', '')
        if backup_rules:
            build_cmd += [("--backup-rules", join(self.buildozer.root_dir,
                                                  backup_rules))]

        # build only in debug right now.
        if self.build_mode == 'debug':
            build_cmd += [("debug", )]
            mode = 'debug'
            mode_sign = mode
        else:
            build_cmd += [("release", )]
            mode_sign = "release"
            mode = self.get_release_mode()

        self.execute_build_package(build_cmd)

        try:
            self.buildozer.hook("android_pre_build_apk")
            self.execute_build_package(build_cmd)
            self.buildozer.hook("android_post_build_apk")
        except:
            # maybe the hook fail because the apk is not
            pass

        build_tools_versions = os.listdir(join(self.android_sdk_dir, "build-tools"))
        build_tools_versions = sorted(build_tools_versions, key=LooseVersion)
        build_tools_version = build_tools_versions[-1]
        gradle_files = ["build.gradle", "gradle", "gradlew"]
        is_gradle_build = build_tools_version >= "25.0" and any(
            (exists(join(dist_dir, x)) for x in gradle_files))
        packagename = config.get('app', 'package.name')

        if is_gradle_build:
            # on gradle build, the apk use the package name, and have no version
            packagename_src = basename(dist_dir)  # gradle specifically uses the folder name
            artifact = u'{packagename}-{mode}.{artifact_format}'.format(
                packagename=packagename_src, mode=mode, artifact_format=self.artifact_format)
            if self.artifact_format == "apk":
                artifact_dir = join(dist_dir, "build", "outputs", "apk", mode_sign)
            elif self.artifact_format == "aab":
                artifact_dir = join(dist_dir, "build", "outputs", "bundle", mode_sign)
            elif self.artifact_format == "aar":
                artifact_dir = join(dist_dir, "build", "outputs", "aar")

        else:
            # on ant, the apk use the title, and have version
            bl = u'\'" ,'
            apptitle = config.get('app', 'title')
            if hasattr(apptitle, 'decode'):
                apptitle = apptitle.decode('utf-8')
            apktitle = ''.join([x for x in apptitle if x not in bl])
            artifact = u'{title}-{version}-{mode}.apk'.format(
                title=apktitle,
                version=version,
                mode=mode)
            artifact_dir = join(dist_dir, "bin")

        artifact_dest = u'{packagename}-{version}-{arch}-{mode}.{artifact_format}'.format(
            packagename=packagename, mode=mode, version=version,
            arch=self.archs_snake, artifact_format=self.artifact_format)

        # copy to our place
        copyfile(join(artifact_dir, artifact), join(self.buildozer.bin_dir, artifact_dest))

        self.buildozer.info('Android packaging done!')
        self.buildozer.info(
            u'APK {0} available in the bin directory'.format(artifact_dest))
        self.buildozer.state['android:latestapk'] = artifact_dest
        self.buildozer.state['android:latestmode'] = self.build_mode

    def _update_libraries_references(self, dist_dir):
        # ensure the project.properties exist
        project_fn = join(dist_dir, 'project.properties')

        if not self.buildozer.file_exists(project_fn):
            content = [
                'target=android-{}\n'.format(self.android_api),
                'APP_PLATFORM={}\n'.format(self.android_minapi)]
        else:
            with io.open(project_fn, encoding='utf-8') as fd:
                content = fd.readlines()

        # extract library reference
        references = []
        for line in content[:]:
            if not line.startswith('android.library.reference.'):
                continue
            content.remove(line)

        # convert our references to relative path
        app_references = self.buildozer.config.getlist(
            'app', 'android.library_references', [])
        source_dir = realpath(expanduser(self.buildozer.config.getdefault(
            'app', 'source.dir', '.')))
        for cref in app_references:
            # get the full path of the current reference
            ref = realpath(join(source_dir, cref))
            if not self.buildozer.file_exists(ref):
                self.buildozer.error(
                    'Invalid library reference (path not found): {}'.format(
                        cref))
                sys.exit(1)
            # get a relative path from the project file
            ref = relpath(ref, realpath(expanduser(dist_dir)))
            # ensure the reference exists
            references.append(ref)

        # recreate the project.properties
        with io.open(project_fn, 'w', encoding='utf-8') as fd:

            try:
                fd.writelines((line.decode('utf-8') for line in content))
            except:
                fd.writelines(content)
            if content and not content[-1].endswith(u'\n'):
                fd.write(u'\n')
            for index, ref in enumerate(references):
                fd.write(u'android.library.reference.{}={}\n'.format(index + 1, ref))

        self.buildozer.debug('project.properties updated')

    @property
    def serials(self):
        if hasattr(self, '_serials'):
            return self._serials
        serial = environ.get('ANDROID_SERIAL')
        if serial:
            return serial.split(',')
        lines = self.buildozer.cmd(
            [self.adb_executable, *self.adb_args, "devices"], get_stdout=True
        )[0].splitlines()
        serials = []
        for serial in lines:
            if not serial:
                continue
            if serial.startswith('*') or serial.startswith('List '):
                continue
            serials.append(serial.split()[0])
        self._serials = serials
        return serials

    def cmd_adb(self, *args):
        '''
        Run adb from the Android SDK.
        Args must come after --, or use
        --alias to make an alias
        '''
        self.check_requirements()
        self.install_platform()
        args = args[0]
        if args and args[0] == '--alias':
            print('To set up ADB in this shell session, execute:')
            print('    alias adb=$(buildozer {} adb --alias 2>&1 >/dev/null)'
                  .format(self.targetname))
            sys.stderr.write(self.adb_executable + '\n')
        else:
            self.buildozer.cmd([self.adb_executable, *self.adb_args, *args])

    def cmd_deploy(self, *args):
        super().cmd_deploy(*args)
        state = self.buildozer.state
        if 'android:latestapk' not in state:
            self.buildozer.error('No APK built yet. Run "debug" first.')

        if state.get('android:latestmode', '') != 'debug':
            self.buildozer.error('Only debug APK are supported for deploy')
            return

        # search the APK in the bin dir
        apk = state['android:latestapk']
        full_apk = join(self.buildozer.bin_dir, apk)
        if not self.buildozer.file_exists(full_apk):
            self.buildozer.error(
                'Unable to found the latest APK. Please run "debug" again.')

        # push on the device
        for serial in self.serials:
            self.buildozer.environ['ANDROID_SERIAL'] = serial
            self.buildozer.info('Deploy on {}'.format(serial))
            self.buildozer.cmd(
                [self.adb_executable, *self.adb_args, "install", "-r", full_apk],
                cwd=self.buildozer.global_platform_dir,
            )
        self.buildozer.environ.pop('ANDROID_SERIAL', None)

        self.buildozer.info('Application pushed.')

    def _get_pid(self):
        pid, *_ = self.buildozer.cmd(
            [
                self.adb_executable,
                *self.adb_args,
                "shell",
                "pidof",
                self._get_package(),
            ],
            get_stdout=True,
            show_output=False,
            break_on_error=False,
            quiet=True,
        )
        if pid:
            return pid.strip()
        return False

    def cmd_logcat(self, *args):
        '''Show the log from the device
        '''
        self.check_requirements()
        serial = self.serials[0:]
        if not serial:
            return
        filters = self.buildozer.config.getrawdefault(
            "app", "android.logcat_filters", "", section_sep=":", split_char=" ")
        filters = " ".join(filters)
        self.buildozer.environ['ANDROID_SERIAL'] = serial[0]
        extra_args = []
        pid = None
        if self.buildozer.config.getdefault('app', 'android.logcat_pid_only'):
            pid = self._get_pid()
            if pid:
                extra_args.extend(('--pid', pid))

        self.buildozer.cmd(
            [self.adb_executable, *self.adb_args, "logcat", filters, *extra_args],
            cwd=self.buildozer.global_platform_dir,
            show_output=True,
            run_condition=self._get_pid if pid else None,
            break_on_error=False,
        )

        self.buildozer.info(f"{self._get_package()} terminated")

        self.buildozer.environ.pop('ANDROID_SERIAL', None)


def get_target(buildozer):
    buildozer.targetname = "android"
    return TargetAndroid(buildozer)
