diff 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
line wrap: on
line diff
--- a/sat/plugins/plugin_misc_android.py	Mon Feb 03 13:46:24 2020 +0100
+++ b/sat/plugins/plugin_misc_android.py	Mon Feb 03 13:49:45 2020 +0100
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-
 # SAT plugin for file tansfer
 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org)
 
@@ -20,6 +19,7 @@
 import sys
 import os
 import os.path
+import json
 from pathlib import Path
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
@@ -49,10 +49,23 @@
     raise exceptions.CancelError("this module is not needed on this platform")
 
 
-from plyer import notification, vibrator
+from plyer import vibrator
 from plyer.platforms.android import activity
+from plyer.platforms.android.notification import AndroidNotification
 from jnius import autoclass
 from android.broadcast import BroadcastReceiver
+from android import python_act
+
+
+Context = autoclass('android.content.Context')
+ConnectivityManager = autoclass('android.net.ConnectivityManager')
+MediaPlayer = autoclass('android.media.MediaPlayer')
+AudioManager = autoclass('android.media.AudioManager')
+
+# notifications
+AndroidString = autoclass('java.lang.String')
+PendingIntent = autoclass('android.app.PendingIntent')
+Intent = autoclass('android.content.Intent')
 
 #: delay between a pause event and sending the inactive indication to server, in seconds
 #: we don't send the indication immediately because user can be just checking something
@@ -84,12 +97,70 @@
 NET_TYPE_WIFI = "wifi"
 NET_TYPE_MOBILE = "mobile"
 NET_TYPE_OTHER = "other"
+INTENT_EXTRA_ACTION = AndroidString("org.salut-a-toi.IntentAction")
 
 
-Context = autoclass('android.content.Context')
-ConnectivityManager = autoclass('android.net.ConnectivityManager')
-MediaPlayer = autoclass('android.media.MediaPlayer')
-AudioManager = autoclass('android.media.AudioManager')
+class Notification(AndroidNotification):
+    # We extend plyer's AndroidNotification instead of creating directly with jnius
+    # because it already handles issues like backward compatibility, and we just want to
+    # slightly modify the behaviour.
+
+    @staticmethod
+    def _set_open_behavior(notification, sat_action):
+        # we reproduce plyer's AndroidNotification._set_open_behavior
+        # bu we add SàT specific extra action data
+
+        app_context = activity.getApplication().getApplicationContext()
+        notification_intent = Intent(app_context, python_act)
+
+        notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
+        notification_intent.setAction(Intent.ACTION_MAIN)
+        notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
+        if sat_action is not None:
+            action_data = AndroidString(json.dumps(sat_action).encode())
+            log.debug(f"adding extra {INTENT_EXTRA_ACTION} ==> {action_data}")
+            notification_intent = notification_intent.putExtra(
+                INTENT_EXTRA_ACTION, action_data)
+
+        # we use PendingIntent.FLAG_UPDATE_CURRENT here, otherwise extra won't be set
+        # in the new intent (the old ACTION_MAIN intent will be reused). This differs
+        # from plyers original behaviour which set no flag here
+        pending_intent = PendingIntent.getActivity(
+            app_context, 0, notification_intent, PendingIntent.FLAG_UPDATE_CURRENT
+        )
+
+        notification.setContentIntent(pending_intent)
+        notification.setAutoCancel(True)
+
+    def _notify(self, **kwargs):
+        # we reproduce plyer's AndroidNotification._notify behaviour here
+        # and we add handling of "sat_action" attribute (SàT specific).
+        # we also set, where suitable, default values to empty string instead of
+        # original None, as a string is expected (in plyer the empty string is used
+        # in the generic "notify" method).
+        sat_action = kwargs.pop("sat_action", None)
+        noti = None
+        message = kwargs.get('message', '').encode('utf-8')
+        ticker = kwargs.get('ticker', '').encode('utf-8')
+        title = AndroidString(
+            kwargs.get('title', '').encode('utf-8')
+        )
+        icon = kwargs.get('app_icon', '')
+
+        if kwargs.get('toast', False):
+            self._toast(message)
+            return
+        else:
+            noti = self._build_notification(title)
+
+        noti.setContentTitle(title)
+        noti.setContentText(AndroidString(message))
+        noti.setTicker(AndroidString(ticker))
+
+        self._set_icons(noti, icon=icon)
+        self._set_open_behavior(noti, sat_action)
+
+        self._open_notification(noti)
 
 
 class FrontendStateProtocol(protocol.Protocol):
@@ -252,9 +323,19 @@
             try:
                 subject = next(iter(mess_data["subject"].values()))
             except StopIteration:
-                subject = "Cagou new message"
+                subject = D_("new message from {contact}").format(
+                    contact = mess_data['from'])
 
-            notification.notify(title=subject, message=message)
+            notification = Notification()
+            notification._notify(
+                title=subject,
+                message=message,
+                sat_action={
+                    "type": "open",
+                    "widget": "chat",
+                    "target": mess_data["from"].userhost(),
+                },
+            )
 
             ringer_mode = self.am.getRingerMode()
             vibrate_mode = ringer_mode == AudioManager.RINGER_MODE_VIBRATE