Skip to content Skip to sidebar Skip to footer

Is It Possible To Read The Notifications Of Other Applications On Android With Delphi?

I'm trying to read notifications from other applications on an application made in Rad Studio XE7 for android. Looking up, I saw that in java, you can access the NotificationListen

Solution 1:

You can read the notifications of other Apps with the use of the NotificationListenerService, and here is how

Step 1 : Getting everything ready

  • In your project folder create a folder called Java

    Now inside that folder create a folder called src and inside it create a folder structure that is the same as your Apps package name E.g : src/com/embarcadero/$yourapp

inside src/com/embarcadero/$yourapp add the following Java file as NotificationService.java

package com.embarcadero.$Yourapp;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.support.v4.content.LocalBroadcastManager;


publicclassNotificationServiceextendsNotificationListenerService {

    staticfinalStringTAG="NotificationService";

    Context context;

    @OverridepublicvoidonCreate() {
        super.onCreate();
        context = getApplicationContext();
        Log.d(TAG,"Service has been started!");
    }
    @OverridepublicvoidonNotificationPosted(StatusBarNotification sbn) {
    Log.d(TAG,"Notification Posted!");
        Stringpack= sbn.getPackageName();
        Bundleextras= sbn.getNotification().extras;
        Stringtitle= extras.getString("android.title");
        Stringtext= extras.getCharSequence("android.text").toString();
        Stringid= sbn.getTag();

        Log.i("Package", pack);
        Log.i("Title",title);
        Log.i("Text",text);
        if (id != null){
            Log.i("Key", id);
        }
        Intentmsgrcv=newIntent("Msg");
        msgrcv.putExtra("package", pack);
        msgrcv.putExtra("key", id);
        msgrcv.putExtra("title", title);
        msgrcv.putExtra("text", text);
        LocalBroadcastManager.getInstance(context).sendBroadcast(msgrcv);

    }

    @OverridepublicvoidonNotificationRemoved(StatusBarNotification sbn) {
        Log.d(TAG,"Notification Removed");

    }
}

and Add the following files to the Java folder : android-support-v4.jar (Found under your SDK Path : android-sdk-windows\extras\android\support\v4)

Add the following batch file build.bat

@echo off

setlocal

REM Set the your paths as needed 
SET PATH=%PATH%;C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows\build-tools\23.0.1
if x%ANDROID% == x set ANDROID=C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows
set ANDROID_PLATFORM=%ANDROID%\platforms\android-23
set DX_LIB=%ANDROID%\build-tools\23.0.1\lib
set EMBO_DEX="C:\Program Files (x86)\Embarcadero\Studio\17.0\lib\android\debug\classes.dex"set PROJ_DIR=C:\Users\PJJ\Documents\allesbeste\Mobiletest\java
REM the PROJ_DIR must point to your Java Folder inside your project folder
set VERBOSE=0
set JAVA="C:\Program Files\Java\jdk1.7.0_71\bin"echo.
echo Compiling the Java service activity source files
echo.
mkdir output 2> nul
mkdir output\classes 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=-verbose
javac %VERBOSE_FLAG% -source 1.7 -target 1.7 -Xlint:deprecation -cp %ANDROID_PLATFORM%\android.jar;%EMBO_LIB%\fmx.jar;%PROJ_DIR%\android-support-v4.jar -d output\classes src\com\embarcadero\AllesbesteToep\NotificationService.java

echo.
echo Creating jar containing the new classes
echo.
mkdir output\jar 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=v
jar c%VERBOSE_FLAG%f output\jar\Delphi_Service.jar -C output\classes com

echo.
echo Converting from jar to dex...
echo.
mkdir output\dex 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=--verbose
call dx --dex %VERBOSE_FLAG% --output=%PROJ_DIR%\output\dex\test_classes.dex --positions=lines %PROJ_DIR%\output\jar\Delphi_Service.jar

echo.
echo Merging dex files
echo.
java -cp %DX_LIB%\dx.jar com.android.dx.merge.DexMerger %PROJ_DIR%\output\dex\classes.dex %PROJ_DIR%\output\dex\test_classes.dex %EMBO_DEX%

echo Tidying up
echo.
REM Just change these as needed
del output\classes\com\embarcadero\AllesbesteToep\NotificationService.class
del output\dex\test_classes.dex
rmdir output\classes\com\embarcadero\AllesbesteToep
rmdir output\classes\com\embarcadero
rmdir output\classes\com
rmdir output\classes
pause

echo.
echo Now we have the end result, which is output\jar\Delphi_Service.jar

:Exit

endlocal

Add the default classes.dex file located at C:\Program Files (x86)\Embarcadero\Studio\17.0\lib\android\debug

Add the fmx.jar also located at C:\Program Files (x86)\Embarcadero\Studio\17.0\lib\android\debug

And rename it to classes.jar

So your Java folder structure now looks like this

  • src //with the correct folder structure and your NotificationService.java inside

  • android-support-v4

  • build
  • classes.dex
  • classes.jar

if all your variables are set correctly in the build file you can now run build.bat to generate the output folder (Special thanks to Brian Long who initially created it)

You will now have 2 folders inside your output folder one called dex and the other jar, Now you will have a new classes.dex file and Delphi_Service executable

Have your App use the new classes.dex filehttp://docwiki.embarcadero.com/RADStudio/XE8/en/Creating_and_Deploying_a_classes.dex_File_Manually

Add the Delphi_Service jar file to your Apphttp://docwiki.embarcadero.com/RADStudio/XE8/en/Adding_A_Java_Library_to_Your_Application_Using_the_Project_Manager

In your project file add the following unit Android.JNI.LocalBroadcastMan

//Created with the Java2OP tool
unit Android.JNI.LocalBroadcastMan;

interfaceuses
  Androidapi.JNIBridge,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Util,
  Androidapi.JNI.Os,
  Androidapi.JNI.Net;

type
// ===== Forward declarations =====

  JLocalBroadcastManager = interface;//android.support.v4.content.LocalBroadcastManager// ===== Interface declarations =====

  JLocalBroadcastManagerClass = interface(JObjectClass)
    ['{03179F7E-C439-4369-93CC-AA2BADC54398}']
    {class} function getInstance(context: JContext): JLocalBroadcastManager; cdecl;
  end;

  [JavaSignature('android/support/v4/content/LocalBroadcastManager')]
  JLocalBroadcastManager = interface(JObject)
    ['{6C255CD6-D94E-40BC-A758-EC4965A40725}']
    procedure registerReceiver(receiver: JBroadcastReceiver; filter: JIntentFilter); cdecl;
    function sendBroadcast(intent: JIntent): Boolean; cdecl;
    procedure sendBroadcastSync(intent: JIntent); cdecl;
    procedure unregisterReceiver(receiver: JBroadcastReceiver); cdecl;
  end;
  TJLocalBroadcastManager = class(TJavaGenericImport<JLocalBroadcastManagerClass, JLocalBroadcastManager>) end;

implementation

procedure RegisterTypes;
begin
  TRegTypes.RegisterType('Android.JNI.LocalBroadcastMan.JLocalBroadcastManager', TypeInfo(Android.JNI.LocalBroadcastMan.JLocalBroadcastManager));
end;

initialization
  RegisterTypes;
end.

Download the Free broadcast receiver component from http://www.fmxexpress.com/free-broadcast-receiver-component-for-delphi-xe7-firemonkey-on-android/

Install the component, now add the BroadcastReceiver unit that come with it to your Project and add the following code in BroadcastReceiver:

Add to your uses : Android.JNI.LocalBroadcastMan

Change the TBroadcastReceiver.Add procedure to

procedure TBroadcastReceiver.Add(Value: String);
{$IFDEF ANDROID}
var
  Filter: JIntentFilter;
  locMan : JLocalBroadcastManager;
{$ENDIF}
begin
  {$IFDEF ANDROID}
  if (FListener = nil) or (FReceiver = nil) thenbegin
    Raise Exception.Create('First use RegisterReceive!');
    Exit;
  end;
  {$ENDIF}

  if FItems <> nilthenif FItems.IndexOf(Value) = -1thenbegin
    {$IFDEF ANDROID}
      filter := TJIntentFilter.Create;
      filter.addAction(StringToJString(Value));
      SharedActivityContext.registerReceiver(FReceiver,filter);
      locMan := TJLocalBroadcastManager.JavaClass.getInstance(SharedActivityContext);
      locMan.registerReceiver(FReceiver,Filter);
    {$ENDIF}
      FItems.Add(Value);
    end;
end;

Add the following Procedure unRegisterReceive

procedure TBroadcastReceiver.unRegisterReceive;
var
    locMan : JLocalBroadcastManager;
begin
    locMan := TJLocalBroadcastManager.JavaClass.getInstance(SharedActivityContext);
    locMan.unregisterReceiver(FReceiver);
end;

Add the following procedure regagain

procedure TBroadcastReceiver.regagain;
var
    locMan : JLocalBroadcastManager;
    filter : JIntentFilter;
begin
    filter := TJIntentFilter.Create;
    filter.addAction(StringToJString('Msg'));
    locMan := TJLocalBroadcastManager.JavaClass.getInstance(SharedActivityContext);
    locMan.registerReceiver(FReceiver,Filter);
end;

Edit your AndroidManifest.template.xml in your project dir to inlcude the service

<serviceandroid:name="com.embarcadero.$Yourapp.NotificationService"android:label="%label%"android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"><intent-filter><actionandroid:name="android.service.notification.NotificationListenerService" /></intent-filter></service>

Step 2 : Putting it all together

Add a broadcastreciever component to your project and

Add the following procedure to your project

procedure startService
var
    ServiceIntent: JIntent;
begin
    BroadcastReceiver1.RegisterReceive;
    BroadcastReceiver1.Add('Msg');
    ServiceIntent := TJIntent.JavaClass.init(SharedActivityContext,
      TJLang_Class.JavaClass.forName(
     StringToJString('com.embarcadero.$yourapp.NotificationService'),True,SharedActivity.getClassLoader));
    SharedActivity.startService(serviceIntent)
end;

The BroadcastReceiver has a onReceive event which will get triggered once the localBroadcastManager broadcasts the intent from the onNotificationPosted method

Thus in onReceive you can now do with that intent whatever it is you want such as

var
    NoteName,NoteTitle,pack : String;
begin
//Do whatever you want with the intent
    NoteName := JStringToString(Intent.getStringExtra(StringToJString('key')));
    NoteTitle := JStringToString(Intent.getStringExtra(StringToJString('title')));
    pack := JStringToString(Intent.getStringExtra(StringToJString('package')));
    ShowMessage('Notification: '+NoteName+' Title :'+ NoteTitle+' package :'+pack);
end;

Now we need to write some code to handle the life cycle of the localBroadcastManager when your App is not in the foreground or when it re-enters the foreground

If you don't use Appevents first take a look here http://www.fmxexpress.com/handle-android-and-ios-lifecycle-events-in-delphi-xe5-firemonkey/

Now in response to EnteredBackground Appevent add the unRegisterReceive procedure : BroadcastReceiver1.unRegisterReceive;

In response to WillBecomeForeground Appevent add the regagain procedure : BroadcastReceiver1.regagain;

Lastly in order for your App to be able to read the notifications of other Apps you need to explicitly give it permission to do so.

Go to your phones Settings and search for Notification Access and you will see your App listed there, simply say it is allowed

Update 1:

Added code to handle the App life cycle, the App should now happily go between foreground and background and continue working

Post a Comment for "Is It Possible To Read The Notifications Of Other Applications On Android With Delphi?"