initial working functionality

- `pupsec`: added `>=2.12.0` constraints to `environment.sdk`
- `ReceivedIntent`: added
- `ReceiveIntent`: `getInitialIntent`, `receivedIntentStream` and `giveResult` added
- `example`: simple working example added
- `ReceiveIntentPlugin`: handling `getInitialIntent` and `giveResult` methods and sending "newIntent" events
This commit is contained in:
Harsh Bhikadia 2021-04-23 14:06:07 +05:30
parent c1edf13ee3
commit ed77c100f0
10 changed files with 302 additions and 360 deletions

3
.gitignore vendored
View file

@ -5,3 +5,6 @@
.pub/ .pub/
build/ build/
# Flutter related
**/pubspec.lock

View file

@ -1,36 +1,130 @@
package com.bhikadia.receive_intent package com.bhikadia.receive_intent
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.annotation.NonNull import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
/** ReceiveIntentPlugin */ /** ReceiveIntentPlugin */
class ReceiveIntentPlugin: FlutterPlugin, MethodCallHandler { class ReceiveIntentPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ActivityAware {
/// The MethodChannel that will the communication between Flutter and native Android /// The MethodChannel that will the communication between Flutter and native Android
/// ///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it /// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity /// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel private lateinit var methodChannel: MethodChannel
private lateinit var eventChannel: EventChannel
private var eventSink: EventSink? = null
private lateinit var context: Context
private var activity: Activity? = null;
private var initialIntentMap: Map<String, Any?>? = null
private var latestIntentMap: Map<String, Any?>? = null
private var initialIntent = true
private fun intentToMap(intent: Intent, fromPackageName: String?): Map<String, Any?> {
return mapOf(
"fromPackageName" to fromPackageName,
"fromSignatures" to fromPackageName?.let { getApplicationSignature(context, it) },
"action" to intent.action,
"data" to intent.dataString,
"categories" to intent.categories,
"extra" to intent.extras?.let { bundleToMap(it) }
)
}
private fun handleIntent(intent: Intent, fromPackageName: String?) {
if (initialIntent) {
initialIntentMap = intentToMap(intent, fromPackageName)
initialIntent = false
}
latestIntentMap = intentToMap(intent, fromPackageName)
eventSink?.success(latestIntentMap)
}
private fun giveResult(result: Result, resultCode: Int?, data: Map<String, Any?>?) {
if (resultCode != null) {
if (data == null)
activity?.setResult(resultCode)
else
activity?.setResult(resultCode, mapToIntent(data))
result.success(null)
}
result.error("InvalidArg", "resultCode can not be null", null)
}
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "receive_intent") context = flutterPluginBinding.applicationContext
channel.setMethodCallHandler(this)
methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "receive_intent")
methodChannel.setMethodCallHandler(this)
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "receive_intent/event")
eventChannel.setStreamHandler(this)
} }
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") { when (call.method) {
result.success("Android ${android.os.Build.VERSION.RELEASE}") "getInitialIntent" -> {
} else { result.success(initialIntentMap)
}
"giveResult" -> {
giveResult(result, call.argument("resultCode"), call.argument("data"))
}
else -> {
result.notImplemented() result.notImplemented()
} }
} }
}
override fun onListen(arguments: Any?, events: EventSink?) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) methodChannel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addOnNewIntentListener(fun(intent: Intent?): Boolean {
intent?.let { handleIntent(it, binding.activity.callingActivity?.packageName) }
return false;
})
handleIntent(binding.activity.intent, binding.activity.callingActivity?.packageName)
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null;
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addOnNewIntentListener(fun(intent: Intent?): Boolean {
intent?.let { handleIntent(it, binding.activity.callingActivity?.packageName) }
return false;
})
handleIntent(binding.activity.intent, binding.activity.callingActivity?.packageName)
}
override fun onDetachedFromActivity() {
activity = null;
} }
} }

View file

@ -0,0 +1,96 @@
package com.bhikadia.receive_intent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import java.security.MessageDigest
fun mapToBundle(map: Map<String, Any?>): Bundle {
val bundle = Bundle();
map.forEach {
val k = it.key
val v = it.value
when (v) {
is Byte -> bundle.putByte(k, v)
is ByteArray -> bundle.putByteArray(k, v)
is Char -> bundle.putChar(k, v)
is CharArray -> bundle.putCharArray(k, v)
is CharSequence -> bundle.putCharSequence(k, v)
is Float -> bundle.putFloat(k, v)
is FloatArray -> bundle.putFloatArray(k, v)
is Parcelable -> bundle.putParcelable(k, v)
is Short -> bundle.putShort(k, v)
is ShortArray -> bundle.putShortArray(k, v)
else -> throw IllegalArgumentException("$v is of a type that is not currently supported")
}
}
return bundle;
}
fun mapToIntent(map: Map<String, Any?>): Intent = Intent().apply {
putExtras(mapToBundle(map))
}
fun bundleToMap(extras: Bundle): Map<String, Any?> {
val map: MutableMap<String, Any?> = HashMap()
val ks = extras.keySet()
val iterator: Iterator<String> = ks.iterator()
while (iterator.hasNext()) {
val key = iterator.next()
map[key] = extras.get(key)
}
return map
}
fun getApplicationSignature(context: Context, packageName: String): List<String> {
val signatureList: List<String>
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// New signature
val sig = context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo
signatureList = if (sig.hasMultipleSigners()) {
// Send all with apkContentsSigners
sig.apkContentsSigners.map {
val digest = MessageDigest.getInstance("SHA")
digest.update(it.toByteArray())
bytesToHex(digest.digest())
}
} else {
// Send one with signingCertificateHistory
sig.signingCertificateHistory.map {
val digest = MessageDigest.getInstance("SHA")
digest.update(it.toByteArray())
bytesToHex(digest.digest())
}
}
} else {
val sig = context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures
signatureList = sig.map {
val digest = MessageDigest.getInstance("SHA")
digest.update(it.toByteArray())
bytesToHex(digest.digest())
}
}
return signatureList
} catch (e: Exception) {
// Handle error
}
return emptyList()
}
fun bytesToHex(bytes: ByteArray): String {
val hexArray = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
val hexChars = CharArray(bytes.size * 2)
var v: Int
for (j in bytes.indices) {
v = bytes[j].toInt() and 0xFF
hexChars[j * 2] = hexArray[v.ushr(4)]
hexChars[j * 2 + 1] = hexArray[v and 0x0F]
}
return String(hexChars)
}

View file

@ -31,6 +31,10 @@
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="RECEIVE_INTENT_EXAMPLE_ACTION" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/material.dart';
import 'package:receive_intent/receive_intent.dart'; import 'package:receive_intent/receive_intent.dart';
void main() { void main() {
@ -14,34 +13,39 @@ class MyApp extends StatefulWidget {
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown'; ReceivedIntent _initialIntent;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initPlatformState(); _init();
} }
// Platform messages are asynchronous, so we initialize in an async method. Future<void> _init() async {
Future<void> initPlatformState() async { final receivedIntent = await ReceiveIntent.getInitialIntent();
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await ReceiveIntent.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_platformVersion = platformVersion; _initialIntent = receivedIntent;
}); });
} }
Widget _buildFromIntent(String label, ReceivedIntent intent) {
return Center(
child: Column(
children: [
Text(label),
Text(
"fromPackage: ${intent?.fromPackageName}\nfromSignatures: ${_initialIntent?.fromSignatures}"),
Text(
'action: ${_initialIntent?.action}\ndata: ${_initialIntent?.data}\ncategories: ${_initialIntent?.categories}'),
Text("extras: ${_initialIntent?.extra}")
],
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
@ -49,8 +53,19 @@ class _MyAppState extends State<MyApp> {
appBar: AppBar( appBar: AppBar(
title: const Text('Plugin example app'), title: const Text('Plugin example app'),
), ),
body: Center( body: Padding(
child: Text('Running on: $_platformVersion\n'), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildFromIntent("INITIAL", _initialIntent),
StreamBuilder<ReceivedIntent>(
stream: ReceiveIntent.receivedIntentStream,
builder: (context, snapshot) =>
_buildFromIntent("STREAMED", snapshot.data),
)
],
),
), ),
), ),
); );

View file

@ -1,161 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
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.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
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
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
receive_intent:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "0.0.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.20.0"

View file

@ -1,14 +1,53 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class ReceiveIntent { class ReceivedIntent {
static const MethodChannel _channel = final String? fromPackageName;
const MethodChannel('receive_intent'); final List<String>? fromSignatures;
final String action;
final String? data;
final List<String>? categories;
final Map<String, dynamic>? extra;
static Future<String> get platformVersion async { const ReceivedIntent({
final String version = await _channel.invokeMethod('getPlatformVersion'); this.fromPackageName,
return version; this.fromSignatures,
required this.action,
this.data,
this.categories,
this.extra,
});
factory ReceivedIntent.fromMap(Map? map) => ReceivedIntent(
fromPackageName: map?["fromPackageName"],
fromSignatures: map?["fromSignatures"],
action: map?["action"],
data: map?["data"],
categories: map?["categories"],
extra: (map?["extra"] as Map?)?.map<String, dynamic>(
(key, value) => MapEntry(key.toString(), value)),
);
}
class ReceiveIntent {
static const MethodChannel _methodChannel =
const MethodChannel('receive_intent');
static const EventChannel _eventChannel =
const EventChannel("receive_intent/event");
static Future<ReceivedIntent?> getInitialIntent() async {
final renameMap = await _methodChannel.invokeMapMethod('getInitialIntent');
print("result: $renameMap");
return ReceivedIntent.fromMap(renameMap);
}
static Stream<ReceivedIntent?> receivedIntentStream = _eventChannel
.receiveBroadcastStream()
.map<ReceivedIntent?>((event) => ReceivedIntent.fromMap(event as Map?));
static Future<void> giveResult(int resultCode, {Map? data}) async {
await _methodChannel.invokeMethod('giveResult',
<String, dynamic>{"resultCode": resultCode, "data": data});
} }
} }

View file

@ -1,147 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
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.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
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
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.20.0"

View file

@ -5,7 +5,7 @@ author:
homepage: homepage:
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.20.0" flutter: ">=1.20.0"
dependencies: dependencies:

View file

@ -1,6 +1,5 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:receive_intent/receive_intent.dart';
void main() { void main() {
const MethodChannel channel = MethodChannel('receive_intent'); const MethodChannel channel = MethodChannel('receive_intent');
@ -17,7 +16,7 @@ void main() {
channel.setMockMethodCallHandler(null); channel.setMockMethodCallHandler(null);
}); });
test('getPlatformVersion', () async { // test('getPlatformVersion', () async {
expect(await ReceiveIntent.platformVersion, '42'); // expect(await ReceiveIntent.platformVersion, '42');
}); // });
} }