LinkScannerService: don't repeatedly scan

- have the system only send 10 events a second, instead of 1000
- don't scan the same package+class name more than once a second
- put each event view scanner on its own thread keyed by package+class
- don't scan if there is already an existing thread for the package+class

this can still cause high cpu usage on views with lots of text
especially if the user continually interacts with it
but is overall much better

to recap:
- a given package+class can only be scanned at most once a second
- a given package+class can only have one scanner at a time
- text to scan will not be scanned if already scanned
- domains to scan will not be scanned if already scanned

Signed-off-by: Tavi <tavi@divested.dev>
This commit is contained in:
Tavi 2024-05-25 18:43:58 -04:00
parent 7dfa3a7267
commit 52b79bf526
No known key found for this signature in database
GPG key ID: E599F62ECBAEAF2E
5 changed files with 39 additions and 4 deletions

View file

@ -5,6 +5,9 @@
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
</SelectionState> </SelectionState>
<SelectionState runConfigName="MainActivity">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>

View file

@ -6,8 +6,8 @@ android {
applicationId "us.spotco.malwarescanner" applicationId "us.spotco.malwarescanner"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 32 targetSdkVersion 32
versionCode 311 versionCode 312
versionName "3.11" versionName "3.12"
resConfigs 'en', 'af', 'cs', 'de', 'el', 'es', 'fi', 'fr', 'gl', 'it', 'pl', 'pt', 'pt-rBR', 'ru', 'tr', 'zh-rCN' resConfigs 'en', 'af', 'cs', 'de', 'el', 'es', 'fi', 'fr', 'gl', 'it', 'pl', 'pt', 'pt-rBR', 'ru', 'tr', 'zh-rCN'
} }
buildTypes { buildTypes {

View file

@ -10,6 +10,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo;
import java.util.Random; import java.util.Random;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ConcurrentSkipListSet;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -18,6 +19,9 @@ public class LinkScannerService extends AccessibilityService {
private static final String hostnameRegex = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$"; //Credit: http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/ private static final String hostnameRegex = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$"; //Credit: http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/
private static final Pattern hostnamePattern = Pattern.compile(hostnameRegex); private static final Pattern hostnamePattern = Pattern.compile(hostnameRegex);
private static final ConcurrentSkipListMap<String, Long> scannedPackages = new ConcurrentSkipListMap<>();
private static final ConcurrentSkipListMap<String, Thread> scannerThreads = new ConcurrentSkipListMap<>();
private static final ConcurrentSkipListSet<Integer> scannedText = new ConcurrentSkipListSet<>(); private static final ConcurrentSkipListSet<Integer> scannedText = new ConcurrentSkipListSet<>();
private static final ConcurrentSkipListSet<String> scannedDomains = new ConcurrentSkipListSet<>(); private static final ConcurrentSkipListSet<String> scannedDomains = new ConcurrentSkipListSet<>();
private NotificationManager notificationManager = null; private NotificationManager notificationManager = null;
@ -35,7 +39,34 @@ public class LinkScannerService extends AccessibilityService {
@Override @Override
public void onAccessibilityEvent(AccessibilityEvent event) { public void onAccessibilityEvent(AccessibilityEvent event) {
if (Database.isDomainDatabaseLoaded()) { if (Database.isDomainDatabaseLoaded()) {
scanViews(event.getSource()); boolean shouldScanView = true;
String packageName = (String) event.getPackageName();
if (event.getSource() != null) {
packageName += event.getSource().getClassName();
}
if (scannedPackages.containsKey(packageName)) {
if ((System.currentTimeMillis() - scannedPackages.get(packageName)) < 1000) {
shouldScanView = false;
}
}
if (shouldScanView) {
if (scannerThreads.containsKey(packageName)) {
Thread oldThread = scannerThreads.get(packageName);
if (oldThread != null) {
if (oldThread.isAlive()) {
shouldScanView = false;
} else {
scannerThreads.remove(packageName);
}
}
}
}
if (shouldScanView) {
scannedPackages.put(packageName, System.currentTimeMillis());
Thread thread = new Thread(() -> scanViews(event.getSource()));
scannerThreads.put(packageName, thread);
thread.start();
}
} }
} }

View file

@ -4,5 +4,5 @@
android:accessibilityEventTypes="typeWindowContentChanged|typeViewTextChanged" android:accessibilityEventTypes="typeWindowContentChanged|typeViewTextChanged"
android:accessibilityFlags="flagDefault" android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="1" android:notificationTimeout="100"
android:canRetrieveWindowContent="true" /> android:canRetrieveWindowContent="true" />

View file

@ -0,0 +1 @@
* Many optimizations to the link scanner feature