Reviewed-on: https://git.openprivacy.ca/cwtch.im/ui/pulls/391pull/393/head
@@ -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. |
@@ -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 | |||
@@ -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]) | |||
@@ -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( | |||
@@ -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 | |||
@@ -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,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 | |||
@@ -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 { | |||
} | |||
} | |||
} | |||
} |