android-notify 1.61.0__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 → android_notify-1.61.1.dev0}/PKG-INFO +64 -47
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/README.md +63 -45
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/config.py +2 -1
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/core.py +3 -4
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/android.py +59 -9
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/channels.py +19 -6
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/facade.py +53 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/helper.py +4 -1
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/java_classes.py +13 -17
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/permissions.py +9 -22
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/sword.py +6 -4
- android_notify-1.61.1.dev0/android_notify/tests/flet/basic/src/core.py +221 -0
- android_notify-1.61.1.dev0/android_notify/tests/flet/flet-working/src/core.py +221 -0
- android_notify-1.61.1.dev0/android_notify/tests/test_notification_sound.py +67 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/widgets/images.py +2 -1
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/widgets/texts.py +5 -1
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify.egg-info/PKG-INFO +64 -47
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify.egg-info/SOURCES.txt +2 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/pyproject.toml +1 -2
- android_notify-1.61.0/android_notify/tests/test_notification_sound.py +0 -24
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/__init__.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/__main__.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/base.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/fallback-icons/flet-appicon.png +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/fallback-icons/pydroid3-appicon.png +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/an_types.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/intents.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/internal/logger.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/styles.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/__init__.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/android_notify_test.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/base_test.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/adv/main.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/adv/tests/__init__.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/adv/tests/test_android_notify_full.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/basic/src/main.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/flet/flet-working/src/main.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/main.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/p4a/hook.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/serivces/wallpaper.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_basic_notifications.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_actions.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_appearance.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_behavior.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_channels.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_clear.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_permission.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_progress.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify/tests/test_notification_styles.py +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify.egg-info/dependency_links.txt +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify.egg-info/entry_points.txt +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify.egg-info/requires.txt +0 -0
- {android_notify-1.61.0 → android_notify-1.61.1.dev0}/android_notify.egg-info/top_level.txt +0 -0
- {android_notify-1.61.0 → 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,14 +80,9 @@ 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
|
|
83
|
+
requirements = python3, kivy, pyjnius, android-notify==1.61.1.dev0
|
|
85
84
|
# Add permission for notifications
|
|
86
85
|
android.permissions = POST_NOTIFICATIONS
|
|
87
|
-
|
|
88
|
-
# Required dependency (write exactly as shown, no quotation marks)
|
|
89
|
-
android.gradle_dependencies = androidx.core:core-ktx:1.15.0
|
|
90
|
-
android.enable_androidx = True
|
|
91
|
-
android.api = 35
|
|
92
86
|
```
|
|
93
87
|
|
|
94
88
|
</details>
|
|
@@ -98,13 +92,13 @@ android.api = 35
|
|
|
98
92
|
<summary><b>Flet apps:</b></summary>
|
|
99
93
|
<br/>
|
|
100
94
|
|
|
101
|
-
|
|
95
|
+
In your `pyproject.toml` file, ensure you include the following:
|
|
102
96
|
|
|
103
97
|
|
|
104
98
|
```toml
|
|
105
99
|
[tool.flet.android]
|
|
106
100
|
dependencies = [
|
|
107
|
-
"pyjnius","android-notify==1.61.
|
|
101
|
+
"pyjnius","android-notify==1.61.1.dev0"
|
|
108
102
|
]
|
|
109
103
|
|
|
110
104
|
[tool.flet.android.permission]
|
|
@@ -114,35 +108,7 @@ dependencies = [
|
|
|
114
108
|
|
|
115
109
|
</details>
|
|
116
110
|
|
|
117
|
-
<details>
|
|
118
|
-
|
|
119
|
-
<summary><b>Desktop</b></summary>
|
|
120
|
-
<br/>
|
|
121
|
-
|
|
122
|
-
For IDE IntelliSense Can be installed via `pip install`:
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
pip install android_notify
|
|
126
|
-
android-notify -v
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
</details>
|
|
130
|
-
|
|
131
|
-
------
|
|
132
|
-
## Installing without Androidx
|
|
133
|
-
How to use without `gradle_dependencies`
|
|
134
|
-
Use `android-notify==1.61.0.dev0` to install via `pip`
|
|
135
111
|
|
|
136
|
-
<details>
|
|
137
|
-
<summary><b>In Kivy</b></summary>
|
|
138
|
-
<br/>
|
|
139
|
-
|
|
140
|
-
```ini
|
|
141
|
-
# buildozer.spec
|
|
142
|
-
requirements = python3, kivy, pyjnius, android-notify==1.61.0.dev0
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
</details>
|
|
146
112
|
|
|
147
113
|
<details>
|
|
148
114
|
|
|
@@ -150,10 +116,10 @@ requirements = python3, kivy, pyjnius, android-notify==1.61.0.dev0
|
|
|
150
116
|
<br/>
|
|
151
117
|
|
|
152
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.
|
|
153
|
-
- 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`
|
|
154
120
|
- Minimal working example
|
|
155
121
|
```py
|
|
156
|
-
# Testing with `android-notify==1.61.
|
|
122
|
+
# Testing with `android-notify==1.61.1.dev0` on pydroid
|
|
157
123
|
from kivy.app import App
|
|
158
124
|
from kivy.uix.boxlayout import BoxLayout
|
|
159
125
|
from kivy.uix.button import Button
|
|
@@ -180,7 +146,9 @@ class AndroidNotifyDemoApp(App):
|
|
|
180
146
|
def send_notification(self, *args):
|
|
181
147
|
Notification(
|
|
182
148
|
title="Hello from Android Notify",
|
|
183
|
-
message="This is a basic notification."
|
|
149
|
+
message="This is a basic notification.",
|
|
150
|
+
channel_id="android_notify_demo",
|
|
151
|
+
channel_name="Android Notify Demo"
|
|
184
152
|
).send()
|
|
185
153
|
|
|
186
154
|
|
|
@@ -191,11 +159,23 @@ if __name__ == "__main__":
|
|
|
191
159
|
</details>
|
|
192
160
|
|
|
193
161
|
|
|
162
|
+
<details>
|
|
163
|
+
|
|
164
|
+
<summary><b>Desktop</b></summary>
|
|
165
|
+
<br/>
|
|
166
|
+
|
|
167
|
+
For IDE IntelliSense Can be installed via `pip install`:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pip install android_notify
|
|
171
|
+
android-notify -v
|
|
172
|
+
```
|
|
173
|
+
</details>
|
|
194
174
|
|
|
195
175
|
## Documentation
|
|
196
|
-
For Dev Version
|
|
176
|
+
For Dev Version use
|
|
197
177
|
```ini
|
|
198
|
-
requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_notify/archive/
|
|
178
|
+
requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_notify/archive/without-androidx.zip
|
|
199
179
|
```
|
|
200
180
|
|
|
201
181
|
<details>
|
|
@@ -204,7 +184,7 @@ requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_noti
|
|
|
204
184
|
- Make things happen without being in your app
|
|
205
185
|
```python
|
|
206
186
|
from android_notify import Notification
|
|
207
|
-
notification = Notification(title="
|
|
187
|
+
notification = Notification(title="Receiver Notification")
|
|
208
188
|
notification.addButton(text="Stop", receiver_name="CarouselReceiver", action="ACTION_STOP")
|
|
209
189
|
notification.addButton(text="Skip", receiver_name="CarouselReceiver", action="ACTION_SKIP")
|
|
210
190
|
```
|
|
@@ -228,6 +208,8 @@ n.send()
|
|
|
228
208
|
<details>
|
|
229
209
|
<summary> <b>To use Custom Sounds </b> </summary>
|
|
230
210
|
|
|
211
|
+
**Option 1: Audio files bundled in `res/raw`**
|
|
212
|
+
|
|
231
213
|
- Put audio files in `res/raw` folder,
|
|
232
214
|
- Then from `buildozer.spec` point to res folder `android.add_resources = res`
|
|
233
215
|
- and includes it's format `source.include_exts = wav`.
|
|
@@ -251,6 +233,37 @@ n=Notification(
|
|
|
251
233
|
n.setSound("sneeze")# for android 7 below
|
|
252
234
|
n.send()
|
|
253
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.
|
|
254
267
|
</details>
|
|
255
268
|
|
|
256
269
|
|
|
@@ -267,7 +280,7 @@ Notification.createChannel(id='shake', name="Shake Passage", vibrate=True)
|
|
|
267
280
|
|
|
268
281
|
n=Notification(title='Vibrate',channel_id='shake')
|
|
269
282
|
n.setVibrate() # for less than android 8
|
|
270
|
-
n.fVibrate() # To Force Vibrate
|
|
283
|
+
n.fVibrate() # To Force Vibrate (when user turned off in settings)
|
|
271
284
|
n.send()
|
|
272
285
|
```
|
|
273
286
|
|
|
@@ -305,5 +318,9 @@ from android_notify import Notification, NotificationHandler
|
|
|
305
318
|
|
|
306
319
|
## ☕ Support the Project
|
|
307
320
|
|
|
308
|
-
If you find this project helpful,
|
|
309
|
-
[
|
|
321
|
+
If you find this project helpful, consider buying me a coffee! 😊
|
|
322
|
+
Or Giving it a star on 🌟 [GitHub](https://github.com/Fector101/android_notify/) Your support helps maintain and improve the project.
|
|
323
|
+
|
|
324
|
+
<a href="https://www.buymeacoffee.com/fector101" target="_blank">
|
|
325
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60">
|
|
326
|
+
</a>
|
|
@@ -58,14 +58,9 @@ 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
|
|
61
|
+
requirements = python3, kivy, pyjnius, android-notify==1.61.1.dev0
|
|
62
62
|
# Add permission for notifications
|
|
63
63
|
android.permissions = POST_NOTIFICATIONS
|
|
64
|
-
|
|
65
|
-
# Required dependency (write exactly as shown, no quotation marks)
|
|
66
|
-
android.gradle_dependencies = androidx.core:core-ktx:1.15.0
|
|
67
|
-
android.enable_androidx = True
|
|
68
|
-
android.api = 35
|
|
69
64
|
```
|
|
70
65
|
|
|
71
66
|
</details>
|
|
@@ -75,13 +70,13 @@ android.api = 35
|
|
|
75
70
|
<summary><b>Flet apps:</b></summary>
|
|
76
71
|
<br/>
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
In your `pyproject.toml` file, ensure you include the following:
|
|
79
74
|
|
|
80
75
|
|
|
81
76
|
```toml
|
|
82
77
|
[tool.flet.android]
|
|
83
78
|
dependencies = [
|
|
84
|
-
"pyjnius","android-notify==1.61.
|
|
79
|
+
"pyjnius","android-notify==1.61.1.dev0"
|
|
85
80
|
]
|
|
86
81
|
|
|
87
82
|
[tool.flet.android.permission]
|
|
@@ -91,35 +86,7 @@ dependencies = [
|
|
|
91
86
|
|
|
92
87
|
</details>
|
|
93
88
|
|
|
94
|
-
<details>
|
|
95
|
-
|
|
96
|
-
<summary><b>Desktop</b></summary>
|
|
97
|
-
<br/>
|
|
98
|
-
|
|
99
|
-
For IDE IntelliSense Can be installed via `pip install`:
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
pip install android_notify
|
|
103
|
-
android-notify -v
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
</details>
|
|
107
|
-
|
|
108
|
-
------
|
|
109
|
-
## Installing without Androidx
|
|
110
|
-
How to use without `gradle_dependencies`
|
|
111
|
-
Use `android-notify==1.61.0.dev0` to install via `pip`
|
|
112
89
|
|
|
113
|
-
<details>
|
|
114
|
-
<summary><b>In Kivy</b></summary>
|
|
115
|
-
<br/>
|
|
116
|
-
|
|
117
|
-
```ini
|
|
118
|
-
# buildozer.spec
|
|
119
|
-
requirements = python3, kivy, pyjnius, android-notify==1.61.0.dev0
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
</details>
|
|
123
90
|
|
|
124
91
|
<details>
|
|
125
92
|
|
|
@@ -127,10 +94,10 @@ requirements = python3, kivy, pyjnius, android-notify==1.61.0.dev0
|
|
|
127
94
|
<br/>
|
|
128
95
|
|
|
129
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.
|
|
130
|
-
- 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`
|
|
131
98
|
- Minimal working example
|
|
132
99
|
```py
|
|
133
|
-
# Testing with `android-notify==1.61.
|
|
100
|
+
# Testing with `android-notify==1.61.1.dev0` on pydroid
|
|
134
101
|
from kivy.app import App
|
|
135
102
|
from kivy.uix.boxlayout import BoxLayout
|
|
136
103
|
from kivy.uix.button import Button
|
|
@@ -157,7 +124,9 @@ class AndroidNotifyDemoApp(App):
|
|
|
157
124
|
def send_notification(self, *args):
|
|
158
125
|
Notification(
|
|
159
126
|
title="Hello from Android Notify",
|
|
160
|
-
message="This is a basic notification."
|
|
127
|
+
message="This is a basic notification.",
|
|
128
|
+
channel_id="android_notify_demo",
|
|
129
|
+
channel_name="Android Notify Demo"
|
|
161
130
|
).send()
|
|
162
131
|
|
|
163
132
|
|
|
@@ -168,11 +137,23 @@ if __name__ == "__main__":
|
|
|
168
137
|
</details>
|
|
169
138
|
|
|
170
139
|
|
|
140
|
+
<details>
|
|
141
|
+
|
|
142
|
+
<summary><b>Desktop</b></summary>
|
|
143
|
+
<br/>
|
|
144
|
+
|
|
145
|
+
For IDE IntelliSense Can be installed via `pip install`:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pip install android_notify
|
|
149
|
+
android-notify -v
|
|
150
|
+
```
|
|
151
|
+
</details>
|
|
171
152
|
|
|
172
153
|
## Documentation
|
|
173
|
-
For Dev Version
|
|
154
|
+
For Dev Version use
|
|
174
155
|
```ini
|
|
175
|
-
requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_notify/archive/
|
|
156
|
+
requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_notify/archive/without-androidx.zip
|
|
176
157
|
```
|
|
177
158
|
|
|
178
159
|
<details>
|
|
@@ -181,7 +162,7 @@ requirements = python3, kivy, pyjnius, https://github.com/Fector101/android_noti
|
|
|
181
162
|
- Make things happen without being in your app
|
|
182
163
|
```python
|
|
183
164
|
from android_notify import Notification
|
|
184
|
-
notification = Notification(title="
|
|
165
|
+
notification = Notification(title="Receiver Notification")
|
|
185
166
|
notification.addButton(text="Stop", receiver_name="CarouselReceiver", action="ACTION_STOP")
|
|
186
167
|
notification.addButton(text="Skip", receiver_name="CarouselReceiver", action="ACTION_SKIP")
|
|
187
168
|
```
|
|
@@ -205,6 +186,8 @@ n.send()
|
|
|
205
186
|
<details>
|
|
206
187
|
<summary> <b>To use Custom Sounds </b> </summary>
|
|
207
188
|
|
|
189
|
+
**Option 1: Audio files bundled in `res/raw`**
|
|
190
|
+
|
|
208
191
|
- Put audio files in `res/raw` folder,
|
|
209
192
|
- Then from `buildozer.spec` point to res folder `android.add_resources = res`
|
|
210
193
|
- and includes it's format `source.include_exts = wav`.
|
|
@@ -228,6 +211,37 @@ n=Notification(
|
|
|
228
211
|
n.setSound("sneeze")# for android 7 below
|
|
229
212
|
n.send()
|
|
230
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.
|
|
231
245
|
</details>
|
|
232
246
|
|
|
233
247
|
|
|
@@ -244,7 +258,7 @@ Notification.createChannel(id='shake', name="Shake Passage", vibrate=True)
|
|
|
244
258
|
|
|
245
259
|
n=Notification(title='Vibrate',channel_id='shake')
|
|
246
260
|
n.setVibrate() # for less than android 8
|
|
247
|
-
n.fVibrate() # To Force Vibrate
|
|
261
|
+
n.fVibrate() # To Force Vibrate (when user turned off in settings)
|
|
248
262
|
n.send()
|
|
249
263
|
```
|
|
250
264
|
|
|
@@ -282,5 +296,9 @@ from android_notify import Notification, NotificationHandler
|
|
|
282
296
|
|
|
283
297
|
## ☕ Support the Project
|
|
284
298
|
|
|
285
|
-
If you find this project helpful,
|
|
286
|
-
[
|
|
299
|
+
If you find this project helpful, consider buying me a coffee! 😊
|
|
300
|
+
Or Giving it a star on 🌟 [GitHub](https://github.com/Fector101/android_notify/) Your support helps maintain and improve the project.
|
|
301
|
+
|
|
302
|
+
<a href="https://www.buymeacoffee.com/fector101" target="_blank">
|
|
303
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="60">
|
|
304
|
+
</a>
|
|
@@ -6,7 +6,7 @@ from android_notify.internal.logger import logger
|
|
|
6
6
|
from android_notify.config import get_python_activity, on_android_platform, get_python_activity_context
|
|
7
7
|
from android_notify.internal.permissions import has_notification_permission, ask_notification_permission
|
|
8
8
|
from android_notify.internal.java_classes import autoclass, BuildVersion, BitmapFactory, NotificationChannel, NotificationManagerCompat, NotificationCompat, NotificationCompatBuilder, \
|
|
9
|
-
NotificationCompatBigTextStyle, NotificationCompatBigPictureStyle, NotificationCompatInboxStyle
|
|
9
|
+
NotificationCompatBigTextStyle, NotificationCompatBigPictureStyle, NotificationCompatInboxStyle
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
@@ -60,6 +60,7 @@ def get_image_uri(relative_path):
|
|
|
60
60
|
|
|
61
61
|
def get_icon_object(uri):
|
|
62
62
|
context = get_python_activity_context()
|
|
63
|
+
IconCompat = autoclass('android.graphics.drawable.Icon')
|
|
63
64
|
bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uri))
|
|
64
65
|
return IconCompat.createWithBitmap(bitmap)
|
|
65
66
|
|
|
@@ -114,9 +115,7 @@ def send_notification(
|
|
|
114
115
|
return None
|
|
115
116
|
context = get_python_activity_context()
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
asks_permission_if_needed()
|
|
119
|
-
|
|
118
|
+
asks_permission_if_needed(legacy=True)
|
|
120
119
|
channel_id = channel_name.replace(' ', '_').lower().lower() if not channel_id else channel_id
|
|
121
120
|
# Get notification manager
|
|
122
121
|
notification_manager = context.getSystemService(context.NOTIFICATION_SERVICE)
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -54,4 +54,7 @@ def execute_callback(callback,arg, from_who="user"):
|
|
|
54
54
|
else:
|
|
55
55
|
callback()
|
|
56
56
|
except Exception as on_permissions_result_callback_error:
|
|
57
|
-
logger.exception(on_permissions_result_callback_error)
|
|
57
|
+
logger.exception(on_permissions_result_callback_error)
|
|
58
|
+
|
|
59
|
+
def on_pydroid_app():
|
|
60
|
+
return "ru.iiec.pydroid3" in os.path.dirname(os.path.abspath(__file__))
|