Untitled

mail@pastecode.io avatar
unknown
kotlin
8 months ago
7.9 kB
9
Indexable
Never
package com.cct.muganim.services

import android.accessibilityservice.AccessibilityService
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.SystemClock
import android.util.Log
import android.util.Patterns
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.cct.muganim.utils.Constant
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URLDecoder


class CustomAccessibleService : AccessibilityService() {
    private var browserApp = ""
    private var browserUrl = ""
    private val serviceScope = CoroutineScope(Dispatchers.Default)


    override fun onTaskRemoved(rootIntent: Intent?) {
        val restartServiceIntent = Intent(applicationContext, this.javaClass)
        val restartServicePendingIntent = PendingIntent.getService(
            applicationContext,
            1,
            restartServiceIntent,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
            } else {
                PendingIntent.FLAG_ONE_SHOT
            }
        )
        val alarmService = applicationContext.getSystemService(ALARM_SERVICE) as AlarmManager
        alarmService[AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 1000] =
            restartServicePendingIntent
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
        event ?: return

        when (event.eventType) {
            AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
            AccessibilityEvent.TYPE_VIEW_CLICKED,
            AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED,
            AccessibilityEvent.TYPE_VIEW_SCROLLED -> processEvent(event)
        }
    }

    private fun processEvent(event: AccessibilityEvent) {
        val parentNodeInfo = event.source ?: return // Early return if source is null
        val packageName = event.packageName.toString()

        // Determine whether to delay URL capture based on the event type
        if (event.eventType == AccessibilityEvent.TYPE_VIEW_CLICKED || event.eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
            // Delay capturing the URL to allow time for the address bar to update
            serviceScope.launch {
                delay(500) // Delay in milliseconds; adjust based on observed behavior
                captureUrlCommonLogic(parentNodeInfo, packageName)
            }
        } else {
            // Proceed to capture the URL without delay
            captureUrlCommonLogic(parentNodeInfo, packageName)
        }
    }

    private fun captureUrlCommonLogic(nodeInfo: AccessibilityNodeInfo, packageName: String) {
        // Consolidate common logic for URL capture here to avoid duplication
        serviceScope.launch {
            try {
                getSupportedBrowsers().firstOrNull { it.packageName == packageName }?.let { browserConfig ->
                    captureUrl(nodeInfo, browserConfig)?.let { capturedUrl ->
                        if (Patterns.WEB_URL.matcher(capturedUrl).matches() && browserUrl != capturedUrl) {
                            withContext(Dispatchers.Main) {
                                browserUrl = capturedUrl
                                browserApp = packageName
                                putMyUrl(capturedUrl, packageName)
                            }
                        }
                    }
                }
            } finally {
                withContext(Dispatchers.Main) {
                    nodeInfo.recycle() // Recycle on the main thread
                }
            }
        }
    }

    private suspend fun putMyUrl(url: String, browser: String) {
        // Decode URL and convert it to URI for easier parsing
        val decodedUrl = withContext(Dispatchers.IO) { URLDecoder.decode(url, "UTF-8") }
        val uri = Uri.parse(decodedUrl)

        // Extract the 'q' query parameter from the URL, or use null if it's not present
        if (uri.isHierarchical) {
            val searchQuery = uri.getQueryParameter("q")?.toLowerCase()

            // Check if the extracted search query matches any blocked keywords
            if (searchQuery != null && Constant.blockedKeywordsList.any { keyword ->
                    searchQuery.contains(
                        keyword.toLowerCase()
                    )
                } && !overlayRecentlyClosed) {
                Log.i(Constant.TAG, "Blocked keyword found in search query. Showing overlay.")
                withContext(Dispatchers.Main) {
                    showBlockingOverlay()
                }
            }
        } else {
            Log.i(Constant.TAG, "URL is not hierarchical and cannot be processed: $decodedUrl")
        }
    }

    companion object {
        var overlayRecentlyClosed = false
    }
    private fun showBlockingOverlay() {
        if (!overlayRecentlyClosed) {
            val overlayIntent = Intent(this, FloatingLayoutService::class.java)
            startService(overlayIntent)
        }
    }


    private fun captureUrl(info: AccessibilityNodeInfo, config: SupportedBrowserConfig): String? {
        Log.d(Constant.TAG, "Trying to capture URL for package: ${config.packageName}")
        val nodes = info.findAccessibilityNodeInfosByViewId(config.addressBarId)
        if (nodes.isNullOrEmpty()) {
            Log.d(Constant.TAG, "No nodes found for ID: ${config.addressBarId} in package: ${config.packageName}")
            return null
        }
        return nodes.firstOrNull()?.text?.toString().also { url ->
            Log.d(Constant.TAG, "Captured URL: $url for package: ${config.packageName}")
        }
    }

    private fun getSupportedBrowsers(): List<SupportedBrowserConfig> {
        return listOf(
            SupportedBrowserConfig("com.android.chrome", "com.android.chrome:id/url_bar"),
            SupportedBrowserConfig("org.mozilla.firefox", "org.mozilla.firefox:id/mozac_browser_toolbar_url_view"),
            SupportedBrowserConfig("com.opera.browser", "com.opera.browser:id/url_field"),

            // add the other ones...
        )
    }

    data class SupportedBrowserConfig(val packageName: String, val addressBarId: String)
    private fun loadBlockedKeywords() {
        serviceScope.launch(Dispatchers.IO) {
            val keywords = readWordsFromAssetFile("extended_test_set_keywords.txt")
            withContext(Dispatchers.Main) {
                Constant.blockedKeywordsList.clear()
                Constant.blockedKeywordsList.addAll(keywords)
                Log.i(Constant.TAG, "Blocked Keywords Reloaded: ${keywords.size}")
            }
        }
    }

    private suspend fun readWordsFromAssetFile(fileName: String): HashSet<String> = withContext(Dispatchers.IO) {
        val keywords = HashSet<String>()
        try {
            applicationContext.assets.open(fileName).use { inputStream ->
                BufferedReader(InputStreamReader(inputStream)).use { reader ->
                    reader.forEachLine { line ->
                        keywords.addAll(line.split("\\s+".toRegex()))
                    }
                }
            }
        } catch (e: Exception) {
            Log.e(Constant.TAG, "Error reading blocked keywords from file: $fileName", e)
        }
        keywords
    }
    override fun onInterrupt() {
        Log.i(Constant.TAG, "Accessibility Service Interrupted")
    }

    override fun onServiceConnected() {
        super.onServiceConnected()
        Log.i(Constant.TAG, "Accessibility Service Connected")
        loadBlockedKeywords()
    }

}
Leave a Comment