Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions app/src/main/java/com/petterp/floatingx/app/kotlin/EdgeCaseTests.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
}
}
31 changes: 31 additions & 0 deletions app/src/main/java/com/petterp/floatingx/app/test/SystemActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -33,6 +34,7 @@ abstract class FxBasisControlImp<F : FxBasisHelper, P : IFxPlatformProvider<F>>(
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -43,6 +44,9 @@ interface IFxControl {
/** 获取浮窗管理器view,即浮窗底层容器 */
fun getManagerView(): FrameLayout?

/** 获取WindowManager.LayoutParams,仅在系统悬浮窗时返回非null值,用于动态修改窗口属性如焦点控制等 */
fun getWindowManagerLayoutParams(): WindowManager.LayoutParams?

/** 用于快速刷新视图内容 */
fun updateViewContent(provider: IFxHolderProvider)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down