App Inventor Extensions


Notification Listener Extension

See the App Inventor Extensions document about how to use an App Inventor Extension.

For questions about this extension or bug reports please start a new thread in the App Inventor Extensions forum. Thank you.

For feature requests please contact me by email. To be a sponsor of a new method already is possible starting from only 10 USD! With your contribution you will help the complete App Inventor community. Thank you.

Nov 8th, 2020: Version 1: initial version for App Inventor. This extension does not work in Kodular, because Kodular still does not support AndroidX libraries.

Description

Notification Listener Extension to listen to all notifications of your device. Packagename, title and text of all notifications will be stored in TinyDB. Also of course works if your app is not running and also survives a reboot of the device.
Minimum API level is 21 (Android 5).
Required permissions: android.permission.BIND_NOTIFICATION_LISTENER_SERVICE

Note: This extension does not work in Kodular, because Kodular still does not support AndroidX libraries!

Methods


Start Service.
This method will open the settings of the device. The user must choose the app from the app list and enable notification access. A warning message will be displayed and the user has to confirm, see screehshot.

Note: You will have to build the app to be able to receive the notifications.

How does it work?
The notification listener service runs in the backround also if your app is closed. It will listen to all notifications and stores them in TinyDB (aka shared preferences) using the name space TaifunNotificationListener. As tag the current datetime will be used in format yyyy-MM-dd HH:mm:ss, as value a JSON string will be stored including the information packagename, title and text. You can use TinyDB blocks to get the information (see screenshot of the blocks below).


Returns true if notification listener is enabled, else false.

Events


Event indicating that a notification has been received.
If your app is up and running, new notifications can be received via this event.

Note: You will have to build the app to be able to receive the notifications.

Example App "Notification Listener Test"

Screenshots:

While starting the service, the user needs to confirm.

If the app is open, you can receive the latest notification in event Received.

If the app is closed, later you can open the app and get a list of all notifications using TinyDB blocks.

Blocks:


Test

Tested successfully on Samsung Galaxy A51 running on Android 10.

Questions and Answers

currently no question available.

For questions about App Inventor,
please ask in the App Inventor community.Thank you.

Java source code

The source code is open source. Please read the Creative Commons Attribution-ShareAlike 3.0 Unported License to find out, what is allowed and under which terms. Thank you.

// -*- mode: java; c-basic-offset: 2; -*-
package com.puravidaapps.TaifunNotificationListener;
// Version 1: initial version


/**
  * Copyright (C) 2020 Pura Vida Apps - All Rights Reserved
  * 
  * This work by Pura Vida Apps is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
  * with attribution (name=Pura Vida Apps and link to the source site=https://puravidaapps.com/notificationlistener.php required. 
  * You may use, distribute and modify this code under the terms of the license, see link above. Please keep this header inside the source code.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  *
  * Author: Taifun, puravidaapps.com
  * Date:   2020-11-08
  *
  * Note:   This example is based on this Stackoverflow answer by Mayur Patel. Thank you Mayur!
  * 
  */


import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import java.util.Calendar;
import org.json.JSONObject;

import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.UsesServices;
import com.google.appinventor.components.annotations.androidmanifest.*;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.Component;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.runtime.EventDispatcher;
import com.google.appinventor.components.runtime.OnDestroyListener;
import com.google.appinventor.components.runtime.OnResumeListener;
import com.google.appinventor.components.runtime.OnStopListener;
import com.google.appinventor.components.runtime.ReplForm;
import com.google.appinventor.components.runtime.util.Dates;


@DesignerComponent(version = TaifunNotificationListener.VERSION,
    description = "TaifunNotificationListener Extension.  Version 1 as of 2020-11-08.",
    category = ComponentCategory.EXTENSION,
    nonVisible = true,
    androidMinSdk = 21,
    iconName = "https://puravidaapps.com/images/taifun16.png",
    helpUrl = "https://puravidaapps.com/notificationlistener.php")
@SimpleObject(external = true)
@UsesServices(services = {
    @ServiceElement(
        name = "com.puravidaapps.TaifunNotificationListener.TaifunNotificationListener$NotificationService", 
	label = "NotificationService", 
	permission = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE", 
	intentFilters = {
	    @IntentFilterElement(actionElements = {@ActionElement(name = "android.service.notification.NotificationListenerService")
	})
    })      
})


public class TaifunNotificationListener extends AndroidNonvisibleComponent implements Component, OnResumeListener, OnStopListener {

  public static final int VERSION = 1;
  private Context context;
  private Activity activity;
  private static final String LOG_TAG = "TaifunNotificationListener";
  private boolean isRepl = false;
  private ComponentContainer container;
  public static SharedPreferences sharedPreferences;
  private boolean listening = false;
  
  /**
   * Creates a new TaifunNotificationListener component.
   *
   * @param container
   */
  public TaifunNotificationListener(ComponentContainer container) {
    super(container.$form());
    this.container = container;
    if (form instanceof ReplForm) { // Note: form is defined in our superclass
      isRepl = true;
    }
    context = (Context) container.$context();
    activity = (Activity) container.$context();
    sharedPreferences = context.getSharedPreferences("TaifunNotificationListener", Context.MODE_PRIVATE);

    startListeningLocal();
    Log.d(LOG_TAG, "TaifunNotificationListener Created");
  }

  
  /**
   * StartService
   */
  @SimpleFunction(description = "Start Service.")
  public void StartService() {
    Log.d(LOG_TAG, "StartService");

    Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");  
    context.startActivity(intent);  
  }

  
 /**
   * CheckService
   */
  @SimpleFunction(description = "Returns true if notification listener is enabled, else false.")
  public boolean CheckService() {  
    try{  
    	String packageName = context.getPackageName();
      if(Settings.Secure.getString(activity.getContentResolver(), "enabled_notification_listeners").contains(packageName)) {  
        return true;  
      } else {  
        return false;  
      }
    }catch(Exception e) {  
      Log.e(LOG_TAG, e.getMessage(), e);
      e.printStackTrace();  
    }  
    return false;  
  }  

  
  private void startListeningLocal() {
    if (!listening) {
      LocalBroadcastManager.getInstance(form).registerReceiver(NotificationReceiver, new IntentFilter("LOCAL"));
      listening = true;
    }
  }


  private void stopListeningLocal() {
    if (listening) {
      // Unregister broadcast listener
      LocalBroadcastManager.getInstance(form).unregisterReceiver(NotificationReceiver);  
      listening = false;
    }
  }
  
  private final BroadcastReceiver NotificationReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      Log.d(LOG_TAG, "NotificationReceiver.onReceive");
      try {
    	  String json = intent.getStringExtra("json");  
        NotificationReceived(json);
      } catch (Exception e) {  
        Log.e(LOG_TAG, e.getMessage(), e);
        e.printStackTrace();  
      }  
    }  
  };  

  /**
   *  NotificationReceived event handler.
   */
  @SimpleEvent (description = "Event indicating that a notification has been received.")
  public void NotificationReceived(String json) {
    Log.d(LOG_TAG, "NotificationReceived: " + json);
    EventDispatcher.dispatchEvent(this, "NotificationReceived", json);
  } 

  @Override
  public void onStop() {
    Log.i(LOG_TAG, "onStop");  
    stopListeningLocal();
  }

  @Override
  public void onResume() {
    Log.i(LOG_TAG, "onResume");  
    startListeningLocal();
  }  

 
  // https://stackoverflow.com/a/56424417/1545993  
  public static class NotificationService extends NotificationListenerService {  
    private static Context context;  
    private String packageName = "";  
    private String title = "";  
    private String text = "";  
  
    @Override  
    public void onCreate() {  
      Log.i(LOG_TAG,"onCreate");
      super.onCreate();  
      context = getApplicationContext();  
    }  

    @Override  
    public void onNotificationPosted(StatusBarNotification sbn) {  
      Log.i(LOG_TAG,"onNotificationPosted: " + sbn.getPackageName());  
      getDetails(sbn);

      // prepare JSON string
      Calendar now = Calendar.getInstance(); 
      JSONObject json = new JSONObject();

      try {
        json.put("packageName", packageName);
        json.put("title", title);
        json.put("text", text);
      } catch (Exception e) {
        Log.e(LOG_TAG, e.getMessage(), e);
    	e.printStackTrace();
      }
        
      // save data in TinyDB aka shared preferences, tag is now, value is the json string
      sharedPreferences = context.getSharedPreferences("TaifunNotificationListener", Context.MODE_PRIVATE);
      SharedPreferences.Editor editor = sharedPreferences.edit();
      editor.putString(Dates.FormatDateTime(now, "yyyy-MM-dd HH:mm:ss"), json.toString());
      editor.commit();

      // send local broadcast to main activity
      Intent intent = new Intent("LOCAL");  
      intent.putExtra("json", json.toString());  
      LocalBroadcastManager.getInstance(context).sendBroadcast(intent);  
    }  
  
    @Override  
    public void onNotificationRemoved(StatusBarNotification sbn) {  
      Log.i(LOG_TAG,"onNotificationRemoved");  
      getDetails(sbn);
    }  

    private void getDetails(StatusBarNotification sbn) {
      Log.i(LOG_TAG,"getDetails");  
      packageName = sbn.getPackageName();  
      Bundle extras = sbn.getNotification().extras;  

      title = "";
      if (extras.getString("android.title") != null) {
        title = extras.getString("android.title");  
      }

      text = "";
      if (extras.getCharSequence("android.text") != null) {
        text = extras.getCharSequence("android.text").toString();
      }
      
      Log.i(LOG_TAG,"packageName: " + packageName + ", title: " + title + ", text: " + text);
    }
  }
  
}

Terms and Conditions

Download


Developing and maintaining snippets, tutorials and extensions for App Inventor takes a lot of time.
I hope it saved some of your time. If yes, then you might consider to donate a small amount!

Donation amount:

or donate some mBTC to Bitcoin Address:
1Jd8kXLHu2Vkuhi15TWHiQm4uE9AGPYxi8
Bitcoin

Thank you! Taifun
 

Download TaifunNotificationListener extension (aix file)
Download Notification Listener Test project (aia file)
Back to top of page ...

Creative Commons License
This work by Pura Vida Apps is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
with attribution (name=Pura Vida Apps and link to the source site) required.

Back to top of page ...


Home | Snippets | Tutorials | Extensions | Links | Search | Privacy Policy | Contact