Skip to content

Commit

Permalink
Merge pull request #108 from b3b/service
Browse files Browse the repository at this point in the history
Issue #107 solution candidate (Android service)
  • Loading branch information
tito committed Apr 24, 2013
2 parents 63e5c47 + fab00c3 commit 354f6aa
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 2 deletions.
66 changes: 66 additions & 0 deletions docs/source/android.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,72 @@ Please have a look into the code and you are very welcome to contribute to
this documentation.


Android Service
---------------

Service part of the application is controlled through the class :class:`AndroidService`.

.. module:: android

.. class:: AndroidService(title, description)

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.

.. method:: start(arg)

Start the service.

:Parameters:
`arg`: str, default to ''
Argument to pass to a service,
through environment variable ``PYTHON_SERVICE_ARGUMENT``.

.. method:: stop()

Stop the service.

Application activity part example, ``main.py``:

.. code-block:: python
from android import AndroidService
...
class ServiceExample(App):
...
def start_service(self):
self.service = AndroidService('Sevice example', 'service is running')
self.service.start('Hello From Service')
def stop_service(self):
self.service.stop()
Application service part example, ``service/main.py``:

.. code-block:: python
import os
import time
# get the argument passed
arg = os.getenv('PYTHON_SERVICE_ARGUMENT')
while True:
# this will print 'Hello From Service' continually, even when application is switched
print arg
time.sleep(1)
How it's working without PyJNIus
--------------------------------

Expand Down
47 changes: 47 additions & 0 deletions recipes/android/src/android.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,50 @@ class AndroidBrowser(object):
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()
43 changes: 43 additions & 0 deletions recipes/android/src/android_jni.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,46 @@ void android_open_url(char *url) {
POP_FRAME;
}

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, "org/renpy/android/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, "org/renpy/android/PythonActivity");
aassert(cls);
mid = (*env)->GetStaticMethodID(env, cls, "stop_service", "()V");
aassert(mid);
}

(*env)->CallStaticVoidMethod(env, cls, mid);
}
8 changes: 8 additions & 0 deletions src/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,19 @@ def make_package(args):
else:
intent_filters = ''

# Figure out if application has service part
service = False
if args.dir:
service_main = join(realpath(args.dir), 'service', 'main.py')
if os.path.exists(service_main):
service = True

# Render the various templates into control files.
render(
'AndroidManifest.tmpl.xml',
'AndroidManifest.xml',
args=args,
service=service,
url_scheme=url_scheme,
intent_filters=intent_filters,
manifest_extra=manifest_extra,
Expand Down
2 changes: 1 addition & 1 deletion src/jni/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ SDL_VIDEO_RENDER_RESIZE := 0

COMPILED_LIBRARIES := sdl_ttf sdl_image sdl_mixer

APPLICATION_ADDITIONAL_CFLAGS := -finline-functions -O2
APPLICATION_ADDITIONAL_CFLAGS := -finline-functions -O2 -DSDL_JAVA_PACKAGE_PATH=$(SDL_JAVA_PACKAGE_PATH)

APPLICATION_ADDITIONAL_LDFLAGS := -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions

Expand Down
13 changes: 13 additions & 0 deletions src/jni/application/python/jniwrapperstuff.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

/* JNI-C++ wrapper stuff */
#ifndef _JNI_WRAPPER_STUFF_H_
#define _JNI_WRAPPER_STUFF_H_

#ifndef SDL_JAVA_PACKAGE_PATH
#error You have to define SDL_JAVA_PACKAGE_PATH to your package path with dots replaced with underscores, for example "com_example_SanAngeles"
#endif
#define JAVA_EXPORT_NAME2(name,package) Java_##package##_##name
#define JAVA_EXPORT_NAME1(name,package) JAVA_EXPORT_NAME2(name,package)
#define JAVA_EXPORT_NAME(name) JAVA_EXPORT_NAME1(name,SDL_JAVA_PACKAGE_PATH)

#endif
30 changes: 30 additions & 0 deletions src/jni/application/python/start.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <jni.h>
#include "SDL.h"
#include "android/log.h"
#include "jniwrapperstuff.h"

#define LOG(x) __android_log_write(ANDROID_LOG_INFO, "python", (x))

Expand Down Expand Up @@ -139,4 +141,32 @@ int main(int argc, char **argv) {
return ret;
}

JNIEXPORT void JNICALL JAVA_EXPORT_NAME(PythonService_nativeStart) ( JNIEnv* env, jobject thiz,
jstring j_android_private,
jstring j_android_argument,
jstring j_python_home,
jstring j_python_path,
jstring j_arg )
{
jboolean iscopy;
const char *android_private = (*env)->GetStringUTFChars(env, j_android_private, &iscopy);
const char *android_argument = (*env)->GetStringUTFChars(env, j_android_argument, &iscopy);
const char *python_home = (*env)->GetStringUTFChars(env, j_python_home, &iscopy);
const char *python_path = (*env)->GetStringUTFChars(env, j_python_path, &iscopy);
const char *arg = (*env)->GetStringUTFChars(env, j_arg, &iscopy);

setenv("ANDROID_PRIVATE", android_private, 1);
setenv("ANDROID_ARGUMENT", android_argument, 1);
setenv("PYTHONOPTIMIZE", "2", 1);
setenv("PYTHONHOME", python_home, 1);
setenv("PYTHONPATH", python_path, 1);
setenv("PYTHON_SERVICE_ARGUMENT", arg, 1);

char *argv[] = { "service" };
/* ANDROID_ARGUMENT points to service subdir,
* so main() will run main.py from this dir
*/
main(1, argv);
}

#endif
24 changes: 23 additions & 1 deletion src/src/org/renpy/android/PythonActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ protected void onCreate(Bundle savedInstanceState) {
//
// Otherwise, we use the public data, if we have it, or the
// private data if we do not.
if (getIntent().getAction().equals("org.renpy.LAUNCH")) {
if (getIntent() != null && getIntent().getAction() != null &&
getIntent().getAction().equals("org.renpy.LAUNCH")) {
mPath = new File(getIntent().getData().getSchemeSpecificPart());

Project p = Project.scanDirectory(mPath);
Expand Down Expand Up @@ -316,5 +317,26 @@ protected void onDestroy() {
//Log.i(TAG, "on destroy (exit1)");
System.exit(0);
}

public static void start_service(String serviceTitle, String serviceDescription,
String pythonServiceArgument) {
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath();
String filesDirectory = PythonActivity.mActivity.mPath.getAbsolutePath();
serviceIntent.putExtra("androidPrivate", argument);
serviceIntent.putExtra("androidArgument", filesDirectory);
serviceIntent.putExtra("pythonHome", argument);
serviceIntent.putExtra("pythonPath", argument + ":" + filesDirectory + "/lib");
serviceIntent.putExtra("serviceTitle", serviceTitle);
serviceIntent.putExtra("serviceDescription", serviceDescription);
serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument);
PythonActivity.mActivity.startService(serviceIntent);
}

public static void stop_service() {
Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class);
PythonActivity.mActivity.stopService(serviceIntent);
}

}

Loading

0 comments on commit 354f6aa

Please sign in to comment.