Android Notification - First Cut
the build was successful Details

This commit has the basics of notifications on Android working again, updated to the
latest Android SDK way of doing things (with channel IDs and grouping).

Android users will get notified when the app is open for new Peer Messages across all profiles.

In the future, this should be extended to add notifications for new peer invites, actual have actionable actions (accept/block)
and maybe even work when the app isn't open...
This commit is contained in:
Sarah Jamie Lewis 2020-11-24 16:45:50 -08:00
parent de56f8be15
commit 76df3c286d
8 changed files with 65 additions and 79 deletions

View File

@ -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
* 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.

View File

@ -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

View File

@ -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])

View File

@ -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(

View File

@ -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

View File

@ -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() {

11
main.go
View File

@ -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

View File

@ -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 {
}
}
}
}