Skip to content

Commit 5aefb90

Browse files
committed
Reflex java implementation
1 parent 0ae22f2 commit 5aefb90

File tree

8 files changed

+319
-28
lines changed

8 files changed

+319
-28
lines changed

android/.idea/gradle.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

android/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ android {
3333
minSdkVersion 16
3434
}
3535
}
36+
37+
dependencies {
38+
implementation 'org.jetbrains:annotations:15.0'
39+
}
Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,74 @@
1-
package com.devsonflutter.reflex;public class EventCallHandler {
1+
/*
2+
3+
Copyright (c) 2022 DevsOnFlutter (Devs On Flutter)
4+
All rights reserved.
5+
6+
The plugin is governed by the BSD-3-clause License. Please see the LICENSE file
7+
for more details.
8+
9+
*/
10+
11+
package com.devsonflutter.reflex;
12+
13+
import android.content.Context;
14+
import android.content.Intent;
15+
import android.content.IntentFilter;
16+
import android.os.Build;
17+
18+
import androidx.annotation.RequiresApi;
19+
20+
import com.devsonflutter.reflex.notification.NotificationListener;
21+
import com.devsonflutter.reflex.notification.NotificationReceiver;
22+
import com.devsonflutter.reflex.notification.ReflexNotification;
23+
import com.devsonflutter.reflex.permission.NotificationPermission;
24+
25+
import io.flutter.Log;
26+
import io.flutter.plugin.common.EventChannel;
27+
28+
public class EventCallHandler implements EventChannel.StreamHandler {
29+
30+
private EventChannel.EventSink mEventSink = null;
31+
private final Context context;
32+
private final NotificationPermission notificationPermission;
33+
34+
EventCallHandler(Context context) {
35+
this.context = context;
36+
notificationPermission = new NotificationPermission(context);
37+
}
38+
39+
private static final String TAG = ReflexPlugin.getPluginTag();
40+
41+
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
42+
@Override
43+
public void onListen(Object arguments, EventChannel.EventSink events) {
44+
mEventSink = events;
45+
listenNotification(mEventSink);
46+
Log.w(TAG,"Listening Reflex Stream...");
47+
}
48+
49+
@Override
50+
public void onCancel(Object arguments) {
51+
mEventSink = null;
52+
Log.w(TAG,"Closing Reflex Stream...");
53+
}
54+
55+
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
56+
public void listenNotification(EventChannel.EventSink eventSink) {
57+
if (notificationPermission.permissionGranted()) {
58+
// Set up receiver
59+
IntentFilter intentFilter = new IntentFilter();
60+
intentFilter.addAction(ReflexNotification.NOTIFICATION_INTENT);
61+
62+
NotificationReceiver receiver = new NotificationReceiver(eventSink);
63+
context.registerReceiver(receiver, intentFilter);
64+
65+
// Listener intent
66+
Intent intent = new Intent(context, NotificationListener.class);
67+
context.startService(intent);
68+
Log.i(TAG, "Started the notification tracking service.");
69+
} else {
70+
notificationPermission.requestPermission();
71+
Log.e(TAG, "Failed to start notification tracking; Permissions were not yet granted.");
72+
}
73+
}
274
}
Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,97 @@
1+
/*
2+
3+
Copyright (c) 2022 DevsOnFlutter (Devs On Flutter)
4+
All rights reserved.
5+
6+
The plugin is governed by the BSD-3-clause License. Please see the LICENSE file
7+
for more details.
8+
9+
*/
10+
111
package com.devsonflutter.reflex;
212

13+
import android.annotation.SuppressLint;
14+
import android.content.Context;
15+
316
import androidx.annotation.NonNull;
417

518
import io.flutter.embedding.engine.plugins.FlutterPlugin;
6-
import io.flutter.plugin.common.MethodCall;
7-
import io.flutter.plugin.common.MethodChannel;
8-
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
9-
import io.flutter.plugin.common.MethodChannel.Result;
10-
11-
/** ReflexPlugin */
12-
public class ReflexPlugin implements FlutterPlugin, MethodCallHandler {
13-
/// The MethodChannel that will the communication between Flutter and native Android
14-
///
15-
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
16-
/// when the Flutter Engine is detached from the Activity
17-
private MethodChannel channel;
19+
import io.flutter.plugin.common.BinaryMessenger;
20+
import io.flutter.plugin.common.EventChannel;
1821

19-
@Override
20-
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
21-
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "reflex");
22-
channel.setMethodCallHandler(this);
22+
/* ReflexPlugin */
23+
public class ReflexPlugin implements FlutterPlugin {
24+
25+
/* ------------- Native Variables -------------- */
26+
@SuppressLint("StaticFieldLeak")
27+
private static Context context = null;
28+
29+
private BinaryMessenger binaryMessenger = null;
30+
31+
// private MethodChannel methodChannel;
32+
// private MethodCallHandlerImplementation methodHandler;
33+
34+
@SuppressLint("StaticFieldLeak")
35+
private static EventCallHandler eventHandler;
36+
37+
private EventChannel eventChannel;
38+
/* ------------- Native Variables -------------- */
39+
40+
/* ------------- Method Channel -------------- */
41+
private static final String CHANNEL_ID = "reflex_method_channel";
42+
43+
public static String getChannelId() {
44+
return CHANNEL_ID;
45+
}
46+
/* ------------- Method Channel -------------- */
47+
48+
/* ------------- Event Channel -------------- */
49+
private static final String STREAM_ID = "reflex_event_channel";
50+
51+
public static String getStreamId() {
52+
return STREAM_ID;
53+
}
54+
/* ------------- Event Channel -------------- */
55+
56+
/* ------------- Plugin Logging TAG -------------- */
57+
private static final String TAG = "[Reflex]";
58+
59+
public static String getPluginTag() {
60+
return TAG;
61+
}
62+
/* ------------- Plugin Logging TAG -------------- */
63+
64+
private void setupChannel(BinaryMessenger messenger, Context context) {
65+
// methodChannel = new MethodChannel(binaryMessenger, CHANNEL_ID);
66+
eventChannel = new EventChannel(binaryMessenger,STREAM_ID);
67+
68+
// methodHandler = new MethodCallHandlerImplementation();
69+
eventHandler = new EventCallHandler(context);
70+
71+
// methodChannel.setMethodCallHandler(methodHandler);
72+
eventChannel.setStreamHandler(eventHandler);
73+
}
74+
75+
private void teardownChannel() {
76+
// methodChannel.setMethodCallHandler(null);
77+
eventChannel.setStreamHandler(null);
78+
binaryMessenger = null;
79+
// methodChannel = null;
80+
// methodHandler = null;
81+
eventChannel = null;
82+
eventHandler = null;
83+
context = null;
2384
}
2485

2586
@Override
26-
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
27-
if (call.method.equals("getPlatformVersion")) {
28-
result.success("Android " + android.os.Build.VERSION.RELEASE);
29-
} else {
30-
result.notImplemented();
31-
}
87+
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
88+
binaryMessenger = flutterPluginBinding.getBinaryMessenger();
89+
context = flutterPluginBinding.getApplicationContext();
90+
setupChannel(binaryMessenger, context);
3291
}
3392

3493
@Override
3594
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
36-
channel.setMethodCallHandler(null);
95+
teardownChannel();
3796
}
3897
}
Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,54 @@
1-
package com.devsonflutter.reflex.notification;public class NotificationListener {
1+
/*
2+
3+
Copyright (c) 2022 DevsOnFlutter (Devs On Flutter)
4+
All rights reserved.
5+
6+
The plugin is governed by the BSD-3-clause License. Please see the LICENSE file
7+
for more details.
8+
9+
*/
10+
11+
package com.devsonflutter.reflex.notification;
12+
13+
import android.annotation.SuppressLint;
14+
import android.app.Notification;
15+
import android.content.Intent;
16+
import android.os.Build.VERSION_CODES;
17+
import android.os.Bundle;
18+
import android.service.notification.NotificationListenerService;
19+
import android.service.notification.StatusBarNotification;
20+
import androidx.annotation.RequiresApi;
21+
22+
import io.flutter.Log;
23+
24+
/* Notification Listener */
25+
@SuppressLint("OverrideAbstract")
26+
@RequiresApi(api = VERSION_CODES.JELLY_BEAN_MR2)
27+
public class NotificationListener extends NotificationListenerService {
28+
29+
@RequiresApi(api = VERSION_CODES.KITKAT)
30+
@Override
31+
public void onNotificationPosted(StatusBarNotification notification) {
32+
Log.d("[Reflex]","Notification Received!");
33+
34+
// Package name as title
35+
String packageName = notification.getPackageName();
36+
37+
// Extra Payload
38+
Bundle extras = notification.getNotification().extras;
39+
40+
Intent intent = new Intent(ReflexNotification.NOTIFICATION_INTENT);
41+
intent.putExtra(ReflexNotification.NOTIFICATION_PACKAGE_NAME, packageName);
42+
43+
if (extras != null) {
44+
CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE);
45+
CharSequence text = extras.getCharSequence(Notification.EXTRA_TEXT);
46+
47+
intent.putExtra(ReflexNotification.NOTIFICATION_TITLE, title.toString());
48+
intent.putExtra(ReflexNotification.NOTIFICATION_MESSAGE, text.toString());
49+
}
50+
51+
sendBroadcast(intent);
52+
}
253
}
54+
Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,45 @@
1-
package com.devsonflutter.reflex.notification;public class NotificationReceiver {
1+
/*
2+
3+
Copyright (c) 2022 DevsOnFlutter (Devs On Flutter)
4+
All rights reserved.
5+
6+
The plugin is governed by the BSD-3-clause License. Please see the LICENSE file
7+
for more details.
8+
9+
*/
10+
11+
package com.devsonflutter.reflex.notification;
12+
13+
import android.content.BroadcastReceiver;
14+
import android.content.Context;
15+
import android.content.Intent;
16+
import android.os.Build.VERSION_CODES;
17+
import androidx.annotation.RequiresApi;
18+
import io.flutter.plugin.common.EventChannel.EventSink;
19+
import java.util.HashMap;
20+
21+
/* Notification Receiver */
22+
public class NotificationReceiver extends BroadcastReceiver {
23+
24+
private final EventSink eventSink;
25+
26+
public NotificationReceiver(EventSink eventSink) {
27+
this.eventSink = eventSink;
28+
}
29+
30+
@RequiresApi(api = VERSION_CODES.JELLY_BEAN_MR2)
31+
@Override
32+
public void onReceive(Context context, Intent intent) {
33+
String packageName = intent.getStringExtra(ReflexNotification.NOTIFICATION_PACKAGE_NAME);
34+
String title = intent.getStringExtra(ReflexNotification.NOTIFICATION_TITLE);
35+
String message = intent.getStringExtra(ReflexNotification.NOTIFICATION_MESSAGE);
36+
37+
// Sending Data from Java to Flutter
38+
HashMap<String, Object> data = new HashMap<>();
39+
data.put("packageName", packageName);
40+
data.put("title", title);
41+
data.put("message", message);
42+
eventSink.success(data);
43+
}
244
}
45+
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1-
package com.devsonflutter.reflex.notification;public class ReflexNotification {
1+
package com.devsonflutter.reflex.notification;
2+
3+
public class ReflexNotification {
4+
public static String NOTIFICATION_INTENT = "notification_event";
5+
public static String NOTIFICATION_PACKAGE_NAME = "notification_package_name";
6+
public static String NOTIFICATION_MESSAGE = "notification_message";
7+
public static String NOTIFICATION_TITLE = "notification_title";
28
}
Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,56 @@
1-
package com.devsonflutter.reflex.permission;public class NotificationPermission {
1+
/*
2+
3+
Copyright (c) 2022 DevsOnFlutter (Devs On Flutter)
4+
All rights reserved.
5+
6+
The plugin is governed by the BSD-3-clause License. Please see the LICENSE file
7+
for more details.
8+
9+
*/
10+
11+
package com.devsonflutter.reflex.permission;
12+
13+
import android.annotation.SuppressLint;
14+
import android.content.ComponentName;
15+
import android.content.Context;
16+
import android.content.Intent;
17+
import android.provider.Settings;
18+
import android.text.TextUtils;
19+
20+
/* Notification Permission */
21+
public class NotificationPermission {
22+
23+
@SuppressLint("StaticFieldLeak")
24+
private static Context context = null;
25+
26+
public NotificationPermission(Context context) {
27+
NotificationPermission.context = context;
28+
}
29+
30+
public void requestPermission() {
31+
/// Sort out permissions for notifications
32+
if (!permissionGranted()) {
33+
Intent permissionScreen = new Intent("android.settings" +
34+
".ACTION_NOTIFICATION_LISTENER_SETTINGS");
35+
permissionScreen.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
36+
context.startActivity(permissionScreen);
37+
}
38+
}
39+
40+
public boolean permissionGranted() {
41+
String packageName = context.getPackageName();
42+
String flat = Settings.Secure.getString(context.getContentResolver(),
43+
"enabled_notification_listeners");
44+
if (!TextUtils.isEmpty(flat)) {
45+
String[] names = flat.split(":");
46+
for (String name : names) {
47+
ComponentName componentName = ComponentName.unflattenFromString(name);
48+
boolean nameMatch = TextUtils.equals(packageName, componentName.getPackageName());
49+
if (nameMatch) {
50+
return true;
51+
}
52+
}
53+
}
54+
return false;
55+
}
256
}

0 commit comments

Comments
 (0)