android-notify 1.3__py3-none-any.whl → 1.60.6.dev0__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.

@@ -0,0 +1,167 @@
1
+ import os, traceback
2
+
3
+ ON_ANDROID = False
4
+ __version__ = "1.60.6.dev0"
5
+
6
+ def is_platform_android():
7
+ if os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME"):
8
+ return True
9
+ kivy_build = os.environ.get('KIVY_BUILD', '')
10
+ if kivy_build in {'android'}:
11
+ return True
12
+ elif 'P4A_BOOTSTRAP' in os.environ:
13
+ return True
14
+ elif 'ANDROID_ARGUMENT' in os.environ:
15
+ return True
16
+
17
+ return False
18
+
19
+
20
+ def on_flet_app():
21
+ return os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME")
22
+
23
+
24
+ def get_activity_class_name():
25
+ ACTIVITY_CLASS_NAME = os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME") # flet python
26
+ if not ACTIVITY_CLASS_NAME:
27
+ try:
28
+ from android import config # type: ignore
29
+ ACTIVITY_CLASS_NAME = config.JAVA_NAMESPACE
30
+ except (ImportError, AttributeError):
31
+ ACTIVITY_CLASS_NAME = 'org.kivy.android'
32
+ return ACTIVITY_CLASS_NAME
33
+
34
+
35
+ if is_platform_android():
36
+ try:
37
+ from jnius import cast, autoclass
38
+ except Exception as error_importing_frm_jnius:
39
+ print('android-notify: No pjnius, not on android? Error-',error_importing_frm_jnius)
40
+ # So commandline still works if java isn't installed and get pyjinus import error
41
+ cast = lambda x: x
42
+ autoclass = lambda x: None
43
+
44
+ try:
45
+ # Android Imports
46
+
47
+ # Get the required Java classes needs to on android to import
48
+ Bundle = autoclass('android.os.Bundle')
49
+ String = autoclass('java.lang.String')
50
+ Intent = autoclass('android.content.Intent')
51
+ PendingIntent = autoclass('android.app.PendingIntent')
52
+ BitmapFactory = autoclass('android.graphics.BitmapFactory')
53
+ BuildVersion = autoclass('android.os.Build$VERSION')
54
+ NotificationManagerClass = autoclass('android.app.NotificationManager')
55
+ NotificationChannel = autoclass('android.app.NotificationChannel')
56
+ RemoteViews = autoclass('android.widget.RemoteViews')
57
+ AndroidNotification = autoclass("android.app.Notification")
58
+ Settings = autoclass("android.provider.Settings")
59
+ Uri = autoclass("android.net.Uri")
60
+ Manifest = autoclass('android.Manifest$permission')
61
+
62
+ ON_ANDROID = bool(RemoteViews)
63
+ except Exception as e:
64
+ from .an_types import *
65
+ print('Exception: ', e)
66
+ print(traceback.format_exc())
67
+ else:
68
+ from .an_types import *
69
+ cast = lambda x: x
70
+ autoclass = lambda x: None
71
+
72
+ if ON_ANDROID:
73
+ try:
74
+ Color = autoclass('android.graphics.Color')
75
+
76
+ # Notification Design
77
+ NotificationCompatBuilder = autoclass('android.app.Notification$Builder')
78
+ NotificationCompatBigTextStyle = autoclass('android.app.Notification$BigTextStyle')
79
+ NotificationCompatBigPictureStyle = autoclass('android.app.Notification$BigPictureStyle')
80
+ NotificationCompatInboxStyle = autoclass('android.app.Notification$InboxStyle')
81
+ # NotificationCompatDecoratedCustomViewStyle = autoclass('androidx.core.app.NotificationCompat$DecoratedCustomViewStyle')
82
+
83
+ except Exception as styles_import_error:
84
+ print('styles_import_error: ', styles_import_error)
85
+
86
+ from .an_types import *
87
+ else:
88
+ from .an_types import *
89
+
90
+
91
+ def from_service_file():
92
+ return 'PYTHON_SERVICE_ARGUMENT' in os.environ
93
+
94
+
95
+ run_on_ui_thread = None
96
+ if on_flet_app() or from_service_file() or not ON_ANDROID:
97
+ def run_on_ui_thread(func):
98
+ """Fallback for Developing on PC"""
99
+
100
+ def wrapper(*args, **kwargs):
101
+ # print("Simulating run on UI thread")
102
+ return func(*args, **kwargs)
103
+
104
+ return wrapper
105
+ else: # TODO find var for kivy
106
+ from android.runnable import run_on_ui_thread
107
+
108
+
109
+ def get_python_activity():
110
+ if not ON_ANDROID:
111
+ from .an_types import PythonActivity
112
+ return PythonActivity
113
+ ACTIVITY_CLASS_NAME = get_activity_class_name()
114
+ if on_flet_app():
115
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME)
116
+ else:
117
+ PythonActivity = autoclass(ACTIVITY_CLASS_NAME + '.PythonActivity')
118
+ return PythonActivity
119
+
120
+
121
+ def get_python_service():
122
+ if not ON_ANDROID:
123
+ return None
124
+ PythonService = autoclass(get_activity_class_name() + '.PythonService')
125
+ return PythonService.mService
126
+
127
+
128
+ def get_python_activity_context():
129
+ if not ON_ANDROID:
130
+ from .an_types import Context
131
+ return Context
132
+
133
+ PythonActivity = get_python_activity()
134
+ if from_service_file():
135
+ service = get_python_service()
136
+ context = service.getApplication().getApplicationContext()
137
+ else:
138
+ context = PythonActivity.mActivity
139
+ return context
140
+
141
+
142
+ if ON_ANDROID:
143
+ context = get_python_activity_context()
144
+ else:
145
+ context = None
146
+
147
+
148
+ def get_notification_manager():
149
+ if not ON_ANDROID:
150
+ return None
151
+ notification_service = context.getSystemService(context.NOTIFICATION_SERVICE)
152
+ return cast(NotificationManagerClass, notification_service)
153
+
154
+
155
+ def app_storage_path():
156
+ if on_flet_app():
157
+ return os.path.join(context.getFilesDir().getAbsolutePath(), 'flet')
158
+ else:
159
+ try:
160
+ from android.storage import app_storage_path as kivy_app_storage_path # type: ignore
161
+ return kivy_app_storage_path()
162
+ except Exception as e:
163
+ return './' # TODO return file main.py path (not android)
164
+
165
+
166
+ def get_package_name():
167
+ return context.getPackageName() # package.domain + "." + package.name
android_notify/core.py CHANGED
@@ -1,50 +1,100 @@
1
1
  """ Non-Advanced Stuff """
2
2
  import random
3
- import os
4
- from jnius import autoclass,cast
3
+ import os, traceback
4
+ from .config import get_python_activity, Manifest, is_platform_android
5
5
 
6
6
  ON_ANDROID = False
7
- try:
8
- # Get the required Java classes
9
- PythonActivity = autoclass('org.kivy.android.PythonActivity')
10
- NotificationChannel = autoclass('android.app.NotificationChannel')
11
- String = autoclass('java.lang.String')
12
- Intent = autoclass('android.content.Intent')
13
- PendingIntent = autoclass('android.app.PendingIntent')
14
- context = PythonActivity.mActivity # Get the app's context
15
- BitmapFactory = autoclass('android.graphics.BitmapFactory')
16
- BuildVersion = autoclass('android.os.Build$VERSION')
17
- ON_ANDROID=True
18
- except Exception as e:
19
- print('This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" to see design patterns and more info.')
20
7
 
21
- if ON_ANDROID:
8
+
9
+ def on_flet_app():
10
+ return os.getenv("MAIN_ACTIVITY_HOST_CLASS_NAME")
11
+
12
+
13
+ if is_platform_android():
22
14
  try:
23
- NotificationManagerCompat = autoclass('androidx.core.app.NotificationManagerCompat')
24
- NotificationCompat = autoclass('androidx.core.app.NotificationCompat')
15
+ from jnius import autoclass # Needs Java to be installed
16
+ PythonActivity = get_python_activity()
17
+ context = PythonActivity.mActivity # Get the app's context
18
+ NotificationChannel = autoclass('android.app.NotificationChannel')
19
+ String = autoclass('java.lang.String')
20
+ Intent = autoclass('android.content.Intent')
21
+ PendingIntent = autoclass('android.app.PendingIntent')
22
+ BitmapFactory = autoclass('android.graphics.BitmapFactory')
23
+ BuildVersion = autoclass('android.os.Build$VERSION')
24
+ Notification = autoclass("android.app.Notification")
25
+ ON_ANDROID = True
26
+ except Exception as e:
27
+ print("android-notify: Error importing Java Classes-",e)
28
+ traceback.print_exc()
29
+
25
30
 
31
+ if ON_ANDROID:
32
+ try:
33
+ NotificationManagerCompat = autoclass('android.app.NotificationManager')
26
34
  # Notification Design
27
- NotificationCompatBuilder = autoclass('androidx.core.app.NotificationCompat$Builder')
28
- NotificationCompatBigTextStyle = autoclass('androidx.core.app.NotificationCompat$BigTextStyle')
29
- NotificationCompatBigPictureStyle = autoclass('androidx.core.app.NotificationCompat$BigPictureStyle')
30
- NotificationCompatInboxStyle = autoclass('androidx.core.app.NotificationCompat$InboxStyle')
35
+ NotificationCompatBuilder = autoclass('android.app.Notification$Builder')
36
+ NotificationCompatBigTextStyle = autoclass('android.app.Notification$BigTextStyle')
37
+ NotificationCompatBigPictureStyle = autoclass('android.app.Notification$BigPictureStyle')
38
+ NotificationCompatInboxStyle = autoclass('android.app.Notification$InboxStyle')
31
39
  except Exception as e:
32
- print("""
33
- Dependency Error: Add the following in buildozer.spec:
34
- * android.gradle_dependencies = androidx.core:core-ktx:1.15.0, androidx.core:core:1.6.0
35
- * android.enable_androidx = True
36
- * android.permissions = POST_NOTIFICATIONS
37
- """)
38
-
39
- def asks_permission_if_needed():
40
+ traceback.print_exc()
41
+ print("Error importing notification styles")
42
+
43
+ from .an_utils import can_show_permission_request_popup, open_settings_screen
44
+
45
+
46
+ def get_app_root_path():
47
+ path = ''
48
+ if on_flet_app():
49
+ path = os.path.join(context.getFilesDir().getAbsolutePath(), 'flet')
50
+ else:
51
+ try:
52
+ from android.storage import app_storage_path # type: ignore
53
+ path = app_storage_path()
54
+ except Exception as e:
55
+ print('android-notify- Error getting apk main file path: ', e)
56
+ return './'
57
+ return os.path.join(path, 'app')
58
+
59
+
60
+ def asks_permission_if_needed(legacy=False, no_androidx=False):
40
61
  """
41
62
  Ask for permission to send notifications if needed.
63
+ legacy parameter will replace no_androidx parameter in Future Versions
42
64
  """
43
- from android.permissions import request_permissions, Permission,check_permission # type: ignore
65
+ if not ON_ANDROID:
66
+ print("android_notify- Can't ask permission when not on android")
67
+ return None
68
+
69
+ if BuildVersion.SDK_INT < 33:
70
+ print("android_notify- On android 12 or less don't need permission")
71
+ return True
72
+
73
+ if not can_show_permission_request_popup():
74
+ print("""android_notify- Permission to send notifications has been denied permanently.
75
+ This happens when the user denies permission twice from the popup.
76
+ Opening notification settings...
77
+ """)
78
+ open_settings_screen()
79
+ return None
80
+
81
+ if on_flet_app() or no_androidx or legacy:
82
+ Activity = autoclass("android.app.Activity")
83
+ PackageManager = autoclass("android.content.pm.PackageManager")
84
+
85
+ permission = Manifest.POST_NOTIFICATIONS
86
+ granted = context.checkSelfPermission(permission)
87
+ if granted != PackageManager.PERMISSION_GRANTED:
88
+ context.requestPermissions([permission], 101)
89
+ else: # android package is from p4a which is for kivy
90
+ try:
91
+ from android.permissions import request_permissions, Permission, check_permission # type: ignore
92
+ permissions = [Permission.POST_NOTIFICATIONS]
93
+ if not all(check_permission(p) for p in permissions):
94
+ request_permissions(permissions)
95
+ except Exception as e:
96
+ print("android_notify- error trying to request notification access: ", e)
44
97
 
45
- permissions=[Permission.POST_NOTIFICATIONS]
46
- if not all(check_permission(p) for p in permissions):
47
- request_permissions(permissions)
48
98
 
49
99
  def get_image_uri(relative_path):
50
100
  """
@@ -52,89 +102,134 @@ def get_image_uri(relative_path):
52
102
  :param relative_path: The relative path to the image (e.g., 'assets/imgs/icon.png').
53
103
  :return: Absolute URI java Object (e.g., 'file:///path/to/file.png').
54
104
  """
55
- from android.storage import app_storage_path # type: ignore
56
-
57
- output_path = os.path.join(app_storage_path(),'app', relative_path)
105
+ app_root_path = get_app_root_path()
106
+ output_path = os.path.join(app_root_path, relative_path)
58
107
  # print(output_path,'output_path') # /data/user/0/org.laner.lan_ft/files/app/assets/imgs/icon.png
59
-
108
+
60
109
  if not os.path.exists(output_path):
61
- raise FileNotFoundError(f"Image not found at path: {output_path}")
62
-
110
+ raise FileNotFoundError(f"\nImage not found at path: {output_path}\n")
111
+
63
112
  Uri = autoclass('android.net.Uri')
64
113
  return Uri.parse(f"file://{output_path}")
65
114
 
66
115
 
116
+ def get_icon_object(uri):
117
+ BitmapFactory = autoclass('android.graphics.BitmapFactory')
118
+ IconCompat = autoclass('androidx.core.graphics.drawable.IconCompat')
119
+
120
+ bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri))
121
+ return IconCompat.createWithBitmap(bitmap)
122
+
123
+
124
+ def insert_app_icon(builder, custom_icon_path):
125
+ if custom_icon_path:
126
+ try:
127
+ uri = get_image_uri(custom_icon_path)
128
+ icon = get_icon_object(uri)
129
+ builder.setSmallIcon(icon)
130
+ except Exception as e:
131
+ print('android_notify- error: ', e)
132
+ builder.setSmallIcon(context.getApplicationInfo().icon)
133
+ else:
134
+ # print('Found res icon -->',context.getApplicationInfo().icon,'<--')
135
+ builder.setSmallIcon(context.getApplicationInfo().icon)
136
+
137
+
67
138
  def send_notification(
68
- title:str,
69
- message:str,
70
- style=None,
71
- img_path=None,
72
- channel_name="Default Channel",
73
- channel_id:str="default_channel"
74
- ):
139
+ title: str,
140
+ message: str,
141
+ style=None,
142
+ img_path=None,
143
+ channel_name="Default Channel",
144
+ channel_id: str = "default_channel",
145
+ custom_app_icon_path="",
146
+
147
+ big_picture_path='',
148
+ large_icon_path='',
149
+ big_text="",
150
+ lines=""
151
+ ):
75
152
  """
76
153
  Send a notification on Android.
77
154
 
78
155
  :param title: Title of the notification.
79
156
  :param message: Message body.
80
- :param style: Style of the notification ('big_text', 'big_picture', 'inbox', 'large_icon').
157
+ :param style: deprecated.
81
158
  :param img_path: Path to the image resource.
82
159
  :param channel_id: Notification channel ID.(Default is lowercase channel name arg in lowercase)
83
160
  """
84
161
  if not ON_ANDROID:
85
- print('This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" for Documentation.')
86
- return
87
- asks_permission_if_needed()
88
- channel_id=channel_name.replace(' ','_').lower().lower() if not channel_id else channel_id
162
+ print(
163
+ 'This Package Only Runs on Android !!! ---> Check "https://github.com/Fector101/android_notify/" for Documentation.')
164
+ return None
165
+
166
+ asks_permission_if_needed(legacy=True)
167
+ channel_id = channel_name.replace(' ', '_').lower().lower() if not channel_id else channel_id
89
168
  # Get notification manager
90
169
  notification_manager = context.getSystemService(context.NOTIFICATION_SERVICE)
91
170
 
92
171
  # importance= autoclass('android.app.NotificationManager').IMPORTANCE_HIGH # also works #NotificationManager.IMPORTANCE_DEFAULT
93
- importance= NotificationManagerCompat.IMPORTANCE_HIGH #autoclass('android.app.NotificationManager').IMPORTANCE_HIGH also works #NotificationManager.IMPORTANCE_DEFAULT
94
-
172
+ importance = NotificationManagerCompat.IMPORTANCE_HIGH # autoclass('android.app.NotificationManager').IMPORTANCE_HIGH also works #NotificationManager.IMPORTANCE_DEFAULT
173
+
95
174
  # Notification Channel (Required for Android 8.0+)
96
175
  if BuildVersion.SDK_INT >= 26:
97
- channel = NotificationChannel(channel_id, channel_name,importance)
176
+ channel = NotificationChannel(channel_id, channel_name, importance)
98
177
  notification_manager.createNotificationChannel(channel)
99
178
 
100
179
  # Build the notification
101
180
  builder = NotificationCompatBuilder(context, channel_id)
102
181
  builder.setContentTitle(title)
103
182
  builder.setContentText(message)
104
- builder.setSmallIcon(context.getApplicationInfo().icon)
105
- builder.setDefaults(NotificationCompat.DEFAULT_ALL)
106
- builder.setPriority(NotificationCompat.PRIORITY_HIGH)
107
-
108
- img=None
183
+ insert_app_icon(builder, custom_app_icon_path)
184
+ builder.setDefaults(Notification.DEFAULT_ALL)
185
+ builder.setPriority(Notification.PRIORITY_HIGH)
186
+
109
187
  if img_path:
188
+ print(
189
+ 'android_notify- img_path arg deprecated use "large_icon_path or big_picture_path or custom_app_icon_path" instead')
190
+ if style:
191
+ print(
192
+ 'android_notify- "style" arg deprecated use args "big_picture_path", "large_icon_path", "big_text", "lines" instead')
193
+
194
+ big_picture = None
195
+ if big_picture_path:
110
196
  try:
111
- img = get_image_uri(img_path)
197
+ big_picture = get_image_uri(big_picture_path)
112
198
  except FileNotFoundError as e:
113
- print('Failed Adding Bitmap: ',e)
114
-
199
+ print('android_notify- Error Getting Uri for big_picture_path: ', e)
200
+
201
+ large_icon = None
202
+ if large_icon_path:
203
+ try:
204
+ large_icon = get_image_uri(large_icon_path)
205
+ except FileNotFoundError as e:
206
+ print('android_notify- Error Getting Uri for large_icon_path: ', e)
207
+
115
208
  # Apply notification styles
116
209
  try:
117
- if style == "big_text":
210
+ if big_text:
118
211
  big_text_style = NotificationCompatBigTextStyle()
119
- big_text_style.bigText(message)
212
+ big_text_style.bigText(big_text)
120
213
  builder.setStyle(big_text_style)
121
- elif style == "big_picture" and img_path:
122
- bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(img))
123
- builder.setLargeIcon(bitmap)
124
- big_picture_style = NotificationCompatBigPictureStyle().bigPicture(bitmap)
125
- builder.setStyle(big_picture_style)
126
- elif style == "inbox":
214
+
215
+ elif lines:
127
216
  inbox_style = NotificationCompatInboxStyle()
128
- for line in message.split("\n"):
217
+ for line in lines.split("\n"):
129
218
  inbox_style.addLine(line)
130
219
  builder.setStyle(inbox_style)
131
- elif style == "large_icon" and img_path:
132
- bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(img))
220
+
221
+ if large_icon:
222
+ bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(large_icon))
133
223
  builder.setLargeIcon(bitmap)
224
+
225
+ if big_picture:
226
+ bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(big_picture))
227
+ big_picture_style = NotificationCompatBigPictureStyle().bigPicture(bitmap)
228
+ builder.setStyle(big_picture_style)
229
+
134
230
  except Exception as e:
135
- print('Failed Adding Style: ',e)
231
+ print('android_notify- Error Failed Adding Style: ', e)
136
232
  # Display the notification
137
233
  notification_id = random.randint(0, 100)
138
234
  notification_manager.notify(notification_id, builder.build())
139
235
  return notification_id
140
-
android_notify/styles.py CHANGED
@@ -1,15 +1,25 @@
1
- class NotificationStyles():
1
+ """Contains Safe way to call Styles"""
2
+
3
+ class NotificationStyles:
2
4
  """ Safely Adding Styles"""
3
- DEFAULT = "simple"
5
+
6
+ # v1.59+
7
+ # Deprecated
8
+ # setBigText == Notification(...,big_picture_path="...",style=NotificationStyles.BIG_TEXT)
9
+ # setLargeIcon == Notification(...,large_icon_path="...",style=NotificationStyles.LARGE_ICON)
10
+ # setBigPicture == Notification(...,body="...",style=NotificationStyles.BIG_PICTURE)
11
+ # setLines == Notification(...,lines_txt="...",style=NotificationStyles.INBOX)
12
+ # Set progress_current_value and progress_max_value for progress style
13
+
14
+ # Use .refresh to apply any new changes after .send
4
15
 
16
+ DEFAULT = "simple"
5
17
  PROGRESS = "progress"
6
18
  INBOX = "inbox"
7
19
  BIG_TEXT = "big_text"
8
-
9
20
  LARGE_ICON = "large_icon"
10
21
  BIG_PICTURE = "big_picture"
11
22
  BOTH_IMGS = "both_imgs"
12
-
13
- MESSAGING = "messaging" # TODO
14
- CUSTOM = "custom" # TODO
15
-
23
+
24
+ # MESSAGING = "messaging" # TODO
25
+ # CUSTOM = "custom" # TODO v1.60