Android 监听状态栏开发

引入库:

[versions]  
lifecycleProcess = "2.8.3"  
  
[libraries]
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleProcess" }

App:

import android.app.Application  
import androidx.lifecycle.ProcessLifecycleOwner  
  
class App : Application() {  
    companion object {  
        private const val TAG = "App"  
    }  
  
    override fun onCreate() {  
        super.onCreate()  
        init()  
    }  
  
    private fun init() {  
        // 监听Activity数量变化  
        registerActivityLifecycleCallbacks(AppLifecycleMonitor())  
        // 注册生命周期监听  
        ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())  
    }  
}

AppLifecycleMonitor:

import android.app.Activity  
import android.app.Application  
import android.os.Bundle  
import android.util.Log  
import com.small.ball.assistant.ScreenEventManager  
import java.util.concurrent.atomic.AtomicInteger  
  
class AppLifecycleMonitor : Application.ActivityLifecycleCallbacks {  
    /**  
     * 前台可见的 Activity 数量  
     */  
    private val foregroundCount = AtomicInteger(0)  
  
    companion object {  
        private const val TAG = "AppLifecycleTracker"  
    }  
  
    override fun onActivityCreated(  
        activity: Activity,  
        savedInstanceState: Bundle?  
    ) {  
        Log.d(TAG, "onActivityCreated: ${foregroundCount.get()}")  
    }  
  
    override fun onActivityStarted(activity: Activity) {  
        Log.d(TAG, "onActivityStarted before: ${foregroundCount.get()}")  
        foregroundCount.incrementAndGet()  
        Log.d(TAG, "onActivityStarted after: ${foregroundCount.get()}")  
        if (foregroundCount.get() == 1) {  
            Log.d(TAG, "onActivityStarted: 应用从后台切到前台")  
            ScreenEventManager.onAppForeground()  
        }  
    }  
  
    override fun onActivityResumed(activity: Activity) {  
        Log.d(TAG, "onActivityResumed: ${foregroundCount.get()}")  
    }  
  
    override fun onActivityPaused(activity: Activity) {  
        Log.d(TAG, "onActivityPaused: ${foregroundCount.get()}")  
    }  
  
    override fun onActivityStopped(activity: Activity) {  
        Log.d(TAG, "onActivityStopped before: ${foregroundCount.get()}")  
        foregroundCount.decrementAndGet()  
        Log.d(TAG, "onActivityStopped after: ${foregroundCount.get()}")  
        if (foregroundCount.get() == 0) {  
            Log.d(TAG, "onActivityStopped: 应用从前台切到后台")  
            ScreenEventManager.onAppBackground()  
        }  
    }  
  
    override fun onActivitySaveInstanceState(  
        activity: Activity,  
        outState: Bundle  
    ) {  
        Log.d(TAG, "onActivitySaveInstanceState: ${foregroundCount.get()}")  
    }  
  
    override fun onActivityDestroyed(activity: Activity) {  
        Log.d(TAG, "onActivityDestroyed: ${foregroundCount.get()}")  
    }  
}

AppLifecycleObserver:

import android.util.Log  
import androidx.lifecycle.DefaultLifecycleObserver  
import androidx.lifecycle.LifecycleOwner  
import com.small.ball.assistant.ScreenEventManager  
  
class AppLifecycleObserver : DefaultLifecycleObserver {  
  
    companion object {  
        private const val TAG = "AppLifecycleObserver"  
    }  
  
    override fun onStart(owner: LifecycleOwner) {  
        Log.d(TAG, "App entered foreground")  
        ScreenEventManager.onAppForeground()  
    }  
  
    override fun onStop(owner: LifecycleOwner) {  
        Log.d(TAG, "App entered background")  
        ScreenEventManager.onAppBackground()  
    }  
}

BorrowId:

enum class BorrowId {  
    UNKNOWN,  
    CAMERA,  
    STATUS_BAR,  
    AIR_CONDITIONER,  
}

ScreenState:

enum class ScreenState {  
    UI,  
    BORROW,  
    TAKE  
}

ScreenStateManager:

import android.util.Log  
import kotlinx.coroutines.CoroutineScope  
import kotlinx.coroutines.Dispatchers  
import kotlinx.coroutines.cancel  
import kotlinx.coroutines.delay  
import kotlinx.coroutines.launch  
import java.util.concurrent.atomic.AtomicReference  
  
object ScreenStateManager {  
    private val currentState = AtomicReference(ScreenState.UI)  
    private val borrowPool = mutableListOf<BorrowId>()  
    private val borrowScope = CoroutineScope(Dispatchers.IO)  
  
    private const val TAG = "ScreenStateManager"  
  
    fun changeState(  
        newState: ScreenState,  
        borrowId: BorrowId = BorrowId.UNKNOWN  
    ) = synchronized(this) {  
        Log.d(TAG, "changeState: old=${currentState.get()}, new=$newState, borrowId=$borrowId")  
  
        when (newState) {  
            ScreenState.UI -> {  
                if (currentState.get() == ScreenState.TAKE) {  
                    currentState.set(newState)  
                    Log.d(TAG, "changeState: TAKE->UI => requestUI()")  
                } else if (currentState.get() == ScreenState.BORROW) {  
                    borrowScope.launch {  
                        delay(200)  
                        val remove = borrowPool.remove(borrowId)  
                        if (remove) {  
                            Log.d(TAG, "changeState: BORROW->UI => unBorrow($borrowId)")  
                        }  
                    }  
                }  
            }  
  
            ScreenState.BORROW -> {  
                if (currentState.get() == ScreenState.UI || currentState.get() == ScreenState.BORROW) {  
                    if (!borrowPool.contains(borrowId)) {  
                        currentState.set(newState)  
                        borrowPool.add(borrowId)  
                        Log.d(TAG, "changeState borrow($borrowId)")  
                    }  
                }  
            }  
  
            ScreenState.TAKE -> {  
                if (currentState.get() == ScreenState.UI) {  
                    currentState.set(newState)  
                    Log.d(TAG, "changeState: UI->TAKE => take()")  
                } else if (currentState.get() == ScreenState.BORROW) {  
                    borrowScope.cancel()  
                    currentState.set(newState)  
                    // 是否清空 borrowPool?  
                    borrowPool.clear()  
                    Log.d(TAG, "changeState: BORROW->TAKE => take()")  
                }  
            }  
        }  
    }  
}

ScreenEventManager:

import android.util.Log  
import java.util.concurrent.atomic.AtomicBoolean  
  
object ScreenEventManager {  
    private val popupShow = AtomicBoolean(false)  
    private val statusBarShow = AtomicBoolean(false)  
  
    private const val TAG = "ScreenEventManager"  
  
    /**  
     * 弹窗出现  
     */  
    fun onPopupShow() {  
        Log.d(TAG, "onPopupShow")  
        popupShow.set(true)  
        ScreenStateManager.changeState(ScreenState.BORROW, BorrowId.CAMERA)  
    }  
  
    /**  
     * 弹窗消失  
     */  
    fun onPopupHide() {  
        Log.d(TAG, "onPopupHide")  
        if (popupShow.get()) {  
            ScreenStateManager.changeState(ScreenState.UI, BorrowId.CAMERA)  
            popupShow.set(false)  
        }  
    }  
  
    /**  
     * 状态栏出现  
     */  
    fun onStatusBarShow() {  
        Log.d(TAG, "onStatusBarShow")  
        statusBarShow.set(true)  
        ScreenStateManager.changeState(ScreenState.BORROW, BorrowId.STATUS_BAR)  
    }  
  
    /**  
     * 状态栏消失  
     */  
    fun onStatusBarHide() {  
        Log.d(TAG, "onStatusBarHide")  
        if (statusBarShow.get()) {  
            ScreenStateManager.changeState(ScreenState.UI, BorrowId.STATUS_BAR)  
            statusBarShow.set(false)  
        }  
    }  
  
    /**  
     * 应用进入前台  
     */  
    fun onAppForeground() {  
        Log.d(TAG, "onAppForeground")  
        ScreenStateManager.changeState(ScreenState.UI)  
    }  
  
    /**  
     * 应用进入后台  
     */  
    fun onAppBackground() {  
        Log.d(TAG, "onAppBackground")  
        ScreenStateManager.changeState(ScreenState.TAKE)  
    }  
}

AccessibilityNodeInfo:

import android.view.accessibility.AccessibilityNodeInfo  
import android.view.accessibility.AccessibilityWindowInfo  
  
fun AccessibilityNodeInfo.fmt() = StringBuilder().apply {  
    append("屏幕ID: ${window.displayId}\n")  
    append("是否激活: ${window.isActive}\n")  
    append("窗口标题: ${window.title}\n")  
    append("窗口类型: ${window.type}\n")  
    append("窗口类型Display: ${typeToString(window.type)}\n")  
    append("窗口层级: ${window.layer}\n")  
    append("是否可用: $isEnabled\n")  
    append("是否可点击: $isClickable\n")  
    append("是否有焦点: $isFocused\n")  
    append("是否可关闭: $isDismissable\n")  
}  
  
private fun typeToString(type: Int): String {  
    when (type) {  
        AccessibilityWindowInfo.TYPE_APPLICATION -> {  
            return "TYPE_APPLICATION"  
        }  
  
        AccessibilityWindowInfo.TYPE_INPUT_METHOD -> {  
            return "TYPE_INPUT_METHOD"  
        }  
  
        AccessibilityWindowInfo.TYPE_SYSTEM -> {  
            return "TYPE_SYSTEM"  
        }  
  
        AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY -> {  
            return "TYPE_ACCESSIBILITY_OVERLAY"  
        }  
  
        AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER -> {  
            return "TYPE_SPLIT_SCREEN_DIVIDER"  
        }  
  
        AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY -> {  
            return "TYPE_MAGNIFICATION_OVERLAY"  
        }  
  
        /*AccessibilityWindowInfo.FLAG_ADD_TYPE_WINDOW_CONTROL -> {  
            return "FLAG_ADD_TYPE_WINDOW_CONTROL"        }*/  
        else -> {  
            return "UNKNOWN"  
        }  
    }  
}

AccessibilityEvent:

import android.os.Build  
import android.view.accessibility.AccessibilityEvent  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACTIVE  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_BOUNDS  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_CHILDREN  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_FOCUSED  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_LAYER  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_PARENT  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_PIP  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_REMOVED  
import android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_TITLE  
  
fun AccessibilityEvent.fmt() = StringBuilder().apply {  
    append("事件时间: $eventTime\n")  
    append("应用的包名: $packageName\n")  
    append("应用的类名: $className\n")  
    append("事件类型: $eventType\n")  
    append("窗口标识符: $windowId\n")  
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {  
        append("窗口屏幕标识符: $displayId\n")  
    }  
    append("窗口是否全屏: $isFullScreen\n")  
    append("窗口源: ${source}\n")  
    if (source != null) {  
        append("${source?.fmt()}")  
    }  
    append("窗口 WindowChanges: $windowChanges\n")  
    append("窗口 WindowChangesDisplay: ${windowChangesDisplay(windowChanges)}\n")  
    // append("事件遍历的颗粒度: $movementGranularity\n")  
    append("窗口 Action: $action\n")  
  
    /*append("密封数量: $recordCount\n")  
    if (recordCount > 0) {        append("密封对象: ${getRecord(0)}\n")  
    }*/    // append("内容变化类型的位掩码: $contentChangeTypes\n")  
    // append("语音变化类型的位掩码: $speechStateChangeTypes\n")  
    // TYPE_WINDOWS_CHANGED}  
  
private fun windowChangesDisplay(type: Int): String {  
    when (type) {  
        WINDOWS_CHANGE_ADDED -> {  
            return "WINDOWS_CHANGE_ADDED"  
        }  
  
        WINDOWS_CHANGE_REMOVED -> {  
            return "WINDOWS_CHANGE_REMOVED"  
        }  
  
        WINDOWS_CHANGE_TITLE -> {  
            return "WINDOWS_CHANGE_TITLE"  
        }  
  
        WINDOWS_CHANGE_BOUNDS -> {  
            return "WINDOWS_CHANGE_BOUNDS"  
        }  
  
        WINDOWS_CHANGE_LAYER -> {  
            return "WINDOWS_CHANGE_LAYER"  
        }  
  
        WINDOWS_CHANGE_ACTIVE -> {  
            return "WINDOWS_CHANGE_ACTIVE"  
        }  
  
        WINDOWS_CHANGE_FOCUSED -> {  
            return "WINDOWS_CHANGE_FOCUSED"  
        }  
  
        WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED -> {  
            return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED"  
        }  
  
        WINDOWS_CHANGE_PARENT -> {  
            return "WINDOWS_CHANGE_PARENT"  
        }  
  
        WINDOWS_CHANGE_CHILDREN -> {  
            return "WINDOWS_CHANGE_CHILDREN"  
        }  
  
        WINDOWS_CHANGE_PIP -> {  
            return "WINDOWS_CHANGE_PIP"  
        }  
  
        else -> {  
            return "UNKNOWN"  
        }  
    }  
}

OverlayService:

import android.accessibilityservice.AccessibilityService  
import android.content.Intent  
import android.util.Log  
import android.view.accessibility.AccessibilityEvent  
import android.view.accessibility.AccessibilityWindowInfo  
  
class OverlayService : AccessibilityService() {  
  
    companion object {  
        private const val TAG = "OverlayService"  
    }  
  
    override fun onServiceConnected() {  
        super.onServiceConnected()  
        Log.d(TAG, "onServiceConnected: run")  
    }  
  
    /**  
     *     * 状态栏展开:  
     * 应用的包名: com.android.systemui  
     * 应用的类名: android.widget.FrameLayout  
     * 窗口类型: 3  
     * 窗口类型Display: TYPE_SYSTEM  
     * 窗口层级: 0  
     */    override fun onAccessibilityEvent(event: AccessibilityEvent) {  
        // Log.d(TAG, "onAccessibilityEvent: run")  
        when (event.eventType) {  
            AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {  
                handlerWindowStateChanged(event)  
            }  
  
            AccessibilityEvent.TYPE_WINDOWS_CHANGED -> {  
                handlerWindowsChanged(event)  
            }  
  
            else -> {}  
        }  
    }  
  
    override fun onInterrupt() {  
        Log.d(TAG, "onInterrupt: run")  
    }  
  
    override fun onUnbind(intent: Intent?): Boolean {  
        return super.onUnbind(intent)  
        Log.d(TAG, "onUnbind: run")  
    }  
  
    private fun handlerWindowStateChanged(event: AccessibilityEvent) {  
        Log.d(TAG, "****** handlerWindowStateChanged ******")  
        Log.d(TAG, event.fmt().toString())  
        Log.d(TAG, "windows size: ${windows.size}")  
        if (windows.isNotEmpty()) {  
            Log.d(TAG, "last window: ${windows[windows.size - 1].id}")  
        }  
  
        if (event.source == null) {  
            Log.d(TAG, "source is null")  
            return  
        }  
  
        val pkgName = event.packageName  
        val clsName = event.className  
        // 弹窗  
        if (clsName!!.contains("Dialog")) {  
            ScreenEventManager.onPopupShow()  
            return  
        }  
        // 状态栏  
        if (pkgName == "com.android.systemui" && clsName == "android.widget.FrameLayout") {  
            ScreenEventManager.onStatusBarShow()  
            return  
        }  
        // 判断本应用 Activity 切换回前台:若包名为本应用,且窗口为 Activity,则认为弹窗消失或遮挡消失  
        if (packageName == packageName && clsName.contains("Activity")) {  
            // 如果此前有弹窗/其他窗口遮挡状态,均视为消失  
            ScreenEventManager.onPopupHide()  
            ScreenEventManager.onStatusBarHide()  
            return  
        }  
  
        // 判断其他应用切换到前台(新窗口出现)  
        if (packageName != packageName && packageName != "com.android.systemui") {  
            // 其他应用窗口成为前台 Activity            // ScreenEventManager.onOtherWindowShown()            return  
        }  
    }  
  
    private fun handlerWindowsChanged(event: AccessibilityEvent) {  
        Log.d(TAG, "====== handlerWindowsChanged ======")  
        Log.d(TAG, event.fmt().toString())  
        Log.d(TAG, "windows size: ${windows.size}")  
        if (windows.isNotEmpty()) {  
            Log.d(TAG, "last window: ${windows[windows.size - 1].id}")  
        }  
  
        // 以包名与 windowType 区分状态栏、悬浮窗、应用窗口等  
        var statusBarFound = false  
        var otherWindowFound = false  
        windows.forEach {  
            when (it.type) {  
                AccessibilityWindowInfo.TYPE_SYSTEM -> {}  
                AccessibilityWindowInfo.TYPE_APPLICATION -> {}  
            }  
        }  
    }  
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容