diff --git a/ANDROID_DEBUGGING.md b/ANDROID_DEBUGGING.md index 9378bffc..bf59e2c3 100644 --- a/ANDROID_DEBUGGING.md +++ b/ANDROID_DEBUGGING.md @@ -79,4 +79,11 @@ Theoretically speaking it should be possible to use `ANDROID_EXTRA_PLUGINS` to i SVG images on Android. However, we have been unable to make it work. If you would like to try, the following issues might be helpful: -* https://bugreports.qt.io/browse/QTBUG-60022 \ No newline at end of file +* https://bugreports.qt.io/browse/QTBUG-60022 + +## Notifications + +- Android 8 (API Level 26) forces you to call setChannelId() +- Android 9 "Do Not Disturb" mode also hides all notifications +- Setting up notification channels only seems possible *once* per install. any changes you need to make +require that the app is reinstalled, or the actual channel deleted and changed. \ No newline at end of file diff --git a/android/src/ca/openprivacy/cwtch/ui/CwtchActivity.java b/android/src/ca/openprivacy/cwtch/ui/CwtchActivity.java index 736c10ca..b99dce53 100644 --- a/android/src/ca/openprivacy/cwtch/ui/CwtchActivity.java +++ b/android/src/ca/openprivacy/cwtch/ui/CwtchActivity.java @@ -23,20 +23,13 @@ import static android.app.Notification.CATEGORY_SERVICE; public class CwtchActivity extends org.qtproject.qt5.android.bindings.QtActivity { private static NotificationManager m_notificationManager; - private static Notification.Builder m_builder; private static Notification.Builder m_builderOngoing; private static CwtchActivity m_instance; - private static int PRIORITY_MIN = -2; // From NotificationCompat - private static int PRIORITY_DEFAULT = 0; // From NotificationCompat - private static String NOTIFICATION_CHANNEL_ID = "cwtch_notification_channel"; + private static int CONTENT_NOTIFICATION_ID = 2; + private static String CONTENT_NOTIFICATION_ID_NAME = "Notifications from Peers"; - private static int ONGOING_NOTIFICATION_ID = 0; - private static String ONGOING_NOTIFICATION_ID_NAME = "ongoing"; - - private static int CONTENT_NOTIFICATION_ID = 1; - private static String CONTENT_NOTIFICATION_ID_NAME = "content"; public CwtchActivity() { @@ -57,66 +50,47 @@ public class CwtchActivity extends org.qtproject.qt5.android.bindings.QtActivity } } - public static void notify(String s) + public static void notify(String s, String o) { if (m_notificationManager == null) { m_notificationManager = (NotificationManager)m_instance.getSystemService(Context.NOTIFICATION_SERVICE); createNotificationChannel(); } - if (m_builder == null) { - m_builder = new Notification.Builder(m_instance); - m_builder.setSmallIcon(R.drawable.ic_launcher); - m_builder.setContentTitle("Cwtch"); - m_builder.setPriority(PRIORITY_DEFAULT); + // Apparently thr android documentation is just wrong and we need to provide a setGroupSummary + // notification regardless of targetted support version... + Notification groupSummary = + new Notification.Builder(m_instance) + .setContentTitle("Cwtch") + .setContentText("New Message from Peer: " + o) + .setGroupSummary(true) + .setWhen(System.currentTimeMillis()) + .setSmallIcon(R.drawable.ic_launcher) + .setGroup(NOTIFICATION_CHANNEL_ID) + .setChannelId(NOTIFICATION_CHANNEL_ID) + .build(); + m_notificationManager.notify(1, groupSummary); - } + Notification.Builder m_builder = new Notification.Builder(m_instance) + .setSmallIcon(R.drawable.ic_launcher) + .setChannelId(NOTIFICATION_CHANNEL_ID) + .setGroup(NOTIFICATION_CHANNEL_ID) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(true) + .setContentTitle("New Message from Peer: " + o) + .setContentText("[redacted: Open Cwtch App to see the Message]"); + m_notificationManager.notify(CONTENT_NOTIFICATION_ID++, m_builder.build()); - m_builder.setContentText(s); - m_notificationManager.notify(CONTENT_NOTIFICATION_ID, m_builder.build()); - } - public static void ongoingNotify(String s) - { - if (m_notificationManager == null) { - m_notificationManager = (NotificationManager)m_instance.getSystemService(Context.NOTIFICATION_SERVICE); - createNotificationChannel(); - } - if (m_builderOngoing == null) { - m_builderOngoing = new Notification.Builder(m_instance); - m_builderOngoing.setSmallIcon(R.drawable.ic_launcher); - m_builderOngoing.setContentTitle("Cwtch"); - m_builderOngoing.setPriority(PRIORITY_MIN); - - m_builderOngoing.setWhen(0); // Don't show the time - m_builderOngoing.setOngoing(true); - if (SDK_INT >= 21) { - m_builderOngoing.setCategory(CATEGORY_SERVICE); - //m_builder.setVisibility(VISIBILITY_SECRET); - } - - } - - m_builderOngoing.setContentText(s); - m_notificationManager.notify(ONGOING_NOTIFICATION_ID, m_builderOngoing.build()); } private static void createNotificationChannel() { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library if (SDK_INT >= 26) { - String description = "Cwtch Ongoing Notification Channel"; - int importance = NotificationManager.IMPORTANCE_LOW; - NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, ONGOING_NOTIFICATION_ID_NAME, importance); - channel.setDescription(description); - // Register the channel with the system; you can't change the importance - // or other notification behaviors after this - m_notificationManager.createNotificationChannel(channel); - - description = "Cwtch Content Notification Channel"; - importance = NotificationManager.IMPORTANCE_DEFAULT; - channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, CONTENT_NOTIFICATION_ID_NAME, importance); + String description = "Cwtch Notification Channel"; + NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, CONTENT_NOTIFICATION_ID_NAME, NotificationManager.IMPORTANCE_HIGH); channel.setDescription(description); // Register the channel with the system; you can't change the importance // or other notification behaviors after this diff --git a/go/handlers/peerHandler.go b/go/handlers/peerHandler.go index 83ce5fa3..e6aba32c 100644 --- a/go/handlers/peerHandler.go +++ b/go/handlers/peerHandler.go @@ -58,7 +58,6 @@ func PeerHandler(onion string, uiManager ui.Manager, subscribed chan bool) { case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampReceived]) uiManager.StoreAndNotify(peer, e.Data[event.RemotePeer], e.Data[event.Data], ts, onion) - case event.PeerAcknowledgement: uiManager.Acknowledge(e.Data[event.RemotePeer], e.Data[event.EventID]) diff --git a/go/ui/android/CwtchActivity.go b/go/ui/android/CwtchActivity.go index 0bfaa2c6..e8750b4b 100644 --- a/go/ui/android/CwtchActivity.go +++ b/go/ui/android/CwtchActivity.go @@ -11,6 +11,7 @@ type CwtchActivity struct { _ func() `constructor:"init"` + _ string `property:"channel"` _ string `property:"notification"` _ func(string) `slot:"updateAndroidNotification"` @@ -20,7 +21,6 @@ type CwtchActivity struct { func (c *CwtchActivity) init() { log.Debugln("CwtchActivity.init()") - c.createOngoingNotification() c.ConnectNotificationChanged(c.updateAndroidNotification) } @@ -29,8 +29,8 @@ func (c *CwtchActivity) updateAndroidNotification(n string) { var err = androidextras.QAndroidJniObject_CallStaticMethodVoid2Caught( "ca/openprivacy/cwtch/ui/CwtchActivity", "notify", - "(Ljava/lang/String;)V", - n, + "(Ljava/lang/String;Ljava/lang/String;)V", + n, c.Channel(), ) if err != nil { @@ -38,20 +38,6 @@ func (c *CwtchActivity) updateAndroidNotification(n string) { } } -func (c *CwtchActivity) createOngoingNotification() { - - var err = androidextras.QAndroidJniObject_CallStaticMethodVoid2Caught( - "ca/openprivacy/cwtch/ui/CwtchActivity", - "ongoingNotify", - "(Ljava/lang/String;)V", - "Cwtch is running", - ) - - if err != nil { - log.Errorf("Error calling Java CwtchActivity.ongoingNotify(): %v\n", err.Error()) - } -} - func (c *CwtchActivity) rootHomeButtonHandle() { log.Infoln("CwtchActivity.rootHomeButtonHandle()!") var err = androidextras.QAndroidJniObject_CallStaticMethodVoid2Caught( diff --git a/go/ui/gcd.go b/go/ui/gcd.go index 3dbf9d2a..39458ab9 100644 --- a/go/ui/gcd.go +++ b/go/ui/gcd.go @@ -8,6 +8,7 @@ import ( "cwtch.im/cwtch/protocol/connections" "cwtch.im/ui/go/constants" "cwtch.im/ui/go/features/groups" + "cwtch.im/ui/go/ui/android" "github.com/therecipe/qt/qml" "strconv" "sync" @@ -22,6 +23,7 @@ import ( type GrandCentralDispatcher struct { core.QObject + AndroidCwtchActivity *android.CwtchActivity QMLEngine *qml.QQmlApplicationEngine Translator, OpaqueTranslator *core.QTranslator @@ -58,6 +60,7 @@ type GrandCentralDispatcher struct { _ func() `signal:"ResetProfileList"` _ func(failed bool) `signal:"ChangePasswordResponse"` _ func(onion string, online bool) `signal:"UpdateProfileNetworkStatus"` + _ func(onion string) `signal:"Notify"` // server management _ func(handle, displayname, image string, status int, autostart bool, bundle string, messages int, key_types []string, keys []string) `signal:"AddServer"` @@ -147,6 +150,7 @@ func (this *GrandCentralDispatcher) init() { this.SetTheme(this.GlobalSettings.Theme) this.SetExperimentsEnabled(this.GlobalSettings.ExperimentsEnabled) this.SetExperiments(this.GlobalSettings.Experiments) + this.AndroidCwtchActivity = android.NewCwtchActivity(nil) } // GetUiManager gets (and creates if required) a ui Manager for the supplied profile id diff --git a/go/ui/manager.go b/go/ui/manager.go index 279d1520..0828df4e 100644 --- a/go/ui/manager.go +++ b/go/ui/manager.go @@ -301,6 +301,11 @@ func (this *manager) MessageJustAdded() { } func (this *manager) StoreAndNotify(pere peer.CwtchPeer, onion string, messageTxt string, sent time.Time, profileOnion string) { + + // Send a New Message from Peer Notification + this.gcd.AndroidCwtchActivity.SetChannel(onion) + this.gcd.AndroidCwtchActivity.NotificationChanged("New Message from Peer") + this.gcd.DoIfProfileElse(this.profile, func() { this.gcd.DoIfConversationElse(onion, func() { this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num()) @@ -314,6 +319,7 @@ func (this *manager) StoreAndNotify(pere peer.CwtchPeer, onion string, messageTx }, func() { the.CwtchApp.GetPeer(profileOnion).StoreMessage(onion, messageTxt, sent) }) + this.gcd.Notify(onion) } // AddMessage adds a message to the message pane for the supplied conversation if it is active @@ -331,6 +337,9 @@ func (this *manager) AddMessage(handle string, from string, message string, from }) this.gcd.IncContactUnreadCount(handle) }) + if !fromMe { + this.gcd.Notify(handle) + } } func (this *manager) ReloadProfiles() { diff --git a/main.go b/main.go index ff02d7db..13cef305 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( os2 "cwtch.im/ui/go/os" "cwtch.im/ui/go/the" "cwtch.im/ui/go/ui" - "cwtch.im/ui/go/ui/android" "encoding/base64" "flag" "git.openprivacy.ca/openprivacy/connectivity/tor" @@ -161,6 +160,7 @@ func mainUi(flagLocal bool, flagClientUI bool) { log.Errorf("Could not access global ui config: %v\n", err) os.Exit(-1) } + gcd := ui.NewGrandCentralDispatcher(nil) gcd.SetOs(runtime.GOOS) dir := core.QCoreApplication_ApplicationDirPath() @@ -186,8 +186,6 @@ func mainUi(flagLocal bool, flagClientUI bool) { gcd.SetBuildDate("now") } - - // this is to load local qml files quickly when developing var qmlSource *core.QUrl if flagLocal { @@ -223,13 +221,13 @@ func mainUi(flagLocal bool, flagClientUI bool) { return nam }) engine.SetNetworkAccessManagerFactory(factory) - engine.RootContext().SetContextProperty("gcd", gcd) gcd.TimelineInterface = ui.NewMessageModel(nil) engine.RootContext().SetContextProperty("mm", gcd.TimelineInterface) - var androidCwtchActivity = android.NewCwtchActivity(nil) - engine.RootContext().SetContextProperty("androidCwtchActivity", androidCwtchActivity) + + engine.RootContext().SetContextProperty("androidCwtchActivity", gcd.AndroidCwtchActivity) + engine.Load(qmlSource) @@ -263,7 +261,6 @@ func loadACN() { } } - // generate a random socks and control port (not real random...these are port numbers...) mrand.Seed(int64(time.Now().Nanosecond())) port := mrand.Intn(1000) + 9600 diff --git a/qml/main.qml b/qml/main.qml index d50a40c4..b2c17069 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -419,6 +419,14 @@ ApplicationWindow { parentStack.updateToolbar() statusbar.resetHeight() } + + onNotify: function(onion) { + // If we are processing QML it means the app is open, and as such we don't want to + // Send a notification - in the future we should probably use an API like this to Cancel notifications + // Until then I am leaving this here for documentation. + // androidCwtchActivity.channel = onion + // androidCwtchActivity.notification = "Message from " + onion; + } } Component.onCompleted: Mutant.standard.imagePath = gcd.assetPath; @@ -433,4 +441,6 @@ ApplicationWindow { } } } + + }