android-notify 1.61.0.dev0__tar.gz → 1.61.1.dev0__tar.gz
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.
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/PKG-INFO +38 -6
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/README.md +37 -4
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/config.py +1 -1
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/android.py +59 -9
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/channels.py +19 -6
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/facade.py +53 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/java_classes.py +3 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/sword.py +6 -4
- android_notify-1.61.1.dev0/android_notify/tests/test_notification_sound.py +67 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/PKG-INFO +38 -6
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/pyproject.toml +1 -2
- android_notify-1.61.0.dev0/android_notify/tests/test_notification_sound.py +0 -24
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/__init__.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/__main__.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/base.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/core.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/fallback-icons/flet-appicon.png +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/fallback-icons/pydroid3-appicon.png +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/an_types.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/helper.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/intents.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/logger.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/permissions.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/styles.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/__init__.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/android_notify_test.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/base_test.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/adv/main.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/adv/tests/__init__.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/adv/tests/test_android_notify_full.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/basic/src/core.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/basic/src/main.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/flet-working/src/core.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/flet-working/src/main.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/main.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/p4a/hook.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/serivces/wallpaper.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_basic_notifications.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_actions.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_appearance.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_behavior.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_channels.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_clear.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_permission.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_progress.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_styles.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/widgets/images.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/widgets/texts.py +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/SOURCES.txt +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/dependency_links.txt +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/entry_points.txt +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/requires.txt +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/top_level.txt +0 -0
- {android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: android-notify
|
|
3
|
-
Version: 1.61.
|
|
3
|
+
Version: 1.61.1.dev0
|
|
4
4
|
Summary: A Python package that simplifies creating Android notifications in Kivy and Flet apps.
|
|
5
5
|
Author-email: Fabian <fector101@yahoo.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,6 @@ Classifier: Operating System :: Android
|
|
|
15
15
|
Classifier: Development Status :: 5 - Production/Stable
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
-
Requires-Python: >=3.6
|
|
19
18
|
Description-Content-Type: text/markdown
|
|
20
19
|
Requires-Dist: pyjnius>=1.4.2
|
|
21
20
|
Provides-Extra: dev
|
|
@@ -81,7 +80,7 @@ In your **`buildozer.spec`** file, ensure you include the following:
|
|
|
81
80
|
|
|
82
81
|
```ini
|
|
83
82
|
# Add pyjnius so ensure it's packaged with the build
|
|
84
|
-
requirements = python3, kivy, pyjnius, android-notify==1.61.
|
|
83
|
+
requirements = python3, kivy, pyjnius, android-notify==1.61.1.dev0
|
|
85
84
|
# Add permission for notifications
|
|
86
85
|
android.permissions = POST_NOTIFICATIONS
|
|
87
86
|
```
|
|
@@ -99,7 +98,7 @@ In your `pyproject.toml` file, ensure you include the following:
|
|
|
99
98
|
```toml
|
|
100
99
|
[tool.flet.android]
|
|
101
100
|
dependencies = [
|
|
102
|
-
"pyjnius","android-notify==1.61.
|
|
101
|
+
"pyjnius","android-notify==1.61.1.dev0"
|
|
103
102
|
]
|
|
104
103
|
|
|
105
104
|
[tool.flet.android.permission]
|
|
@@ -117,10 +116,10 @@ dependencies = [
|
|
|
117
116
|
<br/>
|
|
118
117
|
|
|
119
118
|
On the [pydroid 3](https://play.google.com/store/apps/details?id=ru.iiec.pydroid3) mobile app for running python code you can test some features.
|
|
120
|
-
- In pip section where you're asked to insert `Libary name` paste `android-notify==1.61.
|
|
119
|
+
- In pip section where you're asked to insert `Libary name` paste `android-notify==1.61.1.dev0`
|
|
121
120
|
- Minimal working example
|
|
122
121
|
```py
|
|
123
|
-
# Testing with `android-notify==1.61.
|
|
122
|
+
# Testing with `android-notify==1.61.1.dev0` on pydroid
|
|
124
123
|
from kivy.app import App
|
|
125
124
|
from kivy.uix.boxlayout import BoxLayout
|
|
126
125
|
from kivy.uix.button import Button
|
|
@@ -209,6 +208,8 @@ n.send()
|
|
|
209
208
|
<details>
|
|
210
209
|
<summary> <b>To use Custom Sounds </b> </summary>
|
|
211
210
|
|
|
211
|
+
**Option 1: Audio files bundled in `res/raw`**
|
|
212
|
+
|
|
212
213
|
- Put audio files in `res/raw` folder,
|
|
213
214
|
- Then from `buildozer.spec` point to res folder `android.add_resources = res`
|
|
214
215
|
- and includes it's format `source.include_exts = wav`.
|
|
@@ -232,6 +233,37 @@ n=Notification(
|
|
|
232
233
|
n.setSound("sneeze")# for android 7 below
|
|
233
234
|
n.send()
|
|
234
235
|
```
|
|
236
|
+
|
|
237
|
+
**Option 2: Local file path or URI (`sound_path`)**
|
|
238
|
+
|
|
239
|
+
You can use a local audio file, a `content://`, `file://`, or `android.resource://` URI directly:
|
|
240
|
+
|
|
241
|
+
```py
|
|
242
|
+
# Using a local file path
|
|
243
|
+
Notification.createChannel(
|
|
244
|
+
id="local_sound",
|
|
245
|
+
name="Local Sound",
|
|
246
|
+
sound_path="/storage/emulated/0/Download/alert.mp3"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Using a content URI (e.g., from media store)
|
|
250
|
+
Notification.createChannel(
|
|
251
|
+
id="uri_sound",
|
|
252
|
+
name="URI Sound",
|
|
253
|
+
sound_path="content://media/external/audio/media/123"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Send notification with custom sound path
|
|
257
|
+
n = Notification(
|
|
258
|
+
title="Custom Sound",
|
|
259
|
+
message="Playing from local path",
|
|
260
|
+
channel_id="local_sound"
|
|
261
|
+
)
|
|
262
|
+
n.setSound(sound_path="/storage/emulated/0/Download/alert.mp3")
|
|
263
|
+
n.send()
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Private files (e.g., in app's `data/` directory) are automatically copied to external storage before playing.
|
|
235
267
|
</details>
|
|
236
268
|
|
|
237
269
|
|
|
@@ -58,7 +58,7 @@ In your **`buildozer.spec`** file, ensure you include the following:
|
|
|
58
58
|
|
|
59
59
|
```ini
|
|
60
60
|
# Add pyjnius so ensure it's packaged with the build
|
|
61
|
-
requirements = python3, kivy, pyjnius, android-notify==1.61.
|
|
61
|
+
requirements = python3, kivy, pyjnius, android-notify==1.61.1.dev0
|
|
62
62
|
# Add permission for notifications
|
|
63
63
|
android.permissions = POST_NOTIFICATIONS
|
|
64
64
|
```
|
|
@@ -76,7 +76,7 @@ In your `pyproject.toml` file, ensure you include the following:
|
|
|
76
76
|
```toml
|
|
77
77
|
[tool.flet.android]
|
|
78
78
|
dependencies = [
|
|
79
|
-
"pyjnius","android-notify==1.61.
|
|
79
|
+
"pyjnius","android-notify==1.61.1.dev0"
|
|
80
80
|
]
|
|
81
81
|
|
|
82
82
|
[tool.flet.android.permission]
|
|
@@ -94,10 +94,10 @@ dependencies = [
|
|
|
94
94
|
<br/>
|
|
95
95
|
|
|
96
96
|
On the [pydroid 3](https://play.google.com/store/apps/details?id=ru.iiec.pydroid3) mobile app for running python code you can test some features.
|
|
97
|
-
- In pip section where you're asked to insert `Libary name` paste `android-notify==1.61.
|
|
97
|
+
- In pip section where you're asked to insert `Libary name` paste `android-notify==1.61.1.dev0`
|
|
98
98
|
- Minimal working example
|
|
99
99
|
```py
|
|
100
|
-
# Testing with `android-notify==1.61.
|
|
100
|
+
# Testing with `android-notify==1.61.1.dev0` on pydroid
|
|
101
101
|
from kivy.app import App
|
|
102
102
|
from kivy.uix.boxlayout import BoxLayout
|
|
103
103
|
from kivy.uix.button import Button
|
|
@@ -186,6 +186,8 @@ n.send()
|
|
|
186
186
|
<details>
|
|
187
187
|
<summary> <b>To use Custom Sounds </b> </summary>
|
|
188
188
|
|
|
189
|
+
**Option 1: Audio files bundled in `res/raw`**
|
|
190
|
+
|
|
189
191
|
- Put audio files in `res/raw` folder,
|
|
190
192
|
- Then from `buildozer.spec` point to res folder `android.add_resources = res`
|
|
191
193
|
- and includes it's format `source.include_exts = wav`.
|
|
@@ -209,6 +211,37 @@ n=Notification(
|
|
|
209
211
|
n.setSound("sneeze")# for android 7 below
|
|
210
212
|
n.send()
|
|
211
213
|
```
|
|
214
|
+
|
|
215
|
+
**Option 2: Local file path or URI (`sound_path`)**
|
|
216
|
+
|
|
217
|
+
You can use a local audio file, a `content://`, `file://`, or `android.resource://` URI directly:
|
|
218
|
+
|
|
219
|
+
```py
|
|
220
|
+
# Using a local file path
|
|
221
|
+
Notification.createChannel(
|
|
222
|
+
id="local_sound",
|
|
223
|
+
name="Local Sound",
|
|
224
|
+
sound_path="/storage/emulated/0/Download/alert.mp3"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Using a content URI (e.g., from media store)
|
|
228
|
+
Notification.createChannel(
|
|
229
|
+
id="uri_sound",
|
|
230
|
+
name="URI Sound",
|
|
231
|
+
sound_path="content://media/external/audio/media/123"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Send notification with custom sound path
|
|
235
|
+
n = Notification(
|
|
236
|
+
title="Custom Sound",
|
|
237
|
+
message="Playing from local path",
|
|
238
|
+
channel_id="local_sound"
|
|
239
|
+
)
|
|
240
|
+
n.setSound(sound_path="/storage/emulated/0/Download/alert.mp3")
|
|
241
|
+
n.send()
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Private files (e.g., in app's `data/` directory) are automatically copied to external storage before playing.
|
|
212
245
|
</details>
|
|
213
246
|
|
|
214
247
|
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/android.py
RENAMED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Android related logic
|
|
3
3
|
"""
|
|
4
|
-
import time
|
|
4
|
+
import time, os
|
|
5
5
|
|
|
6
6
|
from .logger import logger
|
|
7
7
|
from ..config import get_notification_manager, on_android_platform, from_service_file, on_flet_app, \
|
|
8
8
|
get_python_activity_context
|
|
9
9
|
from .permissions import has_notification_permission
|
|
10
10
|
from .java_classes import autoclass, BuildVersion, Uri, NotificationCompat, NotificationManagerCompat, \
|
|
11
|
-
NotificationManager, Context
|
|
11
|
+
NotificationManager, Context, File
|
|
12
12
|
from .an_types import Importance
|
|
13
13
|
|
|
14
14
|
|
|
@@ -98,22 +98,72 @@ def get_sound_uri(res_sound_name):
|
|
|
98
98
|
return Uri.parse(f"android.resource://{package_name}/raw/{res_sound_name}")
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def
|
|
101
|
+
def get_sound_uri_from_path(sound_path):
|
|
102
|
+
if not on_android_platform() or not sound_path:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
if sound_path.startswith("content://") or sound_path.startswith("file://") or sound_path.startswith("android.resource://"):
|
|
106
|
+
try:
|
|
107
|
+
return Uri.parse(sound_path)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.exception(f"Error parsing sound URI: {sound_path}")
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
# Resolve relative paths
|
|
113
|
+
abs_path = os.path.abspath(sound_path)
|
|
114
|
+
if not os.path.exists(abs_path):
|
|
115
|
+
logger.warning(f"Sound file does not exist: {sound_path} (resolved to {abs_path})")
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
# Check if the file is in private storage (contains /data/)
|
|
119
|
+
if "/data/" in abs_path or not (abs_path.startswith("/storage") or abs_path.startswith("/sdcard")):
|
|
120
|
+
try:
|
|
121
|
+
context = get_python_activity_context()
|
|
122
|
+
ext_files = context.getExternalFilesDir("Notifications")
|
|
123
|
+
if ext_files:
|
|
124
|
+
ext_files_path = ext_files.getAbsolutePath()
|
|
125
|
+
dest_file = os.path.join(ext_files_path, os.path.basename(abs_path))
|
|
126
|
+
|
|
127
|
+
# Copy if destination doesn't exist or is older than the source
|
|
128
|
+
if not os.path.exists(dest_file) or os.path.getmtime(abs_path) > os.path.getmtime(dest_file):
|
|
129
|
+
import shutil
|
|
130
|
+
shutil.copy2(abs_path, dest_file)
|
|
131
|
+
abs_path = dest_file
|
|
132
|
+
except Exception as copy_error:
|
|
133
|
+
logger.exception(f"Failed to copy private sound file {abs_path} to external files: {copy_error}")
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
java_file = File(abs_path)
|
|
137
|
+
return Uri.fromFile(java_file)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.exception(f"Error generating Uri from file path: {abs_path}")
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def set_sound(builder, res_sound_name=None, sound_path=None):
|
|
102
144
|
"""
|
|
103
145
|
Sets sound for devices less than android 8 (For 8+ use createChannel)
|
|
104
146
|
:param builder: builder instance
|
|
105
147
|
:param res_sound_name: audio file name (without .wav or .mp3) locate in res/raw/
|
|
148
|
+
:param sound_path: local file path or uri string
|
|
106
149
|
"""
|
|
107
150
|
|
|
108
151
|
if not on_android_platform():
|
|
109
152
|
return None
|
|
110
153
|
|
|
111
|
-
if
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
154
|
+
if BuildVersion.SDK_INT < 26:
|
|
155
|
+
sound_uri = None
|
|
156
|
+
if sound_path:
|
|
157
|
+
sound_uri = get_sound_uri_from_path(sound_path)
|
|
158
|
+
elif res_sound_name:
|
|
159
|
+
sound_uri = get_sound_uri(res_sound_name)
|
|
160
|
+
|
|
161
|
+
if sound_uri:
|
|
162
|
+
try:
|
|
163
|
+
builder.setSound(sound_uri)
|
|
164
|
+
return True
|
|
165
|
+
except Exception as failed_adding_sound_for_devices_below_android8:
|
|
166
|
+
logger.exception(failed_adding_sound_for_devices_below_android8)
|
|
117
167
|
return None
|
|
118
168
|
|
|
119
169
|
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/channels.py
RENAMED
|
@@ -5,9 +5,9 @@ from typing import Any
|
|
|
5
5
|
|
|
6
6
|
from android_notify.config import get_notification_manager, on_android_platform
|
|
7
7
|
|
|
8
|
-
from android_notify.internal.java_classes import BuildVersion, NotificationChannel
|
|
8
|
+
from android_notify.internal.java_classes import BuildVersion, NotificationChannel, AudioAttributes, AudioAttributesBuilder
|
|
9
9
|
from android_notify.internal.an_types import Importance
|
|
10
|
-
from android_notify.internal.android import get_sound_uri, get_android_importance
|
|
10
|
+
from android_notify.internal.android import get_sound_uri, get_sound_uri_from_path, get_android_importance
|
|
11
11
|
from android_notify.internal.logger import logger
|
|
12
12
|
|
|
13
13
|
def does_channel_exist(channel_id):
|
|
@@ -24,7 +24,7 @@ def does_channel_exist(channel_id):
|
|
|
24
24
|
return False
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def create_channel(id__, name: str, description='', importance: Importance = 'urgent', res_sound_name=None,vibrate=False):
|
|
27
|
+
def create_channel(id__, name: str, description='', importance: Importance = 'urgent', res_sound_name=None, sound_path=None, vibrate=False):
|
|
28
28
|
"""
|
|
29
29
|
Creates a user visible toggle button for specific notifications, Required For Android 8.0+
|
|
30
30
|
:param id__: Used to send other notifications later through same channel.
|
|
@@ -32,12 +32,13 @@ def create_channel(id__, name: str, description='', importance: Importance = 'ur
|
|
|
32
32
|
:param description: user-visible detail about channel (Not required defaults to empty str).
|
|
33
33
|
:param importance: ['urgent', 'high', 'medium', 'low', 'none'] defaults to 'urgent' i.e. makes a sound and shows briefly
|
|
34
34
|
:param res_sound_name: audio file name (without .wav or .mp3) locate in res/raw/
|
|
35
|
+
:param sound_path: local file path or uri string
|
|
35
36
|
:param vibrate: if channel notifications should vibrate or not
|
|
36
37
|
:return: boolean if channel created
|
|
37
38
|
"""
|
|
38
39
|
def info_log():
|
|
39
40
|
logger.info(
|
|
40
|
-
f"Created {name} channel, id: {id__}, description: {description}, res_sound_name: {res_sound_name},vibrate: {vibrate}")
|
|
41
|
+
f"Created {name} channel, id: {id__}, description: {description}, res_sound_name: {res_sound_name}, sound_path: {sound_path}, vibrate: {vibrate}")
|
|
41
42
|
|
|
42
43
|
if not on_android_platform():
|
|
43
44
|
info_log()
|
|
@@ -45,14 +46,26 @@ def create_channel(id__, name: str, description='', importance: Importance = 'ur
|
|
|
45
46
|
|
|
46
47
|
notification_manager = get_notification_manager()
|
|
47
48
|
android_importance_value = get_android_importance(importance)
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
if sound_path:
|
|
51
|
+
sound_uri = get_sound_uri_from_path(sound_path)
|
|
52
|
+
else:
|
|
53
|
+
sound_uri = get_sound_uri(res_sound_name)
|
|
49
54
|
|
|
50
55
|
if not does_channel_exist(id__):
|
|
51
56
|
channel = NotificationChannel(id__, name, android_importance_value)
|
|
52
57
|
if description:
|
|
53
58
|
channel.setDescription(description)
|
|
54
59
|
if sound_uri:
|
|
55
|
-
|
|
60
|
+
try:
|
|
61
|
+
aa_builder = AudioAttributesBuilder()
|
|
62
|
+
aa_builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
|
63
|
+
aa_builder.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
|
64
|
+
audio_attributes = aa_builder.build()
|
|
65
|
+
channel.setSound(sound_uri, audio_attributes)
|
|
66
|
+
except Exception as sound_attributes_error:
|
|
67
|
+
logger.warning(f"Could not build AudioAttributes, falling back to None: {sound_attributes_error}")
|
|
68
|
+
channel.setSound(sound_uri, None)
|
|
56
69
|
if vibrate:
|
|
57
70
|
# channel.setVibrationPattern([0, 500, 200, 500]) # Using Phone's default pattern
|
|
58
71
|
# Android 15 ignored long patterns, didn't vibrate when not in silent and
|
|
@@ -106,6 +106,49 @@ class Uri:
|
|
|
106
106
|
def __init__(self, package_name):
|
|
107
107
|
logger.debug("FACADE_URI")
|
|
108
108
|
|
|
109
|
+
@classmethod
|
|
110
|
+
def parse(cls, uri_string):
|
|
111
|
+
logger.debug(f"[MOCK] Uri.parse called with uri_string={uri_string}")
|
|
112
|
+
return cls
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def fromFile(cls, java_file):
|
|
116
|
+
logger.debug(f"[MOCK] Uri.fromFile called with file={java_file}")
|
|
117
|
+
return cls
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AudioAttributes:
|
|
121
|
+
CONTENT_TYPE_SONIFICATION = 4
|
|
122
|
+
USAGE_NOTIFICATION = 5
|
|
123
|
+
USAGE_ALARM = 4
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class AudioAttributesBuilder:
|
|
127
|
+
def __init__(self):
|
|
128
|
+
logger.debug("[MOCK] AudioAttributesBuilder initialized")
|
|
129
|
+
|
|
130
|
+
def setContentType(self, content_type):
|
|
131
|
+
logger.debug(f"[MOCK] AudioAttributesBuilder.setContentType called with content_type={content_type}")
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def setUsage(self, usage):
|
|
135
|
+
logger.debug(f"[MOCK] AudioAttributesBuilder.setUsage called with usage={usage}")
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def build(self):
|
|
139
|
+
logger.debug("[MOCK] AudioAttributesBuilder.build called")
|
|
140
|
+
return AudioAttributes()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class File:
|
|
144
|
+
def __init__(self, path):
|
|
145
|
+
self.path = path
|
|
146
|
+
logger.debug(f"[MOCK] File initialized with path={path}")
|
|
147
|
+
|
|
148
|
+
def getAbsolutePath(self):
|
|
149
|
+
logger.debug(f"[MOCK] File.getAbsolutePath called, returning {self.path}")
|
|
150
|
+
return self.path
|
|
151
|
+
|
|
109
152
|
|
|
110
153
|
class NotificationManager:
|
|
111
154
|
pass
|
|
@@ -402,6 +445,16 @@ class Context:
|
|
|
402
445
|
logger.debug("[MOCK] Context.getPackageName called")
|
|
403
446
|
return None # TODO get package name from buildozer.spec file
|
|
404
447
|
|
|
448
|
+
@staticmethod
|
|
449
|
+
def getExternalFilesDir(directory_type):
|
|
450
|
+
logger.debug(f"[MOCK] Context.getExternalFilesDir called with type={directory_type}")
|
|
451
|
+
return File("mock_external_files_dir")
|
|
452
|
+
|
|
453
|
+
@staticmethod
|
|
454
|
+
def getExternalCacheDir():
|
|
455
|
+
logger.debug("[MOCK] Context.getExternalCacheDir called")
|
|
456
|
+
return File("mock_external_cache_dir")
|
|
457
|
+
|
|
405
458
|
|
|
406
459
|
class PackageManager:
|
|
407
460
|
@property
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/java_classes.py
RENAMED
|
@@ -40,6 +40,9 @@ if on_android_platform():
|
|
|
40
40
|
Color = autoclass('android.graphics.Color')
|
|
41
41
|
Context = autoclass('android.content.Context')
|
|
42
42
|
PackageManager = autoclass("android.content.pm.PackageManager")
|
|
43
|
+
AudioAttributes = autoclass('android.media.AudioAttributes')
|
|
44
|
+
AudioAttributesBuilder = autoclass('android.media.AudioAttributes$Builder')
|
|
45
|
+
File = autoclass('java.io.File')
|
|
43
46
|
except Exception as e:
|
|
44
47
|
from .facade import *
|
|
45
48
|
logger.exception("Didn't get Basic Java Classes")
|
|
@@ -149,7 +149,7 @@ class Notification(BaseNotification):
|
|
|
149
149
|
return does_channel_exist(channel_id=channel_id)
|
|
150
150
|
|
|
151
151
|
@classmethod
|
|
152
|
-
def createChannel(cls, id, name: str, description='', importance: Importance = 'urgent', res_sound_name=None, vibrate=False):
|
|
152
|
+
def createChannel(cls, id, name: str, description='', importance: Importance = 'urgent', res_sound_name=None, sound_path=None, vibrate=False):
|
|
153
153
|
"""
|
|
154
154
|
Creates a user visible toggle button for specific notifications, Required For Android 8.0+
|
|
155
155
|
:param id: Used to send other notifications later through same channel.
|
|
@@ -157,10 +157,11 @@ class Notification(BaseNotification):
|
|
|
157
157
|
:param description: user-visible detail about channel (Not required defaults to empty str).
|
|
158
158
|
:param importance: ['urgent', 'high', 'medium', 'low', 'none'] defaults to 'urgent' i.e. makes a sound and shows briefly
|
|
159
159
|
:param res_sound_name: audio file name (without .wav or .mp3) locate in res/raw/
|
|
160
|
+
:param sound_path: local file path or uri string
|
|
160
161
|
:param vibrate: if channel notifications should vibrate or not
|
|
161
162
|
:return: boolean if channel created
|
|
162
163
|
"""
|
|
163
|
-
return create_channel(id__=id, name=name, description=description, importance=importance, res_sound_name=res_sound_name, vibrate=vibrate)
|
|
164
|
+
return create_channel(id__=id, name=name, description=description, importance=importance, res_sound_name=res_sound_name, sound_path=sound_path, vibrate=vibrate)
|
|
164
165
|
|
|
165
166
|
@classmethod
|
|
166
167
|
def deleteChannel(cls, channel_id):
|
|
@@ -558,13 +559,14 @@ class Notification(BaseNotification):
|
|
|
558
559
|
"""Pass in a list of strings to be used for lines"""
|
|
559
560
|
set_lines(builder=self.builder, lines=lines)
|
|
560
561
|
|
|
561
|
-
def setSound(self, res_sound_name):
|
|
562
|
+
def setSound(self, res_sound_name=None, sound_path=None):
|
|
562
563
|
"""
|
|
563
564
|
Sets sound for devices less than android 8 (For 8+ use createChannel)
|
|
564
565
|
:param res_sound_name: audio file name (without .wav or .mp3) locate in res/raw/
|
|
566
|
+
:param sound_path: local file path or uri string
|
|
565
567
|
"""
|
|
566
568
|
|
|
567
|
-
return set_sound(self.builder, res_sound_name)
|
|
569
|
+
return set_sound(self.builder, res_sound_name=res_sound_name, sound_path=sound_path)
|
|
568
570
|
|
|
569
571
|
def fill_args(self, silent: bool = False, persistent=False, close_on_click=True):
|
|
570
572
|
"""Name Makes More sense than start_building
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from android_notify import Notification
|
|
2
|
+
from .base_test import AndroidNotifyBaseTest, secs5
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestNotificationSound(AndroidNotifyBaseTest):
|
|
7
|
+
|
|
8
|
+
def test_set_sound(self):
|
|
9
|
+
try:
|
|
10
|
+
time.sleep(secs5)
|
|
11
|
+
Notification.createChannel(
|
|
12
|
+
id="sound_test",
|
|
13
|
+
name="Sound Test",
|
|
14
|
+
res_sound_name="sneeze"
|
|
15
|
+
)
|
|
16
|
+
n = Notification(
|
|
17
|
+
title="Sound Test",
|
|
18
|
+
message="Testing custom sound",
|
|
19
|
+
channel_id="sound_test"
|
|
20
|
+
)
|
|
21
|
+
n.setSound("sneeze")
|
|
22
|
+
n.send()
|
|
23
|
+
except Exception as e:
|
|
24
|
+
self.fail(f"Sound failed: {e}")
|
|
25
|
+
|
|
26
|
+
def test_set_sound_path(self):
|
|
27
|
+
import os
|
|
28
|
+
mock_file = "test_mock_sound.mp3"
|
|
29
|
+
with open(mock_file, "w") as f:
|
|
30
|
+
f.write("mock audio data")
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
Notification.createChannel(
|
|
34
|
+
id="sound_path_test",
|
|
35
|
+
name="Sound Path Test",
|
|
36
|
+
sound_path=mock_file
|
|
37
|
+
)
|
|
38
|
+
n = Notification(
|
|
39
|
+
title="Sound Path Test",
|
|
40
|
+
message="Testing custom sound path",
|
|
41
|
+
channel_id="sound_path_test"
|
|
42
|
+
)
|
|
43
|
+
n.setSound(sound_path=mock_file)
|
|
44
|
+
n.send()
|
|
45
|
+
except Exception as e:
|
|
46
|
+
self.fail(f"Sound path failed: {e}")
|
|
47
|
+
finally:
|
|
48
|
+
if os.path.exists(mock_file):
|
|
49
|
+
os.remove(mock_file)
|
|
50
|
+
|
|
51
|
+
def test_set_sound_content_uri(self):
|
|
52
|
+
try:
|
|
53
|
+
content_uri = "content://media/external/audio/media/123"
|
|
54
|
+
Notification.createChannel(
|
|
55
|
+
id="sound_uri_test",
|
|
56
|
+
name="Sound Uri Test",
|
|
57
|
+
sound_path=content_uri
|
|
58
|
+
)
|
|
59
|
+
n = Notification(
|
|
60
|
+
title="Sound Uri Test",
|
|
61
|
+
message="Testing content URI",
|
|
62
|
+
channel_id="sound_uri_test"
|
|
63
|
+
)
|
|
64
|
+
n.setSound(sound_path=content_uri)
|
|
65
|
+
n.send()
|
|
66
|
+
except Exception as e:
|
|
67
|
+
self.fail(f"Sound content URI failed: {e}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: android-notify
|
|
3
|
-
Version: 1.61.
|
|
3
|
+
Version: 1.61.1.dev0
|
|
4
4
|
Summary: A Python package that simplifies creating Android notifications in Kivy and Flet apps.
|
|
5
5
|
Author-email: Fabian <fector101@yahoo.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,7 +15,6 @@ Classifier: Operating System :: Android
|
|
|
15
15
|
Classifier: Development Status :: 5 - Production/Stable
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
-
Requires-Python: >=3.6
|
|
19
18
|
Description-Content-Type: text/markdown
|
|
20
19
|
Requires-Dist: pyjnius>=1.4.2
|
|
21
20
|
Provides-Extra: dev
|
|
@@ -81,7 +80,7 @@ In your **`buildozer.spec`** file, ensure you include the following:
|
|
|
81
80
|
|
|
82
81
|
```ini
|
|
83
82
|
# Add pyjnius so ensure it's packaged with the build
|
|
84
|
-
requirements = python3, kivy, pyjnius, android-notify==1.61.
|
|
83
|
+
requirements = python3, kivy, pyjnius, android-notify==1.61.1.dev0
|
|
85
84
|
# Add permission for notifications
|
|
86
85
|
android.permissions = POST_NOTIFICATIONS
|
|
87
86
|
```
|
|
@@ -99,7 +98,7 @@ In your `pyproject.toml` file, ensure you include the following:
|
|
|
99
98
|
```toml
|
|
100
99
|
[tool.flet.android]
|
|
101
100
|
dependencies = [
|
|
102
|
-
"pyjnius","android-notify==1.61.
|
|
101
|
+
"pyjnius","android-notify==1.61.1.dev0"
|
|
103
102
|
]
|
|
104
103
|
|
|
105
104
|
[tool.flet.android.permission]
|
|
@@ -117,10 +116,10 @@ dependencies = [
|
|
|
117
116
|
<br/>
|
|
118
117
|
|
|
119
118
|
On the [pydroid 3](https://play.google.com/store/apps/details?id=ru.iiec.pydroid3) mobile app for running python code you can test some features.
|
|
120
|
-
- In pip section where you're asked to insert `Libary name` paste `android-notify==1.61.
|
|
119
|
+
- In pip section where you're asked to insert `Libary name` paste `android-notify==1.61.1.dev0`
|
|
121
120
|
- Minimal working example
|
|
122
121
|
```py
|
|
123
|
-
# Testing with `android-notify==1.61.
|
|
122
|
+
# Testing with `android-notify==1.61.1.dev0` on pydroid
|
|
124
123
|
from kivy.app import App
|
|
125
124
|
from kivy.uix.boxlayout import BoxLayout
|
|
126
125
|
from kivy.uix.button import Button
|
|
@@ -209,6 +208,8 @@ n.send()
|
|
|
209
208
|
<details>
|
|
210
209
|
<summary> <b>To use Custom Sounds </b> </summary>
|
|
211
210
|
|
|
211
|
+
**Option 1: Audio files bundled in `res/raw`**
|
|
212
|
+
|
|
212
213
|
- Put audio files in `res/raw` folder,
|
|
213
214
|
- Then from `buildozer.spec` point to res folder `android.add_resources = res`
|
|
214
215
|
- and includes it's format `source.include_exts = wav`.
|
|
@@ -232,6 +233,37 @@ n=Notification(
|
|
|
232
233
|
n.setSound("sneeze")# for android 7 below
|
|
233
234
|
n.send()
|
|
234
235
|
```
|
|
236
|
+
|
|
237
|
+
**Option 2: Local file path or URI (`sound_path`)**
|
|
238
|
+
|
|
239
|
+
You can use a local audio file, a `content://`, `file://`, or `android.resource://` URI directly:
|
|
240
|
+
|
|
241
|
+
```py
|
|
242
|
+
# Using a local file path
|
|
243
|
+
Notification.createChannel(
|
|
244
|
+
id="local_sound",
|
|
245
|
+
name="Local Sound",
|
|
246
|
+
sound_path="/storage/emulated/0/Download/alert.mp3"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Using a content URI (e.g., from media store)
|
|
250
|
+
Notification.createChannel(
|
|
251
|
+
id="uri_sound",
|
|
252
|
+
name="URI Sound",
|
|
253
|
+
sound_path="content://media/external/audio/media/123"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Send notification with custom sound path
|
|
257
|
+
n = Notification(
|
|
258
|
+
title="Custom Sound",
|
|
259
|
+
message="Playing from local path",
|
|
260
|
+
channel_id="local_sound"
|
|
261
|
+
)
|
|
262
|
+
n.setSound(sound_path="/storage/emulated/0/Download/alert.mp3")
|
|
263
|
+
n.send()
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Private files (e.g., in app's `data/` directory) are automatically copied to external storage before playing.
|
|
235
267
|
</details>
|
|
236
268
|
|
|
237
269
|
|
|
@@ -4,14 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "android-notify"
|
|
7
|
-
version = "1.61.
|
|
7
|
+
version = "1.61.1.dev0"
|
|
8
8
|
description = "A Python package that simplifies creating Android notifications in Kivy and Flet apps."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
authors = [
|
|
11
11
|
{ name = "Fabian", email = "fector101@yahoo.com" }
|
|
12
12
|
]
|
|
13
13
|
license = "MIT"
|
|
14
|
-
requires-python = ">=3.6"
|
|
15
14
|
dependencies = [
|
|
16
15
|
"pyjnius>=1.4.2"
|
|
17
16
|
]
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from android_notify import Notification
|
|
2
|
-
from .base_test import AndroidNotifyBaseTest, secs5
|
|
3
|
-
import time
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class TestNotificationSound(AndroidNotifyBaseTest):
|
|
7
|
-
|
|
8
|
-
def test_set_sound(self):
|
|
9
|
-
try:
|
|
10
|
-
time.sleep(secs5)
|
|
11
|
-
Notification.createChannel(
|
|
12
|
-
id="sound_test",
|
|
13
|
-
name="Sound Test",
|
|
14
|
-
res_sound_name="sneeze"
|
|
15
|
-
)
|
|
16
|
-
n = Notification(
|
|
17
|
-
title="Sound Test",
|
|
18
|
-
message="Testing custom sound",
|
|
19
|
-
channel_id="sound_test"
|
|
20
|
-
)
|
|
21
|
-
n.setSound("sneeze")
|
|
22
|
-
n.send()
|
|
23
|
-
except Exception as e:
|
|
24
|
-
self.fail(f"Sound failed: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/an_types.py
RENAMED
|
File without changes
|
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/intents.py
RENAMED
|
File without changes
|
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/internal/permissions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/adv/main.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify/tests/serivces/wallpaper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/requires.txt
RENAMED
|
File without changes
|
{android_notify-1.61.0.dev0 → android_notify-1.61.1.dev0}/android_notify.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|