3 changed files with 0 additions and 610 deletions
@ -1,133 +0,0 @@ |
|||||
package com.saitong.voitrans |
|
||||
|
|
||||
import android.content.Context |
|
||||
import android.content.Intent |
|
||||
import android.net.Uri |
|
||||
import android.os.Build |
|
||||
import android.provider.Settings |
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin |
|
||||
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 |
|
||||
|
|
||||
class FloatingWindowPlugin: FlutterPlugin, MethodCallHandler { |
|
||||
private lateinit var channel: MethodChannel |
|
||||
private lateinit var context: Context |
|
||||
|
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|
||||
context = flutterPluginBinding.applicationContext |
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "floating_window") |
|
||||
channel.setMethodCallHandler(this) |
|
||||
|
|
||||
// 设置回调通道 |
|
||||
FloatingWindowService.methodChannel = channel |
|
||||
} |
|
||||
|
|
||||
override fun onMethodCall(call: MethodCall, result: Result) { |
|
||||
when (call.method) { |
|
||||
"enableFloatingWindow" -> { |
|
||||
enableFloatingWindow(result) |
|
||||
} |
|
||||
"disableFloatingWindow" -> { |
|
||||
disableFloatingWindow(result) |
|
||||
} |
|
||||
"showFloatingWindow" -> { |
|
||||
showFloatingWindow(result) |
|
||||
} |
|
||||
"hideFloatingWindow" -> { |
|
||||
hideFloatingWindow(result) |
|
||||
} |
|
||||
"updateContent" -> { |
|
||||
updateContent(call, result) |
|
||||
} |
|
||||
"setPosition" -> { |
|
||||
setPosition(call, result) |
|
||||
} |
|
||||
else -> { |
|
||||
result.notImplemented() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun enableFloatingWindow(result: Result) { |
|
||||
try { |
|
||||
val intent = Intent(context, FloatingWindowService::class.java) |
|
||||
context.startService(intent) |
|
||||
result.success(true) |
|
||||
} catch (e: Exception) { |
|
||||
result.error("ENABLE_ERROR", "Failed to enable floating window: ${e.message}", null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun disableFloatingWindow(result: Result) { |
|
||||
try { |
|
||||
val intent = Intent(context, FloatingWindowService::class.java) |
|
||||
context.stopService(intent) |
|
||||
result.success(true) |
|
||||
} catch (e: Exception) { |
|
||||
result.error("DISABLE_ERROR", "Failed to disable floating window: ${e.message}", null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun showFloatingWindow(result: Result) { |
|
||||
try { |
|
||||
// 服务已经在运行,不需要额外操作 |
|
||||
result.success(true) |
|
||||
} catch (e: Exception) { |
|
||||
result.error("SHOW_ERROR", "Failed to show floating window: ${e.message}", null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun hideFloatingWindow(result: Result) { |
|
||||
try { |
|
||||
FloatingWindowService.serviceInstance?.stopSelf() |
|
||||
result.success(true) |
|
||||
} catch (e: Exception) { |
|
||||
result.error("HIDE_ERROR", "Failed to hide floating window: ${e.message}", null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun updateContent(call: MethodCall, result: Result) { |
|
||||
try { |
|
||||
val args = call.arguments as? Map<String, Any> |
|
||||
if (args != null) { |
|
||||
val intent = Intent(context, FloatingWindowService::class.java).apply { |
|
||||
action = FloatingWindowService.ACTION_UPDATE_CONTENT |
|
||||
putExtra(FloatingWindowService.EXTRA_SOURCE_TEXT, args["sourceText"] as? String ?: "") |
|
||||
putExtra(FloatingWindowService.EXTRA_TRANSLATED_TEXT, args["translatedText"] as? String ?: "") |
|
||||
putExtra(FloatingWindowService.EXTRA_SOURCE_LANGUAGE, args["sourceLanguage"] as? String ?: "") |
|
||||
putExtra(FloatingWindowService.EXTRA_TARGET_LANGUAGE, args["targetLanguage"] as? String ?: "") |
|
||||
putExtra(FloatingWindowService.EXTRA_TIMESTAMP, (args["timestamp"] as? Number)?.toLong() ?: 0L) |
|
||||
putExtra(FloatingWindowService.EXTRA_IS_INTERMEDIATE, args["isIntermediate"] as? Boolean ?: false) |
|
||||
} |
|
||||
context.startService(intent) |
|
||||
} |
|
||||
result.success(true) |
|
||||
} catch (e: Exception) { |
|
||||
result.error("UPDATE_ERROR", "Failed to update content: ${e.message}", null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun setPosition(call: MethodCall, result: Result) { |
|
||||
try { |
|
||||
val args = call.arguments as? Map<String, Any> |
|
||||
if (args != null) { |
|
||||
val intent = Intent(context, FloatingWindowService::class.java).apply { |
|
||||
action = FloatingWindowService.ACTION_SET_POSITION |
|
||||
putExtra(FloatingWindowService.EXTRA_X, (args["x"] as? Number)?.toInt() ?: 0) |
|
||||
putExtra(FloatingWindowService.EXTRA_Y, (args["y"] as? Number)?.toInt() ?: 0) |
|
||||
} |
|
||||
context.startService(intent) |
|
||||
} |
|
||||
result.success(true) |
|
||||
} catch (e: Exception) { |
|
||||
result.error("POSITION_ERROR", "Failed to set position: ${e.message}", null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { |
|
||||
channel.setMethodCallHandler(null) |
|
||||
FloatingWindowService.methodChannel = null |
|
||||
} |
|
||||
} |
|
||||
@ -1,378 +0,0 @@ |
|||||
package com.saitong.voitrans |
|
||||
|
|
||||
import android.app.Service |
|
||||
import android.content.Intent |
|
||||
import android.graphics.PixelFormat |
|
||||
import android.os.Build |
|
||||
import android.os.IBinder |
|
||||
import android.view.* |
|
||||
import android.widget.TextView |
|
||||
import android.widget.ImageView |
|
||||
import android.widget.LinearLayout |
|
||||
import android.widget.FrameLayout |
|
||||
import androidx.cardview.widget.CardView |
|
||||
import io.flutter.plugin.common.MethodChannel |
|
||||
import java.text.SimpleDateFormat |
|
||||
import java.util.* |
|
||||
|
|
||||
class FloatingWindowService : Service() { |
|
||||
private var windowManager: WindowManager? = null |
|
||||
private var floatingView: View? = null |
|
||||
private var isMinimized = true |
|
||||
private lateinit var layoutParams: WindowManager.LayoutParams |
|
||||
|
|
||||
private lateinit var sourceTextView: TextView |
|
||||
private lateinit var translatedTextView: TextView |
|
||||
private lateinit var languageInfoView: TextView |
|
||||
private lateinit var timestampView: TextView |
|
||||
private lateinit var minimizedView: CardView |
|
||||
private lateinit var expandedView: CardView |
|
||||
|
|
||||
companion object { |
|
||||
var methodChannel: MethodChannel? = null |
|
||||
var serviceInstance: FloatingWindowService? = null |
|
||||
|
|
||||
const val ACTION_UPDATE_CONTENT = "UPDATE_CONTENT" |
|
||||
const val ACTION_SET_POSITION = "SET_POSITION" |
|
||||
|
|
||||
const val EXTRA_SOURCE_TEXT = "source_text" |
|
||||
const val EXTRA_TRANSLATED_TEXT = "translated_text" |
|
||||
const val EXTRA_SOURCE_LANGUAGE = "source_language" |
|
||||
const val EXTRA_TARGET_LANGUAGE = "target_language" |
|
||||
const val EXTRA_TIMESTAMP = "timestamp" |
|
||||
const val EXTRA_IS_INTERMEDIATE = "is_intermediate" |
|
||||
const val EXTRA_X = "x" |
|
||||
const val EXTRA_Y = "y" |
|
||||
} |
|
||||
|
|
||||
override fun onCreate() { |
|
||||
super.onCreate() |
|
||||
serviceInstance = this |
|
||||
createFloatingWindow() |
|
||||
} |
|
||||
|
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
|
||||
when (intent?.action) { |
|
||||
ACTION_UPDATE_CONTENT -> updateContent(intent) |
|
||||
ACTION_SET_POSITION -> setPosition(intent) |
|
||||
} |
|
||||
return START_STICKY |
|
||||
} |
|
||||
|
|
||||
override fun onBind(intent: Intent?): IBinder? = null |
|
||||
|
|
||||
private fun createFloatingWindow() { |
|
||||
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager |
|
||||
|
|
||||
// 创建布局 |
|
||||
floatingView = createFloatingLayout() |
|
||||
|
|
||||
// 初始化视图组件 |
|
||||
initViews() |
|
||||
|
|
||||
// 设置点击事件 |
|
||||
setupClickListeners() |
|
||||
|
|
||||
// 设置窗口参数 |
|
||||
layoutParams = WindowManager.LayoutParams().apply { |
|
||||
width = WindowManager.LayoutParams.WRAP_CONTENT |
|
||||
height = WindowManager.LayoutParams.WRAP_CONTENT |
|
||||
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY |
|
||||
} else { |
|
||||
WindowManager.LayoutParams.TYPE_PHONE |
|
||||
} |
|
||||
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or |
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or |
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
|
||||
format = PixelFormat.TRANSLUCENT |
|
||||
gravity = Gravity.TOP or Gravity.START |
|
||||
x = 100 |
|
||||
y = 100 |
|
||||
} |
|
||||
|
|
||||
windowManager?.addView(floatingView, layoutParams) |
|
||||
|
|
||||
// 默认显示最小化状态 |
|
||||
updateViewState(true) |
|
||||
} |
|
||||
|
|
||||
private fun createFloatingLayout(): View { |
|
||||
val context = this |
|
||||
val density = resources.displayMetrics.density |
|
||||
|
|
||||
// 创建根布局 |
|
||||
val rootLayout = FrameLayout(context).apply { |
|
||||
layoutParams = ViewGroup.LayoutParams( |
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, |
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
// 创建最小化视图 |
|
||||
val minimizedCard = CardView(context).apply { |
|
||||
id = View.generateViewId() |
|
||||
layoutParams = FrameLayout.LayoutParams( |
|
||||
(60 * density).toInt(), |
|
||||
(60 * density).toInt() |
|
||||
).apply { |
|
||||
setMargins((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt()) |
|
||||
} |
|
||||
radius = 30 * density |
|
||||
cardElevation = 8 * density |
|
||||
setCardBackgroundColor(0xFFE3F2FD.toInt()) |
|
||||
} |
|
||||
|
|
||||
val minimizedContent = LinearLayout(context).apply { |
|
||||
orientation = LinearLayout.VERTICAL |
|
||||
gravity = Gravity.CENTER |
|
||||
layoutParams = ViewGroup.LayoutParams( |
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, |
|
||||
ViewGroup.LayoutParams.MATCH_PARENT |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
val icon = TextView(context).apply { |
|
||||
text = "译" |
|
||||
textSize = 14f |
|
||||
setTextColor(0xFF1976D2.toInt()) |
|
||||
gravity = Gravity.CENTER |
|
||||
} |
|
||||
|
|
||||
minimizedContent.addView(icon) |
|
||||
minimizedCard.addView(minimizedContent) |
|
||||
|
|
||||
// 创建展开视图 |
|
||||
val expandedCard = CardView(context).apply { |
|
||||
id = View.generateViewId() |
|
||||
layoutParams = FrameLayout.LayoutParams( |
|
||||
(320 * density).toInt(), |
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT |
|
||||
).apply { |
|
||||
setMargins((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt()) |
|
||||
} |
|
||||
radius = 16 * density |
|
||||
cardElevation = 12 * density |
|
||||
setCardBackgroundColor(0xFFFFFFFF.toInt()) |
|
||||
visibility = View.GONE |
|
||||
} |
|
||||
|
|
||||
val expandedContent = LinearLayout(context).apply { |
|
||||
orientation = LinearLayout.VERTICAL |
|
||||
setPadding( |
|
||||
(16 * density).toInt(), |
|
||||
(16 * density).toInt(), |
|
||||
(16 * density).toInt(), |
|
||||
(16 * density).toInt() |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
// 标题栏 |
|
||||
val titleLayout = LinearLayout(context).apply { |
|
||||
orientation = LinearLayout.HORIZONTAL |
|
||||
gravity = Gravity.CENTER_VERTICAL |
|
||||
} |
|
||||
|
|
||||
val titleText = TextView(context).apply { |
|
||||
text = "实时翻译" |
|
||||
textSize = 16f |
|
||||
setTextColor(0xFF333333.toInt()) |
|
||||
layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) |
|
||||
} |
|
||||
|
|
||||
val closeButton = TextView(context).apply { |
|
||||
id = View.generateViewId() |
|
||||
text = "✕" |
|
||||
textSize = 18f |
|
||||
setTextColor(0xFF666666.toInt()) |
|
||||
gravity = Gravity.CENTER |
|
||||
setPadding((8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt(), (8 * density).toInt()) |
|
||||
} |
|
||||
|
|
||||
titleLayout.addView(titleText) |
|
||||
titleLayout.addView(closeButton) |
|
||||
|
|
||||
// 语言信息 |
|
||||
val languageInfo = TextView(context).apply { |
|
||||
id = View.generateViewId() |
|
||||
text = "中文 → 英文" |
|
||||
textSize = 12f |
|
||||
setTextColor(0xFF666666.toInt()) |
|
||||
setPadding(0, (8 * density).toInt(), 0, (8 * density).toInt()) |
|
||||
} |
|
||||
|
|
||||
// 源文本 |
|
||||
val sourceText = TextView(context).apply { |
|
||||
id = View.generateViewId() |
|
||||
text = "正在识别..." |
|
||||
textSize = 14f |
|
||||
setTextColor(0xFF333333.toInt()) |
|
||||
setPadding(0, (8 * density).toInt(), 0, (4 * density).toInt()) |
|
||||
maxLines = 3 |
|
||||
} |
|
||||
|
|
||||
// 翻译文本 |
|
||||
val translatedText = TextView(context).apply { |
|
||||
id = View.generateViewId() |
|
||||
text = "翻译中..." |
|
||||
textSize = 14f |
|
||||
setTextColor(0xFF1976D2.toInt()) |
|
||||
setPadding(0, (4 * density).toInt(), 0, (8 * density).toInt()) |
|
||||
maxLines = 3 |
|
||||
} |
|
||||
|
|
||||
// 时间戳 |
|
||||
val timestamp = TextView(context).apply { |
|
||||
id = View.generateViewId() |
|
||||
text = "" |
|
||||
textSize = 10f |
|
||||
setTextColor(0xFF999999.toInt()) |
|
||||
gravity = Gravity.END |
|
||||
} |
|
||||
|
|
||||
expandedContent.addView(titleLayout) |
|
||||
expandedContent.addView(languageInfo) |
|
||||
expandedContent.addView(sourceText) |
|
||||
expandedContent.addView(translatedText) |
|
||||
expandedContent.addView(timestamp) |
|
||||
expandedCard.addView(expandedContent) |
|
||||
|
|
||||
rootLayout.addView(minimizedCard) |
|
||||
rootLayout.addView(expandedCard) |
|
||||
|
|
||||
// 保存引用 |
|
||||
minimizedView = minimizedCard |
|
||||
expandedView = expandedCard |
|
||||
sourceTextView = sourceText |
|
||||
translatedTextView = translatedText |
|
||||
languageInfoView = languageInfo |
|
||||
timestampView = timestamp |
|
||||
|
|
||||
return rootLayout |
|
||||
} |
|
||||
|
|
||||
private fun initViews() { |
|
||||
// Views are already initialized in createFloatingLayout |
|
||||
} |
|
||||
|
|
||||
private fun setupClickListeners() { |
|
||||
// 长按关闭 |
|
||||
floatingView!!.setOnLongClickListener { |
|
||||
closeFloatingWindow() |
|
||||
true |
|
||||
} |
|
||||
|
|
||||
// 拖拽功能 |
|
||||
setupDragListener() |
|
||||
} |
|
||||
|
|
||||
private fun setupDragListener() { |
|
||||
var initialX = 0 |
|
||||
var initialY = 0 |
|
||||
var initialTouchX = 0f |
|
||||
var initialTouchY = 0f |
|
||||
var dragging = false |
|
||||
val threshold = (4 * resources.displayMetrics.density).toInt() |
|
||||
floatingView!!.setOnTouchListener { _, event -> |
|
||||
when (event.action) { |
|
||||
MotionEvent.ACTION_DOWN -> { |
|
||||
initialX = layoutParams.x |
|
||||
initialY = layoutParams.y |
|
||||
initialTouchX = event.rawX |
|
||||
initialTouchY = event.rawY |
|
||||
dragging = false |
|
||||
true |
|
||||
} |
|
||||
MotionEvent.ACTION_MOVE -> { |
|
||||
val dx = (event.rawX - initialTouchX).toInt() |
|
||||
val dy = (event.rawY - initialTouchY).toInt() |
|
||||
if (Math.abs(dx) > threshold || Math.abs(dy) > threshold) dragging = true |
|
||||
val dm = resources.displayMetrics |
|
||||
val maxX = dm.widthPixels - (floatingView?.width ?: 0) |
|
||||
val maxY = dm.heightPixels - (floatingView?.height ?: 0) |
|
||||
layoutParams.x = (initialX + dx).coerceIn(0, maxX) |
|
||||
layoutParams.y = (initialY + dy).coerceIn(0, maxY) |
|
||||
windowManager?.updateViewLayout(floatingView, layoutParams) |
|
||||
true |
|
||||
} |
|
||||
MotionEvent.ACTION_UP -> { |
|
||||
if (!dragging) { |
|
||||
isMinimized = !isMinimized |
|
||||
updateViewState(isMinimized) |
|
||||
notifyStateChange(if (isMinimized) "minimized" else "expanded") |
|
||||
} |
|
||||
dragging = false |
|
||||
true |
|
||||
} |
|
||||
else -> true |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun updateViewState(minimized: Boolean) { |
|
||||
if (minimized) { |
|
||||
minimizedView.visibility = View.VISIBLE |
|
||||
expandedView.visibility = View.GONE |
|
||||
} else { |
|
||||
minimizedView.visibility = View.GONE |
|
||||
expandedView.visibility = View.VISIBLE |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun updateContent(intent: Intent) { |
|
||||
val sourceText = intent.getStringExtra(EXTRA_SOURCE_TEXT) ?: "" |
|
||||
val translatedText = intent.getStringExtra(EXTRA_TRANSLATED_TEXT) ?: "" |
|
||||
val sourceLanguage = intent.getStringExtra(EXTRA_SOURCE_LANGUAGE) ?: "" |
|
||||
val targetLanguage = intent.getStringExtra(EXTRA_TARGET_LANGUAGE) ?: "" |
|
||||
val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, 0) |
|
||||
val isIntermediate = intent.getBooleanExtra(EXTRA_IS_INTERMEDIATE, false) |
|
||||
|
|
||||
// 更新UI |
|
||||
sourceTextView.text = if (sourceText.isNotEmpty()) sourceText else "正在识别..." |
|
||||
translatedTextView.text = if (translatedText.isNotEmpty()) translatedText else "翻译中..." |
|
||||
languageInfoView.text = "$sourceLanguage → $targetLanguage" |
|
||||
|
|
||||
if (timestamp > 0) { |
|
||||
val dateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) |
|
||||
timestampView.text = dateFormat.format(Date(timestamp)) |
|
||||
} |
|
||||
|
|
||||
// 如果是中间结果,可以添加一些视觉提示 |
|
||||
if (isIntermediate) { |
|
||||
translatedTextView.alpha = 0.7f |
|
||||
} else { |
|
||||
translatedTextView.alpha = 1.0f |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun setPosition(intent: Intent) { |
|
||||
val x = intent.getIntExtra(EXTRA_X, 0) |
|
||||
val y = intent.getIntExtra(EXTRA_Y, 0) |
|
||||
|
|
||||
floatingView?.let { |
|
||||
layoutParams.x = x |
|
||||
layoutParams.y = y |
|
||||
windowManager?.updateViewLayout(it, layoutParams) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun closeFloatingWindow() { |
|
||||
stopSelf() |
|
||||
notifyFloatingWindowClosed() |
|
||||
} |
|
||||
|
|
||||
private fun notifyStateChange(state: String) { |
|
||||
methodChannel?.invokeMethod("onFloatingWindowStateChanged", mapOf("state" to state)) |
|
||||
} |
|
||||
|
|
||||
private fun notifyFloatingWindowClosed() { |
|
||||
methodChannel?.invokeMethod("onFloatingWindowClosed", null) |
|
||||
} |
|
||||
|
|
||||
override fun onDestroy() { |
|
||||
floatingView?.let { windowManager?.removeView(it) } |
|
||||
serviceInstance = null |
|
||||
methodChannel = null |
|
||||
super.onDestroy() |
|
||||
} |
|
||||
} |
|
||||
@ -1,99 +0,0 @@ |
|||||
package com.saitong.voitrans |
|
||||
|
|
||||
import android.content.Context |
|
||||
import android.content.Intent |
|
||||
import android.net.Uri |
|
||||
import android.os.Build |
|
||||
import android.content.pm.PackageManager |
|
||||
import android.provider.Settings |
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin |
|
||||
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 |
|
||||
|
|
||||
class OverlayPermissionPlugin: FlutterPlugin, MethodCallHandler { |
|
||||
private lateinit var channel: MethodChannel |
|
||||
private lateinit var context: Context |
|
||||
|
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { |
|
||||
context = flutterPluginBinding.applicationContext |
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "overlay_permission") |
|
||||
channel.setMethodCallHandler(this) |
|
||||
} |
|
||||
|
|
||||
override fun onMethodCall(call: MethodCall, result: Result) { |
|
||||
when (call.method) { |
|
||||
"hasOverlayPermission" -> { |
|
||||
result.success(hasOverlayPermission()) |
|
||||
} |
|
||||
"requestOverlayPermission" -> { |
|
||||
requestOverlayPermission(result) |
|
||||
} |
|
||||
else -> { |
|
||||
result.notImplemented() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun hasOverlayPermission(): Boolean { |
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
||||
Settings.canDrawOverlays(context) |
|
||||
} else { |
|
||||
true |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun requestOverlayPermission(result: Result) { |
|
||||
try { |
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
||||
if (hasOverlayPermission()) { |
|
||||
result.success(true) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
val openedBySystem = startActivitySafe( |
|
||||
Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply { |
|
||||
data = Uri.parse("package:${context.packageName}") |
|
||||
} |
|
||||
) |
|
||||
if (openedBySystem) { |
|
||||
result.success(true) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
val openedByAppDetails = startActivitySafe( |
|
||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { |
|
||||
data = Uri.parse("package:${context.packageName}") |
|
||||
} |
|
||||
) |
|
||||
if (openedByAppDetails) { |
|
||||
result.success(true) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
result.success(false) |
|
||||
} else { |
|
||||
result.success(true) |
|
||||
} |
|
||||
} catch (e: Exception) { |
|
||||
result.error("REQUEST_ERROR", "Failed to request overlay permission: ${e.message}", null) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private fun startActivitySafe(intent: Intent): Boolean { |
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
|
||||
val pm: PackageManager = context.packageManager |
|
||||
if (intent.resolveActivity(pm) == null) return false |
|
||||
return try { |
|
||||
context.startActivity(intent) |
|
||||
true |
|
||||
} catch (_: Exception) { |
|
||||
false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { |
|
||||
channel.setMethodCallHandler(null) |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue