Skip to content

Commit

Permalink
initial working functionality
Browse files Browse the repository at this point in the history
- `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
  • Loading branch information
daadu committed Apr 23, 2021
1 parent c1edf13 commit ed77c10
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 363 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
.pub/

build/

# Flutter related
**/pubspec.lock
Original file line number Diff line number Diff line change
@@ -1,36 +1,130 @@
package com.bhikadia.receive_intent

import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.annotation.NonNull

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.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar


/** ReceiveIntentPlugin */
class ReceiveIntentPlugin: FlutterPlugin, MethodCallHandler {
/// 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
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "receive_intent")
channel.setMethodCallHandler(this)
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}

override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
class ReceiveIntentPlugin : FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ActivityAware {
/// 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
/// when the Flutter Engine is detached from the Activity
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) {
context = flutterPluginBinding.applicationContext

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) {
when (call.method) {
"getInitialIntent" -> {
result.success(initialIntentMap)
}
"giveResult" -> {
giveResult(result, call.argument("resultCode"), call.argument("data"))
}
else -> {
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) {
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;
}
}
96 changes: 96 additions & 0 deletions android/src/main/kotlin/com/bhikadia/receive_intent/Utils.kt
Original file line number Diff line number Diff line change
@@ -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)
}
4 changes: 4 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="RECEIVE_INTENT_EXAMPLE_ACTION" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
55 changes: 35 additions & 20 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:receive_intent/receive_intent.dart';

void main() {
Expand All @@ -14,43 +13,59 @@ class MyApp extends StatefulWidget {
}

class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
ReceivedIntent _initialIntent;

@override
void initState() {
super.initState();
initPlatformState();
_init();
}

// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
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.
Future<void> _init() async {
final receivedIntent = await ReceiveIntent.getInitialIntent();

if (!mounted) return;

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
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text('Running on: $_platformVersion\n'),
body: Padding(
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),
)
],
),
),
),
);
Expand Down
Loading

0 comments on commit ed77c10

Please sign in to comment.