android-notify 1.58__py3-none-any.whl → 1.59.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of android-notify might be problematic. Click here for more details.
- android_notify/{types_idea.py → an_types.py} +20 -1
- android_notify/an_utils.py +10 -0
- android_notify/base.py +10 -3
- android_notify/styles.py +7 -1
- android_notify/sword.py +422 -182
- android_notify-1.59.1.dist-info/METADATA +119 -0
- android_notify-1.59.1.dist-info/RECORD +12 -0
- {android_notify-1.58.dist-info → android_notify-1.59.1.dist-info}/WHEEL +1 -1
- android_notify-1.58.dist-info/METADATA +0 -575
- android_notify-1.58.dist-info/RECORD +0 -11
- {android_notify-1.58.dist-info → android_notify-1.59.1.dist-info}/top_level.txt +0 -0
android_notify/sword.py
CHANGED
|
@@ -2,34 +2,41 @@
|
|
|
2
2
|
import traceback
|
|
3
3
|
import os,re
|
|
4
4
|
import threading
|
|
5
|
-
|
|
5
|
+
from .an_types import Importance
|
|
6
|
+
from .an_utils import can_accept_arguments
|
|
6
7
|
from .styles import NotificationStyles
|
|
7
8
|
from .base import BaseNotification
|
|
8
9
|
DEV=0
|
|
9
10
|
ON_ANDROID = False
|
|
10
11
|
|
|
12
|
+
# noinspection PyBroadException
|
|
11
13
|
try:
|
|
12
14
|
# Android Imports
|
|
13
|
-
from jnius import autoclass,cast
|
|
14
|
-
from android import activity
|
|
15
|
-
from android.config import ACTIVITY_CLASS_NAME
|
|
16
|
-
from android.runnable import run_on_ui_thread
|
|
15
|
+
from jnius import autoclass,cast
|
|
16
|
+
from android import activity
|
|
17
|
+
from android.config import ACTIVITY_CLASS_NAME
|
|
18
|
+
from android.runnable import run_on_ui_thread
|
|
17
19
|
|
|
18
|
-
# Get the required Java classes
|
|
20
|
+
# Get the required Java classes needs to on android to import
|
|
19
21
|
Bundle = autoclass('android.os.Bundle')
|
|
20
22
|
PythonActivity = autoclass('org.kivy.android.PythonActivity')
|
|
21
23
|
String = autoclass('java.lang.String')
|
|
22
24
|
Intent = autoclass('android.content.Intent')
|
|
23
25
|
PendingIntent = autoclass('android.app.PendingIntent')
|
|
24
|
-
context = PythonActivity.mActivity
|
|
26
|
+
context = PythonActivity.mActivity
|
|
25
27
|
BitmapFactory = autoclass('android.graphics.BitmapFactory')
|
|
26
28
|
BuildVersion = autoclass('android.os.Build$VERSION')
|
|
29
|
+
VersionCodes = autoclass('android.os.Build$VERSION_CODES')
|
|
27
30
|
NotificationManager = autoclass('android.app.NotificationManager')
|
|
28
31
|
NotificationChannel = autoclass('android.app.NotificationChannel')
|
|
29
32
|
IconCompat = autoclass('androidx.core.graphics.drawable.IconCompat')
|
|
30
33
|
ON_ANDROID = True
|
|
31
|
-
except Exception as e
|
|
32
|
-
|
|
34
|
+
except Exception as e:
|
|
35
|
+
if e.name != 'android':
|
|
36
|
+
print('Exception: ',e)
|
|
37
|
+
print(traceback.format_exc())
|
|
38
|
+
|
|
39
|
+
MESSAGE='This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" to see design patterns and more info.'
|
|
33
40
|
# from .types_idea import *
|
|
34
41
|
# print(MESSAGE) Already Printing in core.py
|
|
35
42
|
|
|
@@ -43,18 +50,18 @@ except Exception as e:# pylint: disable=W0718
|
|
|
43
50
|
|
|
44
51
|
if ON_ANDROID:
|
|
45
52
|
try:
|
|
46
|
-
from android.permissions import request_permissions, Permission,check_permission
|
|
47
|
-
from android.storage import app_storage_path
|
|
53
|
+
from android.permissions import request_permissions, Permission,check_permission
|
|
54
|
+
from android.storage import app_storage_path
|
|
48
55
|
|
|
49
56
|
NotificationManagerCompat = autoclass('androidx.core.app.NotificationManagerCompat')
|
|
50
57
|
NotificationCompat = autoclass('androidx.core.app.NotificationCompat')
|
|
51
58
|
|
|
52
59
|
# Notification Design
|
|
53
|
-
NotificationCompatBuilder = autoclass('androidx.core.app.NotificationCompat$Builder')
|
|
54
|
-
NotificationCompatBigTextStyle = autoclass('androidx.core.app.NotificationCompat$BigTextStyle')
|
|
55
|
-
NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle')
|
|
60
|
+
NotificationCompatBuilder = autoclass('androidx.core.app.NotificationCompat$Builder')
|
|
61
|
+
NotificationCompatBigTextStyle = autoclass('androidx.core.app.NotificationCompat$BigTextStyle')
|
|
62
|
+
NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle')
|
|
56
63
|
NotificationCompatInboxStyle = autoclass('androidx.core.app.NotificationCompat$InboxStyle')
|
|
57
|
-
except Exception as e
|
|
64
|
+
except Exception as e:
|
|
58
65
|
print(e)
|
|
59
66
|
print("""
|
|
60
67
|
Dependency Error: Add the following in buildozer.spec:
|
|
@@ -74,11 +81,12 @@ class Notification(BaseNotification):
|
|
|
74
81
|
both_imgs == using lager icon and big picture
|
|
75
82
|
:param big_picture_path: Relative Path to the image resource.
|
|
76
83
|
:param large_icon_path: Relative Path to the image resource.
|
|
77
|
-
:param progress_current_value:
|
|
78
|
-
:param progress_max_value:
|
|
79
|
-
:param body: large text For `big_Text` style, while `message` acts as
|
|
84
|
+
:param progress_current_value: integer To set progress bar current value.
|
|
85
|
+
:param progress_max_value: integer To set Max range for progress bar.
|
|
86
|
+
:param body: large text For `big_Text` style, while `message` acts as subtitle.
|
|
80
87
|
---
|
|
81
88
|
(Advance Options)
|
|
89
|
+
:param id: Pass in Old 'id' to use old instance
|
|
82
90
|
:param callback: Function for notification Click.
|
|
83
91
|
:param channel_name: - str Defaults to "Default Channel"
|
|
84
92
|
:param channel_id: - str Defaults to "default_channel"
|
|
@@ -86,17 +94,19 @@ class Notification(BaseNotification):
|
|
|
86
94
|
(Options during Dev On PC)
|
|
87
95
|
:param logs: - Bool Defaults to True
|
|
88
96
|
"""
|
|
89
|
-
|
|
97
|
+
|
|
98
|
+
notification_ids = [0]
|
|
90
99
|
button_ids=[0]
|
|
91
100
|
btns_box={}
|
|
92
101
|
main_functions={}
|
|
93
102
|
|
|
94
103
|
# During Development (When running on PC)
|
|
95
104
|
BaseNotification.logs=not ON_ANDROID
|
|
96
|
-
def __init__(self,**kwargs):
|
|
105
|
+
def __init__(self,**kwargs): #@dataclass already does work
|
|
97
106
|
super().__init__(**kwargs)
|
|
98
107
|
|
|
99
|
-
self.__id = self.
|
|
108
|
+
self.__id = self.id or self.__get_unique_id() # Different use from self.name all notifications require `integers` id's not `strings`
|
|
109
|
+
self.id = self.__id # To use same Notification in different instances
|
|
100
110
|
|
|
101
111
|
# To Track progressbar last update (According to Android Docs Don't update bar to often, I also faced so issues when doing that)
|
|
102
112
|
self.__update_timer = None
|
|
@@ -104,19 +114,155 @@ class Notification(BaseNotification):
|
|
|
104
114
|
self.__progress_bar_title = ''
|
|
105
115
|
self.__cooldown = 0
|
|
106
116
|
|
|
107
|
-
self.
|
|
117
|
+
self.__built_parameter_filled=False
|
|
118
|
+
self.__using_set_priority_method=False
|
|
119
|
+
|
|
120
|
+
self.__format_channel(self.channel_name, self.channel_id)
|
|
108
121
|
if not ON_ANDROID:
|
|
109
122
|
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
self.
|
|
123
|
+
|
|
124
|
+
NotificationHandler.asks_permission()
|
|
125
|
+
notification_service = context.getSystemService(context.NOTIFICATION_SERVICE)
|
|
126
|
+
self.notification_manager = cast(NotificationManager, notification_service)
|
|
127
|
+
self.__builder = NotificationCompatBuilder(context, self.channel_id)
|
|
128
|
+
|
|
129
|
+
def cancel(self,_id=0):
|
|
130
|
+
"""
|
|
131
|
+
Removes a Notification instance from tray
|
|
132
|
+
:param _id: not required uses Notification instance id as default
|
|
133
|
+
"""
|
|
134
|
+
if ON_ANDROID:
|
|
135
|
+
self.notification_manager.cancel(_id or self.__id)
|
|
136
|
+
if self.logs:
|
|
137
|
+
print('Removed Notification.')
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def cancelAll(cls):
|
|
141
|
+
"""
|
|
142
|
+
Removes all app Notifications from tray
|
|
143
|
+
"""
|
|
144
|
+
if ON_ANDROID:
|
|
145
|
+
cls.__return_notification_manger().cancelAll()
|
|
146
|
+
if cls.logs:
|
|
147
|
+
print('Removed All Notifications.')
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def createChannel(cls, id, name:str, description='',importance:Importance='urgent'):
|
|
151
|
+
"""
|
|
152
|
+
Creates a user visible toggle button for specific notifications, Required For Android 8.0+
|
|
153
|
+
:param id: Used to send other notifications later through same channel.
|
|
154
|
+
:param name: user-visible channel name.
|
|
155
|
+
:param description: user-visible detail about channel (Not required defaults to empty str).
|
|
156
|
+
:param importance: ['urgent', 'high', 'medium', 'low', 'none'] defaults to 'urgent' i.e. makes a sound and shows briefly
|
|
157
|
+
:return: boolean if channel created
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
if not ON_ANDROID:
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
notification_manager=cls.__return_notification_manger()
|
|
164
|
+
android_importance_value = cls.__get_android_importance(importance)
|
|
165
|
+
|
|
166
|
+
if BuildVersion.SDK_INT >= 26 and notification_manager.getNotificationChannel(id) is None:
|
|
167
|
+
channel = NotificationChannel(id, name, android_importance_value)
|
|
168
|
+
if description:
|
|
169
|
+
channel.setDescription(description)
|
|
170
|
+
notification_manager.createNotificationChannel(channel)
|
|
171
|
+
return True
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def deleteChannel(cls, channel_id):
|
|
176
|
+
"""Delete a Channel Matching channel_id"""
|
|
177
|
+
if not ON_ANDROID:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
cls.__return_notification_manger().deleteNotificationChannel(channel_id)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def deleteAllChannel(cls):
|
|
184
|
+
"""Deletes all notification channel
|
|
185
|
+
:returns amount deleted
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
amount = 0
|
|
189
|
+
if not ON_ANDROID:
|
|
190
|
+
return amount
|
|
191
|
+
|
|
192
|
+
notification_manager = cls.__return_notification_manger()
|
|
193
|
+
channels = cls.getChannels()
|
|
194
|
+
for index in range(channels.size()):
|
|
195
|
+
amount += 1
|
|
196
|
+
channel = channels.get(index)
|
|
197
|
+
channel_id = channel.getId()
|
|
198
|
+
notification_manager.deleteNotificationChannel(channel_id)
|
|
199
|
+
return amount
|
|
200
|
+
|
|
201
|
+
def refresh(self):
|
|
202
|
+
"""TO apply new components on notification"""
|
|
203
|
+
if self.__built_parameter_filled:
|
|
204
|
+
# Don't dispatch before filling required values `self.__create_basic_notification`
|
|
205
|
+
# We generally shouldn't dispatch till user call .send()
|
|
206
|
+
self.__dispatch_notification()
|
|
207
|
+
|
|
208
|
+
def setBigPicture(self,path):
|
|
209
|
+
"""
|
|
210
|
+
set a Big Picture at the bottom
|
|
211
|
+
:param path: can be `Relative Path` or `URL`
|
|
212
|
+
:return:
|
|
213
|
+
"""
|
|
214
|
+
if ON_ANDROID:
|
|
215
|
+
self.__build_img(path, NotificationStyles.BIG_PICTURE)
|
|
216
|
+
elif self.logs:
|
|
217
|
+
# When on android there are other logs
|
|
218
|
+
print('Done setting big picture')
|
|
219
|
+
|
|
220
|
+
def setSmallIcon(self,path):
|
|
221
|
+
"""
|
|
222
|
+
sets small icon to the top left
|
|
223
|
+
:param path: can be `Relative Path` or `URL`
|
|
224
|
+
:return:
|
|
225
|
+
"""
|
|
226
|
+
if ON_ANDROID:
|
|
227
|
+
self.__insert_app_icon(path)
|
|
228
|
+
elif self.logs:
|
|
229
|
+
# When on android there are other logs
|
|
230
|
+
print('Done setting small icon')
|
|
231
|
+
|
|
232
|
+
def setLargeIcon(self,path):
|
|
233
|
+
"""
|
|
234
|
+
sets Large icon to the right
|
|
235
|
+
:param path: can be `Relative Path` or `URL`
|
|
236
|
+
:return:
|
|
237
|
+
"""
|
|
238
|
+
if ON_ANDROID:
|
|
239
|
+
self.__build_img(path, NotificationStyles.LARGE_ICON)
|
|
240
|
+
elif self.logs:
|
|
241
|
+
#When on android there are other logs
|
|
242
|
+
print('Done setting large icon')
|
|
243
|
+
|
|
244
|
+
def setBigText(self,body):
|
|
245
|
+
"""Sets a big text for when drop down button is pressed
|
|
246
|
+
|
|
247
|
+
:param body: The big text that will be displayed
|
|
248
|
+
"""
|
|
249
|
+
if ON_ANDROID:
|
|
250
|
+
big_text_style = NotificationCompatBigTextStyle()
|
|
251
|
+
big_text_style.bigText(str(body))
|
|
252
|
+
self.__builder.setStyle(big_text_style)
|
|
253
|
+
elif self.logs:
|
|
254
|
+
# When on android there are other logs
|
|
255
|
+
print('Done setting big text')
|
|
256
|
+
|
|
114
257
|
def showInfiniteProgressBar(self):
|
|
115
258
|
"""Displays an (Infinite) progress Bar in Notification, that continues loading indefinitely.
|
|
116
259
|
Can be Removed By `removeProgressBar` Method
|
|
117
260
|
"""
|
|
118
|
-
self.
|
|
119
|
-
|
|
261
|
+
if self.logs:
|
|
262
|
+
print('Showing infinite progressbar')
|
|
263
|
+
if ON_ANDROID:
|
|
264
|
+
self.__builder.setProgress(0,0, True)
|
|
265
|
+
self.refresh()
|
|
120
266
|
|
|
121
267
|
def updateTitle(self,new_title):
|
|
122
268
|
"""Changes Old Title
|
|
@@ -129,7 +275,7 @@ class Notification(BaseNotification):
|
|
|
129
275
|
print(f'new notification title: {self.title}')
|
|
130
276
|
if ON_ANDROID:
|
|
131
277
|
self.__builder.setContentTitle(String(self.title))
|
|
132
|
-
self.
|
|
278
|
+
self.refresh()
|
|
133
279
|
|
|
134
280
|
def updateMessage(self,new_message):
|
|
135
281
|
"""Changes Old Message
|
|
@@ -142,7 +288,7 @@ class Notification(BaseNotification):
|
|
|
142
288
|
print(f'new notification message: {self.message}')
|
|
143
289
|
if ON_ANDROID:
|
|
144
290
|
self.__builder.setContentText(String(self.message))
|
|
145
|
-
self.
|
|
291
|
+
self.refresh()
|
|
146
292
|
|
|
147
293
|
def updateProgressBar(self,current_value:int,message:str='',title:str='',cooldown=0.5):
|
|
148
294
|
"""Updates progress bar current value
|
|
@@ -151,7 +297,7 @@ class Notification(BaseNotification):
|
|
|
151
297
|
current_value (int): the value from progressbar current progress
|
|
152
298
|
message (str): defaults to last message
|
|
153
299
|
title (str): defaults to last title
|
|
154
|
-
cooldown (float, optional):
|
|
300
|
+
cooldown (float, optional): Little Time to Wait before change actually reflects, to avoid android Ignoring Change, Defaults to 0.5secs
|
|
155
301
|
|
|
156
302
|
NOTE: There is a 0.5sec delay for value change, if updating title,msg with progressbar frequently pass them in too to avoid update issues
|
|
157
303
|
"""
|
|
@@ -187,7 +333,7 @@ class Notification(BaseNotification):
|
|
|
187
333
|
if self.__progress_bar_title:
|
|
188
334
|
self.updateTitle(self.__progress_bar_title)
|
|
189
335
|
|
|
190
|
-
self.
|
|
336
|
+
self.refresh()
|
|
191
337
|
self.__update_timer = None
|
|
192
338
|
|
|
193
339
|
|
|
@@ -204,7 +350,7 @@ class Notification(BaseNotification):
|
|
|
204
350
|
message (str, optional): notification message. Defaults to 'last message'.
|
|
205
351
|
show_on_update (bool, optional): To show notification briefly when progressbar removed. Defaults to True.
|
|
206
352
|
title (str, optional): notification title. Defaults to 'last title'.
|
|
207
|
-
cooldown (float, optional):
|
|
353
|
+
cooldown (float, optional): Little Time to Wait before change actually reflects, to avoid android Ignoring Change, Defaults to 0.5secs
|
|
208
354
|
|
|
209
355
|
In-Built Delay of 0.5sec According to Android Docs Don't Update Progressbar too Frequently
|
|
210
356
|
"""
|
|
@@ -220,7 +366,6 @@ class Notification(BaseNotification):
|
|
|
220
366
|
self.__update_timer = None
|
|
221
367
|
|
|
222
368
|
|
|
223
|
-
|
|
224
369
|
def delayed_update():
|
|
225
370
|
if self.logs:
|
|
226
371
|
msg = message or self.message
|
|
@@ -236,11 +381,23 @@ class Notification(BaseNotification):
|
|
|
236
381
|
self.updateTitle(title)
|
|
237
382
|
self.__builder.setOnlyAlertOnce(not show_on_update)
|
|
238
383
|
self.__builder.setProgress(0, 0, False)
|
|
239
|
-
self.
|
|
384
|
+
self.refresh()
|
|
240
385
|
|
|
241
386
|
# Incase `self.updateProgressBar delayed_update` is called right before this method, so android doesn't bounce update
|
|
242
387
|
threading.Timer(cooldown, delayed_update).start()
|
|
243
388
|
|
|
389
|
+
def setPriority(self,importance:Importance):
|
|
390
|
+
"""
|
|
391
|
+
For devices less than android 8
|
|
392
|
+
:param importance: ['urgent', 'high', 'medium', 'low', 'none'] defaults to 'urgent' i.e. makes a sound and shows briefly
|
|
393
|
+
:return:
|
|
394
|
+
"""
|
|
395
|
+
self.__using_set_priority_method=True
|
|
396
|
+
if ON_ANDROID:
|
|
397
|
+
android_importance_value = self.__get_android_importance(importance)
|
|
398
|
+
if not isinstance(android_importance_value, str): # Can be an empty str if importance='none'
|
|
399
|
+
self.__builder.setPriority(android_importance_value)
|
|
400
|
+
|
|
244
401
|
def send(self,silent:bool=False,persistent=False,close_on_click=True):
|
|
245
402
|
"""Sends notification
|
|
246
403
|
|
|
@@ -249,26 +406,29 @@ class Notification(BaseNotification):
|
|
|
249
406
|
persistent (bool): True To not remove Notification When User hits clears All notifications button
|
|
250
407
|
close_on_click (bool): True if you want Notification to be removed when clicked
|
|
251
408
|
"""
|
|
252
|
-
self.silent=
|
|
409
|
+
self.silent= silent or self.silent
|
|
253
410
|
if ON_ANDROID:
|
|
254
|
-
self.
|
|
255
|
-
self.
|
|
411
|
+
self.__start_notification_build(persistent, close_on_click)
|
|
412
|
+
self.__dispatch_notification()
|
|
413
|
+
|
|
256
414
|
if self.logs:
|
|
257
415
|
string_to_display=''
|
|
258
416
|
print("\n Sent Notification!!!")
|
|
259
417
|
for name,value in vars(self).items():
|
|
260
|
-
if value and name in ["title", "message", "style", "body", "large_icon_path", "big_picture_path", "progress_current_value", "progress_max_value", "channel_name"]:
|
|
418
|
+
if value and name in ["title", "message", "style", "body", "large_icon_path", "big_picture_path", "progress_current_value", "progress_max_value", "channel_name",'body','name']:
|
|
261
419
|
if name == "progress_max_value":
|
|
262
420
|
if self.style == NotificationStyles.PROGRESS:
|
|
263
421
|
string_to_display += f'\n {name}: {value}'
|
|
422
|
+
elif name == "body":
|
|
423
|
+
if self.style == NotificationStyles.BIG_TEXT:
|
|
424
|
+
string_to_display += f'\n {name}: {value}'
|
|
264
425
|
else:
|
|
265
426
|
string_to_display += f'\n {name}: {value}'
|
|
266
427
|
|
|
267
428
|
string_to_display +="\n (Won't Print Logs When Complied,except if selected `Notification.logs=True`)"
|
|
268
429
|
print(string_to_display)
|
|
269
430
|
if DEV:
|
|
270
|
-
print(f'channel_name: {self.channel_name}, Channel ID: {self.channel_id},
|
|
271
|
-
print('Can\'t Send Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" for Documentation.\n' if DEV else '\n') # pylint: disable=C0301
|
|
431
|
+
print(f'channel_name: {self.channel_name}, Channel ID: {self.channel_id}, __id: {self.__id}')
|
|
272
432
|
|
|
273
433
|
def addButton(self, text:str,on_release):
|
|
274
434
|
"""For adding action buttons
|
|
@@ -283,7 +443,7 @@ class Notification(BaseNotification):
|
|
|
283
443
|
if not ON_ANDROID:
|
|
284
444
|
return
|
|
285
445
|
|
|
286
|
-
btn_id= self.
|
|
446
|
+
btn_id= self.__get_id_for_button()
|
|
287
447
|
action = f"BTN_ACTION_{btn_id}"
|
|
288
448
|
|
|
289
449
|
action_intent = Intent(context, PythonActivity)
|
|
@@ -325,9 +485,9 @@ class Notification(BaseNotification):
|
|
|
325
485
|
"""
|
|
326
486
|
if ON_ANDROID:
|
|
327
487
|
self.__builder.mActions.clear()
|
|
328
|
-
self.
|
|
488
|
+
self.refresh()
|
|
329
489
|
if self.logs:
|
|
330
|
-
print('Removed
|
|
490
|
+
print('Removed Notification Buttons')
|
|
331
491
|
|
|
332
492
|
@run_on_ui_thread
|
|
333
493
|
def addNotificationStyle(self,style:str,already_sent=False):
|
|
@@ -340,108 +500,108 @@ class Notification(BaseNotification):
|
|
|
340
500
|
"""
|
|
341
501
|
|
|
342
502
|
if not ON_ANDROID:
|
|
343
|
-
# TODO for logs when not on android and style related to imgs
|
|
503
|
+
# TODO for logs when not on android and style related to imgs extract app path from buildozer.spec and print
|
|
344
504
|
return False
|
|
345
505
|
|
|
346
506
|
if style == NotificationStyles.BIG_TEXT:
|
|
347
|
-
|
|
348
|
-
big_text_style.bigText(str(self.body))
|
|
349
|
-
self.__builder.setStyle(big_text_style)
|
|
507
|
+
self.setBigText(self.body)
|
|
350
508
|
|
|
351
509
|
elif style == NotificationStyles.INBOX:
|
|
352
|
-
inbox_style = NotificationCompatInboxStyle()
|
|
510
|
+
inbox_style = NotificationCompatInboxStyle()
|
|
353
511
|
for line in self.message.split("\n"):
|
|
354
512
|
inbox_style.addLine(str(line))
|
|
355
513
|
self.__builder.setStyle(inbox_style)
|
|
356
514
|
|
|
357
515
|
elif (style == NotificationStyles.LARGE_ICON and self.large_icon_path) or (style == NotificationStyles.BIG_PICTURE and self.big_picture_path):
|
|
358
516
|
img = self.large_icon_path if style == NotificationStyles.LARGE_ICON else self.big_picture_path
|
|
359
|
-
self.
|
|
517
|
+
self.__build_img(img, style)
|
|
360
518
|
|
|
361
519
|
elif style == NotificationStyles.BOTH_IMGS and (self.big_picture_path or self.large_icon_path):
|
|
362
520
|
if self.big_picture_path:
|
|
363
|
-
self.
|
|
521
|
+
self.setBigPicture(self.big_picture_path)
|
|
364
522
|
if self.large_icon_path:
|
|
365
|
-
self.
|
|
523
|
+
self.setLargeIcon(self.large_icon_path)
|
|
366
524
|
|
|
367
525
|
elif style == NotificationStyles.PROGRESS:
|
|
368
526
|
self.__builder.setProgress(self.progress_max_value, self.progress_current_value, False)
|
|
369
527
|
|
|
370
528
|
if already_sent:
|
|
371
|
-
self.
|
|
529
|
+
self.refresh()
|
|
372
530
|
|
|
373
531
|
return True
|
|
374
|
-
# elif style == 'custom':
|
|
375
|
-
# self.__builder = self.__doCustomStyle()
|
|
376
532
|
|
|
377
|
-
def
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
533
|
+
def __dispatch_notification(self):
|
|
534
|
+
if NotificationHandler.has_permission():
|
|
535
|
+
self.notification_manager.notify(self.__id, self.__builder.build())
|
|
536
|
+
else:
|
|
537
|
+
print('Permission not granted to send notifications')
|
|
538
|
+
# Not asking for permission too frequently, This makes dialog popup to stop showing
|
|
539
|
+
# NotificationHandler.asks_permission()
|
|
540
|
+
|
|
541
|
+
def __start_notification_build(self, persistent, close_on_click):
|
|
542
|
+
self.__create_basic_notification(persistent, close_on_click)
|
|
381
543
|
if self.style not in ['simple','']:
|
|
382
544
|
self.addNotificationStyle(self.style)
|
|
383
545
|
|
|
384
|
-
def
|
|
385
|
-
# Notification Channel (Required for Android 8.0+)
|
|
386
|
-
# print("THis is cchannel is ",self.channel_id) #"
|
|
546
|
+
def __create_basic_notification(self, persistent, close_on_click):
|
|
387
547
|
if BuildVersion.SDK_INT >= 26 and self.notification_manager.getNotificationChannel(self.channel_id) is None:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
self.channel_id,
|
|
392
|
-
self.channel_name,
|
|
393
|
-
importance
|
|
394
|
-
)
|
|
395
|
-
self.notification_manager.createNotificationChannel(channel)
|
|
396
|
-
|
|
548
|
+
self.createChannel(self.channel_id, self.channel_name)
|
|
549
|
+
elif not self.__using_set_priority_method:
|
|
550
|
+
self.setPriority('medium' if self.silent else 'urgent')
|
|
397
551
|
# Build the notification
|
|
398
552
|
# str() This is to prevent Error When user does Notification.title='blah' instead of Notification(title='blah'
|
|
399
|
-
# TODO fix this by creating a
|
|
553
|
+
# TODO fix this by creating a on_title method in other versions
|
|
400
554
|
self.__builder.setContentTitle(str(self.title))
|
|
401
555
|
self.__builder.setContentText(str(self.message))
|
|
402
|
-
self.
|
|
403
|
-
self.__builder.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
404
|
-
self.__builder.setPriority(NotificationCompat.PRIORITY_DEFAULT if self.silent else NotificationCompat.PRIORITY_HIGH)
|
|
556
|
+
self.__insert_app_icon()
|
|
557
|
+
self.__builder.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
405
558
|
self.__builder.setOnlyAlertOnce(True)
|
|
406
559
|
self.__builder.setOngoing(persistent)
|
|
407
560
|
self.__builder.setAutoCancel(close_on_click)
|
|
408
|
-
self.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
self.
|
|
561
|
+
self.__add_intent_to_open_app()
|
|
562
|
+
self.__built_parameter_filled = True
|
|
563
|
+
|
|
564
|
+
def __insert_app_icon(self,path=''):
|
|
565
|
+
if path or self.app_icon not in ['','Defaults to package app icon']:
|
|
566
|
+
if self.logs:
|
|
567
|
+
print('getting custom icon...')
|
|
568
|
+
self.__set_icon_from_bitmap(path or self.app_icon)
|
|
416
569
|
else:
|
|
570
|
+
if self.logs:
|
|
571
|
+
print('using default icon...')
|
|
417
572
|
self.__builder.setSmallIcon(context.getApplicationInfo().icon)
|
|
418
573
|
|
|
419
|
-
def
|
|
574
|
+
def __build_img(self, user_img, img_style):
|
|
420
575
|
if user_img.startswith('http://') or user_img.startswith('https://'):
|
|
421
|
-
def callback(
|
|
422
|
-
self.
|
|
576
|
+
def callback(bitmap_):
|
|
577
|
+
self.__apply_notification_image(bitmap_,img_style)
|
|
423
578
|
thread = threading.Thread(
|
|
424
|
-
target=self.
|
|
579
|
+
target=self.__get_bitmap_from_url,
|
|
425
580
|
args=[user_img,callback]
|
|
426
581
|
)
|
|
427
582
|
thread.start()
|
|
428
583
|
else:
|
|
429
|
-
bitmap = self.
|
|
584
|
+
bitmap = self.__get_img_from_path(user_img)
|
|
430
585
|
if bitmap:
|
|
431
|
-
self.
|
|
586
|
+
self.__apply_notification_image(bitmap, img_style)
|
|
432
587
|
|
|
433
|
-
def
|
|
434
|
-
"""Path can be link or relative path"""
|
|
588
|
+
def __set_icon_from_bitmap(self, img_path):
|
|
589
|
+
"""Path can be a link or relative path"""
|
|
435
590
|
if img_path.startswith('http://') or img_path.startswith('https://'):
|
|
436
|
-
def callback(
|
|
437
|
-
|
|
438
|
-
|
|
591
|
+
def callback(bitmap_):
|
|
592
|
+
if bitmap_:
|
|
593
|
+
icon_ = IconCompat.createWithBitmap(bitmap_)
|
|
594
|
+
self.__builder.setSmallIcon(icon_)
|
|
595
|
+
else:
|
|
596
|
+
if self.logs:
|
|
597
|
+
print('Using Default Icon as fallback......')
|
|
598
|
+
self.__builder.setSmallIcon(context.getApplicationInfo().icon)
|
|
439
599
|
threading.Thread(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
600
|
+
target=self.__get_bitmap_from_url,
|
|
601
|
+
args=[img_path,callback]
|
|
602
|
+
).start()
|
|
443
603
|
else:
|
|
444
|
-
bitmap = self.
|
|
604
|
+
bitmap = self.__get_img_from_path(img_path)
|
|
445
605
|
if bitmap:
|
|
446
606
|
icon = IconCompat.createWithBitmap(bitmap)
|
|
447
607
|
self.__builder.setSmallIcon(icon)
|
|
@@ -450,8 +610,9 @@ class Notification(BaseNotification):
|
|
|
450
610
|
print('Failed getting img for custom notification icon defaulting to app icon')
|
|
451
611
|
self.__builder.setSmallIcon(context.getApplicationInfo().icon)
|
|
452
612
|
|
|
453
|
-
|
|
454
|
-
|
|
613
|
+
@staticmethod
|
|
614
|
+
def __get_img_from_path(relative_path):
|
|
615
|
+
app_folder=os.path.join(app_storage_path(),'app')
|
|
455
616
|
output_path = os.path.join(app_folder, relative_path)
|
|
456
617
|
if not os.path.exists(output_path):
|
|
457
618
|
print(f"\nImage not found at path: {output_path}, (Local images gotten from App Path)")
|
|
@@ -463,7 +624,7 @@ class Notification(BaseNotification):
|
|
|
463
624
|
uri = Uri.parse(f"file://{output_path}")
|
|
464
625
|
return BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri))
|
|
465
626
|
|
|
466
|
-
def
|
|
627
|
+
def __get_bitmap_from_url(self, url, callback):
|
|
467
628
|
"""Gets Bitmap from url
|
|
468
629
|
|
|
469
630
|
Args:
|
|
@@ -483,52 +644,36 @@ class Notification(BaseNotification):
|
|
|
483
644
|
if bitmap:
|
|
484
645
|
callback(bitmap)
|
|
485
646
|
else:
|
|
486
|
-
print('Error No Bitmap ------------')
|
|
487
|
-
except Exception as
|
|
488
|
-
|
|
489
|
-
|
|
647
|
+
print('Error No Bitmap for small icon ------------')
|
|
648
|
+
except Exception as extracting_bitmap_frm_URL_error:
|
|
649
|
+
callback(None)
|
|
650
|
+
# TODO get all types of JAVA Error that can fail here
|
|
651
|
+
print('Error Type ',extracting_bitmap_frm_URL_error)
|
|
490
652
|
print('Failed to get Bitmap from URL ',traceback.format_exc())
|
|
491
653
|
|
|
492
654
|
@run_on_ui_thread
|
|
493
|
-
def
|
|
494
|
-
if self.logs:
|
|
495
|
-
print('appying notification image-------')
|
|
655
|
+
def __apply_notification_image(self, bitmap, img_style):
|
|
496
656
|
try:
|
|
497
|
-
if img_style == NotificationStyles.BIG_PICTURE:
|
|
498
|
-
big_picture_style = NotificationCompatBigPictureStyle().bigPicture(bitmap)
|
|
657
|
+
if img_style == NotificationStyles.BIG_PICTURE and bitmap:
|
|
658
|
+
big_picture_style = NotificationCompatBigPictureStyle().bigPicture(bitmap)
|
|
499
659
|
self.__builder.setStyle(big_picture_style)
|
|
500
|
-
elif img_style == NotificationStyles.LARGE_ICON:
|
|
660
|
+
elif img_style == NotificationStyles.LARGE_ICON and bitmap:
|
|
501
661
|
self.__builder.setLargeIcon(bitmap)
|
|
502
|
-
|
|
662
|
+
# LargeIcon requires smallIcon to be already set
|
|
663
|
+
# 'setLarge, setBigPic' tries to dispatch before filling required values `self.__create_basic_notification`
|
|
664
|
+
self.refresh()
|
|
503
665
|
if self.logs:
|
|
504
666
|
print('Done adding image to notification-------')
|
|
505
|
-
except Exception as
|
|
667
|
+
except Exception as notification_image_error:
|
|
506
668
|
img = self.large_icon_path if img_style == NotificationStyles.LARGE_ICON else self.big_picture_path
|
|
507
|
-
print(f'Failed adding Image of style: {img_style} || From path: {img}, Exception {
|
|
508
|
-
print('could
|
|
509
|
-
|
|
510
|
-
def __getUniqueID(self):
|
|
511
|
-
notification_id = self.notification_ids[-1] + 1
|
|
512
|
-
self.notification_ids.append(notification_id)
|
|
513
|
-
return notification_id
|
|
669
|
+
print(f'Failed adding Image of style: {img_style} || From path: {img}, Exception {notification_image_error}')
|
|
670
|
+
print('could not get Img traceback: ',traceback.format_exc())
|
|
514
671
|
|
|
515
|
-
def
|
|
516
|
-
"""
|
|
517
|
-
Ask for permission to send notifications if needed.
|
|
518
|
-
"""
|
|
519
|
-
def on_permissions_result(permissions, grant): # pylint: disable=unused-argument
|
|
520
|
-
if self.logs:
|
|
521
|
-
print("Permission Grant State: ",grant)
|
|
522
|
-
|
|
523
|
-
permissions=[Permission.POST_NOTIFICATIONS] # pylint: disable=E0606
|
|
524
|
-
if not all(check_permission(p) for p in permissions):
|
|
525
|
-
request_permissions(permissions,on_permissions_result) # pylint: disable=E0606
|
|
526
|
-
|
|
527
|
-
def __addIntentToOpenApp(self):
|
|
672
|
+
def __add_intent_to_open_app(self):
|
|
528
673
|
intent = Intent(context, PythonActivity)
|
|
529
|
-
action = str(self.
|
|
674
|
+
action = str(self.name or self.__id)
|
|
530
675
|
intent.setAction(action)
|
|
531
|
-
self.
|
|
676
|
+
self.__add_data_to_intent(intent)
|
|
532
677
|
self.main_functions[action]=self.callback
|
|
533
678
|
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
|
534
679
|
|
|
@@ -538,32 +683,38 @@ class Notification(BaseNotification):
|
|
|
538
683
|
)
|
|
539
684
|
self.__builder.setContentIntent(pending_intent)
|
|
540
685
|
|
|
541
|
-
def
|
|
542
|
-
"""
|
|
686
|
+
def __add_data_to_intent(self, intent):
|
|
687
|
+
"""Persist Some data to notification object for later use"""
|
|
543
688
|
bundle = Bundle()
|
|
544
689
|
bundle.putString("title", self.title or 'Title Placeholder')
|
|
545
|
-
bundle.putInt("notify_id", self.__id)
|
|
690
|
+
# bundle.putInt("notify_id", self.__id)
|
|
691
|
+
bundle.putInt("notify_id", 101)
|
|
546
692
|
intent.putExtras(bundle)
|
|
547
693
|
|
|
548
|
-
def
|
|
694
|
+
def __get_id_for_button(self):
|
|
549
695
|
btn_id = self.button_ids[-1] + 1
|
|
550
696
|
self.button_ids.append(btn_id)
|
|
551
697
|
return btn_id
|
|
552
698
|
|
|
553
|
-
def
|
|
554
|
-
|
|
555
|
-
|
|
699
|
+
def __format_channel(self, channel_name:str='Default Channel',channel_id:str='default_channel'):
|
|
700
|
+
"""
|
|
701
|
+
Formats and sets self.channel_name and self.channel_id to formatted version
|
|
702
|
+
:param channel_name:
|
|
703
|
+
:param channel_id:
|
|
704
|
+
:return:
|
|
705
|
+
"""
|
|
706
|
+
# Shorten channel name # android docs as at most 40 chars
|
|
707
|
+
if channel_name != 'Default Channel':
|
|
708
|
+
cleaned_name = channel_name.strip()
|
|
556
709
|
self.channel_name = cleaned_name[:40] if cleaned_name else 'Default Channel'
|
|
557
710
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
# Generate channel_id from channel_name if only channel_name is provided
|
|
563
|
-
generated_id = self.__generate_channel_id(inputted_kwargs['channel_name'])
|
|
564
|
-
self.channel_id = generated_id
|
|
711
|
+
# If no channel_id then generating channel_id from passed in channel_name
|
|
712
|
+
if channel_id == 'default_channel':
|
|
713
|
+
generated_id = self.__generate_channel_id(channel_name)
|
|
714
|
+
self.channel_id = generated_id
|
|
565
715
|
|
|
566
|
-
|
|
716
|
+
@staticmethod
|
|
717
|
+
def __generate_channel_id(channel_name: str) -> str:
|
|
567
718
|
"""
|
|
568
719
|
Generate a readable and consistent channel ID from a channel name.
|
|
569
720
|
|
|
@@ -581,40 +732,84 @@ class Notification(BaseNotification):
|
|
|
581
732
|
channel_id = channel_id.strip('_')
|
|
582
733
|
return channel_id[:50]
|
|
583
734
|
|
|
735
|
+
def __get_unique_id(self):
|
|
736
|
+
notification_id = self.notification_ids[-1] + 1
|
|
737
|
+
self.notification_ids.append(notification_id)
|
|
738
|
+
return notification_id
|
|
739
|
+
|
|
740
|
+
@staticmethod
|
|
741
|
+
def __return_notification_manger():
|
|
742
|
+
notification_service = context.getSystemService(context.NOTIFICATION_SERVICE)
|
|
743
|
+
return cast(NotificationManager, notification_service)
|
|
744
|
+
|
|
745
|
+
@classmethod
|
|
746
|
+
def getChannels(cls):
|
|
747
|
+
"""Return all existing channels"""
|
|
748
|
+
if not ON_ANDROID:
|
|
749
|
+
return []
|
|
750
|
+
|
|
751
|
+
return cls.__return_notification_manger().getNotificationChannels()
|
|
752
|
+
|
|
753
|
+
@staticmethod
|
|
754
|
+
def __get_android_importance(importance:Importance):
|
|
755
|
+
"""
|
|
756
|
+
Returns Android Importance Values
|
|
757
|
+
:param importance: ['urgent','high','medium','low','none']
|
|
758
|
+
:return: Android equivalent int or empty str
|
|
759
|
+
"""
|
|
760
|
+
value=''
|
|
761
|
+
if importance == 'urgent':
|
|
762
|
+
value = NotificationCompat.PRIORITY_HIGH if BuildVersion.SDK_INT <= 25 else NotificationManagerCompat.IMPORTANCE_HIGH
|
|
763
|
+
elif importance == 'high':
|
|
764
|
+
value = NotificationCompat.PRIORITY_DEFAULT if BuildVersion.SDK_INT <= 25 else NotificationManagerCompat.IMPORTANCE_DEFAULT
|
|
765
|
+
elif importance == 'medium':
|
|
766
|
+
value = NotificationCompat.PRIORITY_LOW if BuildVersion.SDK_INT <= 25 else NotificationManagerCompat.IMPORTANCE_LOW
|
|
767
|
+
elif importance == 'low':
|
|
768
|
+
value = NotificationCompat.PRIORITY_MIN if BuildVersion.SDK_INT <= 25 else NotificationManagerCompat.IMPORTANCE_MIN
|
|
769
|
+
elif importance == 'none':
|
|
770
|
+
value = '' if BuildVersion.SDK_INT <= 25 else NotificationManagerCompat.IMPORTANCE_NONE
|
|
771
|
+
|
|
772
|
+
return value
|
|
773
|
+
# side-note 'medium' = NotificationCompat.PRIORITY_LOW and 'low' = NotificationCompat.PRIORITY_MIN # weird but from docs
|
|
774
|
+
# TODO method to create channel groups
|
|
775
|
+
|
|
776
|
+
|
|
584
777
|
class NotificationHandler:
|
|
585
778
|
"""For Notification Operations """
|
|
586
|
-
|
|
779
|
+
__name = None
|
|
587
780
|
__bound = False
|
|
588
|
-
|
|
781
|
+
__requesting_permission=False
|
|
589
782
|
@classmethod
|
|
590
|
-
def
|
|
591
|
-
"""Returns
|
|
783
|
+
def get_name(cls):
|
|
784
|
+
"""Returns name or id str for Clicked Notification."""
|
|
592
785
|
if not cls.is_on_android():
|
|
593
786
|
return "Not on Android"
|
|
594
787
|
|
|
595
|
-
saved_intent = cls.
|
|
596
|
-
|
|
788
|
+
saved_intent = cls.__name
|
|
789
|
+
cls.__name = None # so value won't be set when opening app not from notification
|
|
790
|
+
# print('saved_intent ',saved_intent)
|
|
791
|
+
# if not saved_intent or (isinstance(saved_intent, str) and saved_intent.startswith("android.intent")):
|
|
597
792
|
# All other notifications are not None after First notification opens app
|
|
598
793
|
# NOTE these notifications are also from Last time app was opened and they Still Give Value after first one opens App
|
|
599
794
|
# TODO Find a way to get intent when App if Swiped From recents
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
print('
|
|
795
|
+
# Below action is always None
|
|
796
|
+
# __PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
|
|
797
|
+
# __mactivity = __PythonActivity.mActivity
|
|
798
|
+
# __context = cast('android.content.Context', __mactivity)
|
|
799
|
+
# __Intent = autoclass('android.content.Intent')
|
|
800
|
+
# __intent = __Intent(__context, __PythonActivity)
|
|
801
|
+
# action = __intent.getAction()
|
|
802
|
+
# print('Start up Intent ----', action)
|
|
803
|
+
# print('start Up Title --->',__intent.getStringExtra("title"))
|
|
608
804
|
|
|
609
805
|
return saved_intent
|
|
610
806
|
|
|
611
807
|
@classmethod
|
|
612
|
-
def
|
|
808
|
+
def __notification_handler(cls, intent):
|
|
613
809
|
"""Calls Function Attached to notification on click.
|
|
614
810
|
Don't Call this function manual, it's Already Attach to Notification.
|
|
615
811
|
|
|
616
|
-
|
|
617
|
-
str: The Identiter of Nofication that was clicked.
|
|
812
|
+
Sets self.__name #action of Notification that was clicked from Notification.name or Notification.id
|
|
618
813
|
"""
|
|
619
814
|
if not cls.is_on_android():
|
|
620
815
|
return "Not on Android"
|
|
@@ -623,13 +818,13 @@ class NotificationHandler:
|
|
|
623
818
|
if DEV:
|
|
624
819
|
print("notifty_functions ",notifty_functions)
|
|
625
820
|
print("buttons_object", buttons_object)
|
|
626
|
-
action = None
|
|
627
821
|
try:
|
|
628
822
|
action = intent.getAction()
|
|
629
|
-
cls.
|
|
823
|
+
cls.__name = action
|
|
630
824
|
|
|
631
|
-
print("The Action --> ",action)
|
|
825
|
+
# print("The Action --> ",action)
|
|
632
826
|
if action == "android.intent.action.MAIN": # Not Open From Notification
|
|
827
|
+
cls.__name = None
|
|
633
828
|
return 'Not notification'
|
|
634
829
|
|
|
635
830
|
print(intent.getStringExtra("title"))
|
|
@@ -638,12 +833,11 @@ class NotificationHandler:
|
|
|
638
833
|
notifty_functions[action]()
|
|
639
834
|
elif action in buttons_object:
|
|
640
835
|
buttons_object[action]()
|
|
641
|
-
except Exception as
|
|
836
|
+
except Exception as notification_handler_function_error:
|
|
837
|
+
print("Error Type ",notification_handler_function_error)
|
|
642
838
|
print('Failed to run function: ', traceback.format_exc())
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
print('Notify Hanlder Failed ',e)
|
|
646
|
-
return action
|
|
839
|
+
except Exception as extracting_notification_props_error:
|
|
840
|
+
print('Notify Handler Failed ',extracting_notification_props_error)
|
|
647
841
|
|
|
648
842
|
@classmethod
|
|
649
843
|
def bindNotifyListener(cls):
|
|
@@ -652,15 +846,16 @@ class NotificationHandler:
|
|
|
652
846
|
return "Not on Android"
|
|
653
847
|
#TODO keep trying BroadcastReceiver
|
|
654
848
|
if cls.__bound:
|
|
655
|
-
print("
|
|
849
|
+
print("binding done already ")
|
|
656
850
|
return True
|
|
657
851
|
try:
|
|
658
|
-
activity.bind(on_new_intent=cls.
|
|
852
|
+
activity.bind(on_new_intent=cls.__notification_handler)
|
|
659
853
|
cls.__bound = True
|
|
660
854
|
return True
|
|
661
|
-
except Exception as
|
|
662
|
-
print('Failed to bin
|
|
855
|
+
except Exception as binding_listener_error:
|
|
856
|
+
print('Failed to bin notifications listener',binding_listener_error)
|
|
663
857
|
return False
|
|
858
|
+
|
|
664
859
|
@classmethod
|
|
665
860
|
def unbindNotifyListener(cls):
|
|
666
861
|
"""Removes Listener for Notifications Click"""
|
|
@@ -669,10 +864,10 @@ class NotificationHandler:
|
|
|
669
864
|
|
|
670
865
|
#Beta TODO use BroadcastReceiver
|
|
671
866
|
try:
|
|
672
|
-
activity.unbind(on_new_intent=cls.
|
|
867
|
+
activity.unbind(on_new_intent=cls.__notification_handler)
|
|
673
868
|
return True
|
|
674
|
-
except Exception as
|
|
675
|
-
print("Failed to unbind notifications listener: ",
|
|
869
|
+
except Exception as unbinding_listener_error:
|
|
870
|
+
print("Failed to unbind notifications listener: ",unbinding_listener_error)
|
|
676
871
|
return False
|
|
677
872
|
|
|
678
873
|
@staticmethod
|
|
@@ -680,4 +875,49 @@ class NotificationHandler:
|
|
|
680
875
|
"""Utility to check if the app is running on Android."""
|
|
681
876
|
return ON_ANDROID
|
|
682
877
|
|
|
878
|
+
@staticmethod
|
|
879
|
+
def has_permission():
|
|
880
|
+
"""
|
|
881
|
+
Checks if device has permission to send notifications
|
|
882
|
+
returns True if device has permission
|
|
883
|
+
"""
|
|
884
|
+
if not ON_ANDROID:
|
|
885
|
+
return True
|
|
886
|
+
return check_permission(Permission.POST_NOTIFICATIONS)
|
|
887
|
+
|
|
888
|
+
@classmethod
|
|
889
|
+
@run_on_ui_thread
|
|
890
|
+
def asks_permission(cls,callback=None):
|
|
891
|
+
"""
|
|
892
|
+
Ask for permission to send notifications if needed.
|
|
893
|
+
Passes True to callback if access granted
|
|
894
|
+
"""
|
|
895
|
+
if cls.__requesting_permission or not ON_ANDROID:
|
|
896
|
+
return True
|
|
897
|
+
|
|
898
|
+
def on_permissions_result(permissions, grants):
|
|
899
|
+
try:
|
|
900
|
+
if callback:
|
|
901
|
+
if can_accept_arguments(callback, True):
|
|
902
|
+
callback(grants[0])
|
|
903
|
+
else:
|
|
904
|
+
callback()
|
|
905
|
+
except Exception as request_permission_error:
|
|
906
|
+
print('Exception: ',request_permission_error)
|
|
907
|
+
print('Permission response callback error: ',traceback.format_exc())
|
|
908
|
+
finally:
|
|
909
|
+
cls.__requesting_permission = False
|
|
910
|
+
|
|
911
|
+
if not cls.has_permission():
|
|
912
|
+
cls.__requesting_permission = True
|
|
913
|
+
request_permissions([Permission.POST_NOTIFICATIONS],on_permissions_result)
|
|
914
|
+
else:
|
|
915
|
+
cls.__requesting_permission = False
|
|
916
|
+
if callback:
|
|
917
|
+
if can_accept_arguments(callback,True):
|
|
918
|
+
callback(True)
|
|
919
|
+
else:
|
|
920
|
+
callback()
|
|
921
|
+
|
|
922
|
+
|
|
683
923
|
NotificationHandler.bindNotifyListener()
|