This commit is contained in:
Hamza-Ayed
2024-07-31 21:19:19 +03:00
parent dea83d970c
commit 2bc71355c3
106 changed files with 4600 additions and 727 deletions

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.phan_tech.flutter_overlay_apps"
>
</manifest>

View File

@@ -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}")
)
)
}
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}
}
}