diff --git a/android/build.gradle b/android/build.gradle index c8a001a..87a86f6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -5,7 +5,7 @@ buildscript { ext.kotlin_version = '1.3.50' repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -17,7 +17,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/android/src/main/kotlin/com/emilecode/android_notification_listener2/AndroidNotificationListener2Plugin.kt b/android/src/main/kotlin/com/emilecode/android_notification_listener2/AndroidNotificationListener2Plugin.kt index b7cdfb7..9ae6d13 100644 --- a/android/src/main/kotlin/com/emilecode/android_notification_listener2/AndroidNotificationListener2Plugin.kt +++ b/android/src/main/kotlin/com/emilecode/android_notification_listener2/AndroidNotificationListener2Plugin.kt @@ -4,23 +4,39 @@ package com.emilecode.android_notification_listener2 /** * Flutter-specific */ +import android.app.Activity +import android.app.Service import io.flutter.plugin.common.EventChannel import io.flutter.plugin.common.EventChannel.StreamHandler import io.flutter.plugin.common.EventChannel.EventSink -import io.flutter.plugin.common.PluginRegistry.Registrar /** * Android-specific */ import android.content.* +import android.os.Handler import android.provider.Settings import android.text.TextUtils +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodChannel +import io.flutter.embedding.engine.FlutterEngine + +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.embedding.engine.plugins.service.ServicePluginBinding +import java.lang.Exception +import java.lang.reflect.Method +import io.flutter.embedding.engine.plugins.service.ServiceAware as ServiceAware + /** * AndroidNotificationListener2Plugin */ -class AndroidNotificationListener2Plugin private constructor(private val context: Context) : StreamHandler { +class AndroidNotificationListener2Plugin : StreamHandler, FlutterPlugin { private var eventSink: EventSink? = null + private var methodChannel: MethodChannel? = null + private var context: Context? = null /** * Called whenever the event channel is subscribed to in Flutter @@ -31,7 +47,7 @@ class AndroidNotificationListener2Plugin private constructor(private val context Start the notification service once permission has been given. */ val listenerIntent = Intent(context, NotificationListener::class.java) - context.startService(listenerIntent) + context!!.startService(listenerIntent) } /** @@ -46,8 +62,8 @@ class AndroidNotificationListener2Plugin private constructor(private val context * If any match is found, return true. Otherwise if no matches were found, return false. */ private fun permissionGiven(): Boolean { - val packageName = context.packageName - val flat = Settings.Secure.getString(context.contentResolver, + val packageName = context!!.packageName + val flat = Settings.Secure.getString(context!!.contentResolver, ENABLED_NOTIFICATION_LISTENERS) if (!TextUtils.isEmpty(flat)) { val names = flat.split(":").toTypedArray() @@ -83,32 +99,64 @@ class AndroidNotificationListener2Plugin private constructor(private val context private const val ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners" private const val ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS" private const val EVENT_CHANNEL_NAME = "notifications.eventChannel" - - /** Plugin registration. */ - @JvmStatic - fun registerWith(registrar: Registrar) { - val channel = EventChannel(registrar.messenger(), EVENT_CHANNEL_NAME) - val context: Context = registrar.activeContext() - val plugin = AndroidNotificationListener2Plugin(context) - channel.setStreamHandler(plugin) - } + private const val COMMAND_CHANEL = "notifications.commandChannel" } /** * Plugin constructor setting the context and registering the notification service. */ init { + + } + + fun lateInit() { /* Check if permission is given, if not then go to the notification settings screen. */ if (!permissionGiven()) { - context.startActivity(Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS)) + requestPermission() } val receiver = NotificationReceiver() val intentFilter = IntentFilter() intentFilter.addAction(NotificationListener.NOTIFICATION_INTENT) - context.registerReceiver(receiver, intentFilter) + context!!.registerReceiver(receiver, intentFilter) /* Start the notification service once permission has been given. */ val listenerIntent = Intent(context, NotificationListener::class.java) - context.startService(listenerIntent) + context!!.startService(listenerIntent) + } + + private fun requestPermission() { + val intent = Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context?.startActivity(intent) + } + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + this.context = binding.applicationContext + EventChannel(binding.binaryMessenger, EVENT_CHANNEL_NAME).setStreamHandler(this) + methodChannel = MethodChannel(binding.getBinaryMessenger(), COMMAND_CHANEL) + methodChannel!!.setMethodCallHandler { call, result -> + run { + when (call.method) { + "init" -> { + lateInit() + result.success(null) + } + "permissionGiven" -> result.success(permissionGiven()) + "requestPermission" -> { + requestPermission() + result.success(null) + } + else -> { // Note the block + throw UnsupportedOperationException("Method ${call.method} is not supported") + } + } + } + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + methodChannel?.setMethodCallHandler(null) + methodChannel = null + this.context = null } } \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index d0d7c02..f54b3e9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -11,19 +11,30 @@ class MyApp extends StatefulWidget { _MyAppState createState() => _MyAppState(); } -class _MyAppState extends State { +class _MyAppState extends State with WidgetsBindingObserver { AndroidNotificationListener _notifications; StreamSubscription _subscription; + bool _permissionGiven = false; + bool _inited = false; @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); initPlatformState(); } // Platform messages are asynchronous, so we initialize in an async method. Future initPlatformState() async { startListening(); + updatePermissionState(); + } + + Future updatePermissionState() async { + var permissionGiven = await _notifications.isPermissionGiven(); + setState(() { + _permissionGiven = permissionGiven; + }); } void onData(NotificationEventV2 event) { @@ -35,7 +46,7 @@ class _MyAppState extends State { } void startListening() { - _notifications = new AndroidNotificationListener(); + _notifications = AndroidNotificationListener.withoutInit(); try { _subscription = _notifications.notificationStream.listen(onData); } on NotificationExceptionV2 catch (exception) { @@ -54,7 +65,42 @@ class _MyAppState extends State { appBar: AppBar( title: const Text('Plugin example app'), ), - ), + body: Column(children: [ + _permissionGiven + ? Text("Permission has granted") + : Text("Permission has not grant"), + _inited + ? Text("Plugin inited") + : Text("Plugin not inited"), + TextButton( + child: Text("Init"), + onPressed: () { + _notifications.init(); + setState(() { + _inited = _notifications.isInited; + }); + }, + ), + TextButton( + child: Text("request permission"), + onPressed: () { + _notifications.requestPermission(); + }, + ), + ])), ); } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + updatePermissionState(); + } + } + + @override + void dispose() { + super.dispose(); + WidgetsBinding.instance.removeObserver(this); + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 7f9edc1..73675f9 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -8,62 +8,48 @@ packages: relative: true source: path version: "2.0.0" - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.13" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.6.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" - charcode: + version: "2.1.0" + characters: dependency: transitive description: - name: charcode + name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" - collection: + version: "1.1.0" + charcode: dependency: transitive description: - name: collection + name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" - convert: + version: "1.2.0" + clock: dependency: transitive description: - name: convert + name: clock url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" - crypto: + version: "1.1.0" + collection: dependency: transitive description: - name: crypto + name: collection url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "1.15.0" cupertino_icons: dependency: "direct main" description: @@ -71,6 +57,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -81,48 +74,27 @@ packages: description: flutter source: sdk version: "0.0.0" - image: - dependency: transitive - description: - name: image - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.12" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -134,63 +106,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.3.0" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "3.6.1" + version: "2.1.0" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" flutter: ">=1.10.0" diff --git a/lib/android_notification_listener2.dart b/lib/android_notification_listener2.dart index d4aefa4..0a30f93 100644 --- a/lib/android_notification_listener2.dart +++ b/lib/android_notification_listener2.dart @@ -45,8 +45,31 @@ NotificationEventV2 _notificationEvent(dynamic data) { } class AndroidNotificationListener { - static const EventChannel _notificationEventChannel = - EventChannel('notifications.eventChannel'); + EventChannel _notificationEventChannel; + + AndroidNotificationListener.withoutInit() { + _notificationEventChannel = EventChannel('notifications.eventChannel'); + } + + factory AndroidNotificationListener() { + final instance = AndroidNotificationListener.withoutInit(); + _init(); + return instance; + } + + init() { + _init(); + } + + bool get isInited => _inited; + + Future isPermissionGiven() { + return _channel.invokeMethod("permissionGiven"); + } + + Future requestPermission() { + return _channel.invokeMethod("requestPermission"); + } Stream _notificationStream; @@ -64,3 +87,17 @@ class AndroidNotificationListener { 'Notification API exclusively available on Android!'); } } + +const MethodChannel _channel = + const MethodChannel('notifications.commandChannel'); + +bool _inited = false; + +void _init() { + if (_inited) { + throw new NotificationExceptionV2( + "You can not initialize this plugin twice."); + } + _channel.invokeMethod("init"); + _inited = true; +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 4270840..6dc48a2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,62 +1,55 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.13" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.6.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" - collection: + version: "1.2.0" + clock: dependency: transitive description: - name: collection + name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" - convert: + version: "1.1.0" + collection: dependency: transitive description: - name: convert + name: collection url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" - crypto: + version: "1.15.0" + fake_async: dependency: transitive description: - name: crypto + name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -67,48 +60,27 @@ packages: description: flutter source: sdk version: "0.0.0" - image: - dependency: transitive - description: - name: image - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.12" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -120,63 +92,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.3.0" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "3.6.1" + version: "2.1.0" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" flutter: ">=1.10.0"