From fe5388f4002781945e3eb1e8c041c650f5695c95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 03:22:54 +0000 Subject: [PATCH 1/3] Initial plan From 015506ceda439a3252c18acf63951ecbdf94d542 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 03:29:39 +0000 Subject: [PATCH 2/3] Implement getWindowManagerLayoutParams() method in IFxControl interface Co-authored-by: Petterpx <41142188+Petterpx@users.noreply.github.com> --- .../kotlin/TestWindowManagerLayoutParams.kt | 85 +++++++++++++++++++ .../floatingx/imp/FxBasisControlImp.kt | 2 + .../floatingx/listener/control/IFxControl.kt | 4 + .../floatingx/view/FxBasicContainerView.kt | 2 + .../floatingx/view/FxSystemContainerView.kt | 2 + .../floatingx/view/IFxInternalHelper.kt | 3 + 6 files changed, 98 insertions(+) create mode 100644 app/src/main/java/com/petterp/floatingx/app/kotlin/TestWindowManagerLayoutParams.kt diff --git a/app/src/main/java/com/petterp/floatingx/app/kotlin/TestWindowManagerLayoutParams.kt b/app/src/main/java/com/petterp/floatingx/app/kotlin/TestWindowManagerLayoutParams.kt new file mode 100644 index 00000000..43516c5b --- /dev/null +++ b/app/src/main/java/com/petterp/floatingx/app/kotlin/TestWindowManagerLayoutParams.kt @@ -0,0 +1,85 @@ +package com.petterp.floatingx.app.kotlin + +import android.content.Context +import android.util.Log +import android.view.WindowManager +import com.petterp.floatingx.listener.control.IFxControl + +/** + * Test class to demonstrate the new getWindowManagerLayoutParams() functionality + * + * This example shows how to request and lose focus for floating windows, + * which was the main use case mentioned in the issue. + */ +object TestWindowManagerLayoutParams { + + /** + * Request focus for a floating window by modifying its WindowManager.LayoutParams + * This matches the user's requested functionality from the issue + */ + fun requestFocusFloatingView(fxControl: IFxControl, context: Context) { + try { + val managerView = fxControl.getManagerView() + val layoutParams = fxControl.getWindowManagerLayoutParams() // NEW METHOD + + if (layoutParams == null) { + Log.w("FloatingPro", "WindowManager.LayoutParams not available - probably an app-level floating window") + return + } + + layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + + managerView?.post { + val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + wm.updateViewLayout(managerView, layoutParams) + Log.d("FloatingPro", "Successfully requested focus for floating window") + } + } catch (e: Exception) { + Log.e("FloatingPro", "Error requesting focus: ${e.message}") + } + } + + /** + * Remove focus from a floating window by modifying its WindowManager.LayoutParams + * This matches the user's requested functionality from the issue + */ + fun loseFocusFloatingView(fxControl: IFxControl, context: Context) { + try { + val managerView = fxControl.getManagerView() + val layoutParams = fxControl.getWindowManagerLayoutParams() // NEW METHOD + + if (layoutParams == null) { + Log.w("FloatingPro", "WindowManager.LayoutParams not available - probably an app-level floating window") + return + } + + layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + + managerView?.post { + val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + wm.updateViewLayout(managerView, layoutParams) + Log.d("FloatingPro", "Successfully removed focus from floating window") + } + } catch (e: Exception) { + Log.e("FloatingPro", "Error losing focus: ${e.message}") + } + } + + /** + * Example function to demonstrate checking if WindowManager.LayoutParams are available + */ + fun checkWindowManagerLayoutParamsAvailability(fxControl: IFxControl) { + val layoutParams = fxControl.getWindowManagerLayoutParams() + if (layoutParams != null) { + Log.i("FloatingPro", "System floating window detected - WindowManager.LayoutParams available") + Log.d("FloatingPro", "Current flags: ${layoutParams.flags}") + Log.d("FloatingPro", "Current type: ${layoutParams.type}") + } else { + Log.i("FloatingPro", "App-level floating window detected - WindowManager.LayoutParams not available") + } + } +} \ No newline at end of file diff --git a/floatingx/src/main/java/com/petterp/floatingx/imp/FxBasisControlImp.kt b/floatingx/src/main/java/com/petterp/floatingx/imp/FxBasisControlImp.kt index 5c3ed6d4..ae637f4e 100644 --- a/floatingx/src/main/java/com/petterp/floatingx/imp/FxBasisControlImp.kt +++ b/floatingx/src/main/java/com/petterp/floatingx/imp/FxBasisControlImp.kt @@ -1,6 +1,7 @@ package com.petterp.floatingx.imp import android.view.View +import android.view.WindowManager import androidx.annotation.LayoutRes import com.petterp.floatingx.assist.helper.FxBasisHelper import com.petterp.floatingx.listener.control.IFxConfigControl @@ -33,6 +34,7 @@ abstract class FxBasisControlImp>( override fun getView() = internalView?.childView override fun getViewHolder() = internalView?.viewHolder override fun getManagerView() = internalView?.containerView + override fun getWindowManagerLayoutParams() = internalView?.windowManagerLayoutParams abstract fun createPlatformProvider(f: F): P open fun createConfigProvider(f: F, p: P): IFxConfigControl = FxBasicConfigProvider(f, p) diff --git a/floatingx/src/main/java/com/petterp/floatingx/listener/control/IFxControl.kt b/floatingx/src/main/java/com/petterp/floatingx/listener/control/IFxControl.kt index 094671d3..f2ff2be3 100644 --- a/floatingx/src/main/java/com/petterp/floatingx/listener/control/IFxControl.kt +++ b/floatingx/src/main/java/com/petterp/floatingx/listener/control/IFxControl.kt @@ -1,6 +1,7 @@ package com.petterp.floatingx.listener.control import android.view.View +import android.view.WindowManager import android.widget.FrameLayout import androidx.annotation.LayoutRes import com.petterp.floatingx.listener.provider.IFxContextProvider @@ -43,6 +44,9 @@ interface IFxControl { /** 获取浮窗管理器view,即浮窗底层容器 */ fun getManagerView(): FrameLayout? + /** 获取WindowManager.LayoutParams,仅在系统悬浮窗时返回非null值,用于动态修改窗口属性如焦点控制等 */ + fun getWindowManagerLayoutParams(): WindowManager.LayoutParams? + /** 用于快速刷新视图内容 */ fun updateViewContent(provider: IFxHolderProvider) diff --git a/floatingx/src/main/java/com/petterp/floatingx/view/FxBasicContainerView.kt b/floatingx/src/main/java/com/petterp/floatingx/view/FxBasicContainerView.kt index 73b5c1c3..1eef9fdd 100644 --- a/floatingx/src/main/java/com/petterp/floatingx/view/FxBasicContainerView.kt +++ b/floatingx/src/main/java/com/petterp/floatingx/view/FxBasicContainerView.kt @@ -7,6 +7,7 @@ import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.view.WindowManager import android.widget.FrameLayout import com.petterp.floatingx.assist.helper.FxBasisHelper import com.petterp.floatingx.util.INVALID_LAYOUT_ID @@ -46,6 +47,7 @@ abstract class FxBasicContainerView @JvmOverloads constructor( override val childView: View? get() = _childView override val containerView: FrameLayout get() = this override val viewHolder: FxViewHolder? get() = _viewHolder + override val windowManagerLayoutParams: WindowManager.LayoutParams? get() = null open fun initView() { diff --git a/floatingx/src/main/java/com/petterp/floatingx/view/FxSystemContainerView.kt b/floatingx/src/main/java/com/petterp/floatingx/view/FxSystemContainerView.kt index 68734efc..2baaa43f 100644 --- a/floatingx/src/main/java/com/petterp/floatingx/view/FxSystemContainerView.kt +++ b/floatingx/src/main/java/com/petterp/floatingx/view/FxSystemContainerView.kt @@ -51,6 +51,8 @@ class FxSystemContainerView @JvmOverloads constructor( override fun getY() = wl.y.toFloat() + override val windowManagerLayoutParams: WindowManager.LayoutParams? get() = if (::wl.isInitialized) wl else null + override fun preCheckPointerDownTouch(event: MotionEvent): Boolean { // 当前屏幕存在手指时,check当前手势是否真的在浮窗之上 return checkPointerDownTouch(this, event) diff --git a/floatingx/src/main/java/com/petterp/floatingx/view/IFxInternalHelper.kt b/floatingx/src/main/java/com/petterp/floatingx/view/IFxInternalHelper.kt index 11e696df..a9f78e91 100644 --- a/floatingx/src/main/java/com/petterp/floatingx/view/IFxInternalHelper.kt +++ b/floatingx/src/main/java/com/petterp/floatingx/view/IFxInternalHelper.kt @@ -2,6 +2,7 @@ package com.petterp.floatingx.view import android.view.MotionEvent import android.view.View +import android.view.WindowManager import android.widget.FrameLayout import androidx.annotation.IdRes import androidx.annotation.LayoutRes @@ -18,6 +19,8 @@ interface IFxInternalHelper { val viewHolder: FxViewHolder? + val windowManagerLayoutParams: WindowManager.LayoutParams? + fun moveLocation(x: Float, y: Float, useAnimation: Boolean = true) fun moveLocationByVector(x: Float, y: Float, useAnimation: Boolean = true) From 525c0725f0f1ca8f5451a9218f89af08f684a8a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Sep 2025 03:32:53 +0000 Subject: [PATCH 3/3] Add comprehensive tests and demo for getWindowManagerLayoutParams() functionality Co-authored-by: Petterpx <41142188+Petterpx@users.noreply.github.com> --- .../floatingx/app/kotlin/EdgeCaseTests.kt | 131 ++++++++++++++++++ .../floatingx/app/test/SystemActivity.kt | 31 +++++ 2 files changed, 162 insertions(+) create mode 100644 app/src/main/java/com/petterp/floatingx/app/kotlin/EdgeCaseTests.kt diff --git a/app/src/main/java/com/petterp/floatingx/app/kotlin/EdgeCaseTests.kt b/app/src/main/java/com/petterp/floatingx/app/kotlin/EdgeCaseTests.kt new file mode 100644 index 00000000..8026310f --- /dev/null +++ b/app/src/main/java/com/petterp/floatingx/app/kotlin/EdgeCaseTests.kt @@ -0,0 +1,131 @@ +package com.petterp.floatingx.app.kotlin + +import android.content.Context +import android.util.Log +import com.petterp.floatingx.FloatingX +import com.petterp.floatingx.listener.control.IFxControl + +/** + * Edge case tests for the new getWindowManagerLayoutParams() functionality + */ +object EdgeCaseTests { + + /** + * Test accessing WindowManager.LayoutParams on various control types + */ + fun testAllControlTypes(context: Context) { + Log.d("EdgeCaseTests", "Starting comprehensive tests...") + + // Test global controls + FloatingX.allControls().forEach { (tag, control) -> + testSingleControl(tag, control) + } + + // Test null control + val nullControl = FloatingX.controlOrNull("non_existent_tag") + if (nullControl == null) { + Log.d("EdgeCaseTests", "✅ Non-existent control returns null as expected") + } else { + Log.w("EdgeCaseTests", "❌ Non-existent control should return null") + } + } + + /** + * Test a single control for WindowManager.LayoutParams availability + */ + private fun testSingleControl(tag: String, control: IFxControl) { + try { + val layoutParams = control.getWindowManagerLayoutParams() + val managerView = control.getManagerView() + val isShowing = control.isShow() + + Log.d("EdgeCaseTests", "Testing control '$tag':") + Log.d("EdgeCaseTests", " - IsShowing: $isShowing") + Log.d("EdgeCaseTests", " - ManagerView: ${managerView != null}") + Log.d("EdgeCaseTests", " - LayoutParams: ${layoutParams != null}") + + if (layoutParams != null) { + Log.d("EdgeCaseTests", " - Flags: ${layoutParams.flags}") + Log.d("EdgeCaseTests", " - Type: ${layoutParams.type}") + Log.d("EdgeCaseTests", " - This appears to be a system floating window") + } else { + Log.d("EdgeCaseTests", " - This appears to be an app-level floating window or uninitialized") + } + + } catch (e: Exception) { + Log.e("EdgeCaseTests", "Error testing control '$tag': ${e.message}", e) + } + } + + /** + * Test safe modification of WindowManager.LayoutParams + */ + fun testSafeModification(control: IFxControl, context: Context): Boolean { + return try { + val originalLayoutParams = control.getWindowManagerLayoutParams() + if (originalLayoutParams == null) { + Log.i("EdgeCaseTests", "No WindowManager.LayoutParams to modify (app-level window)") + return true + } + + // Store original flags + val originalFlags = originalLayoutParams.flags + Log.d("EdgeCaseTests", "Original flags: $originalFlags") + + // Test modification + val newFlags = originalFlags or android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + originalLayoutParams.flags = newFlags + Log.d("EdgeCaseTests", "Modified flags: ${originalLayoutParams.flags}") + + // Verify change took effect + val changeApplied = originalLayoutParams.flags == newFlags + Log.d("EdgeCaseTests", "Change applied successfully: $changeApplied") + + // Restore original flags + originalLayoutParams.flags = originalFlags + Log.d("EdgeCaseTests", "Restored flags: ${originalLayoutParams.flags}") + + true + } catch (e: Exception) { + Log.e("EdgeCaseTests", "Error during safe modification: ${e.message}", e) + false + } + } + + /** + * Test behavior when control is hidden/shown + */ + fun testShowHideBehavior(control: IFxControl) { + Log.d("EdgeCaseTests", "Testing show/hide behavior...") + + val initialLayoutParams = control.getWindowManagerLayoutParams() + val wasShowing = control.isShow() + + try { + if (wasShowing) { + control.hide() + val hiddenLayoutParams = control.getWindowManagerLayoutParams() + Log.d("EdgeCaseTests", "Hidden - LayoutParams available: ${hiddenLayoutParams != null}") + + control.show() + val shownLayoutParams = control.getWindowManagerLayoutParams() + Log.d("EdgeCaseTests", "Shown - LayoutParams available: ${shownLayoutParams != null}") + } else { + control.show() + val shownLayoutParams = control.getWindowManagerLayoutParams() + Log.d("EdgeCaseTests", "Shown - LayoutParams available: ${shownLayoutParams != null}") + + control.hide() + val hiddenLayoutParams = control.getWindowManagerLayoutParams() + Log.d("EdgeCaseTests", "Hidden - LayoutParams available: ${hiddenLayoutParams != null}") + } + } finally { + // Restore original state + if (wasShowing) { + control.show() + } else { + control.hide() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/petterp/floatingx/app/test/SystemActivity.kt b/app/src/main/java/com/petterp/floatingx/app/test/SystemActivity.kt index 79fb049b..1e1f8ce4 100644 --- a/app/src/main/java/com/petterp/floatingx/app/test/SystemActivity.kt +++ b/app/src/main/java/com/petterp/floatingx/app/test/SystemActivity.kt @@ -56,6 +56,37 @@ class SystemActivity : AppCompatActivity() { FloatingX.controlOrNull(MultipleFxActivity.TAG_1) ?.updateView(R.layout.item_floating_new) } + + // Test the new getWindowManagerLayoutParams() functionality + addItemView("测试 WindowManager.LayoutParams 获取") { + val control = FloatingX.controlOrNull(MultipleFxActivity.TAG_1) + if (control != null) { + val layoutParams = control.getWindowManagerLayoutParams() + if (layoutParams != null) { + android.util.Log.i("SystemActivity", "系统悬浮窗 - 成功获取 WindowManager.LayoutParams") + android.util.Log.d("SystemActivity", "当前 flags: ${layoutParams.flags}") + android.util.Log.d("SystemActivity", "当前 type: ${layoutParams.type}") + } else { + android.util.Log.i("SystemActivity", "非系统悬浮窗 - WindowManager.LayoutParams 不可用") + } + } + } + + addItemView("请求悬浮窗焦点 (演示用例)") { + val control = FloatingX.controlOrNull(MultipleFxActivity.TAG_1) + if (control != null) { + com.petterp.floatingx.app.kotlin.TestWindowManagerLayoutParams + .requestFocusFloatingView(control, this@SystemActivity) + } + } + + addItemView("移除悬浮窗焦点 (演示用例)") { + val control = FloatingX.controlOrNull(MultipleFxActivity.TAG_1) + if (control != null) { + com.petterp.floatingx.app.kotlin.TestWindowManagerLayoutParams + .loseFocusFloatingView(control, this@SystemActivity) + } + } } } }