-
Notifications
You must be signed in to change notification settings - Fork 1
How to Use Buttons to Communicate with Service
Fabian edited this page Mar 17, 2026
·
4 revisions
For Communicating with Service from notification Buttons Using python-osc
# buildozer.spec
android.add_src = src
p4a.hook = p4a/hook.py
services = Wallpapercarousel:./services/wallpaper.py:foreground
requirements = python3, kivy, pyjnius, android_notify==1.60.10, python-osc
android.gradle_dependencies = androidx.core:core-ktx:1.12.0
android.permissions = INTERNET, POST_NOTIFICATIONS-
Change
packagevariable with values in yourbuildozer.spec->"package.domain.package.name" -
Using actions
ACTION_RESUME,ACTION_PAUSEandACTION_STOP, Note Actions can actually be named anything.
# p4a/hook.py
from pathlib import Path
from pythonforandroid.toolchain import ToolchainCL #type: ignore
def after_apk_build(toolchain: ToolchainCL):
manifest_file = Path(toolchain._dist.dist_dir) / "src" / "main" / "AndroidManifest.xml"
old_manifest = manifest_file.read_text(encoding="utf-8")
package = "org.wally.waller"
service_name="Wallpapercarousel"
foreground_type="dataSync"
target = f'android:name="{package}.Service{service_name.capitalize()}"'
# Inject foregroundServiceType
pos = old_manifest.find(target)
if pos != -1:
end = old_manifest.find("/>", pos)
old_manifest = (old_manifest[:end] + f'android:foregroundServiceType="{foreground_type}"' + old_manifest[end:])
print(f"Successfully Added foregroundServiceType to Service{service_name}")
# Your custom receiver XML
receiver_name = "CarouselReceiver"
receiver_xml = f'''
<receiver android:name="{package}.{receiver_name}"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="ACTION_RESUME" />
<action android:name="ACTION_PAUSE" />
<action android:name="ACTION_STOP" />
</intent-filter>
</receiver>
'''
# Insert before the closing </application>
new_manifest = old_manifest.replace('</application>', f'{receiver_xml}\n</application>')
manifest_file.write_text(new_manifest, encoding="utf-8")
print(new_manifest)
if old_manifest != new_manifest:
print("Receiver added successfully")
else:
print("Failed to add receiver")In main.py get free port and send to Service
import socket
from android import mActivity #type: ignore
from jnius import autoclass
def get_free_port():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", 0)) # bind to a random free port
port = s.getsockname()[1]
s.close()
return port
port = get_free_port()
context = mActivity.getApplicationContext()
service_name = "Wallpapercarousel"
service = autoclass(context.getPackageName() + '.Service' + service_name.capitalize())
service.start(mActivity, str(port))write the port for java receiver to see.
#./services/wallpaper.py
import os, threading, traceback, time
from jnius import autoclass
from android_notify import Notification
from android_notify.config import get_python_service
from pythonosc import dispatcher, osc_server
BuildVersion = autoclass("android.os.Build$VERSION")
ServiceInfo = autoclass("android.content.pm.ServiceInfo")
# get sent port from main UI
def get_service_port():
port = 5006
try:
port = int(os.environ.get('PYTHON_SERVICE_ARGUMENT', '5006'))
except (TypeError, ValueError):
print("python getting PORT!")
return port
# Start foreground service
service = get_python_service()
service_port = get_service_port()
foreground_type = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC if BuildVersion.SDK_INT >= 30 else 0
notification = Notification(title="Hey From Service", name="i_have_a_name")
notification.setData({"service_port": service_port}) # Save for JAVA
notification.addButton(text="Resume", receiver_name="CarouselReceiver", action="ACTION_RESUME")
notification.addButton(text="Pause", receiver_name="CarouselReceiver", action="ACTION_PAUSE")
notification.addButton(text="Stop", receiver_name="CarouselReceiver", action="ACTION_STOP")
builder = notification.fill_args()
service.startForeground(notification.id, builder.build(), foreground_type)
service.setAutoRestartService(True) # auto-restart if killed
class MyWallpaperReceiver:
def __init__(self):
self.live = True
self.paused = False
self.lock = threading.Lock()
threading.Thread(target=self.heart, daemon=True).start()
def heart(self):
start = time.time()
fmt = lambda s: f"{int(s // 3600)}h {int((s % 3600) // 60)}m {int(s % 60)}s"
while self.live:
with self.lock:
paused = self.paused
if not paused:
elapsed = time.time() - start
notification.updateTitle(f"Running for {fmt(elapsed)}")
time.sleep(1)
def stop(self, *args):
self.live = False
print("python service stop args:", args)
service.stopSelf()
def pause(self, *args):
with self.lock:
self.paused = True
print("python service pause data received:", args)
notification.updateTitle("Carousel Paused")
def resume(self, *args):
with self.lock:
self.paused = False
print("python service resume data received:", args)
notification.updateTitle("Carousel Resumed")
myWallpaperReceiver = MyWallpaperReceiver()
myDispatcher = dispatcher.Dispatcher()
myDispatcher.map("/pause", myWallpaperReceiver.pause)
myDispatcher.map("/resume", myWallpaperReceiver.resume)
myDispatcher.map("/stop", myWallpaperReceiver.stop)
server = osc_server.ThreadingOSCUDPServer(("0.0.0.0", service_port), myDispatcher)
try:
server.serve_forever()
except Exception as e:
print("python Service Main loop Failed:", e)
traceback.print_exc()
# Avoiding process is bad java.lang.SecurityException// src/CarouselReceiver.java
package org.wally.waller;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class CarouselReceiver extends BroadcastReceiver {
private static final String TAG = "CarouselReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Toast.makeText(context, "Action received: " + action, Toast.LENGTH_LONG).show();
// Read port sent by Service
int port = 5006; // default
String portStr = intent.getStringExtra("service_port");
if (portStr != null && !portStr.isEmpty()) {
try {
int port = Integer.parseInt(portStr.trim());
Log.d(TAG, "Service port obtained from Intent: " + port);
} catch (NumberFormatException e) {
Log.e(TAG, "Invalid service_port in Intent: " + portStr + ", using fallback", e);
}
}else{
Log.e(TAG, "Error reading port from intent, using default port: " + port, e);
}
// Determine OSC message
String oscAddress = "";
String oscArg = "";
if ("ACTION_STOP".equals(action)) {
oscAddress = "/stop";
oscArg = "hello1 frm java STOP";
} else if ("ACTION_RESUME".equals(action)) {
oscAddress = "/resume";
oscArg = "hello frm java RESUME";
} else if ("ACTION_PAUSE".equals(action)) {
oscAddress = "/pause";
oscArg = "hello frm java PAUSE";
} else {
Toast.makeText(context, "Unknown action: " + action, Toast.LENGTH_SHORT).show();
Log.w(TAG, "Unknown action from python: " + action);
return;
}
final int finalPort = port;
final String finalAddress = oscAddress;
final String finalArg = oscArg;
new Thread(() -> {
try {
sendOscMessage("127.0.0.1", finalPort, finalAddress, finalArg);
Log.d(TAG, "OSC message sent to python: " + finalAddress + " " + finalArg);
} catch (Exception e) {
Log.e(TAG, "Failed to send OSC message to python", e);
}
}).start();
}
private void sendOscMessage(String host, int port, String address, String arg) throws Exception {
InetAddress serverAddr = InetAddress.getByName(host);
DatagramSocket socket = new DatagramSocket();
byte[] addrBytes = (address + "\0").getBytes("US-ASCII");
// OSC messages are padded to 4 bytes, simple approach:
int padding = 4 - (addrBytes.length % 4);
byte[] paddedAddr = new byte[addrBytes.length + padding];
System.arraycopy(addrBytes, 0, paddedAddr, 0, addrBytes.length);
// Arguments
String typeTag = ",s\0\0"; // string argument
byte[] typeBytes = typeTag.getBytes("US-ASCII");
byte[] argBytes = (arg + "\0").getBytes("US-ASCII");
int argPadding = 4 - (argBytes.length % 4);
byte[] paddedArg = new byte[argBytes.length + argPadding];
System.arraycopy(argBytes, 0, paddedArg, 0, argBytes.length);
// Combine all
byte[] message = new byte[paddedAddr.length + typeBytes.length + paddedArg.length];
System.arraycopy(paddedAddr, 0, message, 0, paddedAddr.length);
System.arraycopy(typeBytes, 0, message, paddedAddr.length, typeBytes.length);
System.arraycopy(paddedArg, 0, message, paddedAddr.length + typeBytes.length, paddedArg.length);
DatagramPacket packet = new DatagramPacket(message, message.length, serverAddr, port);
socket.send(packet);
socket.close();
}
}