A lightweight native Android app that intercepts notifications on the device and triggers actions based on configurable rules.
Find a file
2026-03-25 13:59:10 +00:00
app More README.md edits 2026-03-25 13:59:10 +00:00
gradle Move toolbar to included file 2026-03-23 23:59:49 +00:00
.gitignore Import from out-of-sync forgejo repo ('The database representation of this repository is out of synchronization...') 2026-03-21 12:39:12 +00:00
build.gradle.kts Import from out-of-sync forgejo repo ('The database representation of this repository is out of synchronization...') 2026-03-21 12:39:12 +00:00
gradle.properties Import from out-of-sync forgejo repo ('The database representation of this repository is out of synchronization...') 2026-03-21 12:39:12 +00:00
gradlew Import from out-of-sync forgejo repo ('The database representation of this repository is out of synchronization...') 2026-03-21 12:39:12 +00:00
gradlew.bat Import from out-of-sync forgejo repo ('The database representation of this repository is out of synchronization...') 2026-03-21 12:39:12 +00:00
LICENSE Import from out-of-sync forgejo repo ('The database representation of this repository is out of synchronization...') 2026-03-21 12:39:12 +00:00
README.md More README.md edits 2026-03-25 13:59:10 +00:00
settings.gradle.kts Import from out-of-sync forgejo repo ('The database representation of this repository is out of synchronization...') 2026-03-21 12:39:12 +00:00

Notifactor (Android App)

A lightweight native Android app that intercepts notifications on the device and triggers actions based on configurable rules.

The app uses Android's NotificationListenerService API. Once granted notification access it receives a callback for every notification posted on the device. It then checks each notification against configured rules and runs the configured action.

Why would you want this?

I don't know why you would want this but I can tell you why I made it: to make it easier to control some functions on Android devices used by people I often help using them in some way. My mother's Android TV (which I use to communicate with her through Linphone), phone and tablet sometimes stop doing the right thing. I live about 1300 km to the north of where she lives so I can't just hop on my bike to fix things. Thus far I relied on a set of Termux scripts on these devices to keep a reverse ssh tunnel open to an endpoint on my server but this has a number of drawbacks: the tunnel is not always there when I need it due to WiFi dropouts and other similar problems and the constant connection uses battery power on the phone and tablet. If only I could cause the tunnel to be created when I need it and brought down when it is not needed... Well, that is possible using Notifactor by sending a notification on a specific channel (ntfy refers to these as topics) whereupon Notifactor runs a Termux script which manages the tunnel (etc.).

Features

  • Define rules to intercept notifications filtered by package name, title and/or body content, e.g. 'notification source io.heckel.ntfy, title contains the word NowListen'
  • Per-rule actions:
    • Run Termux script with the notification title as $1, notification body as $2 and notification source package as $3
    • Launch app - open any installed app by package name, e.g. org.mozilla.firefox
    • HTTP request - POST {package, title, text} JSON to a (webhook) URL
    • Send custom Intent - broadcast a custom intent action with title/text/package as extras

Why not use Tasker or Macrodroid or ...

By all means use them if you have them. I do not and I tend to shun proprietary apps so I made this instead. They're also far more complicated to set up than Notifactor so if all you want is to fire up some actions based on received notifications Notifactor is more than sufficient.

But but security...

Yes, that is really something to think about. With great power - and Notifactor can give you a lot of power since it essentially is a notification-based remote procedure call transport - comes great responsibility. It would be quite foolish to just send commands through notifications to be executed on the device. Something like the following can be used to mitigate the risks:

controller

# on the sending (controller) side:

CHANNEL="j235RGHDR3465"
SECRET="57c403cda50e9151019f2343479b1f9d"
notification_url="https://ntfy.example.org/$CHANNEL"
notification_id_stack="$HOME/.config/notifactor/${CHANNEL}.stack"

# Use a random salt and the secret to create a command hash
# (Yes, md5 is supposedly broken but for this purpose it is just fine)
hash_command () {
        cmd="$1"
        salt=$(echo "$RANDOM$RANDOM$RANDOM"|md5sum)
        salt=${salt:0:5}
        data=$(echo -n "${salt}${SECRET}${cmd}"|md5sum)
        data="$salt${data:0:32}"
        echo -n "$data"
}

# Send the command hash to the notification channel
send_notification () {
        cmd="$1"
        message_id=$(curl -s -X POST -d $(hash_command "$cmd") $notification_url|jq -r '.id')
        touch "$notification_id_stack"
        echo $message_id >> $notification_id_stack
}

# Delete notifications _on the receiving devices_
delete_notifications () {
        if [ -f "$notification_id_stack" ]; then
                cat $notification_id_stack|while read id; do
                        curl -s -X DELETE $notification_url/$id
                done
                > $notification_id_stack
        fi
}


case "$1" in
    "command_1")
        send_notification "$1"
        # do stuff for command 1
        ...
        # check whether the command is complete, then...
        delete_notifications
        ;;
    "command_2")
        send_notification "$1"
        # do stuff for command 2
        ...
        # check whether the command is complete, then...
        delete_notifications
        ;;
    *)
        echo "$1: unknown command"
        ;;
esac

controlled device

# on the receiving (controlled device) side

#!/data/data/com.termux/files/usr/bin/bash

# The channel title is set by the app which receives the notifcation. In this example using ntfy
# I have set the title to _Notifactor_ in 'Subscription settings' -> 'Appearance' -> 'Display name' 
CHANNEL_TITLE="Notifactor" 
SECRET="57c403cda50e9151019f2343479b1f9d"
AVAILABLE_COMMANDS="command_1 command_2"

LOG=$HOME/var/log/notifactor_script.log

# Notifactor calls scripts with the notification title as $1:
if [ "$CHANNEL_TITLE" != "$1" ]; then
	echo -e "\nNot my channel: $1\n" >> $LOG
	exit
fi

# ... and the notification body text as $2:
hashed_cmd="$2"

unhash () {
	salt=${1:0:5}
	cmdhash=${1:5}
	for cmd in $AVAILABLE_COMMANDS; do
		testhash=$(echo -n "${salt}${SECRET}${cmd}"|md5sum)
		if [ "${testhash:0:32}" == "$cmdhash" ]; then
			echo $cmd
			break
		fi
	done
}

if [ -n "$hashed_cmd" ]; then
	cmd=$(unhash "$hashed_cmd")
	if [ -n "$cmd" ]; then
		case "$cmd" in
			"command_1")
				printf "\ncommand_1" %s\n" "$(date)" >> $LOG
				;;
			"command_2")
				printf "\ncommand_2 %s\n" "$(date)" >> $LOG
				;;
		esac
	else
		printf "\nBad hash at %s\n" "$(date)" >> $LOG
		exit
	fi
else
	printf "\nNo hash at %s\n" "$(date)" >> $LOG
fi

Building the APK

The project currently uses Gradle 9.2.1, it should build with JDK 17 or higher and can be built from the command line or through Android Studio. How, you ask? Well, let's just say that those who need to be told how to build Android packages are better off learning how to do this from better sources than this README.md.

Testing, testing...

Granting notification access

On first run you'll be prompted to grant notification access (for showing error messages). Use the menu to grant notification listener access (mandatory) and Termux access (optional, needed to run Termux scripts).

Run Termux script

Termux script setup

By default Termux does not allow other apps to send it commands but that can be changed by adding allow-external-apps = true to $HOME/.termux/termux.properties, like so:

In the Termux terminal:

echo "allow-external-apps = true" >> $HOME/.termux/termux.properties

Restart the Termux app (swipe it out of the task switcher or use forced stop) after adding this parameter.

FYI the Termux documentation is inconclusive on the necessity of this setting. The termux Tasker documentation states it is optional and only needed to launch tasks outside of $HOME/.termux/tasker while the RUN_COMMAND documentation states it is mandatory to execute commands. The latter seems to be closer to the truth than the former since you're greeted with a fiery red error message if you forget to set it.

Create a script

# in a Termux terminal

mkdir -p $HOME/bin
cat > $HOME/bin/notifactor_test.sh << 'EOF'
#!/data/data/com.termux/files/usr/bin/bash

TITLE="$1" # notification title
TEXT="$2"  # notification body text
PKG="$3"   # notification source app

echo "$(date): [$PKG] $TITLE - $TEXT" >> $HOME/notifactor.log
EOF
chmod +x $HOME/bin/notifactor_test.sh

Create a rule in the app

This test is based on the assumption you're using the ntfy app as a notification source. If you're using something else - e.g. Nextpush or Gotify - you'll need to change the value in the Source app package field to match it.

field value
Source app package io.heckel.ntfy
Title contains (leave empty to match all)
Body contains (leave empty to match all)
Action Run Termux script
Script path $HOME/bin/notifactor_test.sh

Now send a few notifications on the configured channel/topic and check the $HMOE/notifactor.log file.

Custom Intent action

For the Send custom Intent action, enter any broadcast action string (e.g. org.example.ACTION_REQUIRED). The broadcast will carry these extras:

Extra key Value
title Notification title
text Notification body
package Source app package name

A BroadcastReceiver in any other app on the device can listen for this action. As it stands this action is mostly useful for those writing their own apps but I might extend it later to make it possible to configure the extras for more flexibility.

HTTP request

The HTTP request option sends the package source, notification title and notification body as a JSON payload for a POST request to the configured URL. It'll look something like this on the receiving web server (using https://github.com/svenstaro/dummyhttp as the receiving server) with the URL set to https://server.example.org:8080 upon receiving a notification through ntfy with title Webtest and text foo bar baz:

┌─Incoming request
│ POST https://server.example.org:8080/ HTTP/2.0
│ Accept-Encoding: gzip
│ Content-Length: 64
│ Content-Type: application/json; charset=utf-8
│ User-Agent: okhttp/4.12.0
│ Body:
│ {"package":"io.heckel.ntfy","title":"Webtest","text":"foo bar baz"}

Launch app

This should be self-explanatory: any notification which triggers the rule causes the configured app to be launched. The contents of the notification are not forwarded to the app.

Used Permissions

Permission Purpose
INTERNET HTTP request (webhook) action
POST_NOTIFICATIONS Show action error notifications
Notification listener access Intercept device notifications
use the first menu entry to request this permission
com.termux.permission.RUN_COMMAND Launch Termux scripts
use the second menu entry to request this permission
android.permission.QUERY_ALL_PACKAGES Launch termux scripts, start apps