Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Content providers #1119

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions buildozer/default.spec
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,16 @@ fullscreen = 0
# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =

# (str) XML file to include at the end of the <application> tag
#android.manifest.content_providers =

# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard

# (list) XML files to add to the src/main/res/xml subdirectory of the build
# such files are usually referenced in the manifest, e.g. to specifiy file paths for a content provider
#android.add_xml_resources =

# (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
Expand Down
14 changes: 14 additions & 0 deletions buildozer/targets/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,20 @@ def build_package(self):
build_cmd += [("--intent-filters", join(self.buildozer.root_dir,
intent_filters))]

# content providers
content_providers = config.getdefault(
'app', 'android.manifest.content_providers', '')
if content_providers:
build_cmd += [('--content-providers', join(self.buildozer.root_dir,
content_providers))]

# extra xml resources
add_xml_resources = config.getlist(
'app', 'android.add_xml_resources', [])
for xml_resource in add_xml_resources:
build_cmd += [('--add-xml-resource', join(self.buildozer.root_dir,
xml_resource))]

# activity launch mode
launch_mode = config.getdefault(
'app', 'android.manifest.launch_mode', '')
Expand Down
244 changes: 204 additions & 40 deletions tests/targets/test_android.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import os
import sys
import pytest
import codecs
import tempfile
from unittest import mock

import pytest

import buildozer as buildozer_module
from buildozer import Buildozer
from buildozer.targets.android import TargetAndroid
from unittest import mock


def patch_buildozer(method):
Expand Down Expand Up @@ -52,25 +52,110 @@ def patch_platform(platform):


class TestTargetAndroid:

@staticmethod
def default_specfile_path():
return os.path.join(os.path.dirname(buildozer_module.__file__), "default.spec")

def setup_method(self):
"""Creates a temporary spec file containing the content of the default.spec."""
self.specfile = tempfile.NamedTemporaryFile(suffix=".spec", delete=False)
default_spec = codecs.open(self.default_specfile_path(), encoding="utf-8")
self.specfile.write(default_spec.read().encode("utf-8"))
self.specfile.close()
self.buildozer = Buildozer(filename=self.specfile.name, target="android")
self.target_android = TargetAndroid(self.buildozer)
"""
Create a temporary directory that will contain the spec file and will
serve as the root_dir.
"""
self.temp_dir = tempfile.TemporaryDirectory()

def tear_method(self):
"""Deletes the temporary spec file."""
os.unlink(self.specfile.name)
"""
Remove the temporary directory created in self.setup_method.
"""
self.temp_dir.cleanup()

def init_target(self, options=None):
"""
Create a buildozer.spec file in the temporary directory and init the
Buildozer and TargetAndroid instances.

The optional argument can be used to overwrite the config options in
the buildozer.spec file, e.g.:

self.init_target({'title': 'Test App'})

will replace line 4 of the default spec file.
"""
if options is None:
options = {}

spec_path = os.path.join(self.temp_dir.name, 'buildozer.spec')

with open(TestTargetAndroid.default_specfile_path()) as f:
default_spec = f.readlines()

spec = []
for line in default_spec:
if line.strip():
key = line.split()[0]

if key.startswith('#'):
key = key[1:]

if key in options:
line = '{} = {}\n'.format(key, options[key])

spec.append(line)

with open(spec_path, 'w') as f:
f.writelines(spec)

self.buildozer = Buildozer(filename=spec_path, target='android')
self.target_android = TargetAndroid(self.buildozer)

def call_build_package(self):
"""
Call the build_package() method of the tested TargetAndroid instance,
patching the functions that would otherwise produce side-effects.

Return the mocked execute_build_package() method of the TargetAndroid
instance so that tests can easily check which command-line arguments
would be passed on to python-for-android's toolchain.
"""
expected_dist_dir = (
'{buildozer_dir}/android/platform/build-armeabi-v7a/dists/myapp__armeabi-v7a'.format(
buildozer_dir=self.buildozer.buildozer_dir)
)

with patch_target_android(
'_update_libraries_references'
) as m_update_libraries_references, patch_target_android(
'_generate_whitelist'
) as m_generate_whitelist, mock.patch(
'buildozer.targets.android.TargetAndroid.execute_build_package'
) as m_execute_build_package, mock.patch(
'buildozer.targets.android.copyfile'
) as m_copyfile, mock.patch(
'buildozer.targets.android.os.listdir'
) as m_listdir:
m_listdir.return_value = ['30.0.0-rc2']
self.target_android.build_package()

assert m_listdir.call_count == 1
assert m_update_libraries_references.call_args_list == [
mock.call(expected_dist_dir)
]
assert m_generate_whitelist.call_args_list == [mock.call(expected_dist_dir)]
assert m_copyfile.call_args_list == [
mock.call(
'{expected_dist_dir}/bin/MyApplication-0.1-debug.apk'.format(
expected_dist_dir=expected_dist_dir
),
'{bin_dir}/myapp-0.1-armeabi-v7a-debug.apk'.format(bin_dir=self.buildozer.bin_dir),
)
]

return m_execute_build_package

def test_init(self):
"""Tests init defaults."""
self.init_target()
assert self.target_android._arch == "armeabi-v7a"
assert self.target_android._build_dir.endswith(
".buildozer/android/platform/build-armeabi-v7a"
Expand Down Expand Up @@ -101,6 +186,7 @@ def test_init_positional_buildozer(self):

def test_sdkmanager(self):
"""Tests the _sdkmanager() method."""
self.init_target()
kwargs = {}
with patch_buildozer_cmd() as m_cmd, patch_buildozer_cmd_expect() as m_cmd_expect, patch_os_isfile() as m_isfile:
m_isfile.return_value = True
Expand All @@ -120,6 +206,7 @@ def test_sdkmanager(self):

def test_check_requirements(self):
"""Basic tests for the check_requirements() method."""
self.init_target()
assert not hasattr(self.target_android, "adb_cmd")
assert not hasattr(self.target_android, "javac_cmd")
assert "PATH" not in self.buildozer.environ
Expand All @@ -140,6 +227,7 @@ def test_check_requirements(self):

def test_check_configuration_tokens(self):
"""Basic tests for the check_configuration_tokens() method."""
self.init_target()
with mock.patch(
"buildozer.targets.android.Target.check_configuration_tokens"
) as m_check_configuration_tokens:
Expand All @@ -149,6 +237,7 @@ def test_check_configuration_tokens(self):
@pytest.mark.parametrize("platform", ["linux", "darwin"])
def test_install_android_sdk(self, platform):
"""Basic tests for the _install_android_sdk() method."""
self.init_target()
with patch_buildozer_file_exists() as m_file_exists, patch_buildozer_download() as m_download:
m_file_exists.return_value = True
sdk_dir = self.target_android._install_android_sdk()
Expand Down Expand Up @@ -179,28 +268,8 @@ def test_install_android_sdk(self, platform):

def test_build_package(self):
"""Basic tests for the build_package() method."""
expected_dist_dir = (
"{buildozer_dir}/android/platform/build-armeabi-v7a/dists/myapp__armeabi-v7a".format(
buildozer_dir=self.buildozer.buildozer_dir)
)
with patch_target_android(
"_update_libraries_references"
) as m_update_libraries_references, patch_target_android(
"_generate_whitelist"
) as m_generate_whitelist, mock.patch(
"buildozer.targets.android.TargetAndroid.execute_build_package"
) as m_execute_build_package, mock.patch(
"buildozer.targets.android.copyfile"
) as m_copyfile, mock.patch(
"buildozer.targets.android.os.listdir"
) as m_listdir:
m_listdir.return_value = ["30.0.0-rc2"]
self.target_android.build_package()
assert m_listdir.call_count == 1
assert m_update_libraries_references.call_args_list == [
mock.call(expected_dist_dir)
]
assert m_generate_whitelist.call_args_list == [mock.call(expected_dist_dir)]
self.init_target()
m_execute_build_package = self.call_build_package()
assert m_execute_build_package.call_args_list == [
mock.call(
[
Expand All @@ -218,11 +287,106 @@ def test_build_package(self):
]
)
]
assert m_copyfile.call_args_list == [

def test_build_package_intent_filters(self):
"""
The build_package() method should honour the manifest.intent_filters
config option.
"""
filters_path = os.path.join(self.temp_dir.name, 'filters.xml')

with open(filters_path, 'w') as f:
f.write('<?xml version="1.0" encoding="utf-8"?>')

self.init_target({
'android.manifest.intent_filters': 'filters.xml'
})

m_execute_build_package = self.call_build_package()

assert m_execute_build_package.call_args_list == [
mock.call(
"{expected_dist_dir}/bin/MyApplication-0.1-debug.apk".format(
expected_dist_dir=expected_dist_dir
),
"{bin_dir}/myapp-0.1-armeabi-v7a-debug.apk".format(bin_dir=self.buildozer.bin_dir),
[
('--name', "'My Application'"),
('--version', '0.1'),
('--package', 'org.test.myapp'),
('--minsdk', '21'),
('--ndk-api', '21'),
('--private', '{buildozer_dir}/android/app'.format(buildozer_dir=self.buildozer.buildozer_dir)),
('--android-entrypoint', 'org.kivy.android.PythonActivity'),
('--android-apptheme', '@android:style/Theme.NoTitleBar'),
('--orientation', 'portrait'),
('--window',),
('--intent-filters', os.path.realpath(filters_path)),
('debug',),
]
)
]

def test_build_package_content_providers(self):
"""
The build_package() method should honour the manifest.content_providers
config option.
"""
providers_path = os.path.join(self.temp_dir.name, 'providers.xml')

with open(providers_path, 'w') as f:
f.write('<?xml version="1.0" encoding="utf-8"?>')

self.init_target({
'android.manifest.content_providers': 'providers.xml'
})

m_execute_build_package = self.call_build_package()

assert m_execute_build_package.call_args_list == [
mock.call(
[
('--name', "'My Application'"),
('--version', '0.1'),
('--package', 'org.test.myapp'),
('--minsdk', '21'),
('--ndk-api', '21'),
('--private', '{buildozer_dir}/android/app'.format(buildozer_dir=self.buildozer.buildozer_dir)),
('--android-entrypoint', 'org.kivy.android.PythonActivity'),
('--android-apptheme', '@android:style/Theme.NoTitleBar'),
('--orientation', 'portrait'),
('--window',),
('--content-providers', os.path.realpath(providers_path)),
('debug',),
]
)
]

def test_build_package_add_xml_resources(self):
"""
The build_package() method should honour the add_xml_resources config
option.
"""
xml_file_path = os.path.join(self.temp_dir.name, 'file_paths.xml')

with open(xml_file_path, 'w') as f:
f.write('<?xml version="1.0" encoding="utf-8"?>')

self.init_target({'android.add_xml_resources': 'file_paths.xml'})

m_execute_build_package = self.call_build_package()

assert m_execute_build_package.call_args_list == [
mock.call(
[
('--name', "'My Application'"),
('--version', '0.1'),
('--package', 'org.test.myapp'),
('--minsdk', '21'),
('--ndk-api', '21'),
('--private', '{buildozer_dir}/android/app'.format(buildozer_dir=self.buildozer.buildozer_dir)),
('--android-entrypoint', 'org.kivy.android.PythonActivity'),
('--android-apptheme', '@android:style/Theme.NoTitleBar'),
('--orientation', 'portrait'),
('--window',),
('--add-xml-resource', os.path.realpath(xml_file_path)),
('debug',),
]
)
]