Reputation: 529
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
Reputation: 643
You can read the notifications of other Apps with the use of the NotificationListenerService
, and here is how
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
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>
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