elcharlie
elcharlie

Reputation: 529

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 NotificationListenerService, but not if with delphi could access this service. Can it be done?

Upvotes: 1

Views: 5220

Answers (1)

Peter-John Jansen
Peter-John Jansen

Reputation: 643

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;


public class NotificationService extends NotificationListenerService {

    static final String TAG = "NotificationService";

    Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
        Log.d(TAG,"Service has been started!");
    }
    @Override

    public void onNotificationPosted(StatusBarNotification sbn) {
    Log.d(TAG,"Notification Posted!");
        String pack = sbn.getPackageName();
        Bundle extras = sbn.getNotification().extras;
        String title = extras.getString("android.title");
        String text = extras.getCharSequence("android.text").toString();
        String id = sbn.getTag();

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

    }

    @Override

    public void onNotificationRemoved(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 file http://docwiki.embarcadero.com/RADStudio/XE8/en/Creating_and_Deploying_a_classes.dex_File_Manually

Add the Delphi_Service jar file to your App http://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;

interface

uses
  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) then
  begin
    Raise Exception.Create('First use RegisterReceive!');
    Exit;
  end;
  {$ENDIF}

  if FItems <> nil then
    if FItems.IndexOf(Value) = -1 then
    begin
    {$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

<service android:name="com.embarcadero.$Yourapp.NotificationService"
                 android:label="%label%"
          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
                 <action android: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

Upvotes: 6

Related Questions