Merge pull request 'Android Notification - First Cut' (#391) from android_tests into master
the build failed Details

Reviewed-on: #391
This commit is contained in:
Dan Ballard 2020-11-24 17:31:00 -08:00
commit 3d509c6810
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 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: 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 public class CwtchActivity extends org.qtproject.qt5.android.bindings.QtActivity
{ {
private static NotificationManager m_notificationManager; private static NotificationManager m_notificationManager;
private static Notification.Builder m_builder;
private static Notification.Builder m_builderOngoing; private static Notification.Builder m_builderOngoing;
private static CwtchActivity m_instance; 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 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() { 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) { if (m_notificationManager == null) {
m_notificationManager = (NotificationManager)m_instance.getSystemService(Context.NOTIFICATION_SERVICE); m_notificationManager = (NotificationManager)m_instance.getSystemService(Context.NOTIFICATION_SERVICE);
createNotificationChannel(); createNotificationChannel();
} }
if (m_builder == null) { // Apparently thr android documentation is just wrong and we need to provide a setGroupSummary
m_builder = new Notification.Builder(m_instance); // notification regardless of targetted support version...
m_builder.setSmallIcon(R.drawable.ic_launcher); Notification groupSummary =
m_builder.setContentTitle("Cwtch"); new Notification.Builder(m_instance)
m_builder.setPriority(PRIORITY_DEFAULT); .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() { private static void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because // Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library // the NotificationChannel class is new and not in the support library
if (SDK_INT >= 26) { if (SDK_INT >= 26) {
String description = "Cwtch Ongoing Notification Channel"; String description = "Cwtch Notification Channel";
int importance = NotificationManager.IMPORTANCE_LOW; NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, CONTENT_NOTIFICATION_ID_NAME, NotificationManager.IMPORTANCE_HIGH);
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);
channel.setDescription(description); channel.setDescription(description);
// Register the channel with the system; you can't change the importance // Register the channel with the system; you can't change the importance
// or other notification behaviors after this // 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 case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampReceived]) ts, _ := time.Parse(time.RFC3339Nano, e.Data[event.TimestampReceived])
uiManager.StoreAndNotify(peer, e.Data[event.RemotePeer], e.Data[event.Data], ts, onion) uiManager.StoreAndNotify(peer, e.Data[event.RemotePeer], e.Data[event.Data], ts, onion)
case event.PeerAcknowledgement: case event.PeerAcknowledgement:
uiManager.Acknowledge(e.Data[event.RemotePeer], e.Data[event.EventID]) uiManager.Acknowledge(e.Data[event.RemotePeer], e.Data[event.EventID])

View File

@ -11,6 +11,7 @@ type CwtchActivity struct {
_ func() `constructor:"init"` _ func() `constructor:"init"`
_ string `property:"channel"`
_ string `property:"notification"` _ string `property:"notification"`
_ func(string) `slot:"updateAndroidNotification"` _ func(string) `slot:"updateAndroidNotification"`
@ -20,7 +21,6 @@ type CwtchActivity struct {
func (c *CwtchActivity) init() { func (c *CwtchActivity) init() {
log.Debugln("CwtchActivity.init()") log.Debugln("CwtchActivity.init()")
c.createOngoingNotification()
c.ConnectNotificationChanged(c.updateAndroidNotification) c.ConnectNotificationChanged(c.updateAndroidNotification)
} }
@ -29,8 +29,8 @@ func (c *CwtchActivity) updateAndroidNotification(n string) {
var err = androidextras.QAndroidJniObject_CallStaticMethodVoid2Caught( var err = androidextras.QAndroidJniObject_CallStaticMethodVoid2Caught(
"ca/openprivacy/cwtch/ui/CwtchActivity", "ca/openprivacy/cwtch/ui/CwtchActivity",
"notify", "notify",
"(Ljava/lang/String;)V", "(Ljava/lang/String;Ljava/lang/String;)V",
n, n, c.Channel(),
) )
if err != nil { 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() { func (c *CwtchActivity) rootHomeButtonHandle() {
log.Infoln("CwtchActivity.rootHomeButtonHandle()!") log.Infoln("CwtchActivity.rootHomeButtonHandle()!")
var err = androidextras.QAndroidJniObject_CallStaticMethodVoid2Caught( var err = androidextras.QAndroidJniObject_CallStaticMethodVoid2Caught(

View File

@ -8,6 +8,7 @@ import (
"cwtch.im/cwtch/protocol/connections" "cwtch.im/cwtch/protocol/connections"
"cwtch.im/ui/go/constants" "cwtch.im/ui/go/constants"
"cwtch.im/ui/go/features/groups" "cwtch.im/ui/go/features/groups"
"cwtch.im/ui/go/ui/android"
"github.com/therecipe/qt/qml" "github.com/therecipe/qt/qml"
"strconv" "strconv"
"sync" "sync"
@ -22,6 +23,7 @@ import (
type GrandCentralDispatcher struct { type GrandCentralDispatcher struct {
core.QObject core.QObject
AndroidCwtchActivity *android.CwtchActivity
QMLEngine *qml.QQmlApplicationEngine QMLEngine *qml.QQmlApplicationEngine
Translator, OpaqueTranslator *core.QTranslator Translator, OpaqueTranslator *core.QTranslator
@ -58,6 +60,7 @@ type GrandCentralDispatcher struct {
_ func() `signal:"ResetProfileList"` _ func() `signal:"ResetProfileList"`
_ func(failed bool) `signal:"ChangePasswordResponse"` _ func(failed bool) `signal:"ChangePasswordResponse"`
_ func(onion string, online bool) `signal:"UpdateProfileNetworkStatus"` _ func(onion string, online bool) `signal:"UpdateProfileNetworkStatus"`
_ func(onion string) `signal:"Notify"`
// server management // server management
_ func(handle, displayname, image string, status int, autostart bool, bundle string, messages int, key_types []string, keys []string) `signal:"AddServer"` _ 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.SetTheme(this.GlobalSettings.Theme)
this.SetExperimentsEnabled(this.GlobalSettings.ExperimentsEnabled) this.SetExperimentsEnabled(this.GlobalSettings.ExperimentsEnabled)
this.SetExperiments(this.GlobalSettings.Experiments) this.SetExperiments(this.GlobalSettings.Experiments)
this.AndroidCwtchActivity = android.NewCwtchActivity(nil)
} }
// GetUiManager gets (and creates if required) a ui Manager for the supplied profile id // 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) { 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.DoIfProfileElse(this.profile, func() {
this.gcd.DoIfConversationElse(onion, func() { this.gcd.DoIfConversationElse(onion, func() {
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num()) this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num())
@ -314,6 +319,7 @@ func (this *manager) StoreAndNotify(pere peer.CwtchPeer, onion string, messageTx
}, func() { }, func() {
the.CwtchApp.GetPeer(profileOnion).StoreMessage(onion, messageTxt, sent) 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 // 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) this.gcd.IncContactUnreadCount(handle)
}) })
if !fromMe {
this.gcd.Notify(handle)
}
} }
func (this *manager) ReloadProfiles() { func (this *manager) ReloadProfiles() {

11
main.go
View File

@ -11,7 +11,6 @@ import (
os2 "cwtch.im/ui/go/os" os2 "cwtch.im/ui/go/os"
"cwtch.im/ui/go/the" "cwtch.im/ui/go/the"
"cwtch.im/ui/go/ui" "cwtch.im/ui/go/ui"
"cwtch.im/ui/go/ui/android"
"encoding/base64" "encoding/base64"
"flag" "flag"
"git.openprivacy.ca/openprivacy/connectivity/tor" "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) log.Errorf("Could not access global ui config: %v\n", err)
os.Exit(-1) os.Exit(-1)
} }
gcd := ui.NewGrandCentralDispatcher(nil) gcd := ui.NewGrandCentralDispatcher(nil)
gcd.SetOs(runtime.GOOS) gcd.SetOs(runtime.GOOS)
dir := core.QCoreApplication_ApplicationDirPath() dir := core.QCoreApplication_ApplicationDirPath()
@ -186,8 +186,6 @@ func mainUi(flagLocal bool, flagClientUI bool) {
gcd.SetBuildDate("now") gcd.SetBuildDate("now")
} }
// this is to load local qml files quickly when developing // this is to load local qml files quickly when developing
var qmlSource *core.QUrl var qmlSource *core.QUrl
if flagLocal { if flagLocal {
@ -223,13 +221,13 @@ func mainUi(flagLocal bool, flagClientUI bool) {
return nam return nam
}) })
engine.SetNetworkAccessManagerFactory(factory) engine.SetNetworkAccessManagerFactory(factory)
engine.RootContext().SetContextProperty("gcd", gcd) engine.RootContext().SetContextProperty("gcd", gcd)
gcd.TimelineInterface = ui.NewMessageModel(nil) gcd.TimelineInterface = ui.NewMessageModel(nil)
engine.RootContext().SetContextProperty("mm", gcd.TimelineInterface) 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) engine.Load(qmlSource)
@ -263,7 +261,6 @@ func loadACN() {
} }
} }
// generate a random socks and control port (not real random...these are port numbers...) // generate a random socks and control port (not real random...these are port numbers...)
mrand.Seed(int64(time.Now().Nanosecond())) mrand.Seed(int64(time.Now().Nanosecond()))
port := mrand.Intn(1000) + 9600 port := mrand.Intn(1000) + 9600

View File

@ -419,6 +419,14 @@ ApplicationWindow {
parentStack.updateToolbar() parentStack.updateToolbar()
statusbar.resetHeight() 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; Component.onCompleted: Mutant.standard.imagePath = gcd.assetPath;
@ -433,4 +441,6 @@ ApplicationWindow {
} }
} }
} }
} }