Open KocWozniakPiotr opened 1 year ago
I solved the problem only partially by bumping my min.ndk
version to my min.sdk
version in buildozer.spec . In my case it's 26
.
Still the problem remains while Notification system tries to access icon when displaying push notifications from background activity rather than the main app itself.
I'm not very familiar with java but in notification.py
it appears self._app_icon = info.icon
doesn't actually have the access to the original app icon anymore when trying to retrieve info from the current package_name.
For now I need to go back again to the "presplash" workaround using Drawable
to get it back to work.
It would be nice to address it in the future version and to be able to change the icon
and id
freely on the go by adding arguments in the notify() itself. It would make the whole thing more flexible and easier to customize different types of notifications.
For instance:
notify(title, message, custom_notification_icon, id)
Possibly duplicate of #748 ?
@KocWozniakPiotr can you share the full output?
Not really. This is not about the background service itself but more about Notification Class not flexible enough.
Ideally you would preferably want to send notifications from a background service and not app itself.
The problem here is lack of parameter different_id
inside notify() .
This leads to the conflict between notification having same ID as background service.
I did modify the notification class to have a different ID - avoiding the ID of the background service and now it works without any problem. As I already mentioned in a previous comment - it would be great if notify() method had more customization parameters to send a particular message to particular channel using different ID. Otherwise it has no practical usage in an actual app.
Since the latest buildozer introduced custom icon_path for later usage as a Drawable
, I no longer have problems displaying (custom) icons inside notifications.
Tam olarak değil. Bu, arka plan hizmetinin kendisiyle ilgili değil, daha çok Bildirim Sınıfının yeterince esnek olmamasıyla ilgili.
İdeal olarak, tercihen uygulamanın kendisinden değil, bir arka plan hizmetinden bildirim göndermek istersiniz. Buradaki sorun, notify() içindeki parametre eksikliğidir
different_id
.Bu, arka plan hizmetiyle aynı kimliğe sahip bildirim arasında çelişkiye yol açar.
Bildirim sınıfını farklı bir kimliğe sahip olacak şekilde değiştirdim - arka plan hizmetinin kimliğinden kaçındım ve şimdi sorunsuz çalışıyor. Daha önceki bir yorumda belirttiğim gibi, notify() yönteminin belirli bir mesajı farklı kimlik kullanarak belirli bir kanala göndermek için daha fazla özelleştirme parametresi olması harika olurdu. Aksi takdirde, gerçek bir uygulamada pratik bir kullanımı yoktur.
En son buildozer, daha sonra kullanım için özel icon_path'i bir olarak sunduğundan
Drawable
, bildirimlerin içinde (özel) simgeleri görüntülemekte artık sorun yaşamıyorum.
Hello. I'm having trouble getting notifications from background service. I'm getting errors. Any chance you could provide a simple example including main.py , service.py and buildozer.spec ? I think I did everything right, but every time I get an error.
Hi. Same problem here:
Notifications works from the main app using:
main.py
import plyer
...
plyer.notification.notify(title = "Hello", message = "World!")
From the service i've tested the following cases:
service_01.py
import plyer
...
plyer.notification.notify(title = "Hello", message = "World!")
from plyer import notification
...
notification.notify(title = "NOTIFY from Service", message = "Hello!")
in boot cases i get NotImplementedError: No usable implementation found!
and AttributeError: 'org.kivy.android.PythonService' object has no attribute 'getComponentName
'`
04-01 11:06:21.966 18450 18469 I Autoupdate: Service_01 running... 1
04-01 11:06:26.973 18450 18469 I Autoupdate: Service_01 running... 2
04-01 11:06:31.980 18450 18469 I Autoupdate: Service_01 running... 3
04-01 11:06:36.984 18450 18469 I Autoupdate: Service_01 running... 4
04-01 11:06:41.990 18450 18469 I Autoupdate: Service_01 running... 5
04-01 11:06:43.156 18450 18469 I Autoupdate: Traceback (most recent call last):
04-01 11:06:43.156 18450 18469 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/v01/arm64-v8a/plyer/utils.py", line 97, in _ensure_obj
04-01 11:06:43.157 18450 18469 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/v01/arm64-v8a/plyer/platforms/android/notification.py", line 201, in instance
04-01 11:06:43.157 18450 18469 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/v01/arm64-v8a/plyer/platforms/android/notification.py", line 48, in __init__
04-01 11:06:43.157 18450 18469 I Autoupdate: AttributeError: 'org.kivy.android.PythonService' object has no attribute 'getComponentName'
04-01 11:06:43.157 18450 18469 I Autoupdate: Traceback (most recent call last):
04-01 11:06:43.157 18450 18469 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/app/service_01.py", line 22, in <module>
04-01 11:06:43.158 18450 18469 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/v01/arm64-v8a/plyer/facades/notification.py", line 84, in notify
04-01 11:06:43.158 18450 18469 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/v01/arm64-v8a/plyer/facades/notification.py", line 93, in _notify
04-01 11:06:43.158 18450 18469 I Autoupdate: NotImplementedError: No usable implementation found!
Using:
from plyer.platforms.android import notification
PlyerNotification = notification.instance()
...
PlyerNotification.notify(title = "NOTIFY from Service", message = "Hello!")
I get error on PlyerNotification = notification.instance()
04-01 10:37:27.362 12967 12983 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/app/service_01.py", line 6, in <module>
04-01 10:37:27.362 12967 12983 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/v01/arm64-v8a/plyer/platforms/android/notification.py", line 201, in instance
04-01 10:37:27.362 12967 12983 I Autoupdate: File "/home/zinig_ub/KIVY_WS/.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/build/python-installs/v01/arm64-v8a/plyer/platforms/android/notification.py", line 48, in __init__
04-01 10:37:27.363 12967 12983 I Autoupdate: AttributeError: 'org.kivy.android.PythonService' object has no attribute 'getComponentName'
@KocWozniakPiotr please, can you share the modification you did to the notification class?
i've found out that in PythonService.java the foreground service notification is created with it's own channel:
...
String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a" + getServiceId();
String channelName = "Background Service" + getServiceId();
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,
NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(chan);
Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setContentTitle(contentTitle);
builder.setContentText(contentText);
builder.setContentIntent(pIntent);
builder.setSmallIcon(smallIconId);
notification = builder.build();
}
startForeground(getServiceId(), notification);
while in class AndroidNotification(Notification): def __init__(self)
the notification is created by referring to the main application activity ID
package_name = activity.getPackageName()
self._ns = None
self._channel_id = package_name
Sorry guys to resurrect old issue. Apologize for not being active during the last 6 months, to be able to reply!
PythonService.java wasn't modified.
First of all, If you want the notification properly working inside the background service process like in my case:
msg_id = 3
notification.notify(chan=msg_id, title=f'channel {msg_id}', message=f'{msg_counter[msg_id]}x {msg_template[msg_id]}')
You can modify notification.py Notification class inside facades directory to add the chan
argument like here:
def notify(self,chan=0, title='', message='', app_name='', app_icon='',
timeout=10, ticker='', toast=False):
If using only one channel is all you want, just use only single channel for instance 0 or 1. Otherwise feel free to add as many channels as you please.
notification.notify(chan=1, title=f'channel no_1', message="message for channel 1"')
regarding channel service inside notification.py in the path /platforms/android
You need to add the channel there and optionally add Drawable which is sadly not supported anymore AFAIK.
This will allow you to use a custom notification icons for each new channel you want to use later on in your app, which is really nice.
The removal of the Drawable = autoclass("{}.R$drawable".format(activity.getPackageName()))
in the newer plyer versions was partially the reason why the notifications kept crashing on the screen.
There are also few other changes for instance:
app_icon = Drawable.your_icon_name_without_extensionname
and few other things I do'nt remember correctly. But you can compare it with the source file.
This is my notification.py :
from android import python_act
from android.runnable import run_on_ui_thread
from jnius import autoclass, cast
from plyer.facades import Notification
from plyer.platforms.android import activity, SDK_INT
AndroidString = autoclass('java.lang.String')
Context = autoclass('android.content.Context')
NotificationBuilder = autoclass('android.app.Notification$Builder')
NotificationManager = autoclass('android.app.NotificationManager')
Drawable = autoclass("{}.R$drawable".format(activity.getPackageName()))
PendingIntent = autoclass('android.app.PendingIntent')
Intent = autoclass('android.content.Intent')
Toast = autoclass('android.widget.Toast')
BitmapFactory = autoclass('android.graphics.BitmapFactory')
class AndroidNotification(Notification):
'''
Implementation of Android notification API.
.. versionadded:: 1.0.0
'''
def __init__(self):
self._ns = None
self._channel_id = None
def _get_notification_service(self):
if not self._ns:
self._ns = cast(NotificationManager, activity.getSystemService(
Context.NOTIFICATION_SERVICE
))
return self._ns
def _build_notification_channel(self, name, _id):
'''
Create a NotificationChannel using channel id of the application
package name (com.xyz, org.xyz, ...) and channel name same as the
provided notification title if the API is high enough, otherwise
do nothing.
.. versionadded:: 1.4.0
'''
if SDK_INT < 26:
return
channel = autoclass('android.app.NotificationChannel')
self._channel_id = activity.getPackageName()
self._channel_id = self._channel_id + str(_id)
app_channel = channel(
self._channel_id, name, NotificationManager.IMPORTANCE_DEFAULT
)
self._get_notification_service().createNotificationChannel(
app_channel
)
return app_channel
@run_on_ui_thread
def _toast(self, message):
'''
Display a popup-like small notification at the bottom of the screen.
.. versionadded:: 1.4.0
'''
Toast.makeText(
activity,
cast('java.lang.CharSequence', AndroidString(message)),
Toast.LENGTH_LONG
).show()
@staticmethod
def _set_icons(notification, icon=None):
'''
Set the small application icon displayed at the top panel together with
WiFi, battery percentage and time and the big optional icon (preferably
PNG format with transparent parts) displayed directly in the
notification body.
.. versionadded:: 1.4.0
'''
app_icon = Drawable.your_icon_name_without_extensionname
notification.setSmallIcon(app_icon)
bitmap_icon = app_icon
if icon is not None:
bitmap_icon = BitmapFactory.decodeFile(icon)
notification.setLargeIcon(bitmap_icon)
elif icon == '':
# we don't want the big icon set,
# only the small one in the top panel
pass
else:
bitmap_icon = BitmapFactory.decodeResource(
python_act.getResources(), app_icon
)
notification.setLargeIcon(bitmap_icon)
def _build_notification(self, title, custom_id):
'''
.. versionadded:: 1.4.0
'''
if SDK_INT < 26:
noti = NotificationBuilder(activity)
else:
self._channel = self._build_notification_channel(title, custom_id)
noti = NotificationBuilder(activity, self._channel_id)
return noti
@staticmethod
def _set_open_behavior(notification):
#Service = autoclass('org.kivy.your_app_name.YourServicenName').mService
#Service.stopForeground(True)
'''
Open the source application when user opens the notification.
.. versionadded:: 1.4.0
'''
# create Intent that navigates back to the application
app_context = activity.getApplication().getApplicationContext()
notification_intent = Intent(app_context, python_act)
# set flags to run our application Activity
notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
notification_intent.setAction(Intent.ACTION_MAIN)
notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
# get our application Activity
pending_intent = PendingIntent.getActivity(
app_context, 0, notification_intent, 0)
notification.setContentIntent(pending_intent)
notification.setAutoCancel(True)
def _open_notification(self, notification, _id):
if SDK_INT >= 16:
notification = notification.build()
else:
notification = notification.getNotification()
self._get_notification_service().notify(_id, notification)
def _notify(self, **kwargs):
noti = None
chan = kwargs.get('chan')
message = kwargs.get('message').encode('utf-8')
ticker = kwargs.get('ticker').encode('utf-8')
title = AndroidString(
kwargs.get('title', '').encode('utf-8')
)
icon = kwargs.get('app_icon')
# decide whether toast only or proper notification
if kwargs.get('toast'):
self._toast(message)
return
else:
noti = self._build_notification(title, chan)
# set basic properties for notification
noti.setContentTitle(title)
noti.setContentText(AndroidString(message))
noti.setTicker(AndroidString(ticker))
# set additional flags for notification
self._set_icons(noti, icon=icon)
self._set_open_behavior(noti)
# launch
self._open_notification(noti, chan)
def instance():
'''
Instance for facade proxy.
'''
return AndroidNotification()
In your builder.specs enable this line:
android.add_resources = data/legal_icons:drawable
you need also to make a dir called data/legal_icons in your project directory and place there your png icon for notifications. It will be then exported during deployment.
THATS IT! Don't forget to remove .pyc files of notification.py in all /platform/android and facades directories and run buildozer debug for the new pyc files to compile again.
It's a pretty hard work around but this is the only way I managed to get notifications to work on many android APIs. Also the kivy version I use is 2.1.0 and plyer 2.1.0 too . I wasn't able to get it run on the never kivy and plyer versions, but you can try yourself maybe it will work for you.
I don't remember exact details what I changed here but this is a working fix which I'm using until now. I'm still running old requirements in buildozer.specs for my app to be as much bug free as possible and not to introduce new problems on every new release of packages:
requirements = python3,kivy==2.1.0,jnius,https://github.com/kivy/python-for-android/archive/refs/tags/v2022.12.20.zip,requests,idna,chardet,android,cryptography,png,pypng,https://github.com/kivy/plyer/archive/refs/tags/2.1.0.zip,kvdroid
The main Service notification needs to be disabled manually by the user to hide it from the screen. There is no other way around I guess.
And here is my service.py if anyone is interested:
from time import sleep
from jnius import autoclass
import socket
import ssl
import configparser
from plyer import notification
# Restarts the service as soon as the script ends.
PythonService = autoclass('org.kivy.android.PythonService')
PythonService.mService.setAutoRestartService(False)
#PythonService.mService.stopForeground(True)
# PythonService has ID 1 - p4a1
# Storing temporary ids of messages in array. They are indexed as templates in a dictionary
msg_storage = []
msg_template = {2: 'Approaching Neburion. Get ready!',
3: 'You got a gift!',
4: 'Solar barrier is weakening!',
5: 'You got a new message',
6: 'All upgrades done!',
7: "Your item has been sold in the shop",
8: 'Merchant appeared in south of Yodanga',
9: 'You will reach Anabris in 1 hour'
}
config = configparser.ConfigParser()
# Reads user id form secret for later communication
open('../settings.ini', 'r')
config.read('../settings.ini')
content = config.get('Startup', 'Secret')
if content == 'None':
user_id = ''
notify_user = False
else:
user_id = content[44:]
notify_user = True
def server_connected():
global user_id, msg_storage
try:
_HOST = socket.gethostbyname('example.com')
except socket.error:
return False
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
context = ssl.SSLContext()
try:
usr = context.wrap_socket(sock, server_hostname=_HOST)
except socket.error:
return False
try:
usr.settimeout(5)
usr.connect((_HOST, 5005))
usr.settimeout(None)
usr.send('x'.encode())
latest_version = usr.recv().decode()
# add this feature later
# login_status = '...new version is available to download!'
usr.send(str('notify' + user_id).encode())
msg_storage = [int(m) for m in (usr.recv(256).decode()).split()]
return True
except socket.error:
return False
msg_counter = [0,0,0,0,0,0,0,0,0,0]
# Continuously performs connection to the server and obtains IDs of notifications to display for a user
while True:
# Adjust sleep depending on duration of your service script
# default should be 300 seconds
sleep(60)
if notify_user:
if server_connected():
for msg_id in msg_storage:
if msg_id > 1:
msg_counter[msg_id] = msg_counter[msg_id] + 1
notification.notify(chan=msg_id, title=f'channel {msg_id}', message=f'{msg_counter[msg_id]}x {msg_template[msg_id]}')
msg_storage.clear()
Hope this helps someone!
It seems plyer notification module works finally out of the box on Android. However I'm having a hard time getting push notifications to work inside my
service.py
script which runs in foreground.When I start some notification from inside of main app
notify()
works fine. Also when a foregroundservice.py
runs alongside. But it fails when I implementnotify()
inside my foreground service. No matter if the app stays active, paused or I close it. The same happens.The main Service Notification stays sticky as it should and when notify() tries to generate a new push notification Service Notification dissapears for a second and appears back. However with no Title and content anymore. Just blank notification.
here is my logcat when background service tries to use plyer push_notification:
01-26 13:09:08.584 2275 2275 D SystemUILog: notification|NotificationListener:onNotificationPosted: StatusBarNotification(pkg=org.kivy.sampleapp user=UserHandle{0} id=1 tag=null key=0|org.kivy.sampleapp|1|null|10615: Notification(channel=org.kivy.p4a1 shortcut=null contentView=null vibrate=null sound=null defaults=0x0 flags=0x62 color=0x00000000 vis=PRIVATE))
In my main.py I'm using the usual service.start()
from jnius import autoclass
service = autoclass('org.kivy.sampleapp.ServiceSyncing')
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
service.start(mActivity, '')
service.py
from jnius import autoclass
from plyer import notification
PythonService = autoclass('org.kivy.android.PythonService')
PythonService.mService.setAutoRestartService(True)
while True:
sleep(60)
notification.notify(title='Hey', message='testing')
buildozer.spec attached as a file
buildozer.txt
I'm kind of lost. I think it might have something to do with
id
orchannel_id
getting changed or because I'm not using the main package/activity ? I'm not very familiar with java code so I cant figure out how and where to alter the code. Before2.1.0
I was able to run it successfully.Any help or workaround for this would be great !