Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_misc_android.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 4b842c1fb686 |
children |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
65 from jnius import autoclass | 65 from jnius import autoclass |
66 from android.broadcast import BroadcastReceiver | 66 from android.broadcast import BroadcastReceiver |
67 from android import python_act | 67 from android import python_act |
68 | 68 |
69 | 69 |
70 Context = autoclass('android.content.Context') | 70 Context = autoclass("android.content.Context") |
71 ConnectivityManager = autoclass('android.net.ConnectivityManager') | 71 ConnectivityManager = autoclass("android.net.ConnectivityManager") |
72 MediaPlayer = autoclass('android.media.MediaPlayer') | 72 MediaPlayer = autoclass("android.media.MediaPlayer") |
73 AudioManager = autoclass('android.media.AudioManager') | 73 AudioManager = autoclass("android.media.AudioManager") |
74 | 74 |
75 # notifications | 75 # notifications |
76 AndroidString = autoclass('java.lang.String') | 76 AndroidString = autoclass("java.lang.String") |
77 PendingIntent = autoclass('android.app.PendingIntent') | 77 PendingIntent = autoclass("android.app.PendingIntent") |
78 Intent = autoclass('android.content.Intent') | 78 Intent = autoclass("android.content.Intent") |
79 | 79 |
80 # DNS | 80 # DNS |
81 # regex to find dns server prop with "getprop" | 81 # regex to find dns server prop with "getprop" |
82 RE_DNS = re.compile(r"^\[net\.[a-z0-9]+\.dns[0-4]\]: \[(.*)\]$", re.MULTILINE) | 82 RE_DNS = re.compile(r"^\[net\.[a-z0-9]+\.dns[0-4]\]: \[(.*)\]$", re.MULTILINE) |
83 SystemProperties = autoclass('android.os.SystemProperties') | 83 SystemProperties = autoclass("android.os.SystemProperties") |
84 | 84 |
85 #: delay between a pause event and sending the inactive indication to server, in seconds | 85 #: delay between a pause event and sending the inactive indication to server, in seconds |
86 #: we don't send the indication immediately because user can be just checking something | 86 #: we don't send the indication immediately because user can be just checking something |
87 #: quickly on an other app. | 87 #: quickly on an other app. |
88 CSI_DELAY = 30 | 88 CSI_DELAY = 30 |
146 notification_intent.add_category(Intent.CATEGORY_LAUNCHER) | 146 notification_intent.add_category(Intent.CATEGORY_LAUNCHER) |
147 if sat_action is not None: | 147 if sat_action is not None: |
148 action_data = AndroidString(json.dumps(sat_action).encode()) | 148 action_data = AndroidString(json.dumps(sat_action).encode()) |
149 log.debug(f"adding extra {INTENT_EXTRA_ACTION} ==> {action_data}") | 149 log.debug(f"adding extra {INTENT_EXTRA_ACTION} ==> {action_data}") |
150 notification_intent = notification_intent.putExtra( | 150 notification_intent = notification_intent.putExtra( |
151 INTENT_EXTRA_ACTION, action_data) | 151 INTENT_EXTRA_ACTION, action_data |
152 ) | |
152 | 153 |
153 # we use PendingIntent.FLAG_UPDATE_CURRENT here, otherwise extra won't be set | 154 # we use PendingIntent.FLAG_UPDATE_CURRENT here, otherwise extra won't be set |
154 # in the new intent (the old ACTION_MAIN intent will be reused). This differs | 155 # in the new intent (the old ACTION_MAIN intent will be reused). This differs |
155 # from plyers original behaviour which set no flag here | 156 # from plyers original behaviour which set no flag here |
156 pending_intent = PendingIntent.getActivity( | 157 pending_intent = PendingIntent.getActivity( |
166 # we also set, where suitable, default values to empty string instead of | 167 # we also set, where suitable, default values to empty string instead of |
167 # original None, as a string is expected (in plyer the empty string is used | 168 # original None, as a string is expected (in plyer the empty string is used |
168 # in the generic "notify" method). | 169 # in the generic "notify" method). |
169 sat_action = kwargs.pop("sat_action", None) | 170 sat_action = kwargs.pop("sat_action", None) |
170 noti = None | 171 noti = None |
171 message = kwargs.get('message', '').encode('utf-8') | 172 message = kwargs.get("message", "").encode("utf-8") |
172 ticker = kwargs.get('ticker', '').encode('utf-8') | 173 ticker = kwargs.get("ticker", "").encode("utf-8") |
173 title = AndroidString( | 174 title = AndroidString(kwargs.get("title", "").encode("utf-8")) |
174 kwargs.get('title', '').encode('utf-8') | 175 icon = kwargs.get("app_icon", "") |
175 ) | 176 |
176 icon = kwargs.get('app_icon', '') | 177 if kwargs.get("toast", False): |
177 | |
178 if kwargs.get('toast', False): | |
179 self._toast(message) | 178 self._toast(message) |
180 return | 179 return |
181 else: | 180 else: |
182 noti = self._build_notification(title) | 181 noti = self._build_notification(title) |
183 | 182 |
208 def __init__(self, android_plugin): | 207 def __init__(self, android_plugin): |
209 self.android_plugin = android_plugin | 208 self.android_plugin = android_plugin |
210 | 209 |
211 def buildProtocol(self, addr): | 210 def buildProtocol(self, addr): |
212 return FrontendStateProtocol(self.android_plugin) | 211 return FrontendStateProtocol(self.android_plugin) |
213 | |
214 | 212 |
215 | 213 |
216 class AndroidPlugin(object): | 214 class AndroidPlugin(object): |
217 | 215 |
218 params = """ | 216 params = """ |
241 | 239 |
242 def __init__(self, host): | 240 def __init__(self, host): |
243 log.info(_("plugin Android initialization")) | 241 log.info(_("plugin Android initialization")) |
244 log.info(f"using Android API {api_version}") | 242 log.info(f"using Android API {api_version}") |
245 self.host = host | 243 self.host = host |
246 self._csi = host.plugins.get('XEP-0352') | 244 self._csi = host.plugins.get("XEP-0352") |
247 self._csi_timer = None | 245 self._csi_timer = None |
248 host.memory.update_params(self.params) | 246 host.memory.update_params(self.params) |
249 try: | 247 try: |
250 os.mkdir(SOCKET_DIR, 0o700) | 248 os.mkdir(SOCKET_DIR, 0o700) |
251 except OSError as e: | 249 except OSError as e: |
266 reactor.listenUNIX(socket_path, factory) | 264 reactor.listenUNIX(socket_path, factory) |
267 else: | 265 else: |
268 raise e | 266 raise e |
269 # we set a low priority because we want the notification to be sent after all | 267 # we set a low priority because we want the notification to be sent after all |
270 # plugins have done their job | 268 # plugins have done their job |
271 host.trigger.add("message_received", self.message_received_trigger, priority=-1000) | 269 host.trigger.add( |
270 "message_received", self.message_received_trigger, priority=-1000 | |
271 ) | |
272 | 272 |
273 # profiles autoconnection | 273 # profiles autoconnection |
274 host.bridge.add_method( | 274 host.bridge.add_method( |
275 "profile_autoconnect_get", | 275 "profile_autoconnect_get", |
276 ".plugin", | 276 ".plugin", |
310 # https://github.com/kivy/pyjnius/issues/59) | 310 # https://github.com/kivy/pyjnius/issues/59) |
311 self.br = BroadcastReceiver( | 311 self.br = BroadcastReceiver( |
312 callback=lambda *args, **kwargs: reactor.callFromThread( | 312 callback=lambda *args, **kwargs: reactor.callFromThread( |
313 self.on_connectivity_change | 313 self.on_connectivity_change |
314 ), | 314 ), |
315 actions=["android.net.conn.CONNECTIVITY_CHANGE"] | 315 actions=["android.net.conn.CONNECTIVITY_CHANGE"], |
316 ) | 316 ) |
317 self.br.start() | 317 self.br.start() |
318 | 318 |
319 @property | 319 @property |
320 def state(self): | 320 def state(self): |
353 | 353 |
354 notification is sent if: | 354 notification is sent if: |
355 - there is a message and it is not a groupchat | 355 - there is a message and it is not a groupchat |
356 - message is not coming from ourself | 356 - message is not coming from ourself |
357 """ | 357 """ |
358 if (mess_data["message"] and mess_data["type"] != C.MESS_TYPE_GROUPCHAT | 358 if ( |
359 and not mess_data["from"].userhostJID() == client.jid.userhostJID()): | 359 mess_data["message"] |
360 and mess_data["type"] != C.MESS_TYPE_GROUPCHAT | |
361 and not mess_data["from"].userhostJID() == client.jid.userhostJID() | |
362 ): | |
360 message = next(iter(mess_data["message"].values())) | 363 message = next(iter(mess_data["message"].values())) |
361 try: | 364 try: |
362 subject = next(iter(mess_data["subject"].values())) | 365 subject = next(iter(mess_data["subject"].values())) |
363 except StopIteration: | 366 except StopIteration: |
364 subject = D_("new message from {contact}").format( | 367 subject = D_("new message from {contact}").format( |
365 contact = mess_data['from']) | 368 contact=mess_data["from"] |
369 ) | |
366 | 370 |
367 notification = Notification() | 371 notification = Notification() |
368 notification._notify( | 372 notification._notify( |
369 title=subject, | 373 title=subject, |
370 message=message, | 374 message=message, |
377 | 381 |
378 ringer_mode = self.am.getRingerMode() | 382 ringer_mode = self.am.getRingerMode() |
379 vibrate_mode = ringer_mode == AudioManager.RINGER_MODE_VIBRATE | 383 vibrate_mode = ringer_mode == AudioManager.RINGER_MODE_VIBRATE |
380 | 384 |
381 ring_setting = self.host.memory.param_get_a( | 385 ring_setting = self.host.memory.param_get_a( |
382 PARAM_RING_NAME, | 386 PARAM_RING_NAME, PARAM_RING_CATEGORY, profile_key=client.profile |
383 PARAM_RING_CATEGORY, | 387 ) |
384 profile_key=client.profile | 388 |
385 ) | 389 if ring_setting != "never" and ringer_mode == AudioManager.RINGER_MODE_NORMAL: |
386 | |
387 if ring_setting != 'never' and ringer_mode == AudioManager.RINGER_MODE_NORMAL: | |
388 self.notif_player.start() | 390 self.notif_player.start() |
389 | 391 |
390 vibration_setting = self.host.memory.param_get_a( | 392 vibration_setting = self.host.memory.param_get_a( |
391 PARAM_VIBRATE_NAME, | 393 PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile |
392 PARAM_VIBRATE_CATEGORY, | 394 ) |
393 profile_key=client.profile | 395 if ( |
394 ) | 396 vibration_setting == "always" |
395 if (vibration_setting == 'always' | 397 or vibration_setting == "vibrate" |
396 or vibration_setting == 'vibrate' and vibrate_mode): | 398 and vibrate_mode |
397 try: | 399 ): |
398 vibrator.vibrate() | 400 try: |
399 except Exception as e: | 401 vibrator.vibrate() |
400 log.warning("Can't use vibrator: {e}".format(e=e)) | 402 except Exception as e: |
403 log.warning("Can't use vibrator: {e}".format(e=e)) | |
401 return mess_data | 404 return mess_data |
402 | 405 |
403 def message_received_trigger(self, client, message_elt, post_treat): | 406 def message_received_trigger(self, client, message_elt, post_treat): |
404 if not self.cagou_active: | 407 if not self.cagou_active: |
405 # we only send notification is the frontend is not displayed | 408 # we only send notification is the frontend is not displayed |
412 def _profile_autoconnect_get(self): | 415 def _profile_autoconnect_get(self): |
413 return defer.ensureDeferred(self.profile_autoconnect_get()) | 416 return defer.ensureDeferred(self.profile_autoconnect_get()) |
414 | 417 |
415 async def _get_profiles_autoconnect(self): | 418 async def _get_profiles_autoconnect(self): |
416 autoconnect_dict = await self.host.memory.storage.get_ind_param_values( | 419 autoconnect_dict = await self.host.memory.storage.get_ind_param_values( |
417 category='Connection', name='autoconnect_backend', | 420 category="Connection", |
421 name="autoconnect_backend", | |
418 ) | 422 ) |
419 return [p for p, v in autoconnect_dict.items() if C.bool(v)] | 423 return [p for p, v in autoconnect_dict.items() if C.bool(v)] |
420 | 424 |
421 async def profile_autoconnect_get(self): | 425 async def profile_autoconnect_get(self): |
422 """Return profile to connect automatically by frontend, if any""" | 426 """Return profile to connect automatically by frontend, if any""" |
424 if not profiles_autoconnect: | 428 if not profiles_autoconnect: |
425 return None | 429 return None |
426 if len(profiles_autoconnect) > 1: | 430 if len(profiles_autoconnect) > 1: |
427 log.warning( | 431 log.warning( |
428 f"More that one profiles with backend autoconnection set found, picking " | 432 f"More that one profiles with backend autoconnection set found, picking " |
429 f"up first one (full list: {profiles_autoconnect!r})") | 433 f"up first one (full list: {profiles_autoconnect!r})" |
434 ) | |
430 return profiles_autoconnect[0] | 435 return profiles_autoconnect[0] |
431 | 436 |
432 # CSI | 437 # CSI |
433 | 438 |
434 def _set_inactive(self): | 439 def _set_inactive(self): |
501 elif net_type == NET_TYPE_WIFI: | 506 elif net_type == NET_TYPE_WIFI: |
502 log.info("WIFI activated") | 507 log.info("WIFI activated") |
503 elif net_type == NET_TYPE_MOBILE: | 508 elif net_type == NET_TYPE_MOBILE: |
504 log.info("mobile data activated") | 509 log.info("mobile data activated") |
505 else: | 510 else: |
506 log.info("network activated (type={net_type_android})" | 511 log.info( |
507 .format(net_type_android=net_type_android)) | 512 "network activated (type={net_type_android})".format( |
513 net_type_android=net_type_android | |
514 ) | |
515 ) | |
508 else: | 516 else: |
509 log.debug("_check_connectivity called without network change ({net_type})" | 517 log.debug( |
510 .format(net_type = net_type)) | 518 "_check_connectivity called without network change ({net_type})".format( |
519 net_type=net_type | |
520 ) | |
521 ) | |
511 | 522 |
512 # we always call _handle_network_change even if there is not connectivity change | 523 # we always call _handle_network_change even if there is not connectivity change |
513 # to be sure to reconnect when necessary | 524 # to be sure to reconnect when necessary |
514 await self._handle_network_change(net_type) | 525 await self._handle_network_change(net_type) |
515 | |
516 | 526 |
517 def on_connectivity_change(self): | 527 def on_connectivity_change(self): |
518 log.debug("on_connectivity_change called") | 528 log.debug("on_connectivity_change called") |
519 d = defer.ensureDeferred(self._check_connectivity()) | 529 d = defer.ensureDeferred(self._check_connectivity()) |
520 d.addErrback(self.host.log_errback) | 530 d.addErrback(self.host.log_errback) |
525 # updating Twisted's resolver accordingly | 535 # updating Twisted's resolver accordingly |
526 dns_servers = await self.get_dns_servers() | 536 dns_servers = await self.get_dns_servers() |
527 | 537 |
528 log.info( | 538 log.info( |
529 "Patching Twisted to use Android DNS resolver ({dns_servers})".format( | 539 "Patching Twisted to use Android DNS resolver ({dns_servers})".format( |
530 dns_servers=', '.join([s[0] for s in dns_servers])) | 540 dns_servers=", ".join([s[0] for s in dns_servers]) |
541 ) | |
531 ) | 542 ) |
532 dns_client.theResolver = dns_client.createResolver(servers=dns_servers) | 543 dns_client.theResolver = dns_client.createResolver(servers=dns_servers) |
533 | 544 |
534 async def get_dns_servers(self): | 545 async def get_dns_servers(self): |
535 servers = [] | 546 servers = [] |
536 | 547 |
537 if api_version < 26: | 548 if api_version < 26: |
538 # thanks to A-IV at https://stackoverflow.com/a/11362271 for the way to go | 549 # thanks to A-IV at https://stackoverflow.com/a/11362271 for the way to go |
539 log.debug("Old API, using SystemProperties to find DNS") | 550 log.debug("Old API, using SystemProperties to find DNS") |
540 for idx in range(1, 5): | 551 for idx in range(1, 5): |
541 addr = SystemProperties.get(f'net.dns{idx}') | 552 addr = SystemProperties.get(f"net.dns{idx}") |
542 if abstract.isIPAddress(addr): | 553 if abstract.isIPAddress(addr): |
543 servers.append((addr, 53)) | 554 servers.append((addr, 53)) |
544 else: | 555 else: |
545 log.debug(f"API {api_version} >= 26, using getprop to find DNS") | 556 log.debug(f"API {api_version} >= 26, using getprop to find DNS") |
546 # use of getprop inspired by various solutions at | 557 # use of getprop inspired by various solutions at |
547 # https://stackoverflow.com/q/3070144 | 558 # https://stackoverflow.com/q/3070144 |
548 # it's the most simple option, and it fit wells with async_process | 559 # it's the most simple option, and it fit wells with async_process |
549 getprop_paths = which('getprop') | 560 getprop_paths = which("getprop") |
550 if getprop_paths: | 561 if getprop_paths: |
551 try: | 562 try: |
552 getprop_path = getprop_paths[0] | 563 getprop_path = getprop_paths[0] |
553 props = await async_process.run(getprop_path) | 564 props = await async_process.run(getprop_path) |
554 servers = [(ip, 53) for ip in RE_DNS.findall(props.decode()) | 565 servers = [ |
555 if abstract.isIPAddress(ip)] | 566 (ip, 53) |
567 for ip in RE_DNS.findall(props.decode()) | |
568 if abstract.isIPAddress(ip) | |
569 ] | |
556 except Exception as e: | 570 except Exception as e: |
557 log.warning(f"Can't use \"getprop\" to find DNS server: {e}") | 571 log.warning(f'Can\'t use "getprop" to find DNS server: {e}') |
558 if not servers: | 572 if not servers: |
559 # FIXME: Cloudflare's 1.1.1.1 seems to have a better privacy policy, to be | 573 # FIXME: Cloudflare's 1.1.1.1 seems to have a better privacy policy, to be |
560 # checked. | 574 # checked. |
561 log.warning( | 575 log.warning( |
562 "no server found, we have to use factory Google DNS, this is not ideal " | 576 "no server found, we have to use factory Google DNS, this is not ideal " |
563 "for privacy" | 577 "for privacy" |
564 ) | 578 ) |
565 servers.append(('8.8.8.8', 53), ('8.8.4.4', 53)) | 579 servers.append(("8.8.8.8", 53), ("8.8.4.4", 53)) |
566 return servers | 580 return servers |