try stock android w/ lbry modification

This commit is contained in:
zeppi 2022-12-02 22:09:28 -05:00
parent 13fc3a7ff2
commit 2926957670
30 changed files with 3232 additions and 426 deletions

View 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()
'''

View file

View file

@ -0,0 +1,8 @@
'''
Android module
==============
'''
# legacy import
from android._android import *

View 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()

View 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)

View 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;
}

View 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);
}

View 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

View 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);
}

View 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)

View file

@ -0,0 +1,7 @@
'''
Android Billing API
===================
'''
from android._android_billing import *

View 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

View 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))

View 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

View 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
)

View file

@ -87,171 +87,3 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe):
recipe = AndroidRecipe() 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()
'''

View file

@ -5,4 +5,4 @@ Android module
''' '''
# legacy import # legacy import
from android._android import * from android._android import * # noqa: F401, F403

View file

@ -2,22 +2,6 @@
include "config.pxi" 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. # Android keycodes.
KEYCODE_UNKNOWN = 0 KEYCODE_UNKNOWN = 0
KEYCODE_SOFT_LEFT = 1 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') version_codes = autoclass('android.os.Build$VERSION_CODES')
python_act = autoclass(JAVA_NAMESPACE + '.PythonActivity') python_act = autoclass(ACTIVITY_CLASS_NAME)
Rect = autoclass('android.graphics.Rect') Rect = autoclass(u'android.graphics.Rect')
mActivity = python_act.mActivity mActivity = python_act.mActivity
if mActivity: if mActivity:
# PyGame backend already has the listener so adding # SDL2 now does not need the listener so there is
# 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. # no point adding a processor intensive layout listenere here.
height = 0 height = 0
def get_keyboard_height(): def get_keyboard_height():
@ -274,42 +256,6 @@ def get_buildinfo():
binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE binfo.VERSION_RELEASE = BUILD_VERSION_RELEASE
return binfo 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. # URL Opening.
def open_url(url): def open_url(url):
@ -332,19 +278,31 @@ class AndroidBrowser(object):
return open_url(url) return open_url(url)
import webbrowser 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() cdef extern void android_stop_service()
def stop_service(): def stop_service():

View file

@ -15,7 +15,7 @@ class BillingService(object):
BILLING_TYPE_SUBSCRIPTION = 'subs' BILLING_TYPE_SUBSCRIPTION = 'subs'
def __init__(self, callback): def __init__(self, callback):
super(BillingService, self).__init__() super().__init__()
self.callback = callback self.callback = callback
self.purchased_items = None self.purchased_items = None
android_billing_service_start() android_billing_service_start()

View file

@ -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() { void android_stop_service() {
static JNIEnv *env = NULL; static JNIEnv *env = NULL;
static jclass *cls = NULL; static jclass *cls = NULL;

View 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

View file

@ -1,18 +1,20 @@
from jnius import PythonJavaClass, java_method, autoclass, cast from jnius import PythonJavaClass, autoclass, java_method
from android.config import JAVA_NAMESPACE, JNI_NAMESPACE from android.config import ACTIVITY_CLASS_NAME, ACTIVITY_CLASS_NAMESPACE
_activity = autoclass(JAVA_NAMESPACE + '.PythonActivity').mActivity _activity = autoclass(ACTIVITY_CLASS_NAME).mActivity
_callbacks = { _callbacks = {
'on_new_intent': [], 'on_new_intent': [],
'on_activity_result': [] } 'on_activity_result': [],
}
class NewIntentListener(PythonJavaClass): class NewIntentListener(PythonJavaClass):
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$NewIntentListener'] __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$NewIntentListener']
__javacontext__ = 'app' __javacontext__ = 'app'
def __init__(self, callback, **kwargs): def __init__(self, callback, **kwargs):
super(NewIntentListener, self).__init__(**kwargs) super().__init__(**kwargs)
self.callback = callback self.callback = callback
@java_method('(Landroid/content/Intent;)V') @java_method('(Landroid/content/Intent;)V')
@ -21,11 +23,11 @@ class NewIntentListener(PythonJavaClass):
class ActivityResultListener(PythonJavaClass): class ActivityResultListener(PythonJavaClass):
__javainterfaces__ = [JNI_NAMESPACE + '/PythonActivity$ActivityResultListener'] __javainterfaces__ = [ACTIVITY_CLASS_NAMESPACE + '$ActivityResultListener']
__javacontext__ = 'app' __javacontext__ = 'app'
def __init__(self, callback): def __init__(self, callback):
super(ActivityResultListener, self).__init__() super().__init__()
self.callback = callback self.callback = callback
@java_method('(IILandroid/content/Intent;)V') @java_method('(IILandroid/content/Intent;)V')
@ -46,6 +48,7 @@ def bind(**kwargs):
_activity.registerActivityResultListener(listener) _activity.registerActivityResultListener(listener)
_callbacks[event].append(listener) _callbacks[event].append(listener)
def unbind(**kwargs): def unbind(**kwargs):
for event, callback in kwargs.items(): for event, callback in kwargs.items():
if event not in _callbacks: if event not in _callbacks:
@ -59,3 +62,153 @@ def unbind(**kwargs):
elif event == 'on_activity_result': elif event == 'on_activity_result':
_activity.unregisterActivityResultListener(listener) _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

View file

@ -3,5 +3,3 @@ Android Billing API
=================== ===================
''' '''
from android._android_billing import *

View file

@ -2,7 +2,7 @@
# Broadcast receiver bridge # Broadcast receiver bridge
from jnius import autoclass, PythonJavaClass, java_method 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): class BroadcastReceiver(object):
@ -20,7 +20,7 @@ class BroadcastReceiver(object):
self.callback(context, intent) self.callback(context, intent)
def __init__(self, callback, actions=None, categories=None): def __init__(self, callback, actions=None, categories=None):
super(BroadcastReceiver, self).__init__() super().__init__()
self.callback = callback self.callback = callback
if not actions and not categories: if not actions and not categories:
@ -28,7 +28,7 @@ class BroadcastReceiver(object):
def _expand_partial_name(partial_name): def _expand_partial_name(partial_name):
if '.' in partial_name: if '.' in partial_name:
return partial_name # Its actually a full dotted name return partial_name # Its actually a full dotted name
else: else:
name = 'ACTION_{}'.format(partial_name.upper()) name = 'ACTION_{}'.format(partial_name.upper())
if not hasattr(Intent, name): if not hasattr(Intent, name):
@ -61,8 +61,8 @@ class BroadcastReceiver(object):
Handler = autoclass('android.os.Handler') Handler = autoclass('android.os.Handler')
self.handlerthread.start() self.handlerthread.start()
self.handler = Handler(self.handlerthread.getLooper()) self.handler = Handler(self.handlerthread.getLooper())
self.context.registerReceiver(self.receiver, self.receiver_filter, None, self.context.registerReceiver(
self.handler) self.receiver, self.receiver_filter, None, self.handler)
def stop(self): def stop(self):
self.context.unregisterReceiver(self.receiver) self.context.unregisterReceiver(self.receiver)
@ -72,8 +72,7 @@ class BroadcastReceiver(object):
def context(self): def context(self):
from os import environ from os import environ
if 'PYTHON_SERVICE_ARGUMENT' in environ: if 'PYTHON_SERVICE_ARGUMENT' in environ:
PythonService = autoclass(JAVA_NAMESPACE + '.PythonService') PythonService = autoclass(SERVICE_CLASS_NAME)
return PythonService.mService return PythonService.mService
PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity') PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
return PythonActivity.mActivity return PythonActivity.mActivity

View 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()

View file

@ -8,36 +8,45 @@ import os
condition = threading.Condition() condition = threading.Condition()
def periodic(): def periodic():
for i in range(0, num_channels): for i in range(0, num_channels):
if i in channels: if i in channels:
channels[i].periodic() channels[i].periodic()
num_channels = 8 num_channels = 8
reserved_channels = 0 reserved_channels = 0
def init(frequency=22050, size=-16, channels=2, buffer=4096): def init(frequency=22050, size=-16, channels=2, buffer=4096):
return None return None
def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096): def pre_init(frequency=22050, size=-16, channels=2, buffersize=4096):
return None return None
def quit(): def quit():
stop() stop()
return None return None
def stop(): def stop():
for i in range(0, num_channels): for i in range(0, num_channels):
sound.stop(i) sound.stop(i)
def pause(): def pause():
for i in range(0, num_channels): for i in range(0, num_channels):
sound.pause(i) sound.pause(i)
def unpause(): def unpause():
for i in range(0, num_channels): for i in range(0, num_channels):
sound.unpause(i) sound.unpause(i)
def get_busy(): def get_busy():
for i in range(0, num_channels): for i in range(0, num_channels):
if sound.busy(i): if sound.busy(i):
@ -45,28 +54,33 @@ def get_busy():
return False return False
def fadeout(time): def fadeout(time):
# Fadeout doesn't work - it just immediately stops playback. # Fadeout doesn't work - it just immediately stops playback.
stop() stop()
# A map from channel number to Channel object. # A map from channel number to Channel object.
channels = { } channels = {}
def set_num_channels(count): def set_num_channels(count):
global num_channels global num_channels
num_channels = count num_channels = count
def get_num_channels(count): def get_num_channels(count):
return num_channels return num_channels
def set_reserved(count): def set_reserved(count):
global reserved_channels global reserved_channels
reserved_channels = count reserved_channels = count
def find_channel(force=False): def find_channel(force=False):
busy = [ ] busy = []
for i in range(reserved_channels, num_channels): for i in range(reserved_channels, num_channels):
c = Channel(i) c = Channel(i)
@ -79,7 +93,10 @@ def find_channel(force=False):
if not force: if not force:
return None 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): class ChannelImpl(object):
@ -99,7 +116,6 @@ class ChannelImpl(object):
if self.loop is not None and sound.queue_depth(self.id) < 2: if self.loop is not None and sound.queue_depth(self.id) < 2:
self.queue(self.loop, loops=1) self.queue(self.loop, loops=1)
def play(self, s, loops=0, maxtime=0, fade_ms=0): def play(self, s, loops=0, maxtime=0, fade_ms=0):
if loops: if loops:
self.loop = s self.loop = s
@ -179,7 +195,8 @@ def Channel(n):
sound_serial = 0 sound_serial = 0
sounds = { } sounds = {}
class Sound(object): class Sound(object):
@ -194,10 +211,10 @@ class Sound(object):
self.serial = str(sound_serial) self.serial = str(sound_serial)
sound_serial += 1 sound_serial += 1
if isinstance(what, file): if isinstance(what, file): # noqa F821
self.file = what self.file = what
else: else:
self.file = file(os.path.abspath(what), "rb") self.file = file(os.path.abspath(what), "rb") # noqa F821
sounds[self.serial] = self sounds[self.serial] = self
@ -212,7 +229,6 @@ class Sound(object):
channel.play(self, loops=loops) channel.play(self, loops=loops)
return channel return channel
def stop(self): def stop(self):
for i in range(0, num_channels): for i in range(0, num_channels):
if Channel(i).get_sound() is self: if Channel(i).get_sound() is self:
@ -242,9 +258,11 @@ class Sound(object):
def get_length(self): def get_length(self):
return 1.0 return 1.0
music_channel = Channel(256) music_channel = Channel(256)
music_sound = None music_sound = None
class music(object): class music(object):
@staticmethod @staticmethod
@ -304,6 +322,3 @@ class music(object):
@staticmethod @staticmethod
def queue(filename): def queue(filename):
return music_channel.queue(Sound(filename)) return music_channel.queue(Sound(filename))

View 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

View file

@ -1,14 +1,17 @@
''' '''
Runnable Runnable
======== ========
''' '''
from jnius import PythonJavaClass, java_method, autoclass from jnius import PythonJavaClass, java_method, autoclass
from android.config import JAVA_NAMESPACE from android.config import ACTIVITY_CLASS_NAME
# reference to the activity # Reference to the activity
_PythonActivity = autoclass(JAVA_NAMESPACE + '.PythonActivity') _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): class Runnable(PythonJavaClass):
@ -20,7 +23,7 @@ class Runnable(PythonJavaClass):
__runnables__ = [] __runnables__ = []
def __init__(self, func): def __init__(self, func):
super(Runnable, self).__init__() super().__init__()
self.func = func self.func = func
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
@ -33,16 +36,23 @@ class Runnable(PythonJavaClass):
def run(self): def run(self):
try: try:
self.func(*self.args, **self.kwargs) self.func(*self.args, **self.kwargs)
except: except: # noqa E722
import traceback import traceback
traceback.print_exc() traceback.print_exc()
Runnable.__runnables__.remove(self) Runnable.__runnables__.remove(self)
def run_on_ui_thread(f): def run_on_ui_thread(f):
'''Decorator to create automatically a :class:`Runnable` object with the '''Decorator to create automatically a :class:`Runnable` object with the
function. The function will be delayed and call into the Activity thread. 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): def f2(*args, **kwargs):
Runnable(f)(*args, **kwargs) rfunction(*args, **kwargs)
return f2 return f2

View 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

View file

@ -3,15 +3,9 @@ import os
library_dirs = ['libs/' + os.environ['ARCH']] library_dirs = ['libs/' + os.environ['ARCH']]
lib_dict = { lib_dict = {
'pygame': ['sdl'],
'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf'] 'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf']
} }
sdl_libs = lib_dict[os.environ['BOOTSTRAP']] if os.environ['BOOTSTRAP'] == 'sdl2' else [] sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], ['main'])
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', modules = [Extension('android._android',
['android/_android.c', 'android/_android_jni.c'], ['android/_android.c', 'android/_android_jni.c'],
@ -22,10 +16,6 @@ modules = [Extension('android._android',
libraries=['log'], libraries=['log'],
library_dirs=library_dirs)] library_dirs=library_dirs)]
if int(os.environ['IS_PYGAME']):
modules.append(renpy_sound)
setup(name='android', setup(name='android',
version='1.0', version='1.0',
packages=['android'], packages=['android'],