comparison sat/plugins/plugin_misc_android.py @ 3106:7f7cdc6ecfd8

plugin android: sound notification + change settings: - new notification sound - vibration and notification sound are now activated depending on ringer mode (normal, vibrate or silent) - notification sound/vibration can be configured in settings.
author Goffi <goffi@goffi.org>
date Fri, 03 Jan 2020 13:21:27 +0100
parents fb49587f55c2
children b86060901278
comparison
equal deleted inserted replaced
3105:eec0c25c796b 3106:7f7cdc6ecfd8
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 import sys 20 import sys
21 import os 21 import os
22 import os.path 22 import os.path
23 import tempfile 23 from pathlib import Path
24 from sat.core.i18n import _, D_ 24 from sat.core.i18n import _, D_
25 from sat.core.constants import Const as C 25 from sat.core.constants import Const as C
26 from sat.core.log import getLogger 26 from sat.core.log import getLogger
27 from sat.core import exceptions 27 from sat.core import exceptions
28 from sat.memory import params
28 from twisted.internet import reactor 29 from twisted.internet import reactor
29 from twisted.internet import protocol 30 from twisted.internet import protocol
30 from twisted.internet import error as int_error 31 from twisted.internet import error as int_error
31 from twisted.web import client as web_client
32 32
33 log = getLogger(__name__) 33 log = getLogger(__name__)
34 34
35 PLUGIN_INFO = { 35 PLUGIN_INFO = {
36 C.PI_NAME: "Android ", 36 C.PI_NAME: "Android ",
56 #: delay between a pause event and sending the inactive indication to server, in seconds 56 #: delay between a pause event and sending the inactive indication to server, in seconds
57 #: we don't send the indication immediately because user can be just checking something 57 #: we don't send the indication immediately because user can be just checking something
58 #: quickly on an other app. 58 #: quickly on an other app.
59 CSI_DELAY = 30 59 CSI_DELAY = 30
60 60
61 PARAM_RING_CATEGORY = "Notifications"
62 PARAM_RING_NAME = "sound"
63 PARAM_RING_LABEL = D_("sound on notifications")
64 RING_OPTS = {
65 "normal": D_("Normal"),
66 "never": D_("Never"),
67 }
61 PARAM_VIBRATE_CATEGORY = "Notifications" 68 PARAM_VIBRATE_CATEGORY = "Notifications"
62 PARAM_VIBRATE_NAME = "vibrate" 69 PARAM_VIBRATE_NAME = "vibrate"
63 PARAM_VIBRATE_LABEL = D_("Vibrate on notifications") 70 PARAM_VIBRATE_LABEL = D_("Vibrate on notifications")
71 VIBRATION_OPTS = {
72 "always": D_("Always"),
73 "vibrate": D_("In vibrate mode"),
74 "never": D_("Never"),
75 }
64 SOCKET_DIR = "/data/data/org.salutatoi.cagou/" 76 SOCKET_DIR = "/data/data/org.salutatoi.cagou/"
65 SOCKET_FILE = ".socket" 77 SOCKET_FILE = ".socket"
66 STATE_RUNNING = b"running" 78 STATE_RUNNING = b"running"
67 STATE_PAUSED = b"paused" 79 STATE_PAUSED = b"paused"
68 STATE_STOPPED = b"stopped" 80 STATE_STOPPED = b"stopped"
73 NET_TYPE_OTHER = "other" 85 NET_TYPE_OTHER = "other"
74 86
75 87
76 Context = autoclass('android.content.Context') 88 Context = autoclass('android.content.Context')
77 ConnectivityManager = autoclass('android.net.ConnectivityManager') 89 ConnectivityManager = autoclass('android.net.ConnectivityManager')
78 90 MediaPlayer = autoclass('android.media.MediaPlayer')
79 91 AudioManager = autoclass('android.media.AudioManager')
80 def determineLength_workaround(self, fObj):
81 """Method working around seek() bug on Android"""
82 try:
83 seek = fObj.seek
84 tell = fObj.tell
85 except AttributeError:
86 return web_client.UNKNOWN_LENGTH
87 originalPosition = tell()
88 seek(os.SEEK_END)
89 end = tell()
90 seek(os.SEEK_SET, originalPosition)
91 return end - originalPosition
92
93
94 def patch_seek_bug():
95 """Check seek bug and apply a workaround if still here
96
97 cf. https://github.com/kivy/python-for-android/issues/1768
98 """
99 with tempfile.TemporaryFile() as f:
100 f.write(b'1234567890')
101 f.seek(0, os.SEEK_END)
102 size = f.tell()
103 if size == 10:
104 log.info("seek() bug not present anymore, workaround code can be removed")
105 else:
106 log.warning("seek() bug detected, applying a workaround")
107 web_client.FileBodyProducer._determineLength = determineLength_workaround
108
109 patch_seek_bug()
110 92
111 93
112 class FrontendStateProtocol(protocol.Protocol): 94 class FrontendStateProtocol(protocol.Protocol):
113 95
114 def __init__(self, android_plugin): 96 def __init__(self, android_plugin):
135 117
136 params = """ 118 params = """
137 <params> 119 <params>
138 <individual> 120 <individual>
139 <category name="{category_name}" label="{category_label}"> 121 <category name="{category_name}" label="{category_label}">
140 <param name="{param_name}" label="{param_label}" value="true" type="bool" security="0" /> 122 <param name="{ring_param_name}" label="{ring_param_label}" type="list" security="0">
123 {ring_options}
124 </param>
125 <param name="{vibrate_param_name}" label="{vibrate_param_label}" type="list" security="0">
126 {vibrate_options}
127 </param>
141 </category> 128 </category>
142 </individual> 129 </individual>
143 </params> 130 </params>
144 """.format( 131 """.format(
145 category_name=PARAM_VIBRATE_CATEGORY, 132 category_name=PARAM_VIBRATE_CATEGORY,
146 category_label=D_(PARAM_VIBRATE_CATEGORY), 133 category_label=D_(PARAM_VIBRATE_CATEGORY),
147 param_name=PARAM_VIBRATE_NAME, 134 vibrate_param_name=PARAM_VIBRATE_NAME,
148 param_label=PARAM_VIBRATE_LABEL, 135 vibrate_param_label=PARAM_VIBRATE_LABEL,
136 vibrate_options=params.makeOptions(VIBRATION_OPTS, "always"),
137 ring_param_name=PARAM_RING_NAME,
138 ring_param_label=PARAM_RING_LABEL,
139 ring_options=params.makeOptions(RING_OPTS, "normal"),
149 ) 140 )
150 141
151 def __init__(self, host): 142 def __init__(self, host):
152 log.info(_("plugin Android initialization")) 143 log.info(_("plugin Android initialization"))
153 self.host = host 144 self.host = host
176 raise e 167 raise e
177 # we set a low priority because we want the notification to be sent after all 168 # we set a low priority because we want the notification to be sent after all
178 # plugins have done their job 169 # plugins have done their job
179 host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=-1000) 170 host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=-1000)
180 171
172 # audio manager, to get ring status
173 self.am = activity.getSystemService(Context.AUDIO_SERVICE)
174
175 # sound notification
176 media_dir = Path(host.memory.getConfig("", "media_dir"))
177 assert media_dir is not None
178 notif_path = media_dir / "sounds" / "notifications" / "music-box.mp3"
179 self.notif_player = MediaPlayer()
180 self.notif_player.setDataSource(str(notif_path))
181 self.notif_player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION)
182 self.notif_player.prepare()
183
181 # Connectivity handling 184 # Connectivity handling
182 self.cm = activity.getSystemService(Context.CONNECTIVITY_SERVICE) 185 self.cm = activity.getSystemService(Context.CONNECTIVITY_SERVICE)
183 self._net_type = None 186 self._net_type = None
184 self._checkConnectivity() 187 self._checkConnectivity()
185 # XXX: we need to keep a reference to BroadcastReceiver to avoid 188 # XXX: we need to keep a reference to BroadcastReceiver to avoid
238 subject = next(iter(mess_data["subject"].values())) 241 subject = next(iter(mess_data["subject"].values()))
239 except StopIteration: 242 except StopIteration:
240 subject = "Cagou new message" 243 subject = "Cagou new message"
241 244
242 notification.notify(title=subject, message=message) 245 notification.notify(title=subject, message=message)
243 if self.host.memory.getParamA( 246
244 PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile 247 ringer_mode = self.am.getRingerMode()
245 ): 248 vibrate_mode = ringer_mode == AudioManager.RINGER_MODE_VIBRATE
246 try: 249
247 vibrator.vibrate() 250 ring_setting = self.host.memory.getParamA(
248 except Exception as e: 251 PARAM_RING_NAME,
249 log.warning("Can't use vibrator: {e}".format(e=e)) 252 PARAM_RING_CATEGORY,
253 profile_key=client.profile
254 )
255
256 if ring_setting != 'never' and ringer_mode == AudioManager.RINGER_MODE_NORMAL:
257 self.notif_player.start()
258
259 vibration_setting = self.host.memory.getParamA(
260 PARAM_VIBRATE_NAME,
261 PARAM_VIBRATE_CATEGORY,
262 profile_key=client.profile
263 )
264 if (vibration_setting == 'always'
265 or vibration_setting == 'vibrate' and vibrate_mode):
266 try:
267 vibrator.vibrate()
268 except Exception as e:
269 log.warning("Can't use vibrator: {e}".format(e=e))
250 return mess_data 270 return mess_data
251 271
252 def messageReceivedTrigger(self, client, message_elt, post_treat): 272 def messageReceivedTrigger(self, client, message_elt, post_treat):
253 if not self.cagou_active: 273 if not self.cagou_active:
254 # we only send notification is the frontend is not displayed 274 # we only send notification is the frontend is not displayed