Created
January 31, 2025 16:14
-
-
Save sajjadyousefnia/3b5e4db585862d05ac00696b0e81ef14 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.yourpackage | |
import android.accessibilityservice.AccessibilityService | |
import android.accessibilityservice.AccessibilityServiceInfo | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.Intent | |
import android.content.IntentFilter | |
import android.net.Uri | |
import android.os.Handler | |
import android.os.Looper | |
import android.util.Log | |
import android.view.accessibility.AccessibilityEvent | |
import android.view.accessibility.AccessibilityNodeInfo | |
import android.widget.Toast | |
class MyAccessibilityService : AccessibilityService() { | |
private val TAG = "MyAccessibilityService" | |
private var isChromeOpen = false | |
private var urls: MutableList<String> = mutableListOf() | |
private var currentUrlIndex = 0 | |
private val handler = Handler(Looper.getMainLooper()) | |
private val urlReceiver = object : BroadcastReceiver() { | |
override fun onReceive(context: Context?, intent: Intent?) { | |
urls = intent?.getStringArrayListExtra("urls")?.toMutableList() ?: mutableListOf() | |
currentUrlIndex = 0 | |
if (urls.isNotEmpty()) { | |
openUrlInChrome(urls[currentUrlIndex]) | |
} | |
} | |
} | |
override fun onServiceConnected() { | |
super.onServiceConnected() | |
val info = AccessibilityServiceInfo() | |
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | |
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC | |
info.notificationTimeout = 100 | |
info.packageNames = arrayOf("com.android.chrome") // Listen only for Chrome | |
this.serviceInfo = info | |
Log.d(TAG, "Accessibility service connected") | |
registerReceiver(urlReceiver, IntentFilter("com.yourpackage.ACTION_SEND_URLS")) | |
} | |
override fun onAccessibilityEvent(event: AccessibilityEvent) { | |
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { | |
val packageName = event.packageName.toString() | |
Log.d(TAG, "onAccessibilityEvent: $packageName") | |
if (packageName == "com.android.chrome") { | |
if (!isChromeOpen) { | |
isChromeOpen = true | |
} else { | |
findAndSavePage() | |
} | |
} else { | |
isChromeOpen = false | |
} | |
} | |
} | |
private fun openUrlInChrome(url: String) { | |
Log.d(TAG, "openUrlInChrome: $url") | |
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) | |
intent.setPackage("com.android.chrome") | |
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | |
try { | |
startActivity(intent) | |
} catch (e: Exception) { | |
Log.e(TAG, "Error opening URL in Chrome", e) | |
Toast.makeText(this, "Error opening URL in Chrome", Toast.LENGTH_SHORT).show() | |
moveToNextUrl() | |
} | |
} | |
private fun findAndSavePage() { | |
Log.d(TAG, "findAndSavePage") | |
val rootNodeInfo = rootInActiveWindow ?: return | |
// Find the menu button (three dots) | |
val menuButton = findNodeByContentDescription(rootNodeInfo, "More options") | |
if (menuButton != null) { | |
menuButton.performAction(AccessibilityNodeInfo.ACTION_CLICK) | |
// Wait for the menu to open | |
handler.postDelayed({ | |
// Find the "Save" or "Bookmark" option | |
val saveOption = findNodeByText(rootInActiveWindow, "Save") | |
if (saveOption != null) { | |
saveOption.performAction(AccessibilityNodeInfo.ACTION_CLICK) | |
Log.d(TAG, "Page saved successfully") | |
moveToNextUrl() | |
} else { | |
Log.e(TAG, "Save option not found") | |
moveToNextUrl() | |
} | |
}, 1000) | |
} else { | |
Log.e(TAG, "Menu button not found") | |
moveToNextUrl() | |
} | |
} | |
private fun moveToNextUrl() { | |
currentUrlIndex++ | |
if (currentUrlIndex < urls.size) { | |
openUrlInChrome(urls[currentUrlIndex]) | |
} else { | |
// All URLs processed | |
Log.d(TAG, "All URLs processed") | |
sendBroadcast(Intent("com.yourpackage.ACTION_URLS_PROCESSED")) | |
} | |
} | |
private fun findNodeByText(nodeInfo: AccessibilityNodeInfo?, text: String): AccessibilityNodeInfo? { | |
if (nodeInfo == null) return null | |
val nodes = nodeInfo.findAccessibilityNodeInfosByText(text) | |
if (nodes.isNotEmpty()) { | |
return nodes[0] | |
} | |
return null | |
} | |
private fun findNodeByContentDescription(nodeInfo: AccessibilityNodeInfo?, contentDescription: String): AccessibilityNodeInfo? { | |
if (nodeInfo == null) return null | |
if (nodeInfo.contentDescription?.toString() == contentDescription) { | |
return nodeInfo | |
} | |
for (i in 0 until nodeInfo.childCount) { | |
val child = nodeInfo.getChild(i) | |
val result = findNodeByContentDescription(child, contentDescription) | |
if (result != null) return result | |
} | |
return null | |
} | |
override fun onInterrupt() { | |
Log.d(TAG, "Accessibility service interrupted") | |
} | |
override fun onDestroy() { | |
super.onDestroy() | |
unregisterReceiver(urlReceiver) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment