Android: Fix push notifications and permission handling

pull/1129/head
Simon Stürz 2025-12-01 12:47:29 +01:00
parent 8f6ed7c8a0
commit 48bed7bf3e
6 changed files with 121 additions and 17 deletions

View File

@ -26,6 +26,8 @@ import java.util.Random;
public class NymeaAppNotificationService extends FirebaseMessagingService {
private static final String TAG = "nymea-app: NymeaAppNotificationService";
private static final String DEFAULT_CHANNEL_ID = "default-channel";
private static final String DEFAULT_CHANNEL_NAME = "nymea notifications";
private int hashId(String id) {
int hash = 7;
@ -59,13 +61,28 @@ public class NymeaAppNotificationService extends FirebaseMessagingService {
super.onMessageReceived(remoteMessage);
RemoteMessage.Notification notification = remoteMessage.getNotification();
String title = notification != null ? notification.getTitle() : null;
String body = notification != null ? notification.getBody() : null;
if (title == null) {
title = remoteMessage.getData().get("title");
}
if (body == null) {
body = remoteMessage.getData().get("body");
}
Log.d(TAG, "Notification from: " + remoteMessage.getFrom());
Log.d(TAG, "Notification title: " + remoteMessage.getNotification().getTitle());
Log.d(TAG, "Notification body: " + remoteMessage.getNotification().getBody());
Log.d(TAG, "Notification title: " + title);
Log.d(TAG, "Notification body: " + body);
Log.d(TAG, "Notification priority: " + remoteMessage.getPriority());
Log.d(TAG, "Notification data: " + remoteMessage.getData());
Log.d(TAG, "Notification message ID: " + remoteMessage.getMessageId());
if (title == null && body == null && remoteMessage.getData().isEmpty()) {
Log.w(TAG, "No notification payload received, skipping notification creation.");
return;
}
Intent intent = new Intent(this, NymeaAppActivity.class);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_SEND);
@ -79,21 +96,36 @@ public class NymeaAppNotificationService extends FirebaseMessagingService {
// Because of this, we need to dynamically fetch the resource from the package resources
int resId = getResources().getIdentifier("notificationicon", "drawable", getPackageName());
Log.d(TAG, "Notification icon resource: " + resId + " Package:" + getPackageName());
if (resId == 0) {
resId = getApplicationInfo().icon;
Log.w(TAG, "Notification icon resource missing, using application icon: " + resId);
}
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
Log.w(TAG, "NotificationManager not available, cannot display notification.");
return;
}
String channelId = resolveStringResource("notification_channel_id", DEFAULT_CHANNEL_ID);
String channelName = resolveStringResource("notification_channel_name", DEFAULT_CHANNEL_NAME);
// Since android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("default-channel", "Default notification channel for nymea-app", NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
NotificationChannel existingChannel = notificationManager.getNotificationChannel(channelId);
if (existingChannel == null) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(channel);
}
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setContentTitle(remoteMessage.getNotification().getTitle())
.setContentText(remoteMessage.getNotification().getBody())
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, channelId)
.setContentTitle(title)
.setContentText(body)
.setSmallIcon(resId)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH);
boolean sound = remoteMessage.getData().get("sound") == null || remoteMessage.getData().get("sound").equals("true");
Log.d(TAG, "Notification sound enabled: " + (sound ? "true" : "false"));
@ -114,4 +146,19 @@ public class NymeaAppNotificationService extends FirebaseMessagingService {
Log.d(TAG, "Posting Notification: " + remoteMessage.getMessageId());
notificationManager.notify(0, notificationBuilder.build());
}
private String resolveStringResource(String resourceName, String fallback) {
int resId = getResources().getIdentifier(resourceName, "string", getPackageName());
if (resId != 0) {
try {
String resolved = getString(resId);
if (resolved != null && !resolved.isEmpty()) {
return resolved;
}
} catch (Resources.NotFoundException e) {
Log.w(TAG, "String resource not found for " + resourceName + ", using fallback");
}
}
return fallback;
}
}

View File

@ -28,6 +28,8 @@
#include <QApplication>
#include <QPermission>
#include <QOperatingSystemVersion>
#include <QFuture>
#include <QtCore/private/qandroidextras_p.h>
#include "logging.h"
NYMEA_LOGGING_CATEGORY(dcPlatformPermissions, "PlatformPermissions")
@ -108,8 +110,30 @@ PlatformPermissions::PermissionStatus PlatformPermissionsAndroid::checkPermissio
});
break;
}
break;
}
case PlatformPermissions::PermissionNotifications: {
if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::Android, 13)) {
status = PermissionStatusGranted;
break;
}
auto futureResult = QtAndroidPrivate::checkPermission("android.permission.POST_NOTIFICATIONS");
QtAndroidPrivate::PermissionResult result = futureResult.result();
switch (result) {
case QtAndroidPrivate::Authorized:
qCDebug(dcPlatformPermissions()) << "Notifications permission already granted.";
status = PermissionStatusGranted;
break;
case QtAndroidPrivate::Denied:
qCDebug(dcPlatformPermissions()) << "Notifications permission denied.";
status = PermissionStatusDenied;
break;
case QtAndroidPrivate::Undetermined:
qCDebug(dcPlatformPermissions()) << "Notifications permission not yet requested. Requesting...";
status = PermissionStatusNotDetermined;
break;
}
break;
}
default:
@ -154,8 +178,7 @@ void PlatformPermissionsAndroid::requestPermission(PlatformPermissions::Permissi
}
case PlatformPermissions::PermissionLocalNetwork: {
QFuture permission_request = QtAndroidPrivate::requestPermission("android.permission.POST_NOTIFICATIONS");
switch(permission_request.result())
{
switch(permission_request.result()) {
case QtAndroidPrivate::Undetermined:
qWarning() << "Permission for posting notifications undetermined!";
break;
@ -169,6 +192,31 @@ void PlatformPermissionsAndroid::requestPermission(PlatformPermissions::Permissi
break;
}
case PlatformPermissions::PermissionNotifications: {
if (QOperatingSystemVersion::current() < QOperatingSystemVersion(QOperatingSystemVersion::Android, 13)) {
qCDebug(dcPlatformPermissions()) << "Notifications permission implicitly granted on Android < 13.";
emit s_instance->notificationsPermissionChanged();
break;
}
QFuture permission_request = QtAndroidPrivate::requestPermission("android.permission.POST_NOTIFICATIONS");
auto result = permission_request.result();
switch(result) {
case QtAndroidPrivate::Undetermined:
qWarning() << "Permission for posting notifications undetermined!";
s_instance->m_requestedButDeniedPermissions.append(platformPermission);
break;
case QtAndroidPrivate::Authorized:
qDebug() << "Permission for posting notifications authorized";
break;
case QtAndroidPrivate::Denied:
qWarning() << "Permission for posting notifications denied!";
s_instance->m_requestedButDeniedPermissions.append(platformPermission);
break;
}
emit s_instance->notificationsPermissionChanged();
break;
}
default:
qCWarning(dcPlatformPermissions()) << "Requested platform permission" << platformPermission << "but is not implemented yet.";
break;
@ -276,4 +324,3 @@ void PlatformPermissionsAndroid::requestPermission(PlatformPermissions::Permissi
// emit s_instance->backgroundLocationPermissionChanged();
// emit s_instance->notificationsPermissionChanged();
// }

View File

@ -33,11 +33,14 @@
PlatformPermissions *PlatformPermissions::instance()
{
#ifdef Q_OS_ANDROID
return new PlatformPermissionsAndroid();
static PlatformPermissionsAndroid instance;
return &instance;
#elif defined Q_OS_IOS
return new PlatformPermissionsIOS();
static PlatformPermissionsIOS instance;
return &instance;
#else
return new PlatformPermissions();
static PlatformPermissions instance;
return &instance;
#endif
}
@ -85,4 +88,3 @@ PlatformPermissions::PermissionStatus PlatformPermissions::checkPermission(Permi
Q_UNUSED(permission)
return PermissionStatusGranted;
}

View File

@ -24,6 +24,7 @@
#include "pushnotifications.h"
#include "platformhelper.h"
#include "platformintegration/platformpermissions.h"
#include <QDebug>
#include <QCoreApplication>
@ -86,6 +87,9 @@ void PushNotifications::setEnabled(bool enabled)
void PushNotifications::registerForPush()
{
#if defined Q_OS_ANDROID && defined WITH_FIREBASE
// Ensure we have runtime permission to post notifications (Android 13+).
PlatformPermissions::instance()->requestPermission(PlatformPermissions::PermissionNotifications);
qDebug() << "Checking for play services";
jboolean playServicesAvailable = QJniObject::callStaticMethod<jboolean>("io.guh.nymeaapp.NymeaAppNotificationService", "checkPlayServices", "()Z");
if (playServicesAvailable) {
@ -102,8 +106,9 @@ void PushNotifications::registerForPush()
firebase::messaging::Initialize(*m_firebaseApp, this);
firebase::messaging::SetListener(this);
// (Optional, Android 13+): Benachrichtigungs-Erlaubnis anfragen
// firebase::messaging::RequestPermission();
// Android 13+ requires the POST_NOTIFICATIONS runtime permission. Request it here so
// Firebase is allowed to show notifications when the app is backgrounded or closed.
firebase::messaging::RequestPermission();

View File

@ -89,6 +89,7 @@
<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/notificationicon"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/notification_icon_color"/>
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="@string/notification_channel_id"/>
<service android:name="com.google.firebase.messaging.MessageForwardingService" android:exported="false">
</service>

View File

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">nymea:app</string>
<string name="notification_channel_id">default-channel</string>
<string name="notification_channel_name">nymea notifications</string>
</resources>