AddManifestText(
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33"/>
- -- ------
----------
'End of default text.
AddReceiverText(ntfyPush,
<intent-filter>
<action android:name="io.heckel.ntfy.MESSAGE_RECEIVED" />
</intent-filter>)
Private Sub Receiver_Receive (FirstTime As Boolean, StartingIntent As Intent)
Log(StartingIntent)
Dim b As Beeper
b.Initialize(500,500)
b.Beep
Sleep(500)
b.Release
If StartingIntent.Action = "io.heckel.ntfy.MESSAGE_RECEIVED" Then
Log("Topic: " & StartingIntent.GetExtra("topic"))
ToastMessageShow(StartingIntent.GetExtra("topic"),True)
Log("Message: " & StartingIntent.GetExtra("message"))
ToastMessageShow(StartingIntent.GetExtra("message"),True)
End If
End Sub
FCM is the only method that an Android app can receive messages without having to run a foreground service.
/**
* The broadcast service is responsible for sending and receiving broadcast intents
* in order to facilitate tasks app integrations.
*/
class BroadcastService(private val ctx: Context) {
fun sendMessage(subscription: Subscription, notification: Notification, muted: Boolean) {
val intent = Intent()
intent.action = MESSAGE_RECEIVED_ACTION
intent.putExtra("id", notification.id)
intent.putExtra("base_url", subscription.baseUrl)
intent.putExtra("topic", subscription.topic)
intent.putExtra("time", notification.timestamp.toInt())
intent.putExtra("title", notification.title)
intent.putExtra("message", decodeMessage(notification))
intent.putExtra("message_bytes", decodeBytesMessage(notification))
intent.putExtra("message_encoding", notification.encoding)
intent.putExtra("tags", notification.tags)
intent.putExtra("tags_map", joinTagsMap(splitTags(notification.tags)))
intent.putExtra("priority", notification.priority)
intent.putExtra("click", notification.click)
intent.putExtra("muted", muted)
intent.putExtra("muted_str", muted.toString())
intent.putExtra("attachment_name", notification.attachment?.name ?: "")
intent.putExtra("attachment_type", notification.attachment?.type ?: "")
intent.putExtra("attachment_size", notification.attachment?.size ?: 0L)
intent.putExtra("attachment_expires", notification.attachment?.expires ?: 0L)
intent.putExtra("attachment_url", notification.attachment?.url ?: "")
Log.d(TAG, "Sending message intent broadcast: ${intent.action} with extras ${intent.extras}")
ctx.sendBroadcast(intent)
}
fun sendUserAction(action: Action) {
val intent = Intent()
intent.action = action.intent ?: USER_ACTION_ACTION
action.extras?.forEach { (key, value) ->
intent.putExtra(key, value)
}
Log.d(TAG, "Sending user action intent broadcast: ${intent.action} with extras ${intent.extras}")
ctx.sendBroadcast(intent)
}
/**
* This receiver is triggered when the SEND_MESSAGE intent is received.
* See AndroidManifest.xml for details.
*/
class BroadcastReceiver : android.content.BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "Broadcast received: $intent")
when (intent.action) {
MESSAGE_SEND_ACTION -> send(context, intent)
}
}
private fun send(ctx: Context, intent: Intent) {
val api = ApiService()
val baseUrl = getStringExtra(intent, "base_url") ?: ctx.getString(R.string.app_base_url)
val topic = getStringExtra(intent, "topic") ?: return
val message = getStringExtra(intent, "message") ?: return
val title = getStringExtra(intent, "title") ?: ""
val tags = getStringExtra(intent,"tags") ?: ""
val priority = when (getStringExtra(intent, "priority")) {
"min", "1" -> 1
"low", "2" -> 2
"default", "3" -> 3
"high", "4" -> 4
"urgent", "max", "5" -> 5
else -> 0
}
val delay = getStringExtra(intent,"delay") ?: ""
GlobalScope.launch(Dispatchers.IO) {
val repository = Repository.getInstance(ctx)
val user = repository.getUser(baseUrl) // May be null
try {
Log.d(TAG, "Publishing message $intent")
api.publish(
baseUrl = baseUrl,
topic = topic,
user = user,
message = message,
title = title,
priority = priority,
tags = splitTags(tags),
delay = delay
)
} catch (e: Exception) {
Log.w(TAG, "Unable to publish message: ${e.message}", e)
}
}
}
/**
* Gets an extra as a String value, even if the extra may be an int or a long.
*/
private fun getStringExtra(intent: Intent, name: String): String? {
if (intent.getStringExtra(name) != null) {
return intent.getStringExtra(name)
} else if (intent.getIntExtra(name, DOES_NOT_EXIST) != DOES_NOT_EXIST) {
return intent.getIntExtra(name, DOES_NOT_EXIST).toString()
} else if (intent.getLongExtra(name, DOES_NOT_EXIST.toLong()) != DOES_NOT_EXIST.toLong()) {
return intent.getLongExtra(name, DOES_NOT_EXIST.toLong()).toString()
}
return null
}
}
companion object {
private const val TAG = "NtfyBroadcastService"
private const val DOES_NOT_EXIST = -2586000
// These constants cannot be changed without breaking the contract; also see manifest
private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED"
private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE"
private const val USER_ACTION_ACTION = "io.heckel.ntfy.USER_ACTION"
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.heckel.ntfy">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <!-- For instant delivery foregrounds service -->
<uses-permission android:name="android.permission.WAKE_LOCK"/> <!-- To keep foreground service awake; soon not needed anymore -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <!-- To restart service on reboot -->
<uses-permission android:name="android.permission.VIBRATE"/> <!-- Incoming notifications should be able to vibrate the phone -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/> <!-- Only required on SDK <= 28 -->
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/> <!-- To reschedule the websocket retry -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- As of Android 13, we need to ask for permission to post notifications -->
<!--
Permission REQUEST_INSTALL_PACKAGES (F-Droid only!):
- Permission is used to install .apk files that were received as attachments
- Google rejected the permission for ntfy, so this permission is STRIPPED OUT by the build process
for the Google Play variant of the app.
-->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application
android:name=".app.Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true">
<!-- Main activity -->
<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Detail activity -->
<activity
android:name=".ui.DetailActivity"
android:parentActivityName=".ui.MainActivity"
android:exported="true">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity"/>
<!-- Open ntfy:// links with the app -->
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="ntfy" />
</intent-filter>
</activity>
<!-- Settings activity -->
<activity
android:name=".ui.SettingsActivity"
android:parentActivityName=".ui.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity"/>
</activity>
<!-- Detail settings activity -->
<activity
android:name=".ui.DetailSettingsActivity"
android:parentActivityName=".ui.DetailActivity">
</activity>
<!-- Share file activity, incoming files/shares -->
<activity android:name=".ui.ShareActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="text/*" />
<data android:mimeType="audio/*" />
<data android:mimeType="video/*" />
<data android:mimeType="application/*" />
</intent-filter>
</activity>
<!-- Hack: Activity used for "view" action button with "clear=true" (to be able to cancel notifications and show a URL) -->
<activity
android:name=".msg.NotificationService$ViewActionWithClearActivity"
android:exported="false">
</activity>
<!-- Subscriber foreground service for hosts other than ntfy.sh -->
<service android:name=".service.SubscriberService"/>
<!-- Subscriber service restart on reboot -->
<receiver
android:name=".service.SubscriberService$BootStartReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<!-- Subscriber service restart on destruction -->
<receiver
android:name=".service.SubscriberService$AutoRestartReceiver"
android:enabled="true"
android:exported="false"/>
<!-- Broadcast receiver to send messages via intents -->
<receiver
android:name=".msg.BroadcastService$BroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="io.heckel.ntfy.SEND_MESSAGE"/>
</intent-filter>
</receiver>
<!-- Broadcast receiver for UnifiedPush; must match https://github.com/UnifiedPush/UP-spec/blob/main/specifications.md -->
<receiver
android:name=".up.BroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="org.unifiedpush.android.distributor.REGISTER"/>
<action android:name="org.unifiedpush.android.distributor.UNREGISTER"/>
<action android:name="org.unifiedpush.android.distributor.feature.BYTES_MESSAGE"/>
</intent-filter>
</receiver>
<!-- Broadcast receiver for the "Download"/"Cancel" attachment action in the notification popup -->
<receiver
android:name=".msg.NotificationService$UserActionBroadcastReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<!-- Broadcast receiver for when the notification is swiped away (currently only to cancel the insistent sound) -->
<receiver
android:name=".msg.NotificationService$DeleteBroadcastReceiver"
android:enabled="true"
android:exported="false">
</receiver>
<!-- Firebase messaging (note that this is empty in the F-Droid flavor) -->
<service
android:name=".firebase.FirebaseService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false"/>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_notification"/>
<!-- FileProvider required for older Android versions (<= P), to allow passing the file URI in the open intent.
Avoids "exposed beyond app through Intent.getData" exception, see see https://stackoverflow.com/a/57288352/1440785 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</application>
</manifest>
FCM is the only method that an Android app can receive messages without having to run a foreground service.
Thank you. I now realize that no other push notification can run without foreground App.No need for any broadcast listener, no need for any 3rdparty app.
Integrate Firebase in ntfy.sh and use the Firebasemessaging in B4A.
Thanks. I now realize that no other push notification apart from FCM can run without foreground App.Based on the permissions they expect your app to run with a foreground service.
Why are you wasting your time with it? Use the regular push notifications. They will work better as it is an OS feature. Third party notifications will never work better.
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?