Skip to content

Commit 6d02b8c

Browse files
committed
Merge branch 'billing-api' of git://github.com/bob-the-hamster/python-for-android
2 parents 1bb5427 + 697e5a6 commit 6d02b8c

21 files changed

+3091
-9
lines changed

docs/source/android.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,3 +579,70 @@ It has several differences from the pygame mixer:
579579
present.
580580

581581

582+
583+
android_billing
584+
~~~~~~~~~~~~~~~
585+
586+
.. module:: android_billing
587+
588+
This billing module give an access to the `In-App Billing <http://developer.android.com/guide/google/play/billing/billing_overview.html>`_:
589+
590+
#. `Setup a test accounts <http://developer.android.com/guide/google/play/billing/billing_admin.html#billing-testing-setup>`, and get your Public Key
591+
#. Export your public key::
592+
593+
export BILLING_PUBKEY="Your public key here"
594+
595+
#. `Setup some In-App product <http://developer.android.com/guide/google/play/billing/billing_admin.html>`_ to buy. Let's say you've created a product with the id "org.kivy.gopremium"
596+
597+
#. In your application, you can use the billing module like this::
598+
599+
600+
from android_billing import BillingService
601+
from kivy.clock import Clock
602+
603+
class MyBillingService(object):
604+
605+
def __init__(self):
606+
super(MyBillingService, self).__init__()
607+
608+
# Start the billing service, and attach our callback
609+
self.service = BillingService(billing_callback)
610+
611+
# Start a clock to check billing service message every seconds
612+
Clock.schedule_interval(self.service.check, 1)
613+
614+
def billing_callback(self, action, *largs):
615+
'''Callback that will receive all the event from the Billing service
616+
'''
617+
if action == BillingService.BILLING_ACTION_ITEMSCHANGED:
618+
items = largs[0]
619+
if 'org.kivy.gopremium' in items:
620+
print 'Congratulation, you have a premium acess'
621+
else:
622+
print 'Unfortunately, you dont have premium access'
623+
624+
def buy(self, sku):
625+
# Method to buy something.
626+
self.service.buy(sku)
627+
628+
def get_purchased_items(self):
629+
# Return all the items purchased
630+
return self.service.get_purchased_items()
631+
632+
#. To initiate a in-app purchase, just call the buy method::
633+
634+
# Note: start the service at the start, and never twice!
635+
bs = MyBillingService()
636+
bs.buy('org.kivy.gopremium')
637+
638+
# Later, when you get the notification that items have been changed, you
639+
# can still check all the items you bought:
640+
print bs.get_purchased_items()
641+
{'org.kivy.gopremium': {'qt: 1}}
642+
643+
#. You'll receive all the notification about the billing process in the callback.
644+
645+
#. Last step, create your application with `--with-billing $BILLING_PUBKEY`::
646+
647+
./build.py ... --with-billing $BILLING_PUBKEY
648+

recipes/android/recipe.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ function build_android() {
3232
# cythonize
3333
try cython android.pyx
3434
try cython android_sound.pyx
35+
try cython android_billing.pyx
3536
try $BUILD_PATH/python-install/bin/python.host setup.py build_ext -i
3637

3738
# copy files
38-
try cp android.so android_sound.so \
39+
try cp android.so android_sound.so android_billing.so \
3940
$BUILD_PATH/python-install/lib/python2.7/lib-dynload/
4041
try cp android_mixer.py \
4142
$BUILD_PATH/python-install/lib/python2.7/
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# -------------------------------------------------------------------
2+
# Billing
3+
cdef extern void android_billing_service_start()
4+
cdef extern void android_billing_service_stop()
5+
cdef extern void android_billing_buy(char *sku)
6+
cdef extern char *android_billing_get_purchased_items()
7+
cdef extern char *android_billing_get_pending_message()
8+
9+
class BillingService(object):
10+
11+
BILLING_ACTION_SUPPORTED = 'billingsupported'
12+
BILLING_ACTION_ITEMSCHANGED = 'itemschanged'
13+
14+
BILLING_TYPE_INAPP = 'inapp'
15+
BILLING_TYPE_SUBSCRIPTION = 'subs'
16+
17+
def __init__(self, callback):
18+
super(BillingService, self).__init__()
19+
self.callback = callback
20+
self.purchased_items = None
21+
android_billing_service_start()
22+
23+
def _stop(self):
24+
android_billing_service_stop()
25+
26+
def buy(self, sku):
27+
cdef char *j_sku = <bytes>sku
28+
android_billing_buy(j_sku)
29+
30+
def get_purchased_items(self):
31+
cdef char *items = NULL
32+
cdef bytes pitem
33+
items = android_billing_get_purchased_items()
34+
if items == NULL:
35+
return []
36+
pitems = items
37+
ret = {}
38+
for item in pitems.split('\n'):
39+
if not item:
40+
continue
41+
sku, qt = item.split(',')
42+
ret[sku] = {'qt': int(qt)}
43+
return ret
44+
45+
def check(self, *largs):
46+
cdef char *message
47+
cdef bytes pymessage
48+
49+
while True:
50+
message = android_billing_get_pending_message()
51+
if message == NULL:
52+
break
53+
pymessage = <bytes>message
54+
self._handle_message(pymessage)
55+
56+
if self.purchased_items is None:
57+
self._check_new_items()
58+
59+
def _handle_message(self, message):
60+
action, data = message.split('|', 1)
61+
#print "HANDLE MESSAGE-----", (action, data)
62+
63+
if action == 'billingSupported':
64+
tp, value = data.split('|')
65+
value = True if value == '1' else False
66+
self.callback(BillingService.BILLING_ACTION_SUPPORTED, tp, value)
67+
68+
elif action == 'requestPurchaseResponse':
69+
self._check_new_items()
70+
71+
elif action == 'purchaseStateChange':
72+
self._check_new_items()
73+
74+
elif action == 'restoreTransaction':
75+
self._check_new_items()
76+
77+
def _check_new_items(self):
78+
items = self.get_purchased_items()
79+
if self.purchased_items != items:
80+
self.purchased_items = items
81+
self.callback(BillingService.BILLING_ACTION_ITEMSCHANGED, self.purchased_items)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#include <jni.h>
2+
#include <stdio.h>
3+
#include <android/log.h>
4+
#include <string.h>
5+
#include <stdlib.h>
6+
7+
JNIEnv *SDL_ANDROID_GetJNIEnv(void);
8+
9+
#define aassert(x) { if (!x) { __android_log_print(ANDROID_LOG_ERROR, "android_jni", "Assertion failed. %s:%d", __FILE__, __LINE__); abort(); }}
10+
#define PUSH_FRAME { (*env)->PushLocalFrame(env, 16); }
11+
#define POP_FRAME { (*env)->PopLocalFrame(env, NULL); }
12+
13+
void android_billing_service_start() {
14+
static JNIEnv *env = NULL;
15+
static jclass *cls = NULL;
16+
static jmethodID mid = NULL;
17+
18+
if (env == NULL) {
19+
env = SDL_ANDROID_GetJNIEnv();
20+
aassert(env);
21+
cls = (*env)->FindClass(env, "org/renpy/android/PythonActivity");
22+
aassert(cls);
23+
mid = (*env)->GetStaticMethodID(env, cls, "billingServiceStart", "()V");
24+
aassert(mid);
25+
}
26+
27+
PUSH_FRAME;
28+
(*env)->CallStaticVoidMethod(env, cls, mid);
29+
POP_FRAME;
30+
}
31+
32+
void android_billing_service_stop() {
33+
static JNIEnv *env = NULL;
34+
static jclass *cls = NULL;
35+
static jmethodID mid = NULL;
36+
37+
if (env == NULL) {
38+
env = SDL_ANDROID_GetJNIEnv();
39+
aassert(env);
40+
cls = (*env)->FindClass(env, "org/renpy/android/PythonActivity");
41+
aassert(cls);
42+
mid = (*env)->GetStaticMethodID(env, cls, "billingServiceStop", "()V");
43+
aassert(mid);
44+
}
45+
46+
PUSH_FRAME;
47+
(*env)->CallStaticVoidMethod(env, cls, mid);
48+
POP_FRAME;
49+
}
50+
51+
void android_billing_buy(char *sku) {
52+
static JNIEnv *env = NULL;
53+
static jclass *cls = NULL;
54+
static jmethodID mid = NULL;
55+
56+
if (env == NULL) {
57+
env = SDL_ANDROID_GetJNIEnv();
58+
aassert(env);
59+
cls = (*env)->FindClass(env, "org/renpy/android/PythonActivity");
60+
aassert(cls);
61+
mid = (*env)->GetStaticMethodID(env, cls, "billingBuy", "(Ljava/lang/String;)V");
62+
aassert(mid);
63+
}
64+
65+
PUSH_FRAME;
66+
67+
(*env)->CallStaticVoidMethod(
68+
env, cls, mid,
69+
(*env)->NewStringUTF(env, sku)
70+
);
71+
72+
POP_FRAME;
73+
}
74+
75+
char *android_billing_get_purchased_items() {
76+
static JNIEnv *env = NULL;
77+
static jclass *cls = NULL;
78+
static jmethodID mid = NULL;
79+
jobject jreading;
80+
81+
if (env == NULL) {
82+
env = SDL_ANDROID_GetJNIEnv();
83+
aassert(env);
84+
cls = (*env)->FindClass(env, "org/renpy/android/PythonActivity");
85+
aassert(cls);
86+
mid = (*env)->GetStaticMethodID(env, cls, "billingGetPurchasedItems", "()Ljava/lang/String;");
87+
aassert(mid);
88+
}
89+
90+
PUSH_FRAME;
91+
jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
92+
const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
93+
POP_FRAME;
94+
95+
return reading;
96+
}
97+
98+
char *android_billing_get_pending_message() {
99+
static JNIEnv *env = NULL;
100+
static jclass *cls = NULL;
101+
static jmethodID mid = NULL;
102+
jobject jreading;
103+
104+
if (env == NULL) {
105+
env = SDL_ANDROID_GetJNIEnv();
106+
aassert(env);
107+
cls = (*env)->FindClass(env, "org/renpy/android/PythonActivity");
108+
aassert(cls);
109+
mid = (*env)->GetStaticMethodID(env, cls, "billingGetPendingMessage", "()Ljava/lang/String;");
110+
aassert(mid);
111+
}
112+
113+
PUSH_FRAME;
114+
jreading = (*env)->CallStaticObjectMethod(env, cls, mid);
115+
const char * reading = (*env)->GetStringUTFChars(env, jreading, 0);
116+
POP_FRAME;
117+
118+
return reading;
119+
}
120+

recipes/android/src/setup.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
libraries=[ 'sdl', 'log' ],
1111
library_dirs=[ 'libs/'+os.environ['ARCH'] ],
1212
),
13+
Extension(
14+
'android_billing', ['android_billing.c', 'android_billing_jni.c'],
15+
libraries=[ 'log' ],
16+
library_dirs=[ 'libs/'+os.environ['ARCH'] ],
17+
),
1318
Extension(
1419
'android_sound', ['android_sound.c', 'android_sound_jni.c',],
1520
libraries=[ 'sdl', 'log' ],

src/build.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ def make_package(args):
257257
manifest_extra=manifest_extra,
258258
)
259259

260+
render(
261+
'Configuration.tmpl.java',
262+
'src/org/renpy/android/Configuration.java',
263+
args=args)
264+
260265
render(
261266
build_tpl,
262267
'build.xml',
@@ -356,6 +361,7 @@ def make_package(args):
356361
ap.add_argument('--install-location', dest='install_location', default='auto', help='The default install location. Should be "auto", "preferExternal" or "internalOnly".')
357362
ap.add_argument('--compile-pyo', dest='compile_pyo', action='store_true', help='Compile all .py files to .pyo, and only distribute the compiled bytecode.')
358363
ap.add_argument('--intent-filters', dest='intent_filters', help='Add intent-filters xml rules to the AndroidManifest.xml file. The argument is a filename containing xml. The filename should be located relative to the python-for-android directory')
364+
ap.add_argument('--with-billing', dest='billing_pubkey', help='If set, the billing service will be added')
359365
ap.add_argument('--blacklist', dest='blacklist',
360366
default=join(curdir, 'blacklist.txt'),
361367
help='Use a blacklist file to match unwanted file in the final APK')
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (C) 2010 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.android.vending.billing;
18+
19+
import android.os.Bundle;
20+
21+
interface IMarketBillingService {
22+
/** Given the arguments in bundle form, returns a bundle for results. */
23+
Bundle sendBillingRequest(in Bundle bundle);
24+
}

0 commit comments

Comments
 (0)