应用程序如何检测当前正在使用 SAW 权限?
How do apps detect that SAW permission is currently being used?
背景
我多次注意到,当我使用使用 SAW(系统警报-window,又名“显示在其他应用程序之上”)权限的应用程序(示例 here), and I open Chrome web browser and reach some website that requires a permission (example here, taken from here)时,它不会让我 grant/deny 权限:
问题
我找不到他们是怎么做到的,以及从哪个 Android 版本可以检查它。
我发现了什么
遗憾的是,尽管我搜索了很多,但实际上我还有更多问题。
例如,网络浏览器为何无法检测到哪个应用程序显示在顶部,并告诉我们禁用它?
或者,现在 Android 12 可能会到来,似乎有一个新的权限来阻止 SAW (here) :
HIDE_OVERLAY_WINDOWS Added in Android S
public static final String HIDE_OVERLAY_WINDOWS Allows an app to
prevent non-system-overlay windows from being drawn on top of it
Constant Value: "android.permission.HIDE_OVERLAY_WINDOWS"
也许对于这种情况,没有多少人问过它,或者出于某种原因我没有选择正确的东西来寻找答案。
问题
如何在我显示某些内容时检测某些应用程序是否正在使用 SAW 权限?
有没有办法检测出是哪个应用做的?
API 可以提供什么,从哪个版本可以使用?
我记得有人告诉我辅助功能可以用来在上面绘图。遗憾的是,我没有找到有关如何执行此操作的教程,也没有找到此类应用程序的示例。这个 API 也能检测到它们吗?或者这不被视为 SAW?在哪里可以找到有关如何操作的教程,以便我查看?
奖励:Android你如何使用新权限隐藏 SAW?
实际上,您可以询问 MotionEvents window 是否被遮挡(警告显示在它们上方),这样您就可以知道自己被遮挡了,但您无法判断哪个应用正在执行此操作
有一些类似的问题需要解释
How to detect when my Activity has been obscured?
Android detect or block floating/overlaying apps
我不确定第 4 项,但通常使用辅助功能来重新显示屏幕并向您展示一些神奇的东西(比如那个被禁止的 Voodoo 应用程序)的应用程序也需要 SAW 许可,据我所知,辅助功能服务只是一些回调。
- 您将权限放在清单中,当您想隐藏 SAW 时只需调用
setHideOverlayWindows(true)
https://developer.android.google.cn/about/versions/12/features#hide-application-overlay-windows
- 对于 API 29+,有一种使用
FLAG_WINDOW_IS_OBSCURED
(or FLAG_WINDOW_IS_PARTIALLY_OBSCURED
来检测 window 叠加层的常用方法:
https://whosebug.com/search?q=FLAG_WINDOW_IS_OBSCURED\
- 没有API检测特定应用程序。
关于Chrome,可以在源码中找到实现:
ModalDialogView.java#L203
好的,我制作了一个示例来展示检测它的所有方法,包括当您使用新的 API (setHideOverlayWindows) 隐藏顶部显示的应用程序时会发生什么:
- https://developer.android.com/reference/android/app/Activity#dispatchTouchEvent(android.view.MotionEvent)
- https://developer.android.com/reference/android/view/View#setOnTouchListener(android.view.View.OnTouchListener)
- https://developer.android.com/reference/android/view/View#onFilterTouchEventForSecurity(android.view.MotionEvent)
清单
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lb.detect_floating_app">
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<application
android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"
android:theme="@style/Theme.DetectFloatingApp">
<activity
android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.kt
class MainActivity : AppCompatActivity() {
var isObscured: Boolean? = null
var isPartiallyObscured: Boolean? = null
var isHidingFloatingApp = false
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.textView).setOnTouchListener(object : View.OnTouchListener {
var isObscured: Boolean? = null
var isPartiallyObscured: Boolean? = null
override fun onTouch(p0: View?, ev: MotionEvent): Boolean {
val checkIsObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
val checkIsPartiallyObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0
if (checkIsObscured != isObscured || checkIsPartiallyObscured != isPartiallyObscured) {
isObscured = checkIsObscured
isPartiallyObscured = checkIsPartiallyObscured
Log.d("AppLog", "${System.currentTimeMillis()} setOnTouchListener isObscured:$isObscured isPartiallyObscured:$isPartiallyObscured")
}
return false
}
})
findViewById<View>(R.id.toggleHideFloatingAppButton).setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
isHidingFloatingApp = !isHidingFloatingApp
window.setHideOverlayWindows(isHidingFloatingApp)
} else
Toast.makeText(this, "need Android API 31", Toast.LENGTH_SHORT).show()
}
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
val checkIsObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
val checkIsPartiallyObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0
if (checkIsObscured != isObscured || checkIsPartiallyObscured != isPartiallyObscured) {
isObscured = checkIsObscured
isPartiallyObscured = checkIsPartiallyObscured
Log.d("AppLog", "${System.currentTimeMillis()} dispatchTouchEvent isObscured:$isObscured isPartiallyObscured:$isPartiallyObscured")
}
return super.dispatchTouchEvent(ev)
}
}
FloatingDetectorFrameLayout.kt
class FloatingDetectorFrameLayout : FrameLayout {
var isObscured: Boolean? = null
var isPartiallyObscured: Boolean? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
override fun onFilterTouchEventForSecurity(ev: MotionEvent): Boolean {
val checkIsObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
val checkIsPartiallyObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0
if (checkIsObscured != isObscured || checkIsPartiallyObscured != isPartiallyObscured) {
isObscured = checkIsObscured
isPartiallyObscured = checkIsPartiallyObscured
Log.d("AppLog", "${System.currentTimeMillis()} onFilterTouchEventForSecurity isObscured:$isObscured isPartiallyObscured:$isPartiallyObscured")
}
return super.onFilterTouchEventForSecurity(ev)
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"
android:orientation="vertical" tools:context=".MainActivity">
<com.lb.detect_floating_app.FloatingDetectorFrameLayout
android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#f00"
android:padding="100dp">
<Button
android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.lb.detect_floating_app.FloatingDetectorFrameLayout>
<Button android:id="@+id/toggleHideFloatingAppButton"
android:text="toggle hide floating app"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>
背景
我多次注意到,当我使用使用 SAW(系统警报-window,又名“显示在其他应用程序之上”)权限的应用程序(示例 here), and I open Chrome web browser and reach some website that requires a permission (example here, taken from here)时,它不会让我 grant/deny 权限:
问题
我找不到他们是怎么做到的,以及从哪个 Android 版本可以检查它。
我发现了什么
遗憾的是,尽管我搜索了很多,但实际上我还有更多问题。
例如,网络浏览器为何无法检测到哪个应用程序显示在顶部,并告诉我们禁用它?
或者,现在 Android 12 可能会到来,似乎有一个新的权限来阻止 SAW (here) :
HIDE_OVERLAY_WINDOWS Added in Android S
public static final String HIDE_OVERLAY_WINDOWS Allows an app to prevent non-system-overlay windows from being drawn on top of it
Constant Value: "android.permission.HIDE_OVERLAY_WINDOWS"
也许对于这种情况,没有多少人问过它,或者出于某种原因我没有选择正确的东西来寻找答案。
问题
如何在我显示某些内容时检测某些应用程序是否正在使用 SAW 权限?
有没有办法检测出是哪个应用做的?
API 可以提供什么,从哪个版本可以使用?
我记得有人告诉我辅助功能可以用来在上面绘图。遗憾的是,我没有找到有关如何执行此操作的教程,也没有找到此类应用程序的示例。这个 API 也能检测到它们吗?或者这不被视为 SAW?在哪里可以找到有关如何操作的教程,以便我查看?
奖励:Android你如何使用新权限隐藏 SAW?
实际上,您可以询问 MotionEvents window 是否被遮挡(警告显示在它们上方),这样您就可以知道自己被遮挡了,但您无法判断哪个应用正在执行此操作
有一些类似的问题需要解释 How to detect when my Activity has been obscured?
Android detect or block floating/overlaying apps
我不确定第 4 项,但通常使用辅助功能来重新显示屏幕并向您展示一些神奇的东西(比如那个被禁止的 Voodoo 应用程序)的应用程序也需要 SAW 许可,据我所知,辅助功能服务只是一些回调。
- 您将权限放在清单中,当您想隐藏 SAW 时只需调用
setHideOverlayWindows(true)
https://developer.android.google.cn/about/versions/12/features#hide-application-overlay-windows
- 对于 API 29+,有一种使用
FLAG_WINDOW_IS_OBSCURED
(orFLAG_WINDOW_IS_PARTIALLY_OBSCURED
来检测 window 叠加层的常用方法:
https://whosebug.com/search?q=FLAG_WINDOW_IS_OBSCURED\ - 没有API检测特定应用程序。
关于Chrome,可以在源码中找到实现: ModalDialogView.java#L203
好的,我制作了一个示例来展示检测它的所有方法,包括当您使用新的 API (setHideOverlayWindows) 隐藏顶部显示的应用程序时会发生什么:
- https://developer.android.com/reference/android/app/Activity#dispatchTouchEvent(android.view.MotionEvent)
- https://developer.android.com/reference/android/view/View#setOnTouchListener(android.view.View.OnTouchListener)
- https://developer.android.com/reference/android/view/View#onFilterTouchEventForSecurity(android.view.MotionEvent)
清单
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lb.detect_floating_app">
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<application
android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"
android:theme="@style/Theme.DetectFloatingApp">
<activity
android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.kt
class MainActivity : AppCompatActivity() {
var isObscured: Boolean? = null
var isPartiallyObscured: Boolean? = null
var isHidingFloatingApp = false
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.textView).setOnTouchListener(object : View.OnTouchListener {
var isObscured: Boolean? = null
var isPartiallyObscured: Boolean? = null
override fun onTouch(p0: View?, ev: MotionEvent): Boolean {
val checkIsObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
val checkIsPartiallyObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0
if (checkIsObscured != isObscured || checkIsPartiallyObscured != isPartiallyObscured) {
isObscured = checkIsObscured
isPartiallyObscured = checkIsPartiallyObscured
Log.d("AppLog", "${System.currentTimeMillis()} setOnTouchListener isObscured:$isObscured isPartiallyObscured:$isPartiallyObscured")
}
return false
}
})
findViewById<View>(R.id.toggleHideFloatingAppButton).setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
isHidingFloatingApp = !isHidingFloatingApp
window.setHideOverlayWindows(isHidingFloatingApp)
} else
Toast.makeText(this, "need Android API 31", Toast.LENGTH_SHORT).show()
}
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
val checkIsObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
val checkIsPartiallyObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0
if (checkIsObscured != isObscured || checkIsPartiallyObscured != isPartiallyObscured) {
isObscured = checkIsObscured
isPartiallyObscured = checkIsPartiallyObscured
Log.d("AppLog", "${System.currentTimeMillis()} dispatchTouchEvent isObscured:$isObscured isPartiallyObscured:$isPartiallyObscured")
}
return super.dispatchTouchEvent(ev)
}
}
FloatingDetectorFrameLayout.kt
class FloatingDetectorFrameLayout : FrameLayout {
var isObscured: Boolean? = null
var isPartiallyObscured: Boolean? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
override fun onFilterTouchEventForSecurity(ev: MotionEvent): Boolean {
val checkIsObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0
val checkIsPartiallyObscured = ev.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0
if (checkIsObscured != isObscured || checkIsPartiallyObscured != isPartiallyObscured) {
isObscured = checkIsObscured
isPartiallyObscured = checkIsPartiallyObscured
Log.d("AppLog", "${System.currentTimeMillis()} onFilterTouchEventForSecurity isObscured:$isObscured isPartiallyObscured:$isPartiallyObscured")
}
return super.onFilterTouchEventForSecurity(ev)
}
}
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"
android:orientation="vertical" tools:context=".MainActivity">
<com.lb.detect_floating_app.FloatingDetectorFrameLayout
android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#f00"
android:padding="100dp">
<Button
android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.lb.detect_floating_app.FloatingDetectorFrameLayout>
<Button android:id="@+id/toggleHideFloatingAppButton"
android:text="toggle hide floating app"
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>