7/31/1
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.phan_tech.flutter_overlay_apps"
|
||||
>
|
||||
</manifest>
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.phan_tech.flutter_overlay_apps
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.annotation.RequiresApi
|
||||
import io.flutter.FlutterInjector
|
||||
import io.flutter.embedding.engine.FlutterEngineCache
|
||||
import io.flutter.embedding.engine.FlutterEngineGroup
|
||||
import io.flutter.embedding.engine.dart.DartExecutor
|
||||
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.*
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
|
||||
const val mainAppMethodChannel: String = "com.phan_tech/flutter_overlay_apps"
|
||||
const val overlayAppMethodChannel: String = "com.phan_tech/flutter_overlay_apps/overlay"
|
||||
const val overlayAppMessageChannel: String = "com.phan_tech/flutter_overlay_apps/overlay/messenger"
|
||||
|
||||
/** FlutterOverlayAppsPlugin */
|
||||
class FlutterOverlayAppsPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, BasicMessageChannel.MessageHandler<Any?> {
|
||||
private lateinit var channel: MethodChannel
|
||||
private lateinit var messenger: BasicMessageChannel<Any?>
|
||||
private lateinit var context: Context
|
||||
private var activity: Activity? = null
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
}
|
||||
|
||||
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
context = binding.activity.applicationContext
|
||||
WindowSetup.messenger = messenger
|
||||
WindowSetup.messenger!!.setMessageHandler(this)
|
||||
|
||||
val engineGroup = FlutterEngineGroup(context)
|
||||
val dartEntrypoint = DartExecutor.DartEntrypoint(
|
||||
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
|
||||
"showOverlay"
|
||||
)
|
||||
val engine = engineGroup.createAndRunEngine(context, dartEntrypoint)
|
||||
FlutterEngineCache.getInstance().put("my_engine_id", engine)
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivityForConfigChanges() {
|
||||
activity = null
|
||||
}
|
||||
|
||||
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
|
||||
activity = binding.activity
|
||||
}
|
||||
|
||||
override fun onDetachedFromActivity() {
|
||||
activity = null
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, mainAppMethodChannel)
|
||||
channel.setMethodCallHandler(this)
|
||||
|
||||
messenger = BasicMessageChannel(flutterPluginBinding.binaryMessenger, overlayAppMessageChannel, JSONMessageCodec.INSTANCE)
|
||||
messenger.setMessageHandler(this)
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||
val currentActivity = activity
|
||||
if (currentActivity == null) {
|
||||
result.error("activity_not_attached", "Activity is not attached", null)
|
||||
return
|
||||
}
|
||||
|
||||
when (call.method) {
|
||||
"showOverlay" -> {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
result.error("1", "SDK version is lower than 23", "Requires Android SDK 23 and above")
|
||||
} else if (!checkPermissions()) {
|
||||
requestPermissions()
|
||||
} else {
|
||||
val height = call.argument<Int>("height")
|
||||
val width = call.argument<Int>("width")
|
||||
val alignment = call.argument<String>("alignment")
|
||||
val closeOnBackButton = call.argument<Boolean>("closeOnBackButton")
|
||||
|
||||
WindowSetup.width = width ?: -1
|
||||
WindowSetup.height = height ?: -1
|
||||
WindowSetup.closeOnBackButton = closeOnBackButton ?: true
|
||||
WindowSetup.setGravityFromAlignment(alignment ?: "center")
|
||||
currentActivity.startService(Intent(context, OverlayService::class.java))
|
||||
result.success(true)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply<Any?>) {
|
||||
val engine = FlutterEngineCache.getInstance().get("my_engine_id")
|
||||
if (engine != null) {
|
||||
val overlayMessageChannel = BasicMessageChannel(engine.dartExecutor, overlayAppMessageChannel, JSONMessageCodec.INSTANCE)
|
||||
overlayMessageChannel.send(message, reply)
|
||||
} else {
|
||||
reply.reply(null)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun checkPermissions(): Boolean {
|
||||
return Settings.canDrawOverlays(context)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun requestPermissions() {
|
||||
activity?.let {
|
||||
it.startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:${it.packageName}")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.phan_tech.flutter_overlay_apps
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.graphics.PixelFormat
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.view.KeyEvent
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.RequiresApi
|
||||
import io.flutter.embedding.android.FlutterView
|
||||
import io.flutter.embedding.engine.FlutterEngineCache
|
||||
import io.flutter.plugin.common.BasicMessageChannel
|
||||
import io.flutter.plugin.common.JSONMessageCodec
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import org.json.JSONObject
|
||||
|
||||
|
||||
class OverlayService : Service() {
|
||||
private var windowManager: WindowManager? = null
|
||||
private lateinit var flutterView: FlutterView
|
||||
private val flutterChannel = MethodChannel(FlutterEngineCache.getInstance().get("my_engine_id")!!.dartExecutor, overlayAppMethodChannel)
|
||||
private val overlayMessageChannel = BasicMessageChannel(FlutterEngineCache.getInstance().get("my_engine_id")!!.dartExecutor, overlayAppMessageChannel, JSONMessageCodec.INSTANCE)
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
// Not used
|
||||
return null
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val engine = FlutterEngineCache.getInstance().get("my_engine_id")!!
|
||||
engine.lifecycleChannel.appIsResumed()
|
||||
|
||||
flutterView = object: FlutterView(applicationContext){
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
return if (event.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// handle the back button code;
|
||||
if(WindowSetup.closeOnBackButton){
|
||||
stopService(Intent(baseContext, OverlayService().javaClass))
|
||||
windowManager?.removeView(flutterView)
|
||||
}else{
|
||||
// send message
|
||||
overlayMessageChannel.send(JSONObject("{\"method\": \"backButton\"}"))// {"method" "backButton"}
|
||||
|
||||
}
|
||||
|
||||
true
|
||||
} else super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flutterView.attachToFlutterEngine(FlutterEngineCache.getInstance().get("my_engine_id")!!)
|
||||
flutterView.fitsSystemWindows = true
|
||||
|
||||
flutterChannel.setMethodCallHandler{ methodCall: MethodCall, result: MethodChannel.Result ->
|
||||
if(methodCall.method == "close"){
|
||||
val closed = stopService(Intent(baseContext, OverlayService().javaClass))
|
||||
result.success(closed)
|
||||
}
|
||||
}
|
||||
overlayMessageChannel.setMessageHandler(MyHandler())
|
||||
|
||||
|
||||
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager?
|
||||
|
||||
val params = WindowManager.LayoutParams(
|
||||
WindowSetup.width,
|
||||
WindowSetup.height,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
||||
else WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
)
|
||||
params.flags = params.flags and WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE.inv()
|
||||
params.gravity = WindowSetup.gravity
|
||||
windowManager!!.addView(flutterView, params)
|
||||
}
|
||||
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
windowManager!!.removeView(flutterView)
|
||||
}
|
||||
}
|
||||
|
||||
class MyHandler: BasicMessageChannel.MessageHandler<Any?>{
|
||||
override fun onMessage(message: Any?, reply: BasicMessageChannel.Reply<Any?>) {
|
||||
WindowSetup.messenger!!.send(message)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.phan_tech.flutter_overlay_apps
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.Gravity
|
||||
import android.view.WindowManager
|
||||
import io.flutter.plugin.common.BasicMessageChannel
|
||||
|
||||
object WindowSetup {
|
||||
var height: Int = -1
|
||||
var width: Int = WindowManager.LayoutParams.MATCH_PARENT
|
||||
var gravity: Int = Gravity.CENTER
|
||||
var messenger : BasicMessageChannel<Any?>? = null
|
||||
var closeOnBackButton : Boolean = true;
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
fun setGravityFromAlignment(alignment: String){
|
||||
when {
|
||||
alignment.lowercase() == "topLeft".lowercase() -> {
|
||||
gravity = Gravity.TOP or Gravity.LEFT
|
||||
}
|
||||
alignment.lowercase() == "topCenter".lowercase() -> {
|
||||
gravity = Gravity.TOP
|
||||
}
|
||||
alignment.lowercase() == "topRight".lowercase() -> {
|
||||
gravity = Gravity.TOP or Gravity.RIGHT
|
||||
}
|
||||
|
||||
|
||||
alignment.lowercase() == "centerLeft".lowercase() -> {
|
||||
gravity = Gravity.CENTER or Gravity.LEFT
|
||||
}
|
||||
alignment.lowercase() == "center".lowercase() -> {
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
alignment.lowercase() == "centerRight".lowercase() -> {
|
||||
gravity = Gravity.CENTER or Gravity.RIGHT
|
||||
}
|
||||
|
||||
|
||||
alignment.lowercase() == "bottomLeft".lowercase() -> {
|
||||
gravity = Gravity.BOTTOM or Gravity.LEFT
|
||||
}
|
||||
alignment.lowercase() == "bottomCenter".lowercase() -> {
|
||||
gravity = Gravity.BOTTOM
|
||||
}
|
||||
alignment.lowercase() == "bottomRight".lowercase() -> {
|
||||
gravity = Gravity.BOTTOM or Gravity.RIGHT
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user