try stock android w/ lbry modification
This commit is contained in:
parent
13fc3a7ff2
commit
2926957670
30 changed files with 3232 additions and 426 deletions
257
MovedRecipes/android/__init__.py
Normal file
257
MovedRecipes/android/__init__.py
Normal file
|
@ -0,0 +1,257 @@
|
|||
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
|
||||
from pythonforandroid.util import current_directory
|
||||
from pythonforandroid import logger
|
||||
|
||||
from os.path import join
|
||||
|
||||
|
||||
class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||
# name = 'android'
|
||||
version = None
|
||||
url = None
|
||||
|
||||
src_filename = 'src'
|
||||
|
||||
depends = [('sdl2', 'genericndkbuild'), 'pyjnius']
|
||||
|
||||
config_env = {}
|
||||
|
||||
def get_recipe_env(self, arch):
|
||||
env = super().get_recipe_env(arch)
|
||||
env.update(self.config_env)
|
||||
return env
|
||||
|
||||
def prebuild_arch(self, arch):
|
||||
super().prebuild_arch(arch)
|
||||
ctx_bootstrap = self.ctx.bootstrap.name
|
||||
|
||||
# define macros for Cython, C, Python
|
||||
tpxi = 'DEF {} = {}\n'
|
||||
th = '#define {} {}\n'
|
||||
tpy = '{} = {}\n'
|
||||
|
||||
# make sure bootstrap name is in unicode
|
||||
if isinstance(ctx_bootstrap, bytes):
|
||||
ctx_bootstrap = ctx_bootstrap.decode('utf-8')
|
||||
bootstrap = bootstrap_name = ctx_bootstrap
|
||||
is_lbry = bootstrap_name in ('lbry',)
|
||||
is_sdl2 = (bootstrap_name == "sdl2")
|
||||
if bootstrap_name in ["sdl2", "webview", "service_only", "service_library", "lbry"]:
|
||||
java_ns = u'org.kivy.android'
|
||||
jni_ns = u'org/kivy/android'
|
||||
else:
|
||||
logger.error((
|
||||
'unsupported bootstrap for android recipe: {}'
|
||||
''.format(bootstrap_name)
|
||||
))
|
||||
exit(1)
|
||||
|
||||
config = {
|
||||
'BOOTSTRAP': bootstrap,
|
||||
'IS_SDL2': int(is_sdl2),
|
||||
'PY2': 0,
|
||||
'JAVA_NAMESPACE': java_ns,
|
||||
'JNI_NAMESPACE': jni_ns,
|
||||
'ACTIVITY_CLASS_NAME': self.ctx.activity_class_name,
|
||||
'ACTIVITY_CLASS_NAMESPACE': self.ctx.activity_class_name.replace('.', '/'),
|
||||
'SERVICE_CLASS_NAME': self.ctx.service_class_name,
|
||||
}
|
||||
|
||||
# create config files for Cython, C and Python
|
||||
with (
|
||||
current_directory(self.get_build_dir(arch.arch))), (
|
||||
open(join('android', 'config.pxi'), 'w')) as fpxi, (
|
||||
open(join('android', 'config.h'), 'w')) as fh, (
|
||||
open(join('android', 'config.py'), 'w')) as fpy:
|
||||
|
||||
for key, value in config.items():
|
||||
fpxi.write(tpxi.format(key, repr(value)))
|
||||
fpy.write(tpy.format(key, repr(value)))
|
||||
|
||||
fh.write(th.format(
|
||||
key,
|
||||
value if isinstance(value, int) else '"{}"'.format(value)
|
||||
))
|
||||
self.config_env[key] = str(value)
|
||||
|
||||
if is_sdl2:
|
||||
fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
|
||||
fh.write(
|
||||
'#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n'
|
||||
)
|
||||
else:
|
||||
fh.write('JNIEnv *WebView_AndroidGetJNIEnv(void);\n')
|
||||
fh.write(
|
||||
'#define SDL_ANDROID_GetJNIEnv WebView_AndroidGetJNIEnv\n'
|
||||
)
|
||||
|
||||
|
||||
recipe = AndroidRecipe()
|
||||
|
||||
'''
|
||||
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
|
||||
from pythonforandroid.util import current_directory
|
||||
from pythonforandroid.patching import will_build
|
||||
from pythonforandroid import logger
|
||||
|
||||
from os.path import join
|
||||
|
||||
|
||||
class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||
# name = 'android'
|
||||
version = None
|
||||
url = None
|
||||
|
||||
src_filename = 'src'
|
||||
|
||||
depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
|
||||
|
||||
config_env = {}
|
||||
|
||||
def get_recipe_env(self, arch):
|
||||
env = super(AndroidRecipe, self).get_recipe_env(arch)
|
||||
env.update(self.config_env)
|
||||
return env
|
||||
|
||||
def prebuild_arch(self, arch):
|
||||
super(AndroidRecipe, self).prebuild_arch(arch)
|
||||
|
||||
tpxi = 'DEF {} = {}\n'
|
||||
th = '#define {} {}\n'
|
||||
tpy = '{} = {}\n'
|
||||
|
||||
bootstrap = bootstrap_name = self.ctx.bootstrap.name
|
||||
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3', 'sdl2_gradle')
|
||||
is_pygame = bootstrap_name in ('pygame',)
|
||||
is_webview = bootstrap_name in ('webview',)
|
||||
is_lbry = bootstrap_name in ('lbry',)
|
||||
|
||||
if is_sdl2 or is_webview or is_lbry:
|
||||
if is_sdl2:
|
||||
bootstrap = 'sdl2'
|
||||
java_ns = 'org.kivy.android'
|
||||
jni_ns = 'org/kivy/android'
|
||||
elif is_pygame:
|
||||
java_ns = 'org.renpy.android'
|
||||
jni_ns = 'org/renpy/android'
|
||||
else:
|
||||
logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name))
|
||||
exit(1)
|
||||
|
||||
config = {
|
||||
'BOOTSTRAP': bootstrap,
|
||||
'IS_SDL2': int(is_sdl2),
|
||||
'IS_PYGAME': int(is_pygame),
|
||||
'PY2': int(will_build('python2')(self)),
|
||||
'JAVA_NAMESPACE': java_ns,
|
||||
'JNI_NAMESPACE': jni_ns,
|
||||
}
|
||||
|
||||
with current_directory(self.get_build_dir(arch.arch)):
|
||||
with open(join('android', 'config.pxi'), 'w') as fpxi:
|
||||
with open(join('android', 'config.h'), 'w') as fh:
|
||||
with open(join('android', 'config.py'), 'w') as fpy:
|
||||
for key, value in config.items():
|
||||
fpxi.write(tpxi.format(key, repr(value)))
|
||||
fpy.write(tpy.format(key, repr(value)))
|
||||
fh.write(th.format(key, value if isinstance(value, int)
|
||||
else '"{}"'.format(value)))
|
||||
self.config_env[key] = str(value)
|
||||
|
||||
if is_sdl2:
|
||||
fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
|
||||
fh.write('#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n')
|
||||
elif is_pygame:
|
||||
fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
|
||||
|
||||
|
||||
recipe = AndroidRecipe()
|
||||
'''
|
||||
|
||||
'''
|
||||
from pythonforandroid.recipe import CythonRecipe, Recipe, IncludedFilesBehaviour
|
||||
from pythonforandroid.util import current_directory
|
||||
from pythonforandroid.patching import will_build
|
||||
from pythonforandroid import logger
|
||||
|
||||
from os.path import join
|
||||
|
||||
|
||||
class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||
# name = 'android'
|
||||
version = None
|
||||
url = None
|
||||
|
||||
src_filename = 'src'
|
||||
|
||||
depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
|
||||
|
||||
call_hostpython_via_targetpython = False
|
||||
|
||||
config_env = {}
|
||||
|
||||
def get_recipe_env(self, arch):
|
||||
env = super(AndroidRecipe, self).get_recipe_env(arch)
|
||||
env.update(self.config_env)
|
||||
|
||||
target_python = Recipe.get_recipe('python2', self.ctx).get_build_dir(arch.arch)
|
||||
env['PYTHON_ROOT'] = join(target_python, 'python-install')
|
||||
env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
|
||||
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + ' -lpython2.7'
|
||||
|
||||
return env
|
||||
|
||||
def prebuild_arch(self, arch):
|
||||
super(AndroidRecipe, self).prebuild_arch(arch)
|
||||
|
||||
tpxi = 'DEF {} = {}\n'
|
||||
th = '#define {} {}\n'
|
||||
tpy = '{} = {}\n'
|
||||
|
||||
bootstrap = bootstrap_name = self.ctx.bootstrap.name
|
||||
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3')
|
||||
is_pygame = bootstrap_name in ('pygame',)
|
||||
is_webview = bootstrap_name in ('webview')
|
||||
is_lbry = bootstrap_name in ('lbry')
|
||||
|
||||
if is_sdl2 or is_webview or is_lbry:
|
||||
if is_sdl2:
|
||||
bootstrap = 'sdl2'
|
||||
java_ns = 'org.kivy.android'
|
||||
jni_ns = 'org/kivy/android'
|
||||
elif is_pygame:
|
||||
java_ns = 'org.renpy.android'
|
||||
jni_ns = 'org/renpy/android'
|
||||
else:
|
||||
logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name))
|
||||
exit(1)
|
||||
|
||||
config = {
|
||||
'BOOTSTRAP': bootstrap,
|
||||
'IS_SDL2': int(is_sdl2),
|
||||
'IS_PYGAME': int(is_pygame),
|
||||
'PY2': int(will_build('python2')(self)),
|
||||
'JAVA_NAMESPACE': java_ns,
|
||||
'JNI_NAMESPACE': jni_ns,
|
||||
}
|
||||
|
||||
with current_directory(self.get_build_dir(arch.arch)):
|
||||
with open(join('android', 'config.pxi'), 'w') as fpxi:
|
||||
with open(join('android', 'config.h'), 'w') as fh:
|
||||
with open(join('android', 'config.py'), 'w') as fpy:
|
||||
for key, value in config.items():
|
||||
fpxi.write(tpxi.format(key, repr(value)))
|
||||
fpy.write(tpy.format(key, repr(value)))
|
||||
fh.write(th.format(key, value if isinstance(value, int)
|
||||
else '"{}"'.format(value)))
|
||||
self.config_env[key] = str(value)
|
||||
|
||||
if is_sdl2:
|
||||
fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
|
||||
fh.write('#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n')
|
||||
elif is_pygame:
|
||||
fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
|
||||
|
||||
|
||||
recipe = AndroidRecipe()
|
||||
'''
|
0
MovedRecipes/android/src/__init__.py
Normal file
0
MovedRecipes/android/src/__init__.py
Normal file
8
MovedRecipes/android/src/android/__init__.py
Normal file
8
MovedRecipes/android/src/android/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
'''
|
||||
Android module
|
||||
==============
|
||||
|
||||
'''
|
||||
|
||||
# legacy import
|
||||
from android._android import *
|
385
MovedRecipes/android/src/android/_android.pyx
Normal file
385
MovedRecipes/android/src/android/_android.pyx
Normal file
|
@ -0,0 +1,385 @@
|
|||
# Android-specific python services.
|
||||
|
||||
include "config.pxi"
|
||||
|
||||
IF BOOTSTRAP == 'pygame':
|
||||
cdef extern int SDL_ANDROID_CheckPause()
|
||||
cdef extern void SDL_ANDROID_WaitForResume() nogil
|
||||
cdef extern void SDL_ANDROID_MapKey(int scancode, int keysym)
|
||||
|
||||
def check_pause():
|
||||
return SDL_ANDROID_CheckPause()
|
||||
|
||||
def wait_for_resume():
|
||||
android_accelerometer_enable(False)
|
||||
SDL_ANDROID_WaitForResume()
|
||||
android_accelerometer_enable(accelerometer_enabled)
|
||||
|
||||
def map_key(scancode, keysym):
|
||||
SDL_ANDROID_MapKey(scancode, keysym)
|
||||
|
||||
# Android keycodes.
|
||||
KEYCODE_UNKNOWN = 0
|
||||
KEYCODE_SOFT_LEFT = 1
|
||||
KEYCODE_SOFT_RIGHT = 2
|
||||
KEYCODE_HOME = 3
|
||||
KEYCODE_BACK = 4
|
||||
KEYCODE_CALL = 5
|
||||
KEYCODE_ENDCALL = 6
|
||||
KEYCODE_0 = 7
|
||||
KEYCODE_1 = 8
|
||||
KEYCODE_2 = 9
|
||||
KEYCODE_3 = 10
|
||||
KEYCODE_4 = 11
|
||||
KEYCODE_5 = 12
|
||||
KEYCODE_6 = 13
|
||||
KEYCODE_7 = 14
|
||||
KEYCODE_8 = 15
|
||||
KEYCODE_9 = 16
|
||||
KEYCODE_STAR = 17
|
||||
KEYCODE_POUND = 18
|
||||
KEYCODE_DPAD_UP = 19
|
||||
KEYCODE_DPAD_DOWN = 20
|
||||
KEYCODE_DPAD_LEFT = 21
|
||||
KEYCODE_DPAD_RIGHT = 22
|
||||
KEYCODE_DPAD_CENTER = 23
|
||||
KEYCODE_VOLUME_UP = 24
|
||||
KEYCODE_VOLUME_DOWN = 25
|
||||
KEYCODE_POWER = 26
|
||||
KEYCODE_CAMERA = 27
|
||||
KEYCODE_CLEAR = 28
|
||||
KEYCODE_A = 29
|
||||
KEYCODE_B = 30
|
||||
KEYCODE_C = 31
|
||||
KEYCODE_D = 32
|
||||
KEYCODE_E = 33
|
||||
KEYCODE_F = 34
|
||||
KEYCODE_G = 35
|
||||
KEYCODE_H = 36
|
||||
KEYCODE_I = 37
|
||||
KEYCODE_J = 38
|
||||
KEYCODE_K = 39
|
||||
KEYCODE_L = 40
|
||||
KEYCODE_M = 41
|
||||
KEYCODE_N = 42
|
||||
KEYCODE_O = 43
|
||||
KEYCODE_P = 44
|
||||
KEYCODE_Q = 45
|
||||
KEYCODE_R = 46
|
||||
KEYCODE_S = 47
|
||||
KEYCODE_T = 48
|
||||
KEYCODE_U = 49
|
||||
KEYCODE_V = 50
|
||||
KEYCODE_W = 51
|
||||
KEYCODE_X = 52
|
||||
KEYCODE_Y = 53
|
||||
KEYCODE_Z = 54
|
||||
KEYCODE_COMMA = 55
|
||||
KEYCODE_PERIOD = 56
|
||||
KEYCODE_ALT_LEFT = 57
|
||||
KEYCODE_ALT_RIGHT = 58
|
||||
KEYCODE_SHIFT_LEFT = 59
|
||||
KEYCODE_SHIFT_RIGHT = 60
|
||||
KEYCODE_TAB = 61
|
||||
KEYCODE_SPACE = 62
|
||||
KEYCODE_SYM = 63
|
||||
KEYCODE_EXPLORER = 64
|
||||
KEYCODE_ENVELOPE = 65
|
||||
KEYCODE_ENTER = 66
|
||||
KEYCODE_DEL = 67
|
||||
KEYCODE_GRAVE = 68
|
||||
KEYCODE_MINUS = 69
|
||||
KEYCODE_EQUALS = 70
|
||||
KEYCODE_LEFT_BRACKET = 71
|
||||
KEYCODE_RIGHT_BRACKET = 72
|
||||
KEYCODE_BACKSLASH = 73
|
||||
KEYCODE_SEMICOLON = 74
|
||||
KEYCODE_APOSTROPHE = 75
|
||||
KEYCODE_SLASH = 76
|
||||
KEYCODE_AT = 77
|
||||
KEYCODE_NUM = 78
|
||||
KEYCODE_HEADSETHOOK = 79
|
||||
KEYCODE_FOCUS = 80
|
||||
KEYCODE_PLUS = 81
|
||||
KEYCODE_MENU = 82
|
||||
KEYCODE_NOTIFICATION = 83
|
||||
KEYCODE_SEARCH = 84
|
||||
KEYCODE_MEDIA_PLAY_PAUSE= 85
|
||||
KEYCODE_MEDIA_STOP = 86
|
||||
KEYCODE_MEDIA_NEXT = 87
|
||||
KEYCODE_MEDIA_PREVIOUS = 88
|
||||
KEYCODE_MEDIA_REWIND = 89
|
||||
KEYCODE_MEDIA_FAST_FORWARD = 90
|
||||
KEYCODE_MUTE = 91
|
||||
|
||||
# Vibration support.
|
||||
cdef extern void android_vibrate(double)
|
||||
|
||||
def vibrate(s):
|
||||
android_vibrate(s)
|
||||
|
||||
# Accelerometer support.
|
||||
cdef extern void android_accelerometer_enable(int)
|
||||
cdef extern void android_accelerometer_reading(float *)
|
||||
|
||||
accelerometer_enabled = False
|
||||
|
||||
def accelerometer_enable(p):
|
||||
global accelerometer_enabled
|
||||
|
||||
android_accelerometer_enable(p)
|
||||
|
||||
accelerometer_enabled = p
|
||||
|
||||
def accelerometer_reading():
|
||||
cdef float rv[3]
|
||||
android_accelerometer_reading(rv)
|
||||
|
||||
return (rv[0], rv[1], rv[2])
|
||||
|
||||
# Wifi reading support
|
||||
cdef extern void android_wifi_scanner_enable()
|
||||
cdef extern char * android_wifi_scan()
|
||||
|
||||
def wifi_scanner_enable():
|
||||
android_wifi_scanner_enable()
|
||||
|
||||
def wifi_scan():
|
||||
cdef char * reading
|
||||
reading = android_wifi_scan()
|
||||
|
||||
reading_list = []
|
||||
|
||||
for line in filter(lambda l: l, reading.split('\n')):
|
||||
[ssid, mac, level] = line.split('\t')
|
||||
reading_list.append((ssid.strip(), mac.upper().strip(), int(level)))
|
||||
|
||||
return reading_list
|
||||
|
||||
# DisplayMetrics information.
|
||||
cdef extern int android_get_dpi()
|
||||
|
||||
def get_dpi():
|
||||
return android_get_dpi()
|
||||
|
||||
|
||||
# Soft keyboard.
|
||||
cdef extern void android_show_keyboard(int)
|
||||
cdef extern void android_hide_keyboard()
|
||||
|
||||
|
||||
from jnius import autoclass, PythonJavaClass, java_method, cast
|
||||
|
||||
# API versions
|
||||
api_version = autoclass('android.os.Build$VERSION').SDK_INT
|
||||
version_codes = autoclass('android.os.Build$VERSION_CODES')
|
||||
|
||||
|
||||
python_act = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
||||
Rect = autoclass('android.graphics.Rect')
|
||||
mActivity = python_act.mActivity
|
||||
if mActivity:
|
||||
# PyGame backend already has the listener so adding
|
||||
# one here leads to a crash/too much cpu usage.
|
||||
# SDL2 now does noe need the listener so there is
|
||||
# no point adding a processor intensive layout listenere here.
|
||||
height = 0
|
||||
def get_keyboard_height():
|
||||
rctx = Rect()
|
||||
mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rctx)
|
||||
# NOTE top should always be zero
|
||||
rctx.top = 0
|
||||
height = mActivity.getWindowManager().getDefaultDisplay().getHeight() - (rctx.bottom - rctx.top)
|
||||
return height
|
||||
else:
|
||||
def get_keyboard_height():
|
||||
return 0
|
||||
|
||||
# Flags for input_type, for requesting a particular type of keyboard
|
||||
#android FLAGS
|
||||
TYPE_CLASS_DATETIME = 4
|
||||
TYPE_CLASS_NUMBER = 2
|
||||
TYPE_NUMBER_VARIATION_NORMAL = 0
|
||||
TYPE_NUMBER_VARIATION_PASSWORD = 16
|
||||
TYPE_CLASS_TEXT = 1
|
||||
TYPE_TEXT_FLAG_AUTO_COMPLETE = 65536
|
||||
TYPE_TEXT_FLAG_AUTO_CORRECT = 32768
|
||||
TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288
|
||||
TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32
|
||||
TYPE_TEXT_VARIATION_NORMAL = 0
|
||||
TYPE_TEXT_VARIATION_PASSWORD = 128
|
||||
TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 112
|
||||
TYPE_TEXT_VARIATION_URI = 16
|
||||
TYPE_CLASS_PHONE = 3
|
||||
|
||||
IF BOOTSTRAP == 'sdl2':
|
||||
def remove_presplash():
|
||||
# Remove android presplash in SDL2 bootstrap.
|
||||
mActivity.removeLoadingScreen()
|
||||
|
||||
def show_keyboard(target, input_type):
|
||||
if input_type == 'text':
|
||||
_input_type = TYPE_CLASS_TEXT
|
||||
elif input_type == 'number':
|
||||
_input_type = TYPE_CLASS_NUMBER
|
||||
elif input_type == 'url':
|
||||
_input_type = \
|
||||
TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI
|
||||
elif input_type == 'mail':
|
||||
_input_type = \
|
||||
TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
elif input_type == 'datetime':
|
||||
_input_type = TYPE_CLASS_DATETIME
|
||||
elif input_type == 'tel':
|
||||
_input_type = TYPE_CLASS_PHONE
|
||||
elif input_type == 'address':
|
||||
_input_type = TYPE_TEXT_VARIATION_POSTAL_ADDRESS
|
||||
|
||||
if hasattr(target, 'password') and target.password:
|
||||
if _input_type == TYPE_CLASS_TEXT:
|
||||
_input_type |= TYPE_TEXT_VARIATION_PASSWORD
|
||||
elif _input_type == TYPE_CLASS_NUMBER:
|
||||
_input_type |= TYPE_NUMBER_VARIATION_PASSWORD
|
||||
|
||||
if hasattr(target, 'keyboard_suggestions') and not target.keyboard_suggestions:
|
||||
if _input_type == TYPE_CLASS_TEXT:
|
||||
_input_type = TYPE_CLASS_TEXT | \
|
||||
TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
|
||||
android_show_keyboard(_input_type)
|
||||
|
||||
def hide_keyboard():
|
||||
android_hide_keyboard()
|
||||
|
||||
# Build info.
|
||||
cdef extern char* BUILD_MANUFACTURER
|
||||
cdef extern char* BUILD_MODEL
|
||||
cdef extern char* BUILD_PRODUCT
|
||||
cdef extern char* BUILD_VERSION_RELEASE
|
||||
|
||||
cdef extern void android_get_buildinfo()
|
||||
|
||||
class BuildInfo:
|
||||
MANUFACTURER = None
|
||||
MODEL = None
|
||||
PRODUCT = None
|
||||
VERSION_RELEASE = None
|
||||
|
||||
def get_buildinfo():
|
||||
android_get_buildinfo()
|
||||
binfo = BuildInfo()
|
||||
binfo.MANUFACTURER = BUILD_MANUFACTURER
|
||||
binfo.MODEL = BUILD_MODEL
|
||||
binfo.PRODUCT = BUILD_PRODUCT
|
||||
binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE
|
||||
return binfo
|
||||
|
||||
IF IS_PYGAME:
|
||||
# Activate input - required to receive input events.
|
||||
cdef extern void android_activate_input()
|
||||
|
||||
def init():
|
||||
android_activate_input()
|
||||
|
||||
# Action send
|
||||
cdef extern void android_action_send(char*, char*, char*, char*, char*)
|
||||
def action_send(mimetype, filename=None, subject=None, text=None,
|
||||
chooser_title=None):
|
||||
cdef char *j_mimetype = <bytes>mimetype
|
||||
cdef char *j_filename = NULL
|
||||
cdef char *j_subject = NULL
|
||||
cdef char *j_text = NULL
|
||||
cdef char *j_chooser_title = NULL
|
||||
if filename is not None:
|
||||
j_filename = <bytes>filename
|
||||
if subject is not None:
|
||||
j_subject = <bytes>subject
|
||||
if text is not None:
|
||||
j_text = <bytes>text
|
||||
if chooser_title is not None:
|
||||
j_chooser_title = <bytes>chooser_title
|
||||
android_action_send(j_mimetype, j_filename, j_subject, j_text,
|
||||
j_chooser_title)
|
||||
|
||||
cdef extern int android_checkstop()
|
||||
cdef extern void android_ackstop()
|
||||
|
||||
def check_stop():
|
||||
return android_checkstop()
|
||||
|
||||
def ack_stop():
|
||||
android_ackstop()
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# URL Opening.
|
||||
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)
|
||||
return True
|
||||
|
||||
# Web browser support.
|
||||
class AndroidBrowser(object):
|
||||
def open(self, url, new=0, autoraise=True):
|
||||
return open_url(url)
|
||||
def open_new(self, url):
|
||||
return open_url(url)
|
||||
def open_new_tab(self, url):
|
||||
return open_url(url)
|
||||
|
||||
import webbrowser
|
||||
webbrowser.register('android', AndroidBrowser, None, -1)
|
||||
|
||||
cdef extern void android_start_service(char *, char *, char *)
|
||||
def start_service(title=None, description=None, arg=None):
|
||||
cdef char *j_title = NULL
|
||||
cdef char *j_description = NULL
|
||||
if title is not None:
|
||||
j_title = <bytes>title
|
||||
if description is not None:
|
||||
j_description = <bytes>description
|
||||
if arg is not None:
|
||||
j_arg = <bytes>arg
|
||||
android_start_service(j_title, j_description, j_arg)
|
||||
|
||||
cdef extern void android_stop_service()
|
||||
def stop_service():
|
||||
android_stop_service()
|
||||
|
||||
class AndroidService(object):
|
||||
'''Android service class.
|
||||
Run ``service/main.py`` from application directory as a service.
|
||||
|
||||
:Parameters:
|
||||
`title`: str, default to 'Python service'
|
||||
Notification title.
|
||||
|
||||
`description`: str, default to 'Kivy Python service started'
|
||||
Notification text.
|
||||
'''
|
||||
|
||||
def __init__(self, title='Python service',
|
||||
description='Kivy Python service started'):
|
||||
self.title = title
|
||||
self.description = description
|
||||
|
||||
def start(self, arg=''):
|
||||
'''Start the service.
|
||||
|
||||
:Parameters:
|
||||
`arg`: str, default to ''
|
||||
Argument to pass to a service,
|
||||
through environment variable ``PYTHON_SERVICE_ARGUMENT``.
|
||||
'''
|
||||
start_service(self.title, self.description, arg)
|
||||
|
||||
def stop(self):
|
||||
'''Stop the service.
|
||||
'''
|
||||
stop_service()
|
||||
|
||||
|
81
MovedRecipes/android/src/android/_android_billing.pyx
Normal file
81
MovedRecipes/android/src/android/_android_billing.pyx
Normal file
|
@ -0,0 +1,81 @@
|
|||
# -------------------------------------------------------------------
|
||||
# Billing
|
||||
cdef extern void android_billing_service_start()
|
||||
cdef extern void android_billing_service_stop()
|
||||
cdef extern void android_billing_buy(char *sku)
|
||||
cdef extern char *android_billing_get_purchased_items()
|
||||
cdef extern char *android_billing_get_pending_message()
|
||||
|
||||
class BillingService(object):
|
||||
|
||||
BILLING_ACTION_SUPPORTED = 'billingsupported'
|
||||
BILLING_ACTION_ITEMSCHANGED = 'itemschanged'
|
||||
|
||||
BILLING_TYPE_INAPP = 'inapp'
|
||||
BILLING_TYPE_SUBSCRIPTION = 'subs'
|
||||
|
||||
def __init__(self, callback):
|
||||
super(BillingService, self).__init__()
|
||||
self.callback = callback
|
||||
self.purchased_items = None
|
||||
android_billing_service_start()
|
||||
|
||||
def _stop(self):
|
||||
android_billing_service_stop()
|
||||
|
||||
def buy(self, sku):
|
||||
cdef char *j_sku = <bytes>sku
|
||||
android_billing_buy(j_sku)
|
||||
|
||||
def get_purchased_items(self):
|
||||
cdef char *items = NULL
|
||||
cdef bytes pitem
|
||||
items = android_billing_get_purchased_items()
|
||||
if items == NULL:
|
||||
return []
|
||||
pitems = items
|
||||
ret = {}
|
||||
for item in pitems.split('\n'):
|
||||
if not item:
|
||||
continue
|
||||
sku, qt = item.split(',')
|
||||
ret[sku] = {'qt': int(qt)}
|
||||
return ret
|
||||
|
||||
def check(self, *largs):
|
||||
cdef char *message
|
||||
cdef bytes pymessage
|
||||
|
||||
while True:
|
||||
message = android_billing_get_pending_message()
|
||||
if message == NULL:
|
||||
break
|
||||
pymessage = <bytes>message
|
||||
self._handle_message(pymessage)
|
||||
|
||||
if self.purchased_items is None:
|
||||
self._check_new_items()
|
||||
|
||||
def _handle_message(self, message):
|
||||
action, data = message.split('|', 1)
|
||||
#print "HANDLE MESSAGE-----", (action, data)
|
||||
|
||||
if action == 'billingSupported':
|
||||
tp, value = data.split('|')
|
||||
value = True if value == '1' else False
|
||||
self.callback(BillingService.BILLING_ACTION_SUPPORTED, tp, value)
|
||||
|
||||
elif action == 'requestPurchaseResponse':
|
||||
self._check_new_items()
|
||||
|
||||
elif action == 'purchaseStateChange':
|
||||
self._check_new_items()
|
||||
|
||||
elif action == 'restoreTransaction':
|
||||
self._check_new_items()
|
||||
|
||||
def _check_new_items(self):
|
||||
items = self.get_purchased_items()
|
||||
if self.purchased_items != items:
|
||||
self.purchased_items = items
|
||||
self.callback(BillingService.BILLING_ACTION_ITEMSCHANGED, self.purchased_items)
|
120
MovedRecipes/android/src/android/_android_billing_jni.c
Normal file
120
MovedRecipes/android/src/android/_android_billing_jni.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <android/log.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, "android_jni", "Assertion failed. %s:%d", __FILE__, __LINE__); abort(); }}
|
||||
#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }
|
||||
#define POP_FRAME { (*env)->PopLocalFrame(env, NULL); }
|
||||
|
||||
void android_billing_service_start() {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "billingServiceStart", "()V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid);
|
||||
POP_FRAME;
|
||||
}
|
||||
|
||||
void android_billing_service_stop() {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "billingServiceStop", "()V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid);
|
||||
POP_FRAME;
|
||||
}
|
||||
|
||||
void android_billing_buy(char *sku) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "billingBuy", "(Ljava/lang/String;)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
(*env)->NewStringUTF(env, sku)
|
||||
);
|
||||
|
||||
POP_FRAME;
|
||||
}
|
||||
|
||||
char *android_billing_get_purchased_items() {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
jobject jreading;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "billingGetPurchasedItems", "()Ljava/lang/String;");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
|
||||
const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
|
||||
POP_FRAME;
|
||||
|
||||
return reading;
|
||||
}
|
||||
|
||||
char *android_billing_get_pending_message() {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
jobject jreading;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "billingGetPendingMessage", "()Ljava/lang/String;");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
|
||||
const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
|
||||
POP_FRAME;
|
||||
|
||||
return reading;
|
||||
}
|
||||
|
358
MovedRecipes/android/src/android/_android_jni.c
Normal file
358
MovedRecipes/android/src/android/_android_jni.c
Normal file
|
@ -0,0 +1,358 @@
|
|||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <android/log.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, "android_jni", "Assertion failed. %s:%d", __FILE__, __LINE__); abort(); }}
|
||||
#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }
|
||||
#define POP_FRAME { (*env)->PopLocalFrame(env, NULL); }
|
||||
|
||||
void android_vibrate(double seconds) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "vibrate", "(D)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
(jdouble) seconds);
|
||||
}
|
||||
|
||||
void android_accelerometer_enable(int enable) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "accelerometerEnable", "(Z)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
(jboolean) enable);
|
||||
}
|
||||
|
||||
void android_wifi_scanner_enable(void){
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "enableWifiScanner", "()V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
|
||||
char * android_wifi_scan() {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
jobject jreading;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "scanWifi", "()Ljava/lang/String;");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
|
||||
const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
|
||||
POP_FRAME;
|
||||
|
||||
return reading;
|
||||
}
|
||||
|
||||
void android_accelerometer_reading(float *values) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
jobject jvalues;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "accelerometerReading", "()[F");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
|
||||
jvalues = (*env)->CallStaticObjectMethod(env, cls, mid);
|
||||
(*env)->GetFloatArrayRegion(env, jvalues, 0, 3, values);
|
||||
|
||||
POP_FRAME;
|
||||
}
|
||||
|
||||
int android_get_dpi(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "getDPI", "()I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
return (*env)->CallStaticIntMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
void android_show_keyboard(int input_type) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "showKeyboard", "(I)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid, (jint) input_type);
|
||||
}
|
||||
|
||||
void android_hide_keyboard(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Hardware");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "hideKeyboard", "()V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
char* BUILD_MANUFACTURER = NULL;
|
||||
char* BUILD_MODEL = NULL;
|
||||
char* BUILD_PRODUCT = NULL;
|
||||
char* BUILD_VERSION_RELEASE = NULL;
|
||||
|
||||
void android_get_buildinfo() {
|
||||
static JNIEnv *env = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
jclass *cls = NULL;
|
||||
jfieldID fid;
|
||||
jstring sval;
|
||||
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
|
||||
cls = (*env)->FindClass(env, "android/os/Build");
|
||||
|
||||
fid = (*env)->GetStaticFieldID(env, cls, "MANUFACTURER", "Ljava/lang/String;");
|
||||
sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
|
||||
BUILD_MANUFACTURER = (*env)->GetStringUTFChars(env, sval, 0);
|
||||
|
||||
fid = (*env)->GetStaticFieldID(env, cls, "MODEL", "Ljava/lang/String;");
|
||||
sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
|
||||
BUILD_MODEL = (*env)->GetStringUTFChars(env, sval, 0);
|
||||
|
||||
fid = (*env)->GetStaticFieldID(env, cls, "PRODUCT", "Ljava/lang/String;");
|
||||
sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
|
||||
BUILD_PRODUCT = (*env)->GetStringUTFChars(env, sval, 0);
|
||||
|
||||
cls = (*env)->FindClass(env, "android/os/Build$VERSION");
|
||||
|
||||
fid = (*env)->GetStaticFieldID(env, cls, "RELEASE", "Ljava/lang/String;");
|
||||
sval = (jstring) (*env)->GetStaticObjectField(env, cls, fid);
|
||||
BUILD_VERSION_RELEASE = (*env)->GetStringUTFChars(env, sval, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#if IS_PYGAME
|
||||
void android_activate_input(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "activateInput", "()V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
int android_checkstop(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "checkStop", "()I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
return (*env)->CallStaticIntMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
void android_ackstop(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "ackStop", "()I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticIntMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
void android_action_send(char *mimeType, char *filename, char *subject, char *text, char *chooser_title) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Action");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "send",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
jstring j_mimeType = (*env)->NewStringUTF(env, mimeType);
|
||||
jstring j_filename = NULL;
|
||||
jstring j_subject = NULL;
|
||||
jstring j_text = NULL;
|
||||
jstring j_chooser_title = NULL;
|
||||
if ( filename != NULL )
|
||||
j_filename = (*env)->NewStringUTF(env, filename);
|
||||
if ( subject != NULL )
|
||||
j_subject = (*env)->NewStringUTF(env, subject);
|
||||
if ( text != NULL )
|
||||
j_text = (*env)->NewStringUTF(env, text);
|
||||
if ( chooser_title != NULL )
|
||||
j_chooser_title = (*env)->NewStringUTF(env, text);
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
j_mimeType, j_filename, j_subject, j_text,
|
||||
j_chooser_title);
|
||||
}
|
||||
|
||||
void android_open_url(char *url) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "openUrl", "(Ljava/lang/String;)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
(*env)->NewStringUTF(env, url)
|
||||
);
|
||||
|
||||
POP_FRAME;
|
||||
}
|
||||
#endif // IS_PYGAME
|
||||
|
||||
void android_start_service(char *title, char *description, char *arg) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "start_service",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
jstring j_title = NULL;
|
||||
jstring j_description = NULL;
|
||||
jstring j_arg = NULL;
|
||||
if ( title != 0 )
|
||||
j_title = (*env)->NewStringUTF(env, title);
|
||||
if ( description != 0 )
|
||||
j_description = (*env)->NewStringUTF(env, description);
|
||||
if ( arg != 0 )
|
||||
j_arg = (*env)->NewStringUTF(env, arg);
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid, j_title, j_description, j_arg);
|
||||
}
|
||||
|
||||
void android_stop_service() {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "stop_service", "()V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid);
|
||||
}
|
125
MovedRecipes/android/src/android/_android_sound.pyx
Normal file
125
MovedRecipes/android/src/android/_android_sound.pyx
Normal file
|
@ -0,0 +1,125 @@
|
|||
cdef extern void android_sound_queue(int, char *, char *, long long, long long)
|
||||
cdef extern void android_sound_play(int, char *, char *, long long, long long)
|
||||
cdef extern void android_sound_stop(int)
|
||||
cdef extern void android_sound_seek(int, float)
|
||||
cdef extern void android_sound_dequeue(int)
|
||||
cdef extern void android_sound_playing_name(int, char *, int)
|
||||
cdef extern void android_sound_pause(int)
|
||||
cdef extern void android_sound_unpause(int)
|
||||
|
||||
cdef extern void android_sound_set_volume(int, float)
|
||||
cdef extern void android_sound_set_secondary_volume(int, float)
|
||||
cdef extern void android_sound_set_pan(int, float)
|
||||
|
||||
cdef extern int android_sound_queue_depth(int)
|
||||
cdef extern int android_sound_get_pos(int)
|
||||
cdef extern int android_sound_get_length(int)
|
||||
|
||||
channels = set()
|
||||
volumes = { }
|
||||
|
||||
def queue(channel, file, name, fadein=0, tight=False):
|
||||
|
||||
channels.add(channel)
|
||||
|
||||
real_fn = file.name
|
||||
base = getattr(file, "base", -1)
|
||||
length = getattr(file, "length", -1)
|
||||
|
||||
android_sound_queue(channel, name, real_fn, base, length)
|
||||
|
||||
def play(channel, file, name, paused=False, fadein=0, tight=False):
|
||||
|
||||
channels.add(channel)
|
||||
|
||||
real_fn = file.name
|
||||
base = getattr(file, "base", -1)
|
||||
length = getattr(file, "length", -1)
|
||||
|
||||
android_sound_play(channel, name, real_fn, base, length)
|
||||
|
||||
def seek(channel, position):
|
||||
android_sound_seek(channel, position)
|
||||
|
||||
def stop(channel):
|
||||
android_sound_stop(channel)
|
||||
|
||||
def dequeue(channel, even_tight=False):
|
||||
android_sound_dequeue(channel)
|
||||
|
||||
def queue_depth(channel):
|
||||
return android_sound_queue_depth(channel)
|
||||
|
||||
def playing_name(channel):
|
||||
cdef char buf[1024]
|
||||
|
||||
android_sound_playing_name(channel, buf, 1024)
|
||||
|
||||
rv = buf
|
||||
if not len(rv):
|
||||
return None
|
||||
return rv
|
||||
|
||||
def pause(channel):
|
||||
android_sound_pause(channel)
|
||||
return
|
||||
|
||||
def unpause(channel):
|
||||
android_sound_unpause(channel)
|
||||
return
|
||||
|
||||
def unpause_all():
|
||||
for i in channels:
|
||||
unpause(i)
|
||||
|
||||
def pause_all():
|
||||
for i in channels:
|
||||
pause(i)
|
||||
|
||||
def fadeout(channel, ms):
|
||||
stop(channel)
|
||||
|
||||
def busy(channel):
|
||||
return playing_name(channel) != None
|
||||
|
||||
def get_pos(channel):
|
||||
return android_sound_get_pos(channel)
|
||||
|
||||
def get_length(channel):
|
||||
return android_sound_get_length(channel)
|
||||
|
||||
def set_volume(channel, volume):
|
||||
android_sound_set_volume(channel, volume)
|
||||
volumes[channel] = volume
|
||||
|
||||
def set_secondary_volume(channel, volume):
|
||||
android_sound_set_secondary_volume(channel, volume)
|
||||
|
||||
def set_pan(channel, pan):
|
||||
android_sound_set_pan(channel, pan)
|
||||
|
||||
def set_end_event(channel, event):
|
||||
return
|
||||
|
||||
def get_volume(channel):
|
||||
return volumes.get(channel, 1.0)
|
||||
|
||||
def init(freq, stereo, samples, status=False):
|
||||
return
|
||||
|
||||
def quit():
|
||||
for i in channels:
|
||||
stop(i)
|
||||
|
||||
def periodic():
|
||||
return
|
||||
|
||||
def alloc_event(surf):
|
||||
return
|
||||
|
||||
def refresh_event():
|
||||
return
|
||||
|
||||
def check_version(version):
|
||||
return
|
||||
|
308
MovedRecipes/android/src/android/_android_sound_jni.c
Normal file
308
MovedRecipes/android/src/android/_android_sound_jni.c
Normal file
|
@ -0,0 +1,308 @@
|
|||
#include <jni.h>
|
||||
#include <stdio.h>
|
||||
#include <android/log.h>
|
||||
#include <string.h>
|
||||
|
||||
JNIEnv *SDL_ANDROID_GetJNIEnv();
|
||||
|
||||
#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, "android_sound_jni", "Assertion failed. %s:%d", __FILE__, __LINE__); abort(); }}
|
||||
#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }
|
||||
#define POP_FRAME { (*env)->PopLocalFrame(env, NULL); }
|
||||
|
||||
|
||||
void android_sound_queue(int channel, char *filename, char *real_fn, long long base, long long length) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "queue", "(ILjava/lang/String;Ljava/lang/String;JJ)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel,
|
||||
(*env)->NewStringUTF(env, filename),
|
||||
(*env)->NewStringUTF(env, real_fn),
|
||||
(jlong) base,
|
||||
(jlong) length);
|
||||
|
||||
POP_FRAME;
|
||||
}
|
||||
|
||||
void android_sound_play(int channel, char *filename, char *real_fn, long long base, long long length) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "play", "(ILjava/lang/String;Ljava/lang/String;JJ)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel,
|
||||
(*env)->NewStringUTF(env, filename),
|
||||
(*env)->NewStringUTF(env, real_fn),
|
||||
(jlong) base,
|
||||
(jlong) length);
|
||||
|
||||
POP_FRAME;
|
||||
}
|
||||
|
||||
void android_sound_seek(int channel, float position){
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "seek", "(IF)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel,
|
||||
(jfloat) position);
|
||||
}
|
||||
|
||||
void android_sound_stop(int channel) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "stop", "(I)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
}
|
||||
|
||||
void android_sound_dequeue(int channel) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "dequeue", "(I)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
}
|
||||
|
||||
int android_sound_queue_depth(int channel) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "queue_depth", "(I)I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticIntMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
}
|
||||
|
||||
void android_sound_playing_name(int channel, char *buf, int buflen) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
jobject s = NULL;
|
||||
char *jbuf;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "playing_name", "(I)Ljava/lang/String;");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
|
||||
s = (*env)->CallStaticObjectMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
|
||||
jbuf = (*env)->GetStringUTFChars(env, s, NULL);
|
||||
strncpy(buf, jbuf, buflen);
|
||||
(*env)->ReleaseStringUTFChars(env, s, jbuf);
|
||||
|
||||
POP_FRAME;
|
||||
}
|
||||
|
||||
void android_sound_set_volume(int channel, float value) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "set_volume", "(IF)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel,
|
||||
(jfloat) value);
|
||||
}
|
||||
|
||||
void android_sound_set_secondary_volume(int channel, float value) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "set_secondary_volume", "(IF)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel,
|
||||
(jfloat) value);
|
||||
}
|
||||
|
||||
void android_sound_set_pan(int channel, float value) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "set_pan", "(IF)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel,
|
||||
(jfloat) value);
|
||||
}
|
||||
|
||||
void android_sound_pause(int channel) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "pause", "(I)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
}
|
||||
|
||||
void android_sound_unpause(int channel) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "unpause", "(I)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
}
|
||||
|
||||
int android_sound_get_pos(int channel) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "get_pos", "(I)I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
return (*env)->CallStaticIntMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
}
|
||||
|
||||
int android_sound_get_length(int channel) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/RenPySound");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "get_length", "(I)I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
return (*env)->CallStaticIntMethod(
|
||||
env, cls, mid,
|
||||
channel);
|
||||
}
|
61
MovedRecipes/android/src/android/activity.py
Normal file
61
MovedRecipes/android/src/android/activity.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
from jnius import PythonJavaClass, java_method, autoclass, cast
|
||||
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
|
||||
|
||||
_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
|
||||
|
||||
_callbacks = {
|
||||
'on_new_intent': [],
|
||||
'on_activity_result': [] }
|
||||
|
||||
class NewIntentListener(PythonJavaClass):
|
||||
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
|
||||
__javacontext__ = 'app'
|
||||
|
||||
def __init__(self, callback, **kwargs):
|
||||
super(NewIntentListener, self).__init__(**kwargs)
|
||||
self.callback = callback
|
||||
|
||||
@java_method('(Landroid/content/Intent;)V')
|
||||
def onNewIntent(self, intent):
|
||||
self.callback(intent)
|
||||
|
||||
|
||||
class ActivityResultListener(PythonJavaClass):
|
||||
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$ActivityResultListener']
|
||||
__javacontext__ = 'app'
|
||||
|
||||
def __init__(self, callback):
|
||||
super(ActivityResultListener, self).__init__()
|
||||
self.callback = callback
|
||||
|
||||
@java_method('(IILandroid/content/Intent;)V')
|
||||
def onActivityResult(self, requestCode, resultCode, intent):
|
||||
self.callback(requestCode, resultCode, intent)
|
||||
|
||||
|
||||
def bind(**kwargs):
|
||||
for event, callback in kwargs.items():
|
||||
if event not in _callbacks:
|
||||
raise Exception('Unknown {!r} event'.format(event))
|
||||
elif event == 'on_new_intent':
|
||||
listener = NewIntentListener(callback)
|
||||
_activity.registerNewIntentListener(listener)
|
||||
_callbacks[event].append(listener)
|
||||
elif event == 'on_activity_result':
|
||||
listener = ActivityResultListener(callback)
|
||||
_activity.registerActivityResultListener(listener)
|
||||
_callbacks[event].append(listener)
|
||||
|
||||
def unbind(**kwargs):
|
||||
for event, callback in kwargs.items():
|
||||
if event not in _callbacks:
|
||||
raise Exception('Unknown {!r} event'.format(event))
|
||||
else:
|
||||
for listener in _callbacks[event][:]:
|
||||
if listener.callback == callback:
|
||||
_callbacks[event].remove(listener)
|
||||
if event == 'on_new_intent':
|
||||
_activity.unregisterNewIntentListener(listener)
|
||||
elif event == 'on_activity_result':
|
||||
_activity.unregisterActivityResultListener(listener)
|
||||
|
7
MovedRecipes/android/src/android/billing.py
Normal file
7
MovedRecipes/android/src/android/billing.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
'''
|
||||
Android Billing API
|
||||
===================
|
||||
|
||||
'''
|
||||
|
||||
from android._android_billing import *
|
79
MovedRecipes/android/src/android/broadcast.py
Normal file
79
MovedRecipes/android/src/android/broadcast.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
# -------------------------------------------------------------------
|
||||
# Broadcast receiver bridge
|
||||
|
||||
from jnius import autoclass, PythonJavaClass, java_method
|
||||
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
|
||||
|
||||
|
||||
class BroadcastReceiver(object):
|
||||
|
||||
class Callback(PythonJavaClass):
|
||||
__javainterfaces__ = [JNI_NAMESPACE + '/GenericBroadcastReceiverCallback']
|
||||
__javacontext__ = 'app'
|
||||
|
||||
def __init__(self, callback, *args, **kwargs):
|
||||
self.callback = callback
|
||||
PythonJavaClass.__init__(self, *args, **kwargs)
|
||||
|
||||
@java_method('(Landroid/content/Context;Landroid/content/Intent;)V')
|
||||
def onReceive(self, context, intent):
|
||||
self.callback(context, intent)
|
||||
|
||||
def __init__(self, callback, actions=None, categories=None):
|
||||
super(BroadcastReceiver, self).__init__()
|
||||
self.callback = callback
|
||||
|
||||
if not actions and not categories:
|
||||
raise Exception('You need to define at least actions or categories')
|
||||
|
||||
def _expand_partial_name(partial_name):
|
||||
if '.' in partial_name:
|
||||
return partial_name # Its actually a full dotted name
|
||||
else:
|
||||
name = 'ACTION_{}'.format(partial_name.upper())
|
||||
if not hasattr(Intent, name):
|
||||
raise Exception('The intent {} doesnt exist'.format(name))
|
||||
return getattr(Intent, name)
|
||||
|
||||
# resolve actions/categories first
|
||||
Intent = autoclass('android.content.Intent')
|
||||
resolved_actions = [_expand_partial_name(x) for x in actions or []]
|
||||
resolved_categories = [_expand_partial_name(x) for x in categories or []]
|
||||
|
||||
# resolve android API
|
||||
GenericBroadcastReceiver = autoclass(JAVA_NAMESPACE + '.GenericBroadcastReceiver')
|
||||
IntentFilter = autoclass('android.content.IntentFilter')
|
||||
HandlerThread = autoclass('android.os.HandlerThread')
|
||||
|
||||
# create a thread for handling events from the receiver
|
||||
self.handlerthread = HandlerThread('handlerthread')
|
||||
|
||||
# create a listener
|
||||
self.listener = BroadcastReceiver.Callback(self.callback)
|
||||
self.receiver = GenericBroadcastReceiver(self.listener)
|
||||
self.receiver_filter = IntentFilter()
|
||||
for x in resolved_actions:
|
||||
self.receiver_filter.addAction(x)
|
||||
for x in resolved_categories:
|
||||
self.receiver_filter.addCategory(x)
|
||||
|
||||
def start(self):
|
||||
Handler = autoclass('android.os.Handler')
|
||||
self.handlerthread.start()
|
||||
self.handler = Handler(self.handlerthread.getLooper())
|
||||
self.context.registerReceiver(self.receiver, self.receiver_filter, None,
|
||||
self.handler)
|
||||
|
||||
def stop(self):
|
||||
self.context.unregisterReceiver(self.receiver)
|
||||
self.handlerthread.quit()
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
from os import environ
|
||||
if 'PYTHON_SERVICE_ARGUMENT' in environ:
|
||||
PythonService = autoclass(JAVA_NAMESPACE + '.PythonService')
|
||||
return PythonService.mService
|
||||
PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
||||
return PythonActivity.mActivity
|
||||
|
309
MovedRecipes/android/src/android/mixer.py
Normal file
309
MovedRecipes/android/src/android/mixer.py
Normal file
|
@ -0,0 +1,309 @@
|
|||
# This module is, as much a possible, a clone of the pygame
|
||||
# mixer api.
|
||||
|
||||
import android._android_sound as sound
|
||||
import time
|
||||
import threading
|
||||
import os
|
||||
|
||||
condition = threading.Condition()
|
||||
|
||||
def periodic():
|
||||
for i in range(0, num_channels):
|
||||
if i in channels:
|
||||
channels[i].periodic()
|
||||
|
||||
num_channels = 8
|
||||
reserved_channels = 0
|
||||
|
||||
def init(frequency=22050, size=-16, channels=2, buffer=4096):
|
||||
return None
|
||||
|
||||
def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):
|
||||
return None
|
||||
|
||||
def quit():
|
||||
stop()
|
||||
return None
|
||||
|
||||
def stop():
|
||||
for i in range(0, num_channels):
|
||||
sound.stop(i)
|
||||
|
||||
def pause():
|
||||
for i in range(0, num_channels):
|
||||
sound.pause(i)
|
||||
|
||||
def unpause():
|
||||
for i in range(0, num_channels):
|
||||
sound.unpause(i)
|
||||
|
||||
def get_busy():
|
||||
for i in range(0, num_channels):
|
||||
if sound.busy(i):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def fadeout(time):
|
||||
# Fadeout doesn't work - it just immediately stops playback.
|
||||
stop()
|
||||
|
||||
|
||||
# A map from channel number to Channel object.
|
||||
channels = { }
|
||||
|
||||
def set_num_channels(count):
|
||||
global num_channels
|
||||
num_channels = count
|
||||
|
||||
def get_num_channels(count):
|
||||
return num_channels
|
||||
|
||||
def set_reserved(count):
|
||||
global reserved_channels
|
||||
reserved_channels = count
|
||||
|
||||
def find_channel(force=False):
|
||||
|
||||
busy = [ ]
|
||||
|
||||
for i in range(reserved_channels, num_channels):
|
||||
c = Channel(i)
|
||||
|
||||
if not c.get_busy():
|
||||
return c
|
||||
|
||||
busy.append(c)
|
||||
|
||||
if not force:
|
||||
return None
|
||||
|
||||
return min(busy, key=lambda x : x.play_time)
|
||||
|
||||
class ChannelImpl(object):
|
||||
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
self.loop = None
|
||||
self.queued = None
|
||||
|
||||
self.play_time = time.time()
|
||||
|
||||
def periodic(self):
|
||||
qd = sound.queue_depth(self.id)
|
||||
|
||||
if qd < 2:
|
||||
self.queued = None
|
||||
|
||||
if self.loop is not None and sound.queue_depth(self.id) < 2:
|
||||
self.queue(self.loop, loops=1)
|
||||
|
||||
|
||||
def play(self, s, loops=0, maxtime=0, fade_ms=0):
|
||||
if loops:
|
||||
self.loop = s
|
||||
|
||||
sound.play(self.id, s.file, s.serial)
|
||||
|
||||
self.play_time = time.time()
|
||||
|
||||
with condition:
|
||||
condition.notify()
|
||||
|
||||
def seek(self, position):
|
||||
sound.seek(self.id, position)
|
||||
|
||||
def stop(self):
|
||||
self.loop = None
|
||||
sound.stop(self.id)
|
||||
|
||||
def pause(self):
|
||||
sound.pause(self.id)
|
||||
|
||||
def unpause(self):
|
||||
sound.pause(self.id)
|
||||
|
||||
def fadeout(self, time):
|
||||
# No fadeout
|
||||
self.stop()
|
||||
|
||||
def set_volume(self, left, right=None):
|
||||
sound.set_volume(self.id, left)
|
||||
|
||||
def get_volume(self):
|
||||
return sound.get_volume(self.id)
|
||||
|
||||
def get_busy(self):
|
||||
return sound.busy(self.id)
|
||||
|
||||
def get_sound(self):
|
||||
is_busy = sound.busy(self.id)
|
||||
if not is_busy:
|
||||
return
|
||||
serial = sound.playing_name(self.id)
|
||||
if not serial:
|
||||
return
|
||||
return sounds.get(serial, None)
|
||||
|
||||
def queue(self, s):
|
||||
self.loop = None
|
||||
self.queued = s
|
||||
|
||||
sound.queue(self.id, s.what, s.serial)
|
||||
|
||||
with condition:
|
||||
condition.notify()
|
||||
|
||||
def get_queue(self):
|
||||
return self.queued
|
||||
|
||||
def get_pos(self):
|
||||
return sound.get_pos(self.id)/1000.
|
||||
|
||||
def get_length(self):
|
||||
return sound.get_length(self.id)/1000.
|
||||
|
||||
|
||||
def Channel(n):
|
||||
"""
|
||||
Gets the channel with the given number.
|
||||
"""
|
||||
|
||||
rv = channels.get(n, None)
|
||||
if rv is None:
|
||||
rv = ChannelImpl(n)
|
||||
channels[n] = rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
sound_serial = 0
|
||||
sounds = { }
|
||||
|
||||
class Sound(object):
|
||||
|
||||
def __init__(self, what):
|
||||
|
||||
# Doesn't support buffers.
|
||||
|
||||
global sound_serial
|
||||
|
||||
self._channel = None
|
||||
self._volume = 1.
|
||||
self.serial = str(sound_serial)
|
||||
sound_serial += 1
|
||||
|
||||
if isinstance(what, file):
|
||||
self.file = what
|
||||
else:
|
||||
self.file = file(os.path.abspath(what), "rb")
|
||||
|
||||
sounds[self.serial] = self
|
||||
|
||||
def play(self, loops=0, maxtime=0, fade_ms=0):
|
||||
# avoid new play if the sound is already playing
|
||||
# -> same behavior as standard pygame.
|
||||
if self._channel is not None:
|
||||
if self._channel.get_sound() is self:
|
||||
return
|
||||
self._channel = channel = find_channel(True)
|
||||
channel.set_volume(self._volume)
|
||||
channel.play(self, loops=loops)
|
||||
return channel
|
||||
|
||||
|
||||
def stop(self):
|
||||
for i in range(0, num_channels):
|
||||
if Channel(i).get_sound() is self:
|
||||
Channel(i).stop()
|
||||
|
||||
def fadeout(self, time):
|
||||
self.stop()
|
||||
|
||||
def set_volume(self, left, right=None):
|
||||
self._volume = left
|
||||
if self._channel:
|
||||
if self._channel.get_sound() is self:
|
||||
self._channel.set_volume(self._volume)
|
||||
|
||||
def get_volume(self):
|
||||
return self._volume
|
||||
|
||||
def get_num_channels(self):
|
||||
rv = 0
|
||||
|
||||
for i in range(0, num_channels):
|
||||
if Channel(i).get_sound() is self:
|
||||
rv += 1
|
||||
|
||||
return rv
|
||||
|
||||
def get_length(self):
|
||||
return 1.0
|
||||
|
||||
music_channel = Channel(256)
|
||||
music_sound = None
|
||||
|
||||
class music(object):
|
||||
|
||||
@staticmethod
|
||||
def load(filename):
|
||||
|
||||
music_channel.stop()
|
||||
|
||||
global music_sound
|
||||
music_sound = Sound(filename)
|
||||
|
||||
@staticmethod
|
||||
def play(loops=0, start=0.0):
|
||||
# No start.
|
||||
|
||||
music_channel.play(music_sound, loops=loops)
|
||||
|
||||
@staticmethod
|
||||
def rewind():
|
||||
music_channel.play(music_sound)
|
||||
|
||||
@staticmethod
|
||||
def seek(position):
|
||||
music_channel.seek(position)
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
music_channel.stop()
|
||||
|
||||
@staticmethod
|
||||
def pause():
|
||||
music_channel.pause()
|
||||
|
||||
@staticmethod
|
||||
def unpause():
|
||||
music_channel.unpause()
|
||||
|
||||
@staticmethod
|
||||
def fadeout(time):
|
||||
music_channel.fadeout(time)
|
||||
|
||||
@staticmethod
|
||||
def set_volume(value):
|
||||
music_channel.set_volume(value)
|
||||
|
||||
@staticmethod
|
||||
def get_volume():
|
||||
return music_channel.get_volume()
|
||||
|
||||
@staticmethod
|
||||
def get_busy():
|
||||
return music_channel.get_busy()
|
||||
|
||||
@staticmethod
|
||||
def get_pos():
|
||||
return music_channel.get_pos()
|
||||
|
||||
@staticmethod
|
||||
def queue(filename):
|
||||
return music_channel.queue(Sound(filename))
|
||||
|
||||
|
||||
|
48
MovedRecipes/android/src/android/runnable.py
Normal file
48
MovedRecipes/android/src/android/runnable.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
'''
|
||||
Runnable
|
||||
========
|
||||
|
||||
'''
|
||||
|
||||
from jnius import PythonJavaClass, java_method, autoclass
|
||||
from android.config import JAVA_NAMESPACE
|
||||
|
||||
# reference to the activity
|
||||
_PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
||||
|
||||
|
||||
class Runnable(PythonJavaClass):
|
||||
'''Wrapper around Java Runnable class. This class can be used to schedule a
|
||||
call of a Python function into the PythonActivity thread.
|
||||
'''
|
||||
|
||||
__javainterfaces__ = ['java/lang/Runnable']
|
||||
__runnables__ = []
|
||||
|
||||
def __init__(self, func):
|
||||
super(Runnable, self).__init__()
|
||||
self.func = func
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
Runnable.__runnables__.append(self)
|
||||
_PythonActivity.mActivity.runOnUiThread(self)
|
||||
|
||||
@java_method('()V')
|
||||
def run(self):
|
||||
try:
|
||||
self.func(*self.args, **self.kwargs)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
Runnable.__runnables__.remove(self)
|
||||
|
||||
def run_on_ui_thread(f):
|
||||
'''Decorator to create automatically a :class:`Runnable` object with the
|
||||
function. The function will be delayed and call into the Activity thread.
|
||||
'''
|
||||
def f2(*args, **kwargs):
|
||||
Runnable(f)(*args, **kwargs)
|
||||
return f2
|
34
MovedRecipes/android/src/setup.py
Executable file
34
MovedRecipes/android/src/setup.py
Executable file
|
@ -0,0 +1,34 @@
|
|||
from distutils.core import setup, Extension
|
||||
import os
|
||||
|
||||
library_dirs = ['libs/' + os.environ['ARCH']]
|
||||
lib_dict = {
|
||||
'pygame': ['sdl'],
|
||||
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
|
||||
}
|
||||
sdl_libs = lib_dict[os.environ['BOOTSTRAP']] if os.environ['BOOTSTRAP'] == 'sdl2' else []
|
||||
|
||||
renpy_sound = Extension('android._android_sound',
|
||||
['android/_android_sound.c', 'android/_android_sound_jni.c', ],
|
||||
libraries=sdl_libs + ['log'],
|
||||
library_dirs=library_dirs)
|
||||
|
||||
modules = [Extension('android._android',
|
||||
['android/_android.c', 'android/_android_jni.c'],
|
||||
libraries=sdl_libs + ['log'],
|
||||
library_dirs=library_dirs),
|
||||
Extension('android._android_billing',
|
||||
['android/_android_billing.c', 'android/_android_billing_jni.c'],
|
||||
libraries=['log'],
|
||||
library_dirs=library_dirs)]
|
||||
|
||||
if int(os.environ['IS_PYGAME']):
|
||||
modules.append(renpy_sound)
|
||||
|
||||
|
||||
setup(name='android',
|
||||
version='1.0',
|
||||
packages=['android'],
|
||||
package_dir={'android': 'android'},
|
||||
ext_modules=modules
|
||||
)
|
|
@ -87,171 +87,3 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
|||
|
||||
|
||||
recipe = AndroidRecipe()
|
||||
|
||||
'''
|
||||
from pythonforandroid.recipe import CythonRecipe, IncludedFilesBehaviour
|
||||
from pythonforandroid.util import current_directory
|
||||
from pythonforandroid.patching import will_build
|
||||
from pythonforandroid import logger
|
||||
|
||||
from os.path import join
|
||||
|
||||
|
||||
class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||
# name = 'android'
|
||||
version = None
|
||||
url = None
|
||||
|
||||
src_filename = 'src'
|
||||
|
||||
depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
|
||||
|
||||
config_env = {}
|
||||
|
||||
def get_recipe_env(self, arch):
|
||||
env = super(AndroidRecipe, self).get_recipe_env(arch)
|
||||
env.update(self.config_env)
|
||||
return env
|
||||
|
||||
def prebuild_arch(self, arch):
|
||||
super(AndroidRecipe, self).prebuild_arch(arch)
|
||||
|
||||
tpxi = 'DEF {} = {}\n'
|
||||
th = '#define {} {}\n'
|
||||
tpy = '{} = {}\n'
|
||||
|
||||
bootstrap = bootstrap_name = self.ctx.bootstrap.name
|
||||
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3', 'sdl2_gradle')
|
||||
is_pygame = bootstrap_name in ('pygame',)
|
||||
is_webview = bootstrap_name in ('webview',)
|
||||
is_lbry = bootstrap_name in ('lbry',)
|
||||
|
||||
if is_sdl2 or is_webview or is_lbry:
|
||||
if is_sdl2:
|
||||
bootstrap = 'sdl2'
|
||||
java_ns = 'org.kivy.android'
|
||||
jni_ns = 'org/kivy/android'
|
||||
elif is_pygame:
|
||||
java_ns = 'org.renpy.android'
|
||||
jni_ns = 'org/renpy/android'
|
||||
else:
|
||||
logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name))
|
||||
exit(1)
|
||||
|
||||
config = {
|
||||
'BOOTSTRAP': bootstrap,
|
||||
'IS_SDL2': int(is_sdl2),
|
||||
'IS_PYGAME': int(is_pygame),
|
||||
'PY2': int(will_build('python2')(self)),
|
||||
'JAVA_NAMESPACE': java_ns,
|
||||
'JNI_NAMESPACE': jni_ns,
|
||||
}
|
||||
|
||||
with current_directory(self.get_build_dir(arch.arch)):
|
||||
with open(join('android', 'config.pxi'), 'w') as fpxi:
|
||||
with open(join('android', 'config.h'), 'w') as fh:
|
||||
with open(join('android', 'config.py'), 'w') as fpy:
|
||||
for key, value in config.items():
|
||||
fpxi.write(tpxi.format(key, repr(value)))
|
||||
fpy.write(tpy.format(key, repr(value)))
|
||||
fh.write(th.format(key, value if isinstance(value, int)
|
||||
else '"{}"'.format(value)))
|
||||
self.config_env[key] = str(value)
|
||||
|
||||
if is_sdl2:
|
||||
fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
|
||||
fh.write('#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n')
|
||||
elif is_pygame:
|
||||
fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
|
||||
|
||||
|
||||
recipe = AndroidRecipe()
|
||||
'''
|
||||
|
||||
'''
|
||||
from pythonforandroid.recipe import CythonRecipe, Recipe, IncludedFilesBehaviour
|
||||
from pythonforandroid.util import current_directory
|
||||
from pythonforandroid.patching import will_build
|
||||
from pythonforandroid import logger
|
||||
|
||||
from os.path import join
|
||||
|
||||
|
||||
class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
|
||||
# name = 'android'
|
||||
version = None
|
||||
url = None
|
||||
|
||||
src_filename = 'src'
|
||||
|
||||
depends = [('pygame', 'sdl2', 'genericndkbuild'), ('python2', 'python3crystax')]
|
||||
|
||||
call_hostpython_via_targetpython = False
|
||||
|
||||
config_env = {}
|
||||
|
||||
def get_recipe_env(self, arch):
|
||||
env = super(AndroidRecipe, self).get_recipe_env(arch)
|
||||
env.update(self.config_env)
|
||||
|
||||
target_python = Recipe.get_recipe('python2', self.ctx).get_build_dir(arch.arch)
|
||||
env['PYTHON_ROOT'] = join(target_python, 'python-install')
|
||||
env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
|
||||
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + ' -lpython2.7'
|
||||
|
||||
return env
|
||||
|
||||
def prebuild_arch(self, arch):
|
||||
super(AndroidRecipe, self).prebuild_arch(arch)
|
||||
|
||||
tpxi = 'DEF {} = {}\n'
|
||||
th = '#define {} {}\n'
|
||||
tpy = '{} = {}\n'
|
||||
|
||||
bootstrap = bootstrap_name = self.ctx.bootstrap.name
|
||||
is_sdl2 = bootstrap_name in ('sdl2', 'sdl2python3')
|
||||
is_pygame = bootstrap_name in ('pygame',)
|
||||
is_webview = bootstrap_name in ('webview')
|
||||
is_lbry = bootstrap_name in ('lbry')
|
||||
|
||||
if is_sdl2 or is_webview or is_lbry:
|
||||
if is_sdl2:
|
||||
bootstrap = 'sdl2'
|
||||
java_ns = 'org.kivy.android'
|
||||
jni_ns = 'org/kivy/android'
|
||||
elif is_pygame:
|
||||
java_ns = 'org.renpy.android'
|
||||
jni_ns = 'org/renpy/android'
|
||||
else:
|
||||
logger.error('unsupported bootstrap for android recipe: {}'.format(bootstrap_name))
|
||||
exit(1)
|
||||
|
||||
config = {
|
||||
'BOOTSTRAP': bootstrap,
|
||||
'IS_SDL2': int(is_sdl2),
|
||||
'IS_PYGAME': int(is_pygame),
|
||||
'PY2': int(will_build('python2')(self)),
|
||||
'JAVA_NAMESPACE': java_ns,
|
||||
'JNI_NAMESPACE': jni_ns,
|
||||
}
|
||||
|
||||
with current_directory(self.get_build_dir(arch.arch)):
|
||||
with open(join('android', 'config.pxi'), 'w') as fpxi:
|
||||
with open(join('android', 'config.h'), 'w') as fh:
|
||||
with open(join('android', 'config.py'), 'w') as fpy:
|
||||
for key, value in config.items():
|
||||
fpxi.write(tpxi.format(key, repr(value)))
|
||||
fpy.write(tpy.format(key, repr(value)))
|
||||
fh.write(th.format(key, value if isinstance(value, int)
|
||||
else '"{}"'.format(value)))
|
||||
self.config_env[key] = str(value)
|
||||
|
||||
if is_sdl2:
|
||||
fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n')
|
||||
fh.write('#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n')
|
||||
elif is_pygame:
|
||||
fh.write('JNIEnv *SDL_ANDROID_GetJNIEnv(void);\n')
|
||||
|
||||
|
||||
recipe = AndroidRecipe()
|
||||
'''
|
|
@ -5,4 +5,4 @@ Android module
|
|||
'''
|
||||
|
||||
# legacy import
|
||||
from android._android import *
|
||||
from android._android import * # noqa: F401, F403
|
||||
|
|
|
@ -2,22 +2,6 @@
|
|||
|
||||
include "config.pxi"
|
||||
|
||||
IF BOOTSTRAP == 'pygame':
|
||||
cdef extern int SDL_ANDROID_CheckPause()
|
||||
cdef extern void SDL_ANDROID_WaitForResume() nogil
|
||||
cdef extern void SDL_ANDROID_MapKey(int scancode, int keysym)
|
||||
|
||||
def check_pause():
|
||||
return SDL_ANDROID_CheckPause()
|
||||
|
||||
def wait_for_resume():
|
||||
android_accelerometer_enable(False)
|
||||
SDL_ANDROID_WaitForResume()
|
||||
android_accelerometer_enable(accelerometer_enabled)
|
||||
|
||||
def map_key(scancode, keysym):
|
||||
SDL_ANDROID_MapKey(scancode, keysym)
|
||||
|
||||
# Android keycodes.
|
||||
KEYCODE_UNKNOWN = 0
|
||||
KEYCODE_SOFT_LEFT = 1
|
||||
|
@ -175,13 +159,11 @@ api_version = autoclass('android.os.Build$VERSION').SDK_INT
|
|||
version_codes = autoclass('android.os.Build$VERSION_CODES')
|
||||
|
||||
|
||||
python_act = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
||||
Rect = autoclass('android.graphics.Rect')
|
||||
python_act = autoclass(ACTIVITY_CLASS_NAME)
|
||||
Rect = autoclass(u'android.graphics.Rect')
|
||||
mActivity = python_act.mActivity
|
||||
if mActivity:
|
||||
# PyGame backend already has the listener so adding
|
||||
# one here leads to a crash/too much cpu usage.
|
||||
# SDL2 now does noe need the listener so there is
|
||||
# SDL2 now does not need the listener so there is
|
||||
# no point adding a processor intensive layout listenere here.
|
||||
height = 0
|
||||
def get_keyboard_height():
|
||||
|
@ -274,42 +256,6 @@ def get_buildinfo():
|
|||
binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE
|
||||
return binfo
|
||||
|
||||
IF IS_PYGAME:
|
||||
# Activate input - required to receive input events.
|
||||
cdef extern void android_activate_input()
|
||||
|
||||
def init():
|
||||
android_activate_input()
|
||||
|
||||
# Action send
|
||||
cdef extern void android_action_send(char*, char*, char*, char*, char*)
|
||||
def action_send(mimetype, filename=None, subject=None, text=None,
|
||||
chooser_title=None):
|
||||
cdef char *j_mimetype = <bytes>mimetype
|
||||
cdef char *j_filename = NULL
|
||||
cdef char *j_subject = NULL
|
||||
cdef char *j_text = NULL
|
||||
cdef char *j_chooser_title = NULL
|
||||
if filename is not None:
|
||||
j_filename = <bytes>filename
|
||||
if subject is not None:
|
||||
j_subject = <bytes>subject
|
||||
if text is not None:
|
||||
j_text = <bytes>text
|
||||
if chooser_title is not None:
|
||||
j_chooser_title = <bytes>chooser_title
|
||||
android_action_send(j_mimetype, j_filename, j_subject, j_text,
|
||||
j_chooser_title)
|
||||
|
||||
cdef extern int android_checkstop()
|
||||
cdef extern void android_ackstop()
|
||||
|
||||
def check_stop():
|
||||
return android_checkstop()
|
||||
|
||||
def ack_stop():
|
||||
android_ackstop()
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# URL Opening.
|
||||
def open_url(url):
|
||||
|
@ -332,19 +278,31 @@ class AndroidBrowser(object):
|
|||
return open_url(url)
|
||||
|
||||
import webbrowser
|
||||
webbrowser.register('android', AndroidBrowser, None, -1)
|
||||
webbrowser.register('android', AndroidBrowser)
|
||||
|
||||
|
||||
def start_service(title="Background Service",
|
||||
description="", arg="",
|
||||
as_foreground=True):
|
||||
# Legacy None value support (for old function signature style):
|
||||
if title is None:
|
||||
title = "Background Service"
|
||||
if description is None:
|
||||
description = ""
|
||||
if arg is None:
|
||||
arg = ""
|
||||
|
||||
# Start service:
|
||||
mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
|
||||
if as_foreground:
|
||||
mActivity.start_service(
|
||||
title, description, arg
|
||||
)
|
||||
else:
|
||||
mActivity.start_service_not_as_foreground(
|
||||
title, description, arg
|
||||
)
|
||||
|
||||
cdef extern void android_start_service(char *, char *, char *)
|
||||
def start_service(title=None, description=None, arg=None):
|
||||
cdef char *j_title = NULL
|
||||
cdef char *j_description = NULL
|
||||
if title is not None:
|
||||
j_title = <bytes>title
|
||||
if description is not None:
|
||||
j_description = <bytes>description
|
||||
if arg is not None:
|
||||
j_arg = <bytes>arg
|
||||
android_start_service(j_title, j_description, j_arg)
|
||||
|
||||
cdef extern void android_stop_service()
|
||||
def stop_service():
|
||||
|
|
|
@ -15,7 +15,7 @@ class BillingService(object):
|
|||
BILLING_TYPE_SUBSCRIPTION = 'subs'
|
||||
|
||||
def __init__(self, callback):
|
||||
super(BillingService, self).__init__()
|
||||
super().__init__()
|
||||
self.callback = callback
|
||||
self.purchased_items = None
|
||||
android_billing_service_start()
|
||||
|
|
|
@ -201,146 +201,6 @@ void android_get_buildinfo() {
|
|||
}
|
||||
}
|
||||
|
||||
#if IS_PYGAME
|
||||
void android_activate_input(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "activateInput", "()V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
int android_checkstop(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "checkStop", "()I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
return (*env)->CallStaticIntMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
void android_ackstop(void) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "ackStop", "()I");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
(*env)->CallStaticIntMethod(env, cls, mid);
|
||||
}
|
||||
|
||||
void android_action_send(char *mimeType, char *filename, char *subject, char *text, char *chooser_title) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/Action");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "send",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
jstring j_mimeType = (*env)->NewStringUTF(env, mimeType);
|
||||
jstring j_filename = NULL;
|
||||
jstring j_subject = NULL;
|
||||
jstring j_text = NULL;
|
||||
jstring j_chooser_title = NULL;
|
||||
if ( filename != NULL )
|
||||
j_filename = (*env)->NewStringUTF(env, filename);
|
||||
if ( subject != NULL )
|
||||
j_subject = (*env)->NewStringUTF(env, subject);
|
||||
if ( text != NULL )
|
||||
j_text = (*env)->NewStringUTF(env, text);
|
||||
if ( chooser_title != NULL )
|
||||
j_chooser_title = (*env)->NewStringUTF(env, text);
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
j_mimeType, j_filename, j_subject, j_text,
|
||||
j_chooser_title);
|
||||
}
|
||||
|
||||
void android_open_url(char *url) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, "org/renpy/android/SDLSurfaceView");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "openUrl", "(Ljava/lang/String;)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
PUSH_FRAME;
|
||||
|
||||
(*env)->CallStaticVoidMethod(
|
||||
env, cls, mid,
|
||||
(*env)->NewStringUTF(env, url)
|
||||
);
|
||||
|
||||
POP_FRAME;
|
||||
}
|
||||
#endif // IS_PYGAME
|
||||
|
||||
void android_start_service(char *title, char *description, char *arg) {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
static jmethodID mid = NULL;
|
||||
|
||||
if (env == NULL) {
|
||||
env = SDL_ANDROID_GetJNIEnv();
|
||||
aassert(env);
|
||||
cls = (*env)->FindClass(env, JNI_NAMESPACE "/PythonActivity");
|
||||
aassert(cls);
|
||||
mid = (*env)->GetStaticMethodID(env, cls, "start_service",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||
aassert(mid);
|
||||
}
|
||||
|
||||
jstring j_title = NULL;
|
||||
jstring j_description = NULL;
|
||||
jstring j_arg = NULL;
|
||||
if ( title != 0 )
|
||||
j_title = (*env)->NewStringUTF(env, title);
|
||||
if ( description != 0 )
|
||||
j_description = (*env)->NewStringUTF(env, description);
|
||||
if ( arg != 0 )
|
||||
j_arg = (*env)->NewStringUTF(env, arg);
|
||||
|
||||
(*env)->CallStaticVoidMethod(env, cls, mid, j_title, j_description, j_arg);
|
||||
}
|
||||
|
||||
void android_stop_service() {
|
||||
static JNIEnv *env = NULL;
|
||||
static jclass *cls = NULL;
|
||||
|
|
67
recipes/android/src/android/_ctypes_library_finder.py
Normal file
67
recipes/android/src/android/_ctypes_library_finder.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def get_activity_lib_dir(activity_name):
|
||||
from jnius import autoclass
|
||||
|
||||
# Get the actual activity instance:
|
||||
activity_class = autoclass(activity_name)
|
||||
if activity_class is None:
|
||||
return None
|
||||
activity = None
|
||||
if hasattr(activity_class, "mActivity") and \
|
||||
activity_class.mActivity is not None:
|
||||
activity = activity_class.mActivity
|
||||
elif hasattr(activity_class, "mService") and \
|
||||
activity_class.mService is not None:
|
||||
activity = activity_class.mService
|
||||
if activity is None:
|
||||
return None
|
||||
|
||||
# Extract the native lib dir from the activity instance:
|
||||
package_name = activity.getApplicationContext().getPackageName()
|
||||
manager = activity.getApplicationContext().getPackageManager()
|
||||
manager_class = autoclass("android.content.pm.PackageManager")
|
||||
native_lib_dir = manager.getApplicationInfo(
|
||||
package_name, manager_class.GET_SHARED_LIBRARY_FILES
|
||||
).nativeLibraryDir
|
||||
return native_lib_dir
|
||||
|
||||
|
||||
def does_libname_match_filename(search_name, file_path):
|
||||
# Filter file names so given search_name="mymodule" we match one of:
|
||||
# mymodule.so (direct name + .so)
|
||||
# libmymodule.so (added lib prefix)
|
||||
# mymodule.arm64.so (added dot-separated middle parts)
|
||||
# mymodule.so.1.3.4 (added dot-separated version tail)
|
||||
# and all above (all possible combinations)
|
||||
import re
|
||||
file_name = os.path.basename(file_path)
|
||||
return (re.match(r"^(lib)?" + re.escape(search_name) +
|
||||
r"\.(.*\.)?so(\.[0-9]+)*$", file_name) is not None)
|
||||
|
||||
|
||||
def find_library(name):
|
||||
# Obtain all places for native libraries:
|
||||
if sys.maxsize > 2**32: # 64bit-build
|
||||
lib_search_dirs = ["/system/lib64", "/system/lib"]
|
||||
else:
|
||||
lib_search_dirs = ["/system/lib"]
|
||||
lib_dir_1 = get_activity_lib_dir("org.kivy.android.PythonActivity")
|
||||
if lib_dir_1 is not None:
|
||||
lib_search_dirs.insert(0, lib_dir_1)
|
||||
lib_dir_2 = get_activity_lib_dir("org.kivy.android.PythonService")
|
||||
if lib_dir_2 is not None and lib_dir_2 not in lib_search_dirs:
|
||||
lib_search_dirs.insert(0, lib_dir_2)
|
||||
|
||||
# Now scan the lib dirs:
|
||||
for lib_dir in [ldir for ldir in lib_search_dirs if os.path.exists(ldir)]:
|
||||
filelist = [
|
||||
f for f in os.listdir(lib_dir)
|
||||
if does_libname_match_filename(name, f)
|
||||
]
|
||||
if len(filelist) > 0:
|
||||
return os.path.join(lib_dir, filelist[0])
|
||||
return None
|
|
@ -1,18 +1,20 @@
|
|||
from jnius import PythonJavaClass, java_method, autoclass, cast
|
||||
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
|
||||
from jnius import PythonJavaClass, autoclass, java_method
|
||||
from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
|
||||
|
||||
_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity
|
||||
_activity = autoclass(ACTIVITY_CLASS_NAME).mActivity
|
||||
|
||||
_callbacks = {
|
||||
'on_new_intent': [],
|
||||
'on_activity_result': [] }
|
||||
'on_activity_result': [],
|
||||
}
|
||||
|
||||
|
||||
class NewIntentListener(PythonJavaClass):
|
||||
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener']
|
||||
__javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$NewIntentListener']
|
||||
__javacontext__ = 'app'
|
||||
|
||||
def __init__(self, callback, **kwargs):
|
||||
super(NewIntentListener, self).__init__(**kwargs)
|
||||
super().__init__(**kwargs)
|
||||
self.callback = callback
|
||||
|
||||
@java_method('(Landroid/content/Intent;)V')
|
||||
|
@ -21,11 +23,11 @@ class NewIntentListener(PythonJavaClass):
|
|||
|
||||
|
||||
class ActivityResultListener(PythonJavaClass):
|
||||
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$ActivityResultListener']
|
||||
__javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$ActivityResultListener']
|
||||
__javacontext__ = 'app'
|
||||
|
||||
def __init__(self, callback):
|
||||
super(ActivityResultListener, self).__init__()
|
||||
super().__init__()
|
||||
self.callback = callback
|
||||
|
||||
@java_method('(IILandroid/content/Intent;)V')
|
||||
|
@ -46,6 +48,7 @@ def bind(**kwargs):
|
|||
_activity.registerActivityResultListener(listener)
|
||||
_callbacks[event].append(listener)
|
||||
|
||||
|
||||
def unbind(**kwargs):
|
||||
for event, callback in kwargs.items():
|
||||
if event not in _callbacks:
|
||||
|
@ -59,3 +62,153 @@ def unbind(**kwargs):
|
|||
elif event == 'on_activity_result':
|
||||
_activity.unregisterActivityResultListener(listener)
|
||||
|
||||
|
||||
# Keep a reference to all the registered classes so that python doesn't
|
||||
# garbage collect them.
|
||||
_lifecycle_callbacks = set()
|
||||
|
||||
|
||||
class ActivityLifecycleCallbacks(PythonJavaClass):
|
||||
"""Callback class for handling PythonActivity lifecycle transitions"""
|
||||
|
||||
__javainterfaces__ = ['android/app/Application$ActivityLifecycleCallbacks']
|
||||
|
||||
def __init__(self, callbacks):
|
||||
super().__init__()
|
||||
|
||||
# It would be nice to use keyword arguments, but PythonJavaClass
|
||||
# doesn't allow that in its __cinit__ method.
|
||||
if not isinstance(callbacks, dict):
|
||||
raise ValueError('callbacks must be a dict instance')
|
||||
self.callbacks = callbacks
|
||||
|
||||
def _callback(self, name, *args):
|
||||
func = self.callbacks.get(name)
|
||||
if func:
|
||||
return func(*args)
|
||||
|
||||
@java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
|
||||
def onActivityCreated(self, activity, savedInstanceState):
|
||||
self._callback('onActivityCreated', activity, savedInstanceState)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityDestroyed(self, activity):
|
||||
self._callback('onActivityDestroyed', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPaused(self, activity):
|
||||
self._callback('onActivityPaused', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
|
||||
def onActivityPostCreated(self, activity, savedInstanceState):
|
||||
self._callback('onActivityPostCreated', activity, savedInstanceState)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPostDestroyed(self, activity):
|
||||
self._callback('onActivityPostDestroyed', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPostPaused(self, activity):
|
||||
self._callback('onActivityPostPaused', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPostResumed(self, activity):
|
||||
self._callback('onActivityPostResumed', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
|
||||
def onActivityPostSaveInstanceState(self, activity, outState):
|
||||
self._callback('onActivityPostSaveInstanceState', activity, outState)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPostStarted(self, activity):
|
||||
self._callback('onActivityPostStarted', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPostStopped(self, activity):
|
||||
self._callback('onActivityPostStopped', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
|
||||
def onActivityPreCreated(self, activity, savedInstanceState):
|
||||
self._callback('onActivityPreCreated', activity, savedInstanceState)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPreDestroyed(self, activity):
|
||||
self._callback('onActivityPreDestroyed', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPrePaused(self, activity):
|
||||
self._callback('onActivityPrePaused', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPreResumed(self, activity):
|
||||
self._callback('onActivityPreResumed', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
|
||||
def onActivityPreSaveInstanceState(self, activity, outState):
|
||||
self._callback('onActivityPreSaveInstanceState', activity, outState)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPreStarted(self, activity):
|
||||
self._callback('onActivityPreStarted', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityPreStopped(self, activity):
|
||||
self._callback('onActivityPreStopped', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityResumed(self, activity):
|
||||
self._callback('onActivityResumed', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;Landroid/os/Bundle;)V')
|
||||
def onActivitySaveInstanceState(self, activity, outState):
|
||||
self._callback('onActivitySaveInstanceState', activity, outState)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityStarted(self, activity):
|
||||
self._callback('onActivityStarted', activity)
|
||||
|
||||
@java_method('(Landroid/app/Activity;)V')
|
||||
def onActivityStopped(self, activity):
|
||||
self._callback('onActivityStopped', activity)
|
||||
|
||||
|
||||
def register_activity_lifecycle_callbacks(**callbacks):
|
||||
"""Register ActivityLifecycleCallbacks instance
|
||||
|
||||
The callbacks are supplied as keyword arguments corresponding to the
|
||||
Application.ActivityLifecycleCallbacks methods such as
|
||||
onActivityStarted. See the ActivityLifecycleCallbacks documentation
|
||||
for the signature of each method.
|
||||
|
||||
The ActivityLifecycleCallbacks instance is returned so it can be
|
||||
supplied to unregister_activity_lifecycle_callbacks if needed.
|
||||
"""
|
||||
instance = ActivityLifecycleCallbacks(callbacks)
|
||||
_lifecycle_callbacks.add(instance)
|
||||
|
||||
# Use the registerActivityLifecycleCallbacks method from the
|
||||
# Activity class if it's available (API 29) since it guarantees the
|
||||
# callbacks will only be run for that activity. Otherwise, fallback
|
||||
# to the method on the Application class (API 14). In practice there
|
||||
# should be no difference since p4a applications only have a single
|
||||
# activity.
|
||||
if hasattr(_activity, 'registerActivityLifecycleCallbacks'):
|
||||
_activity.registerActivityLifecycleCallbacks(instance)
|
||||
else:
|
||||
app = _activity.getApplication()
|
||||
app.registerActivityLifecycleCallbacks(instance)
|
||||
return instance
|
||||
|
||||
|
||||
def unregister_activity_lifecycle_callbacks(instance):
|
||||
"""Unregister ActivityLifecycleCallbacks instance"""
|
||||
if hasattr(_activity, 'unregisterActivityLifecycleCallbacks'):
|
||||
_activity.unregisterActivityLifecycleCallbacks(instance)
|
||||
else:
|
||||
app = _activity.getApplication()
|
||||
app.unregisterActivityLifecycleCallbacks(instance)
|
||||
|
||||
try:
|
||||
_lifecycle_callbacks.remove(instance)
|
||||
except KeyError:
|
||||
pass
|
||||
|
|
|
@ -3,5 +3,3 @@ Android Billing API
|
|||
===================
|
||||
|
||||
'''
|
||||
|
||||
from android._android_billing import *
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Broadcast receiver bridge
|
||||
|
||||
from jnius import autoclass, PythonJavaClass, java_method
|
||||
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE
|
||||
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE, ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
|
||||
|
||||
|
||||
class BroadcastReceiver(object):
|
||||
|
@ -20,7 +20,7 @@ class BroadcastReceiver(object):
|
|||
self.callback(context, intent)
|
||||
|
||||
def __init__(self, callback, actions=None, categories=None):
|
||||
super(BroadcastReceiver, self).__init__()
|
||||
super().__init__()
|
||||
self.callback = callback
|
||||
|
||||
if not actions and not categories:
|
||||
|
@ -61,8 +61,8 @@ class BroadcastReceiver(object):
|
|||
Handler = autoclass('android.os.Handler')
|
||||
self.handlerthread.start()
|
||||
self.handler = Handler(self.handlerthread.getLooper())
|
||||
self.context.registerReceiver(self.receiver, self.receiver_filter, None,
|
||||
self.handler)
|
||||
self.context.registerReceiver(
|
||||
self.receiver, self.receiver_filter, None, self.handler)
|
||||
|
||||
def stop(self):
|
||||
self.context.unregisterReceiver(self.receiver)
|
||||
|
@ -72,8 +72,7 @@ class BroadcastReceiver(object):
|
|||
def context(self):
|
||||
from os import environ
|
||||
if 'PYTHON_SERVICE_ARGUMENT' in environ:
|
||||
PythonService = autoclass(JAVA_NAMESPACE + '.PythonService')
|
||||
PythonService = autoclass(SERVICE_CLASS_NAME)
|
||||
return PythonService.mService
|
||||
PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
||||
PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
|
||||
return PythonActivity.mActivity
|
||||
|
||||
|
|
9
recipes/android/src/android/loadingscreen.py
Normal file
9
recipes/android/src/android/loadingscreen.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
from jnius import autoclass
|
||||
|
||||
from android.config import ACTIVITY_CLASS_NAME
|
||||
|
||||
|
||||
def hide_loading_screen():
|
||||
mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
|
||||
mActivity.removeLoadingScreen()
|
|
@ -8,36 +8,45 @@ import os
|
|||
|
||||
condition = threading.Condition()
|
||||
|
||||
|
||||
def periodic():
|
||||
for i in range(0, num_channels):
|
||||
if i in channels:
|
||||
channels[i].periodic()
|
||||
|
||||
|
||||
num_channels = 8
|
||||
reserved_channels = 0
|
||||
|
||||
|
||||
def init(frequency=22050, size=-16, channels=2, buffer=4096):
|
||||
return None
|
||||
|
||||
|
||||
def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):
|
||||
return None
|
||||
|
||||
|
||||
def quit():
|
||||
stop()
|
||||
return None
|
||||
|
||||
|
||||
def stop():
|
||||
for i in range(0, num_channels):
|
||||
sound.stop(i)
|
||||
|
||||
|
||||
def pause():
|
||||
for i in range(0, num_channels):
|
||||
sound.pause(i)
|
||||
|
||||
|
||||
def unpause():
|
||||
for i in range(0, num_channels):
|
||||
sound.unpause(i)
|
||||
|
||||
|
||||
def get_busy():
|
||||
for i in range(0, num_channels):
|
||||
if sound.busy(i):
|
||||
|
@ -45,6 +54,7 @@ def get_busy():
|
|||
|
||||
return False
|
||||
|
||||
|
||||
def fadeout(time):
|
||||
# Fadeout doesn't work - it just immediately stops playback.
|
||||
stop()
|
||||
|
@ -53,17 +63,21 @@ def fadeout(time):
|
|||
# A map from channel number to Channel object.
|
||||
channels = {}
|
||||
|
||||
|
||||
def set_num_channels(count):
|
||||
global num_channels
|
||||
num_channels = count
|
||||
|
||||
|
||||
def get_num_channels(count):
|
||||
return num_channels
|
||||
|
||||
|
||||
def set_reserved(count):
|
||||
global reserved_channels
|
||||
reserved_channels = count
|
||||
|
||||
|
||||
def find_channel(force=False):
|
||||
|
||||
busy = []
|
||||
|
@ -79,7 +93,10 @@ def find_channel(force=False):
|
|||
if not force:
|
||||
return None
|
||||
|
||||
return min(busy, key=lambda x : x.play_time)
|
||||
busy.sort(key=lambda x: x.play_time)
|
||||
|
||||
return busy[0]
|
||||
|
||||
|
||||
class ChannelImpl(object):
|
||||
|
||||
|
@ -99,7 +116,6 @@ class ChannelImpl(object):
|
|||
if self.loop is not None and sound.queue_depth(self.id) < 2:
|
||||
self.queue(self.loop, loops=1)
|
||||
|
||||
|
||||
def play(self, s, loops=0, maxtime=0, fade_ms=0):
|
||||
if loops:
|
||||
self.loop = s
|
||||
|
@ -181,6 +197,7 @@ def Channel(n):
|
|||
sound_serial = 0
|
||||
sounds = {}
|
||||
|
||||
|
||||
class Sound(object):
|
||||
|
||||
def __init__(self, what):
|
||||
|
@ -194,10 +211,10 @@ class Sound(object):
|
|||
self.serial = str(sound_serial)
|
||||
sound_serial += 1
|
||||
|
||||
if isinstance(what, file):
|
||||
if isinstance(what, file): # noqa F821
|
||||
self.file = what
|
||||
else:
|
||||
self.file = file(os.path.abspath(what), "rb")
|
||||
self.file = file(os.path.abspath(what), "rb") # noqa F821
|
||||
|
||||
sounds[self.serial] = self
|
||||
|
||||
|
@ -212,7 +229,6 @@ class Sound(object):
|
|||
channel.play(self, loops=loops)
|
||||
return channel
|
||||
|
||||
|
||||
def stop(self):
|
||||
for i in range(0, num_channels):
|
||||
if Channel(i).get_sound() is self:
|
||||
|
@ -242,9 +258,11 @@ class Sound(object):
|
|||
def get_length(self):
|
||||
return 1.0
|
||||
|
||||
|
||||
music_channel = Channel(256)
|
||||
music_sound = None
|
||||
|
||||
|
||||
class music(object):
|
||||
|
||||
@staticmethod
|
||||
|
@ -304,6 +322,3 @@ class music(object):
|
|||
@staticmethod
|
||||
def queue(filename):
|
||||
return music_channel.queue(Sound(filename))
|
||||
|
||||
|
||||
|
||||
|
|
618
recipes/android/src/android/permissions.py
Normal file
618
recipes/android/src/android/permissions.py
Normal file
|
@ -0,0 +1,618 @@
|
|||
import threading
|
||||
|
||||
try:
|
||||
from jnius import autoclass, PythonJavaClass, java_method
|
||||
except ImportError:
|
||||
# To allow importing by build/manifest-creating code without
|
||||
# pyjnius being present:
|
||||
def autoclass(item):
|
||||
raise RuntimeError("pyjnius not available")
|
||||
|
||||
|
||||
from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
|
||||
|
||||
|
||||
class Permission:
|
||||
ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"
|
||||
ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION"
|
||||
ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"
|
||||
ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_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_ADVERTISE = (
|
||||
"android.permission.BLUETOOTH_ADVERTISE"
|
||||
)
|
||||
BLUETOOTH_CONNECT = (
|
||||
"android.permission.BLUETOOTH_CONNECT"
|
||||
)
|
||||
BLUETOOTH_SCAN = (
|
||||
"android.permission.BLUETOOTH_SCAN"
|
||||
)
|
||||
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"
|
||||
)
|
||||
NEARBY_WIFI_DEVICES = (
|
||||
"android.permission.NEARBY_WIFI_DEVICES"
|
||||
)
|
||||
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"
|
||||
)
|
||||
POST_NOTIFICATIONS = (
|
||||
"android.permission.POST_NOTIFICATIONS"
|
||||
)
|
||||
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_MEDIA_AUDIO = (
|
||||
"android.permission.READ_MEDIA_AUDIO"
|
||||
)
|
||||
READ_MEDIA_IMAGES = (
|
||||
"android.permission.READ_MEDIA_IMAGES"
|
||||
)
|
||||
READ_MEDIA_VIDEO = (
|
||||
"android.permission.READ_MEDIA_VIDEO"
|
||||
)
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
PERMISSION_GRANTED = 0
|
||||
PERMISSION_DENIED = -1
|
||||
|
||||
|
||||
class _onRequestPermissionsCallback(PythonJavaClass):
|
||||
"""Callback class for registering a Python callback from
|
||||
onRequestPermissionsResult in PythonActivity.
|
||||
"""
|
||||
__javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$PermissionsCallback']
|
||||
__javacontext__ = 'app'
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
super().__init__()
|
||||
|
||||
@java_method('(I[Ljava/lang/String;[I)V')
|
||||
def onRequestPermissionsResult(self, requestCode,
|
||||
permissions, grantResults):
|
||||
self.func(requestCode, permissions, grantResults)
|
||||
|
||||
|
||||
class _RequestPermissionsManager:
|
||||
"""Internal class for requesting Android permissions.
|
||||
|
||||
Permissions are requested through the method 'request_permissions' which
|
||||
accepts a list of permissions and an optional callback.
|
||||
|
||||
Any callback will asynchronously receive arguments from
|
||||
onRequestPermissionsResult on PythonActivity after requestPermissions is
|
||||
called.
|
||||
|
||||
The callback supplied must accept two arguments: 'permissions' and
|
||||
'grantResults' (as supplied to onPermissionsCallbackResult).
|
||||
|
||||
Note that for SDK_INT < 23, run-time permissions are not required, and so
|
||||
the callback will be called immediately.
|
||||
|
||||
The attribute '_java_callback' is initially None, but is set when the first
|
||||
permissions request is made. It is set to an instance of
|
||||
onRequestPermissionsCallback, which allows the Java callback to be
|
||||
propagated to the class method 'python_callback'. This is then, in turn,
|
||||
used to call an application callback if provided to request_permissions.
|
||||
|
||||
The attribute '_callback_id' is incremented with each call to
|
||||
request_permissions which has a callback (the value '1' is used for any
|
||||
call which does not pass a callback). This is passed to requestCode in
|
||||
the Java call, and used to identify (via the _callbacks dictionary)
|
||||
the matching call.
|
||||
"""
|
||||
_SDK_INT = None
|
||||
_java_callback = None
|
||||
_callbacks = {1: None}
|
||||
_callback_id = 1
|
||||
# Lock to prevent multiple calls to request_permissions being handled
|
||||
# simultaneously (as incrementing _callback_id is not atomic)
|
||||
_lock = threading.Lock()
|
||||
|
||||
@classmethod
|
||||
def register_callback(cls):
|
||||
"""Register Java callback for requestPermissions."""
|
||||
cls._java_callback = _onRequestPermissionsCallback(cls.python_callback)
|
||||
mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
|
||||
mActivity.addPermissionsCallback(cls._java_callback)
|
||||
|
||||
@classmethod
|
||||
def request_permissions(cls, permissions, callback=None):
|
||||
"""Requests Android permissions from PythonActivity.
|
||||
If 'callback' is supplied, the request is made with a new requestCode
|
||||
and the callback is stored in the _callbacks dict. When a Java callback
|
||||
with the matching requestCode is received, callback will be called
|
||||
with arguments of 'permissions' and 'grant_results'.
|
||||
"""
|
||||
if not cls._SDK_INT:
|
||||
# Get the Android build version and store it
|
||||
VERSION = autoclass('android.os.Build$VERSION')
|
||||
cls.SDK_INT = VERSION.SDK_INT
|
||||
if cls.SDK_INT < 23:
|
||||
# No run-time permissions needed, return immediately.
|
||||
if callback:
|
||||
callback(permissions, [True for x in permissions])
|
||||
return
|
||||
# Request permissions
|
||||
with cls._lock:
|
||||
if not cls._java_callback:
|
||||
cls.register_callback()
|
||||
mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
|
||||
if not callback:
|
||||
mActivity.requestPermissions(permissions)
|
||||
else:
|
||||
cls._callback_id += 1
|
||||
mActivity.requestPermissionsWithRequestCode(
|
||||
permissions, cls._callback_id)
|
||||
cls._callbacks[cls._callback_id] = callback
|
||||
|
||||
@classmethod
|
||||
def python_callback(cls, requestCode, permissions, grantResults):
|
||||
"""Calls the relevant callback with arguments of 'permissions'
|
||||
and 'grantResults'."""
|
||||
# Convert from Android codes to True/False
|
||||
grant_results = [x == PERMISSION_GRANTED for x in grantResults]
|
||||
if cls._callbacks.get(requestCode):
|
||||
cls._callbacks[requestCode](permissions, grant_results)
|
||||
|
||||
|
||||
# Public API methods for requesting permissions
|
||||
|
||||
def request_permissions(permissions, callback=None):
|
||||
"""Requests Android permissions.
|
||||
|
||||
Args:
|
||||
permissions (str): A list of permissions to requests (str)
|
||||
callback (callable, optional): A function to call when the request
|
||||
is completed (callable)
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Notes:
|
||||
|
||||
Permission strings can be imported from the 'Permission' class in this
|
||||
module. For example:
|
||||
|
||||
from android import Permission
|
||||
permissions_list = [Permission.CAMERA,
|
||||
Permission.WRITE_EXTERNAL_STORAGE]
|
||||
|
||||
See the p4a source file 'permissions.py' for a list of valid permission
|
||||
strings (pythonforandroid/recipes/android/src/android/permissions.py).
|
||||
|
||||
Any callback supplied must accept two arguments:
|
||||
permissions (list of str): A list of permission strings
|
||||
grant_results (list of bool): A list of bools indicating whether the
|
||||
respective permission was granted.
|
||||
See Android documentation for onPermissionsCallbackResult for
|
||||
further information.
|
||||
|
||||
Note that if the request is interupted the callback may contain an empty
|
||||
list of permissions, without permissions being granted; the App should
|
||||
check that each permission requested has been granted.
|
||||
|
||||
Also note that when calling request_permission on SDK_INT < 23, the
|
||||
callback will be returned immediately as requesting permissions is not
|
||||
required.
|
||||
"""
|
||||
_RequestPermissionsManager.request_permissions(permissions, callback)
|
||||
|
||||
|
||||
def request_permission(permission, callback=None):
|
||||
request_permissions([permission], callback)
|
||||
|
||||
|
||||
def check_permission(permission):
|
||||
"""Checks if an app holds the passed permission.
|
||||
|
||||
Args:
|
||||
- permission An Android permission (str)
|
||||
|
||||
Returns:
|
||||
bool: True if the app holds the permission given, False otherwise.
|
||||
"""
|
||||
mActivity = autoclass(ACTIVITY_CLASS_NAME).mActivity
|
||||
result = bool(mActivity.checkCurrentPermission(
|
||||
permission + ""
|
||||
))
|
||||
return result
|
|
@ -1,14 +1,17 @@
|
|||
'''
|
||||
Runnable
|
||||
========
|
||||
|
||||
'''
|
||||
|
||||
from jnius import PythonJavaClass, java_method, autoclass
|
||||
from android.config import JAVA_NAMESPACE
|
||||
from android.config import ACTIVITY_CLASS_NAME
|
||||
|
||||
# reference to the activity
|
||||
_PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity')
|
||||
# Reference to the activity
|
||||
_PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
|
||||
|
||||
# Cache of functions table. In older Android versions the number of JNI references
|
||||
# is limited, so by caching them we avoid running out.
|
||||
__functionstable__ = {}
|
||||
|
||||
|
||||
class Runnable(PythonJavaClass):
|
||||
|
@ -20,7 +23,7 @@ class Runnable(PythonJavaClass):
|
|||
__runnables__ = []
|
||||
|
||||
def __init__(self, func):
|
||||
super(Runnable, self).__init__()
|
||||
super().__init__()
|
||||
self.func = func
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
|
@ -33,16 +36,23 @@ class Runnable(PythonJavaClass):
|
|||
def run(self):
|
||||
try:
|
||||
self.func(*self.args, **self.kwargs)
|
||||
except:
|
||||
except: # noqa E722
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
Runnable.__runnables__.remove(self)
|
||||
|
||||
|
||||
def run_on_ui_thread(f):
|
||||
'''Decorator to create automatically a :class:`Runnable` object with the
|
||||
function. The function will be delayed and call into the Activity thread.
|
||||
'''
|
||||
if f not in __functionstable__:
|
||||
rfunction = Runnable(f) # store the runnable function
|
||||
__functionstable__[f] = {"rfunction": rfunction}
|
||||
rfunction = __functionstable__[f]["rfunction"]
|
||||
|
||||
def f2(*args, **kwargs):
|
||||
Runnable(f)(*args, **kwargs)
|
||||
rfunction(*args, **kwargs)
|
||||
|
||||
return f2
|
||||
|
|
117
recipes/android/src/android/storage.py
Normal file
117
recipes/android/src/android/storage.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
from jnius import autoclass, cast
|
||||
import os
|
||||
|
||||
from android.config import ACTIVITY_CLASS_NAME, SERVICE_CLASS_NAME
|
||||
|
||||
|
||||
Environment = autoclass('android.os.Environment')
|
||||
File = autoclass('java.io.File')
|
||||
|
||||
|
||||
def _android_has_is_removable_func():
|
||||
VERSION = autoclass('android.os.Build$VERSION')
|
||||
return (VERSION.SDK_INT >= 24)
|
||||
|
||||
|
||||
def _get_sdcard_path():
|
||||
""" Internal function to return getExternalStorageDirectory()
|
||||
path. This is internal because it may either return the internal,
|
||||
or an external sd card, depending on the device.
|
||||
Use primary_external_storage_path()
|
||||
or secondary_external_storage_path() instead which try to
|
||||
distinguish this properly.
|
||||
"""
|
||||
return (
|
||||
Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
)
|
||||
|
||||
|
||||
def _get_activity():
|
||||
"""
|
||||
Retrieves the activity from `PythonActivity` fallback to `PythonService`.
|
||||
"""
|
||||
PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
|
||||
activity = PythonActivity.mActivity
|
||||
if activity is None:
|
||||
# assume we're running from the background service
|
||||
PythonService = autoclass(SERVICE_CLASS_NAME)
|
||||
activity = PythonService.mService
|
||||
return activity
|
||||
|
||||
|
||||
def app_storage_path():
|
||||
""" Locate the built-in device storage used for this app only.
|
||||
|
||||
This storage is APP-SPECIFIC, and not visible to other apps.
|
||||
It will be wiped when your app is uninstalled.
|
||||
|
||||
Returns directory path to storage.
|
||||
"""
|
||||
activity = _get_activity()
|
||||
currentActivity = cast('android.app.Activity', activity)
|
||||
context = cast('android.content.ContextWrapper',
|
||||
currentActivity.getApplicationContext())
|
||||
file_p = cast('java.io.File', context.getFilesDir())
|
||||
return os.path.normpath(os.path.abspath(
|
||||
file_p.getAbsolutePath().replace("/", os.path.sep)))
|
||||
|
||||
|
||||
def primary_external_storage_path():
|
||||
""" Locate the built-in device storage that user can see via file browser.
|
||||
Often found at: /sdcard/
|
||||
|
||||
This is storage is SHARED, and visible to other apps and the user.
|
||||
It will remain untouched when your app is uninstalled.
|
||||
|
||||
Returns directory path to storage.
|
||||
|
||||
WARNING: You need storage permissions to access this storage.
|
||||
"""
|
||||
if _android_has_is_removable_func():
|
||||
sdpath = _get_sdcard_path()
|
||||
# Apparently this can both return primary (built-in) or
|
||||
# secondary (removable) external storage depending on the device,
|
||||
# therefore check that we got what we wanted:
|
||||
if not Environment.isExternalStorageRemovable(File(sdpath)):
|
||||
return sdpath
|
||||
if "EXTERNAL_STORAGE" in os.environ:
|
||||
return os.environ["EXTERNAL_STORAGE"]
|
||||
raise RuntimeError(
|
||||
"unexpectedly failed to determine " +
|
||||
"primary external storage path"
|
||||
)
|
||||
|
||||
|
||||
def secondary_external_storage_path():
|
||||
""" Locate the external SD Card storage, which may not be present.
|
||||
Often found at: /sdcard/External_SD/
|
||||
|
||||
This storage is SHARED, visible to other apps, and may not be
|
||||
be available if the user didn't put in an external SD card.
|
||||
It will remain untouched when your app is uninstalled.
|
||||
|
||||
Returns None if not found, otherwise path to storage.
|
||||
|
||||
WARNING: You need storage permissions to access this storage.
|
||||
If it is not writable and presents as empty even with
|
||||
permissions, then the external sd card may not be present.
|
||||
"""
|
||||
if _android_has_is_removable_func:
|
||||
# See if getExternalStorageDirectory() returns secondary ext storage:
|
||||
sdpath = _get_sdcard_path()
|
||||
# Apparently this can both return primary (built-in) or
|
||||
# secondary (removable) external storage depending on the device,
|
||||
# therefore check that we got what we wanted:
|
||||
if Environment.isExternalStorageRemovable(File(sdpath)):
|
||||
if os.path.exists(sdpath):
|
||||
return sdpath
|
||||
|
||||
# See if we can take a guess based on environment variables:
|
||||
p = None
|
||||
if "SECONDARY_STORAGE" in os.environ:
|
||||
p = os.environ["SECONDARY_STORAGE"]
|
||||
elif "EXTERNAL_SDCARD_STORAGE" in os.environ:
|
||||
p = os.environ["EXTERNAL_SDCARD_STORAGE"]
|
||||
if p is not None and os.path.exists(p):
|
||||
return p
|
||||
return None
|
|
@ -3,15 +3,9 @@ import os
|
|||
|
||||
library_dirs = ['libs/' + os.environ['ARCH']]
|
||||
lib_dict = {
|
||||
'pygame': ['sdl'],
|
||||
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
|
||||
}
|
||||
sdl_libs = lib_dict[os.environ['BOOTSTRAP']] if os.environ['BOOTSTRAP'] == 'sdl2' else []
|
||||
|
||||
renpy_sound = Extension('android._android_sound',
|
||||
['android/_android_sound.c', 'android/_android_sound_jni.c', ],
|
||||
libraries=sdl_libs + ['log'],
|
||||
library_dirs=library_dirs)
|
||||
sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], ['main'])
|
||||
|
||||
modules = [Extension('android._android',
|
||||
['android/_android.c', 'android/_android_jni.c'],
|
||||
|
@ -22,10 +16,6 @@ modules = [Extension('android._android',
|
|||
libraries=['log'],
|
||||
library_dirs=library_dirs)]
|
||||
|
||||
if int(os.environ['IS_PYGAME']):
|
||||
modules.append(renpy_sound)
|
||||
|
||||
|
||||
setup(name='android',
|
||||
version='1.0',
|
||||
packages=['android'],
|
||||
|
|
Loading…
Reference in a new issue