comparison sat/plugins/plugin_misc_android.py @ 3154:f2d3ab4390a3

plugin android: attach an action dict to notification: instead of calling plyer's generic notify, AndroidNotification is now inherited to add a SàT specific behaviour: a "sat_action" dictionary is now attached, to indicate to the frontend what to do when the notification is opened.
author Goffi <goffi@goffi.org>
date Mon, 03 Feb 2020 13:49:45 +0100
parents 559a625a236b
children b5c058c7692e
comparison
equal deleted inserted replaced
3153:2c7b42f53e9a 3154:f2d3ab4390a3
1 #!/usr/bin/env python3 1 #!/usr/bin/env python3
2
3 2
4 # SAT plugin for file tansfer 3 # SAT plugin for file tansfer
5 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org) 4 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org)
6 5
7 # This program is free software: you can redistribute it and/or modify 6 # This program is free software: you can redistribute it and/or modify
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 18
20 import sys 19 import sys
21 import os 20 import os
22 import os.path 21 import os.path
22 import json
23 from pathlib import Path 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
47 47
48 if sys.platform != "android": 48 if sys.platform != "android":
49 raise exceptions.CancelError("this module is not needed on this platform") 49 raise exceptions.CancelError("this module is not needed on this platform")
50 50
51 51
52 from plyer import notification, vibrator 52 from plyer import vibrator
53 from plyer.platforms.android import activity 53 from plyer.platforms.android import activity
54 from plyer.platforms.android.notification import AndroidNotification
54 from jnius import autoclass 55 from jnius import autoclass
55 from android.broadcast import BroadcastReceiver 56 from android.broadcast import BroadcastReceiver
57 from android import python_act
58
59
60 Context = autoclass('android.content.Context')
61 ConnectivityManager = autoclass('android.net.ConnectivityManager')
62 MediaPlayer = autoclass('android.media.MediaPlayer')
63 AudioManager = autoclass('android.media.AudioManager')
64
65 # notifications
66 AndroidString = autoclass('java.lang.String')
67 PendingIntent = autoclass('android.app.PendingIntent')
68 Intent = autoclass('android.content.Intent')
56 69
57 #: delay between a pause event and sending the inactive indication to server, in seconds 70 #: delay between a pause event and sending the inactive indication to server, in seconds
58 #: we don't send the indication immediately because user can be just checking something 71 #: we don't send the indication immediately because user can be just checking something
59 #: quickly on an other app. 72 #: quickly on an other app.
60 CSI_DELAY = 30 73 CSI_DELAY = 30
82 STATES = (STATE_RUNNING, STATE_PAUSED, STATE_STOPPED) 95 STATES = (STATE_RUNNING, STATE_PAUSED, STATE_STOPPED)
83 NET_TYPE_NONE = "no network" 96 NET_TYPE_NONE = "no network"
84 NET_TYPE_WIFI = "wifi" 97 NET_TYPE_WIFI = "wifi"
85 NET_TYPE_MOBILE = "mobile" 98 NET_TYPE_MOBILE = "mobile"
86 NET_TYPE_OTHER = "other" 99 NET_TYPE_OTHER = "other"
87 100 INTENT_EXTRA_ACTION = AndroidString("org.salut-a-toi.IntentAction")
88 101
89 Context = autoclass('android.content.Context') 102
90 ConnectivityManager = autoclass('android.net.ConnectivityManager') 103 class Notification(AndroidNotification):
91 MediaPlayer = autoclass('android.media.MediaPlayer') 104 # We extend plyer's AndroidNotification instead of creating directly with jnius
92 AudioManager = autoclass('android.media.AudioManager') 105 # because it already handles issues like backward compatibility, and we just want to
106 # slightly modify the behaviour.
107
108 @staticmethod
109 def _set_open_behavior(notification, sat_action):
110 # we reproduce plyer's AndroidNotification._set_open_behavior
111 # bu we add SàT specific extra action data
112
113 app_context = activity.getApplication().getApplicationContext()
114 notification_intent = Intent(app_context, python_act)
115
116 notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
117 notification_intent.setAction(Intent.ACTION_MAIN)
118 notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
119 if sat_action is not None:
120 action_data = AndroidString(json.dumps(sat_action).encode())
121 log.debug(f"adding extra {INTENT_EXTRA_ACTION} ==> {action_data}")
122 notification_intent = notification_intent.putExtra(
123 INTENT_EXTRA_ACTION, action_data)
124
125 # we use PendingIntent.FLAG_UPDATE_CURRENT here, otherwise extra won't be set
126 # in the new intent (the old ACTION_MAIN intent will be reused). This differs
127 # from plyers original behaviour which set no flag here
128 pending_intent = PendingIntent.getActivity(
129 app_context, 0, notification_intent, PendingIntent.FLAG_UPDATE_CURRENT
130 )
131
132 notification.setContentIntent(pending_intent)
133 notification.setAutoCancel(True)
134
135 def _notify(self, **kwargs):
136 # we reproduce plyer's AndroidNotification._notify behaviour here
137 # and we add handling of "sat_action" attribute (SàT specific).
138 # we also set, where suitable, default values to empty string instead of
139 # original None, as a string is expected (in plyer the empty string is used
140 # in the generic "notify" method).
141 sat_action = kwargs.pop("sat_action", None)
142 noti = None
143 message = kwargs.get('message', '').encode('utf-8')
144 ticker = kwargs.get('ticker', '').encode('utf-8')
145 title = AndroidString(
146 kwargs.get('title', '').encode('utf-8')
147 )
148 icon = kwargs.get('app_icon', '')
149
150 if kwargs.get('toast', False):
151 self._toast(message)
152 return
153 else:
154 noti = self._build_notification(title)
155
156 noti.setContentTitle(title)
157 noti.setContentText(AndroidString(message))
158 noti.setTicker(AndroidString(ticker))
159
160 self._set_icons(noti, icon=icon)
161 self._set_open_behavior(noti, sat_action)
162
163 self._open_notification(noti)
93 164
94 165
95 class FrontendStateProtocol(protocol.Protocol): 166 class FrontendStateProtocol(protocol.Protocol):
96 167
97 def __init__(self, android_plugin): 168 def __init__(self, android_plugin):
250 and not mess_data["from"].userhostJID() == client.jid.userhostJID()): 321 and not mess_data["from"].userhostJID() == client.jid.userhostJID()):
251 message = next(iter(mess_data["message"].values())) 322 message = next(iter(mess_data["message"].values()))
252 try: 323 try:
253 subject = next(iter(mess_data["subject"].values())) 324 subject = next(iter(mess_data["subject"].values()))
254 except StopIteration: 325 except StopIteration:
255 subject = "Cagou new message" 326 subject = D_("new message from {contact}").format(
256 327 contact = mess_data['from'])
257 notification.notify(title=subject, message=message) 328
329 notification = Notification()
330 notification._notify(
331 title=subject,
332 message=message,
333 sat_action={
334 "type": "open",
335 "widget": "chat",
336 "target": mess_data["from"].userhost(),
337 },
338 )
258 339
259 ringer_mode = self.am.getRingerMode() 340 ringer_mode = self.am.getRingerMode()
260 vibrate_mode = ringer_mode == AudioManager.RINGER_MODE_VIBRATE 341 vibrate_mode = ringer_mode == AudioManager.RINGER_MODE_VIBRATE
261 342
262 ring_setting = self.host.memory.getParamA( 343 ring_setting = self.host.memory.getParamA(