Added service split for Android and notifications on Android and Linux

This commit is contained in:
Mark Qvist 2022-10-02 01:14:29 +02:00
parent e58c8a96be
commit 7ee0171ec7
8 changed files with 336 additions and 103 deletions

View File

@ -72,8 +72,6 @@ class SidebandApp(MDApp):
self.android_service = None self.android_service = None
self.app_dir = plyer.storagepath.get_application_dir() self.app_dir = plyer.storagepath.get_application_dir()
# TODO: Remove
RNS.log("Application directory is: "+str(self.app_dir))
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
self.sideband = SidebandCore(self, is_client=True, android_app_dir=self.app_dir) self.sideband = SidebandCore(self, is_client=True, android_app_dir=self.app_dir)
@ -82,20 +80,16 @@ class SidebandApp(MDApp):
self.conversations_view = None self.conversations_view = None
self.flag_new_conversations = False
self.flag_unread_conversations = False
self.flag_new_announces = False
self.lxmf_sync_dialog_open = False
self.sync_dialog = None self.sync_dialog = None
Window.softinput_mode = "below_target" Window.softinput_mode = "below_target"
self.icon = self.sideband.asset_dir+"/icon.png" self.icon = self.sideband.asset_dir+"/icon.png"
self.notification_icon = self.sideband.asset_dir+"/notification_icon.png" self.notification_icon = self.sideband.asset_dir+"/notification_icon.png"
self.check_permissions()
def start_core(self, dt): def start_core(self, dt):
self.start_service() self.start_service()
self.open_conversations()
Clock.schedule_interval(self.jobs, 1) Clock.schedule_interval(self.jobs, 1)
def dismiss_splash(dt): def dismiss_splash(dt):
@ -105,8 +99,15 @@ class SidebandApp(MDApp):
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
Clock.schedule_once(dismiss_splash, 0) Clock.schedule_once(dismiss_splash, 0)
if self.sideband.first_run: self.sideband.setstate("app.loaded", True)
self.guide_action() self.sideband.setstate("app.running", True)
self.sideband.setstate("app.foreground", True)
if self.sideband.first_run:
self.guide_action()
self.request_notifications_permission()
else:
self.open_conversations()
self.app_state = SidebandApp.ACTIVE self.app_state = SidebandApp.ACTIVE
@ -125,8 +126,6 @@ class SidebandApp(MDApp):
time.sleep(7) time.sleep(7)
# Start local core instance # Start local core instance
# TODO: Remove log
RNS.log("Starting local core")
self.sideband.start() self.sideband.start()
else: else:
@ -150,14 +149,20 @@ class SidebandApp(MDApp):
mActivity.startActivity(shareIntent) mActivity.startActivity(shareIntent)
def on_pause(self): def on_pause(self):
self.sideband.setstate("app.running", True)
self.sideband.setstate("app.foreground", False)
self.app_state = SidebandApp.PAUSED self.app_state = SidebandApp.PAUSED
self.sideband.should_persist_data() self.sideband.should_persist_data()
return True return True
def on_resume(self): def on_resume(self):
self.sideband.setstate("app.running", True)
self.sideband.setstate("app.foreground", True)
self.app_state = SidebandApp.ACTIVE self.app_state = SidebandApp.ACTIVE
def on_stop(self): def on_stop(self):
self.sideband.setstate("app.running", False)
self.sideband.setstate("app.foreground", False)
self.app_state = SidebandApp.STOPPING self.app_state = SidebandApp.STOPPING
def is_in_foreground(self): def is_in_foreground(self):
@ -166,25 +171,27 @@ class SidebandApp(MDApp):
else: else:
return False return False
def notify(self, title, content): def check_permissions(self):
notifications_enabled = True if RNS.vendor.platformutils.get_platform() == "android":
if check_permission("android.permission.POST_NOTIFICATIONS"):
if notifications_enabled: RNS.log("Have notification permissions")
if RNS.vendor.platformutils.get_platform() == "android": self.sideband.setpersistent("permissions.notifications", True)
notifications_permitted = False
if check_permission("android.permission.POST_NOTIFICATIONS"):
notifications_permitted = True
else:
RNS.log("Requesting notification permission")
request_permissions(["android.permission.POST_NOTIFICATIONS"])
else: else:
notifications_permitted = True RNS.log("Do not have notification permissions")
self.sideband.setpersistent("permissions.notifications", False)
else:
self.sideband.setpersistent("permissions.notifications", True)
if notifications_permitted: def request_permissions(self):
if RNS.vendor.platformutils.get_platform() == "android": self.request_notifications_permission()
plyer.notification.notify(title, content, notification_icon=self.notification_icon)
else: def request_notifications_permission(self):
plyer.notification.notify(title, content) if RNS.vendor.platformutils.get_platform() == "android":
if not check_permission("android.permission.POST_NOTIFICATIONS"):
RNS.log("Requesting notification permission")
request_permissions(["android.permission.POST_NOTIFICATIONS"])
self.check_permissions()
def build(self): def build(self):
FONT_PATH = self.sideband.asset_dir+"/fonts" FONT_PATH = self.sideband.asset_dir+"/fonts"
@ -201,22 +208,22 @@ class SidebandApp(MDApp):
self.message_area_detect() self.message_area_detect()
elif self.root.ids.screen_manager.current == "conversations_screen": elif self.root.ids.screen_manager.current == "conversations_screen":
if self.flag_new_conversations: if self.sideband.getstate("app.flags.new_conversations"):
RNS.log("Updating because of new conversations flag") RNS.log("Updating because of new conversations flag")
if self.conversations_view != None: if self.conversations_view != None:
self.conversations_view.update() self.conversations_view.update()
if self.flag_unread_conversations: if self.sideband.getstate("app.flags.unread_conversations"):
RNS.log("Updating because of unread messages flag") RNS.log("Updating because of unread messages flag")
if self.conversations_view != None: if self.conversations_view != None:
self.conversations_view.update() self.conversations_view.update()
if self.lxmf_sync_dialog_open and self.sync_dialog != None: if self.sideband.getstate("app.flags.lxmf_sync_dialog_open") and self.sync_dialog != None:
self.sync_dialog.ids.sync_progress.value = self.sideband.get_sync_progress()*100 self.sync_dialog.ids.sync_progress.value = self.sideband.get_sync_progress()*100
self.sync_dialog.ids.sync_status.text = self.sideband.get_sync_status() self.sync_dialog.ids.sync_status.text = self.sideband.get_sync_status()
elif self.root.ids.screen_manager.current == "announces_screen": elif self.root.ids.screen_manager.current == "announces_screen":
if self.flag_new_announces: if self.sideband.getstate("app.flags.new_announces"):
RNS.log("Updating because of new announces flag") RNS.log("Updating because of new announces flag")
if self.announces_view != None: if self.announces_view != None:
self.announces_view.update() self.announces_view.update()
@ -304,6 +311,9 @@ class SidebandApp(MDApp):
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.should_persist_data() self.sideband.should_persist_data()
self.sideband.setstate("app.running", False)
self.sideband.setstate("app.foreground", False)
def final_exit(dt): def final_exit(dt):
RNS.exit() RNS.exit()
MDApp.get_running_app().stop() MDApp.get_running_app().stop()
@ -328,13 +338,6 @@ class SidebandApp(MDApp):
yes_button.bind(on_release=dl_yes) yes_button.bind(on_release=dl_yes)
dialog.open() dialog.open()
def conversation_update(self, context_dest):
pass
# if self.root.ids.messages_scrollview.active_conversation == context_dest:
# self.messages_view.update_widget()
# else:
# RNS.log("Not updating since context_dest does not match active")
################################################# #################################################
# Screens # # Screens #
@ -372,12 +375,14 @@ class SidebandApp(MDApp):
self.root.ids.messages_scrollview.scroll_y = 0 self.root.ids.messages_scrollview.scroll_y = 0
self.root.ids.messages_toolbar.title = self.sideband.peer_display_name(context_dest) self.root.ids.messages_toolbar.title = self.sideband.peer_display_name(context_dest)
self.root.ids.messages_scrollview.active_conversation = context_dest self.root.ids.messages_scrollview.active_conversation = context_dest
self.sideband.setstate("app.active_conversation", context_dest)
self.root.ids.nokeys_text.text = "" self.root.ids.nokeys_text.text = ""
self.message_area_detect() self.message_area_detect()
self.update_message_widgets() self.update_message_widgets()
self.root.ids.screen_manager.current = "messages_screen" self.root.ids.screen_manager.current = "messages_screen"
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
self.sideband.read_conversation(context_dest) self.sideband.read_conversation(context_dest)
@ -480,6 +485,7 @@ class SidebandApp(MDApp):
self.root.ids.screen_manager.current = "conversations_screen" self.root.ids.screen_manager.current = "conversations_screen"
self.root.ids.messages_scrollview.active_conversation = None self.root.ids.messages_scrollview.active_conversation = None
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def connectivity_status(self, sender): def connectivity_status(self, sender):
hs = dp(22) hs = dp(22)
@ -567,18 +573,12 @@ class SidebandApp(MDApp):
) )
dialog.d_content = dialog_content dialog.d_content = dialog_content
def dl_close(s): def dl_close(s):
self.lxmf_sync_dialog_open = False self.sideband.setstate("app.flags.lxmf_sync_dialog_open", False)
dialog.dismiss() dialog.dismiss()
self.sideband.cancel_lxmf_sync() self.sideband.cancel_lxmf_sync()
# def dl_stop(s):
# self.lxmf_sync_dialog_open = False
# dialog.dismiss()
# self.sideband.cancel_lxmf_sync()
close_button.bind(on_release=dl_close) close_button.bind(on_release=dl_close)
# stop_button.bind(on_release=dl_stop) self.sideband.setstate("app.flags.lxmf_sync_dialog_open", True)
self.lxmf_sync_dialog_open = True
self.sync_dialog = dialog_content self.sync_dialog = dialog_content
dialog.open() dialog.open()
dialog_content.ids.sync_progress.value = self.sideband.get_sync_progress()*100 dialog_content.ids.sync_progress.value = self.sideband.get_sync_progress()*100
@ -652,6 +652,7 @@ class SidebandApp(MDApp):
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.screen_manager.current = "information_screen" self.root.ids.screen_manager.current = "information_screen"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def close_information_action(self, sender=None): def close_information_action(self, sender=None):
self.open_conversations(direction="right") self.open_conversations(direction="right")
@ -743,6 +744,7 @@ class SidebandApp(MDApp):
self.root.ids.screen_manager.current = "settings_screen" self.root.ids.screen_manager.current = "settings_screen"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def close_settings_action(self, sender=None): def close_settings_action(self, sender=None):
self.open_conversations(direction="right") self.open_conversations(direction="right")
@ -852,6 +854,7 @@ class SidebandApp(MDApp):
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.screen_manager.current = "connectivity_screen" self.root.ids.screen_manager.current = "connectivity_screen"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def close_connectivity_action(self, sender=None): def close_connectivity_action(self, sender=None):
@ -875,6 +878,7 @@ class SidebandApp(MDApp):
self.root.ids.announces_scrollview.add_widget(self.announces_view.get_widget()) self.root.ids.announces_scrollview.add_widget(self.announces_view.get_widget())
self.root.ids.screen_manager.current = "announces_screen" self.root.ids.screen_manager.current = "announces_screen"
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def close_announces_action(self, sender=None): def close_announces_action(self, sender=None):
self.open_conversations(direction="right") self.open_conversations(direction="right")
@ -900,6 +904,7 @@ class SidebandApp(MDApp):
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.screen_manager.current = "keys_screen" self.root.ids.screen_manager.current = "keys_screen"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def close_keys_action(self, sender=None): def close_keys_action(self, sender=None):
self.open_conversations(direction="right") self.open_conversations(direction="right")
@ -1031,6 +1036,7 @@ Thank you very much for using Free Communications Systems.
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.screen_manager.current = "guide_screen" self.root.ids.screen_manager.current = "guide_screen"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
################################################# #################################################
@ -1049,6 +1055,7 @@ Thank you very much for using Free Communications Systems.
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.screen_manager.current = "map_screen" self.root.ids.screen_manager.current = "map_screen"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)
def broadcasts_action(self, sender=None): def broadcasts_action(self, sender=None):
def link_exec(sender=None, event=None): def link_exec(sender=None, event=None):
@ -1062,6 +1069,7 @@ Thank you very much for using Free Communications Systems.
self.root.ids.screen_manager.transition.direction = "left" self.root.ids.screen_manager.transition.direction = "left"
self.root.ids.screen_manager.current = "broadcasts_screen" self.root.ids.screen_manager.current = "broadcasts_screen"
self.root.ids.nav_drawer.set_state("closed") self.root.ids.nav_drawer.set_state("closed")
self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current)

View File

@ -130,20 +130,22 @@ public class PythonService extends Service implements Runnable {
} else { } else {
// for android 8+ we need to create our own channel // for android 8+ we need to create our own channel
// https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1 // https://stackoverflow.com/questions/47531742/startforeground-fail-after-upgrade-to-android-8-1
String NOTIFICATION_CHANNEL_ID = "org.kivy.p4a"; //TODO: make this configurable String NOTIFICATION_CHANNEL_ID = "io.unsigned.sideband.reticulum";
String channelName = "Background Service"; //TODO: make this configurable String channelName = "Background Service";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName,
NotificationManager.IMPORTANCE_NONE); NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE); chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); chan.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
chan.setShowBadge(false);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.createNotificationChannel(chan); manager.createNotificationChannel(chan);
Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID); Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setContentTitle("Sideband"); builder.setContentTitle("Sideband Active");
builder.setContentText("Reticulum Running"); // builder.setContentText("Reticulum Active");
builder.setContentIntent(pIntent); builder.setContentIntent(pIntent);
// builder.setOngoing(true);
// TODO: Generalise this // TODO: Generalise this
Bitmap icon_bitmap = BitmapFactory.decodeFile("/data/user/0/io.unsigned.sideband/files/app/assets/notification_icon.png"); Bitmap icon_bitmap = BitmapFactory.decodeFile("/data/user/0/io.unsigned.sideband/files/app/assets/notification_icon.png");

View File

@ -46,7 +46,7 @@ class Notification:
''' '''
def notify(self, title='', message='', app_name='', app_icon='', notification_icon=None, def notify(self, title='', message='', app_name='', app_icon='', notification_icon=None,
timeout=10, ticker='', toast=False, hints={}): timeout=10, ticker='', toast=False, hints={}, context_override=None):
''' '''
Send a notification. Send a notification.
@ -84,7 +84,7 @@ class Notification:
self._notify( self._notify(
title=title, message=message, title=title, message=message,
app_icon=app_icon, app_name=app_name, notification_icon=notification_icon, app_icon=app_icon, app_name=app_name, notification_icon=notification_icon,
timeout=timeout, ticker=ticker, toast=toast, hints=hints timeout=timeout, ticker=ticker, toast=toast, hints=hints, context_override=context_override
) )
# private # private

View File

@ -180,6 +180,7 @@ class AndroidNotification(Notification):
) )
icon = kwargs.get('app_icon') icon = kwargs.get('app_icon')
notification_icon = kwargs.get('notification_icon') notification_icon = kwargs.get('notification_icon')
context_override = kwargs.get('context_override')
# decide whether toast only or proper notification # decide whether toast only or proper notification
if kwargs.get('toast'): if kwargs.get('toast'):

View File

@ -9,7 +9,18 @@ Logger.setLevel(LOG_LEVELS["debug"])
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
from jnius import autoclass, cast from jnius import autoclass, cast
from android import python_act
Context = autoclass('android.content.Context') Context = autoclass('android.content.Context')
Intent = autoclass('android.content.Intent')
BitmapFactory = autoclass('android.graphics.BitmapFactory')
Icon = autoclass("android.graphics.drawable.Icon")
PendingIntent = autoclass('android.app.PendingIntent')
AndroidString = autoclass('java.lang.String')
NotificationManager = autoclass('android.app.NotificationManager')
Context = autoclass('android.content.Context')
NotificationBuilder = autoclass('android.app.Notification$Builder')
NotificationChannel = autoclass('android.app.NotificationChannel')
from sideband.core import SidebandCore from sideband.core import SidebandCore
else: else:
@ -20,6 +31,64 @@ class AppProxy():
pass pass
class SidebandService(): class SidebandService():
def android_notification(self, title="", content="", ticker="", group=None, context_id=None):
package_name = "io.unsigned.sideband"
if not self.notification_service:
self.notification_service = cast(NotificationManager, self.app_context.getSystemService(
Context.NOTIFICATION_SERVICE
))
channel_id = package_name
group_id = ""
if group != None:
channel_id += "."+str(group)
group_id += str(group)
if context_id != None:
channel_id += "."+str(context_id)
group_id += "."+str(context_id)
if not title or title == "":
channel_name = "Sideband"
else:
channel_name = title
self.notification_channel = NotificationChannel(channel_id, channel_name, NotificationManager.IMPORTANCE_DEFAULT)
self.notification_channel.enableVibration(True)
self.notification_channel.setShowBadge(True)
self.notification_service.createNotificationChannel(self.notification_channel)
notification = NotificationBuilder(self.app_context, channel_id)
notification.setContentTitle(title)
notification.setContentText(AndroidString(content))
# if group != None:
# notification.setGroup(group_id)
if not self.notification_small_icon:
path = self.sideband.notification_icon
bitmap = BitmapFactory.decodeFile(path)
self.notification_small_icon = Icon.createWithBitmap(bitmap)
notification.setSmallIcon(self.notification_small_icon)
# large_icon_path = self.sideband.icon
# bitmap_icon = BitmapFactory.decodeFile(large_icon_path)
# notification.setLargeIcon(bitmap_icon)
if not self.notification_intent:
notification_intent = Intent(self.app_context, python_act)
notification_intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
notification_intent.setAction(Intent.ACTION_MAIN)
notification_intent.addCategory(Intent.CATEGORY_LAUNCHER)
self.notification_intent = PendingIntent.getActivity(self.app_context, 0, notification_intent, 0)
notification.setContentIntent(self.notification_intent)
notification.setAutoCancel(True)
built_notification = notification.build()
self.notification_service.notify(0, built_notification)
def __init__(self): def __init__(self):
self.argument = environ.get('PYTHON_SERVICE_ARGUMENT', '') self.argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
@ -34,15 +103,20 @@ class SidebandService():
self.app_context = None self.app_context = None
self.wifi_manager = None self.wifi_manager = None
self.notification_service = None
self.notification_channel = None
self.notification_intent = None
self.notification_small_icon = None
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
self.android_service = autoclass('org.kivy.android.PythonService').mService self.android_service = autoclass('org.kivy.android.PythonService').mService
self.app_context = self.android_service.getApplication().getApplicationContext() self.app_context = self.android_service.getApplication().getApplicationContext()
self.wifi_manager = self.app_context.getSystemService(Context.WIFI_SERVICE) self.wifi_manager = self.app_context.getSystemService(Context.WIFI_SERVICE)
# The returned instance is an android.net.wifi.WifiManager # The returned instance /\ is an android.net.wifi.WifiManager
# TODO: Remove?
RNS.log("Sideband background service created, starting core...", RNS.LOG_DEBUG)
self.sideband = SidebandCore(self.app_proxy, is_service=True, android_app_dir=self.app_dir) self.sideband = SidebandCore(self.app_proxy, is_service=True, android_app_dir=self.app_dir)
self.sideband.service_context = self.android_service
self.sideband.owner_service = self
self.sideband.start() self.sideband.start()
def start(self): def start(self):
@ -77,7 +151,3 @@ class SidebandService():
sbs = SidebandService() sbs = SidebandService()
sbs.start() sbs.start()
# TODO: Remove
print("SBS: Service thread done")
RNS.log("Service thread done")

View File

@ -8,6 +8,9 @@ import sqlite3
import RNS.vendor.umsgpack as msgpack import RNS.vendor.umsgpack as msgpack
if RNS.vendor.platformutils.get_platform() == "android":
from jnius import autoclass, cast
class PropagationNodeDetector(): class PropagationNodeDetector():
EMITTED_DELTA_GRACE = 300 EMITTED_DELTA_GRACE = 300
EMITTED_DELTA_IGNORE = 10 EMITTED_DELTA_IGNORE = 10
@ -60,6 +63,8 @@ class SidebandCore():
MAX_ANNOUNCES = 48 MAX_ANNOUNCES = 48
JOB_INTERVAL = 1
aspect_filter = "lxmf.delivery" aspect_filter = "lxmf.delivery"
def received_announce(self, destination_hash, announced_identity, app_data): def received_announce(self, destination_hash, announced_identity, app_data):
# Add the announce to the directory announce # Add the announce to the directory announce
@ -75,12 +80,6 @@ class SidebandCore():
else: else:
self.is_standalone = False self.is_standalone = False
if self.is_client:
from .serviceproxy import ServiceProxy
self.serviceproxy = ServiceProxy(self)
else:
self.serviceproxy = None
self.owner_app = owner_app self.owner_app = owner_app
self.reticulum = None self.reticulum = None
@ -90,11 +89,18 @@ class SidebandCore():
if RNS.vendor.platformutils.get_platform() == "android": if RNS.vendor.platformutils.get_platform() == "android":
self.app_dir = android_app_dir+"/io.unsigned.sideband/files/" self.app_dir = android_app_dir+"/io.unsigned.sideband/files/"
self.rns_configdir = self.app_dir+"/app_storage/reticulum" self.rns_configdir = self.app_dir+"/app_storage/reticulum"
self.asset_dir = self.app_dir+"/app/assets"
else:
self.asset_dir = plyer.storagepath.get_application_dir()+"/sbapp/assets"
self.icon = self.asset_dir+"/icon.png"
self.icon_48 = self.asset_dir+"/icon_48.png"
self.icon_32 = self.asset_dir+"/icon_32.png"
self.notification_icon = self.asset_dir+"/notification_icon.png"
if not os.path.isdir(self.app_dir+"/app_storage"): if not os.path.isdir(self.app_dir+"/app_storage"):
os.makedirs(self.app_dir+"/app_storage") os.makedirs(self.app_dir+"/app_storage")
self.asset_dir = self.app_dir+"/app/assets"
self.config_path = self.app_dir+"/app_storage/sideband_config" self.config_path = self.app_dir+"/app_storage/sideband_config"
self.identity_path = self.app_dir+"/app_storage/primary_identity" self.identity_path = self.app_dir+"/app_storage/primary_identity"
self.db_path = self.app_dir+"/app_storage/sideband.db" self.db_path = self.app_dir+"/app_storage/sideband.db"
@ -195,6 +201,9 @@ class SidebandCore():
if not os.path.isfile(self.db_path): if not os.path.isfile(self.db_path):
self.__db_init() self.__db_init()
else:
self._db_initstate()
self._db_initpersistent()
def __save_config(self): def __save_config(self):
@ -222,11 +231,32 @@ class SidebandCore():
RNS.log("Error while setting LXMF propagation node: "+str(e), RNS.LOG_ERROR) RNS.log("Error while setting LXMF propagation node: "+str(e), RNS.LOG_ERROR)
def notify(self, title, content, group=None, context_id=None):
notifications_enabled = True
if notifications_enabled:
if RNS.vendor.platformutils.get_platform() == "android":
if self.getpersistent("permissions.notifications"):
notifications_permitted = True
else:
notifications_permitted = False
else:
notifications_permitted = True
if notifications_permitted:
if RNS.vendor.platformutils.get_platform() == "android":
if self.is_service:
self.owner_service.android_notification(title, content, group=group, context_id=context_id)
else:
plyer.notification.notify(title, content, notification_icon=self.notification_icon, context_override=None)
else:
plyer.notification.notify(title, content, app_icon=self.icon_32)
def log_announce(self, dest, app_data, dest_type): def log_announce(self, dest, app_data, dest_type):
try: try:
RNS.log("Received "+str(dest_type)+" announce for "+RNS.prettyhexrep(dest)+" with data: "+app_data.decode("utf-8")) RNS.log("Received "+str(dest_type)+" announce for "+RNS.prettyhexrep(dest)+" with data: "+app_data.decode("utf-8"))
self._db_save_announce(dest, app_data, dest_type) self._db_save_announce(dest, app_data, dest_type)
self.owner_app.flag_new_announces = True self.setstate("app.flags.new_announces", True)
except Exception as e: except Exception as e:
RNS.log("Exception while decoding LXMF destination announce data:"+str(e)) RNS.log("Exception while decoding LXMF destination announce data:"+str(e))
@ -345,6 +375,27 @@ class SidebandCore():
else: else:
return [] return []
def gui_foreground(self):
return self._db_getstate("app.foreground")
def gui_display(self):
return self._db_getstate("app.displaying")
def gui_conversation(self):
return self._db_getstate("app.active_conversation")
def setstate(self, prop, val):
self._db_setstate(prop, val)
def getstate(self, prop):
return self._db_getstate(prop)
def setpersistent(self, prop, val):
self._db_setpersistent(prop, val)
def getpersistent(self, prop):
return self._db_getpersistent(prop)
def __event_conversations_changed(self): def __event_conversations_changed(self):
pass pass
@ -364,6 +415,100 @@ class SidebandCore():
dbc.execute("DROP TABLE IF EXISTS announce") dbc.execute("DROP TABLE IF EXISTS announce")
dbc.execute("CREATE TABLE announce (id PRIMARY KEY, received INTEGER, source BLOB, data BLOB, dest_type BLOB)") dbc.execute("CREATE TABLE announce (id PRIMARY KEY, received INTEGER, source BLOB, data BLOB, dest_type BLOB)")
dbc.execute("DROP TABLE IF EXISTS state")
dbc.execute("CREATE TABLE state (property BLOB PRIMARY KEY, value BLOB)")
dbc.execute("DROP TABLE IF EXISTS persistent")
dbc.execute("CREATE TABLE persistent (property BLOB PRIMARY KEY, value BLOB)")
db.commit()
db.close()
def _db_initstate(self):
db = sqlite3.connect(self.db_path)
dbc = db.cursor()
dbc.execute("DROP TABLE IF EXISTS state")
dbc.execute("CREATE TABLE state (property BLOB PRIMARY KEY, value BLOB)")
self._db_setstate("database_ready", True)
def _db_getstate(self, prop):
db = sqlite3.connect(self.db_path)
dbc = db.cursor()
query = "select * from state where property=:uprop"
dbc.execute(query, {"uprop": prop.encode("utf-8")})
result = dbc.fetchall()
db.close()
if len(result) < 1:
return None
else:
try:
entry = result[0]
val = msgpack.unpackb(entry[1])
return val
except Exception as e:
RNS.log("Could not unpack state value from database for property \""+str(prop)+"\". The contained exception was: "+str(e), RNS.LOG_ERROR)
return None
def _db_setstate(self, prop, val):
db = sqlite3.connect(self.db_path)
dbc = db.cursor()
uprop = prop.encode("utf-8")
bval = msgpack.packb(val)
if self._db_getstate(prop) == None:
query = "INSERT INTO state (property, value) values (?, ?)"
data = (uprop, bval)
dbc.execute(query, data)
else:
query = "UPDATE state set value=:bval where property=:uprop;"
dbc.execute(query, {"bval": bval, "uprop": uprop})
db.commit()
db.close()
def _db_initpersistent(self):
db = sqlite3.connect(self.db_path)
dbc = db.cursor()
dbc.execute("CREATE TABLE IF NOT EXISTS persistent (property BLOB PRIMARY KEY, value BLOB)")
def _db_getpersistent(self, prop):
db = sqlite3.connect(self.db_path)
dbc = db.cursor()
query = "select * from persistent where property=:uprop"
dbc.execute(query, {"uprop": prop.encode("utf-8")})
result = dbc.fetchall()
db.close()
if len(result) < 1:
return None
else:
try:
entry = result[0]
val = msgpack.unpackb(entry[1])
return val
except Exception as e:
RNS.log("Could not unpack persistent value from database for property \""+str(prop)+"\". The contained exception was: "+str(e), RNS.LOG_ERROR)
return None
def _db_setpersistent(self, prop, val):
db = sqlite3.connect(self.db_path)
dbc = db.cursor()
uprop = prop.encode("utf-8")
bval = msgpack.packb(val)
if self._db_getpersistent(prop) == None:
query = "INSERT INTO persistent (property, value) values (?, ?)"
data = (uprop, bval)
dbc.execute(query, data)
else:
query = "UPDATE persistent set value=:bval where property=:uprop;"
dbc.execute(query, {"bval": bval, "uprop": uprop})
db.commit() db.commit()
db.close() db.close()
@ -687,7 +832,11 @@ class SidebandCore():
db.close() db.close()
def lxmf_announce(self): def lxmf_announce(self):
self.lxmf_destination.announce() if self.is_standalone or self.is_service:
self.lxmf_destination.announce()
self.setstate("wants.announce", False)
else:
self.setstate("wants.announce", True)
def is_known(self, dest_hash): def is_known(self, dest_hash):
try: try:
@ -710,10 +859,20 @@ class SidebandCore():
RNS.log("Error while querying for key: "+str(e), RNS.LOG_ERROR) RNS.log("Error while querying for key: "+str(e), RNS.LOG_ERROR)
return False return False
def _service_jobs(self):
if self.is_service:
while True:
time.sleep(SidebandCore.JOB_INTERVAL)
if self.getstate("wants.announce"):
self.lxmf_announce()
def __start_jobs_deferred(self): def __start_jobs_deferred(self):
if self.config["start_announce"]: if self.config["start_announce"]:
# TODO: (Service) Handle this in service self.lxmf_announce()
self.lxmf_destination.announce()
if self.is_service:
self.service_thread = threading.Thread(target=self._service_jobs, daemon=True)
self.service_thread.start()
def __start_jobs_immediate(self): def __start_jobs_immediate(self):
# TODO: Reset loglevel # TODO: Reset loglevel
@ -828,19 +987,14 @@ class SidebandCore():
RNS.log("Error while adding I2P Interface. The contained exception was: "+str(e)) RNS.log("Error while adding I2P Interface. The contained exception was: "+str(e))
self.interface_i2p = None self.interface_i2p = None
if self.is_service or self.is_standalone: RNS.log("Reticulum started, activating LXMF...")
RNS.log("Reticulum started, activating LXMF...") self.message_router = LXMF.LXMRouter(identity = self.identity, storagepath = self.lxmf_storage, autopeer = True)
self.message_router = LXMF.LXMRouter(identity = self.identity, storagepath = self.lxmf_storage, autopeer = True) self.message_router.register_delivery_callback(self.lxmf_delivery)
self.message_router.register_delivery_callback(self.lxmf_delivery)
self.lxmf_destination = self.message_router.register_delivery_identity(self.identity, display_name=self.config["display_name"]) self.lxmf_destination = self.message_router.register_delivery_identity(self.identity, display_name=self.config["display_name"])
self.lxmf_destination.set_default_app_data(self.get_display_name_bytes) self.lxmf_destination.set_default_app_data(self.get_display_name_bytes)
self.rns_dir = RNS.Reticulum.configdir self.rns_dir = RNS.Reticulum.configdir
else:
self.message_router = self.serviceproxy
self.lxmf_destination = self.serviceproxy
if self.config["lxmf_propagation_node"] != None and self.config["lxmf_propagation_node"] != "": if self.config["lxmf_propagation_node"] != None and self.config["lxmf_propagation_node"] != "":
self.set_active_propagation_node(self.config["lxmf_propagation_node"]) self.set_active_propagation_node(self.config["lxmf_propagation_node"])
@ -941,23 +1095,21 @@ class SidebandCore():
if self._db_conversation(context_dest) == None: if self._db_conversation(context_dest) == None:
self._db_create_conversation(context_dest) self._db_create_conversation(context_dest)
self.owner_app.flag_new_conversations = True self.setstate("app.flags.new_conversations", True)
if self.owner_app.root.ids.screen_manager.current == "messages_screen": if self.gui_display() == "messages_screen":
if self.owner_app.root.ids.messages_scrollview.active_conversation != context_dest: if self.gui_conversation() != context_dest:
self.unread_conversation(context_dest) self.unread_conversation(context_dest)
self.owner_app.flag_unread_conversations = True self.setstate("app.flags.unread_conversations", True)
else: else:
if self.owner_app.is_in_foreground(): if self.gui_foreground():
should_notify = False should_notify = False
else: else:
self.unread_conversation(context_dest) self.unread_conversation(context_dest)
self.owner_app.flag_unread_conversations = True self.setstate("app.flags.unread_conversations", True)
try: if self.is_client:
self.owner_app.conversation_update(context_dest) should_notify = False
except Exception as e:
RNS.log("Error in conversation update callback: "+str(e))
if should_notify: if should_notify:
nlen = 128 nlen = 128
@ -966,7 +1118,7 @@ class SidebandCore():
if len(text) > nlen: if len(text) > nlen:
text += "..." text += "..."
self.owner_app.notify(title="Message from "+self.peer_display_name(context_dest), content=notification_content) self.notify(title=self.peer_display_name(context_dest), content=notification_content, group="LXM", context_id=RNS.hexrep(context_dest, delimit=False))
def start(self): def start(self):
@ -976,6 +1128,8 @@ class SidebandCore():
thread = threading.Thread(target=self.__start_jobs_deferred) thread = threading.Thread(target=self.__start_jobs_deferred)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
self._db_setstate("core.started", True)
RNS.log("Sideband Core "+str(self)+" started") RNS.log("Sideband Core "+str(self)+" started")
def request_lxmf_sync(self, limit = None): def request_lxmf_sync(self, limit = None):

View File

@ -39,10 +39,8 @@ class Announces():
def update(self): def update(self):
self.clear_list() self.clear_list()
self.announces = self.app.sideband.list_announces() self.announces = self.app.sideband.list_announces()
self.update_widget() self.update_widget()
self.app.sideband.setstate("app.flags.new_announces", False)
self.app.flag_new_announces = False
def update_widget(self): def update_widget(self):
if self.list == None: if self.list == None:

View File

@ -45,14 +45,14 @@ class Conversations():
self.added_item_dests = [] self.added_item_dests = []
def update(self): def update(self):
if self.app.flag_unread_conversations: if self.app.sideband.getstate("app.flags.unread_conversations"):
self.clear_list() self.clear_list()
self.context_dests = self.app.sideband.list_conversations() self.context_dests = self.app.sideband.list_conversations()
self.update_widget() self.update_widget()
self.app.flag_new_conversations = False self.app.sideband.setstate("app.flags.unread_conversations", False)
self.app.flag_unread_conversations = False self.app.sideband.setstate("app.flags.new_conversations", False)
def update_widget(self): def update_widget(self):
if self.list == None: if self.list == None: