mirror of
https://github.com/MaintainTeam/Hypatia.git
synced 2025-03-01 05:48:23 +03:00
Many changes
- Bump version - Don't find files recursively on main thread - Reduce number of threadpools - Ensure there are always enough threads available - Cap the queue limit on thread pools - Do work on main thread if queues are full, thereby blocking - Use more thread safe objects - Replace some runnables with lambas - Code cleanup - Typo fixes Closes https://gitlab.com/divested-mobile/hypatia/-/issues/6 Closes https://github.com/Divested-Mobile/Hypatia/issues/13 Closes https://github.com/Divested-Mobile/Hypatia/issues/1 Signed-off-by: Tad <tad@spotco.us>
This commit is contained in:
parent
9113af8374
commit
ca4f26fd7d
7 changed files with 107 additions and 134 deletions
|
@ -6,8 +6,8 @@ android {
|
||||||
applicationId "us.spotco.malwarescanner"
|
applicationId "us.spotco.malwarescanner"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 75
|
versionCode 76
|
||||||
versionName "2.21"
|
versionName "2.22"
|
||||||
resConfigs "en", "de", "es", "fr", "it", "pt", "ru"
|
resConfigs "en", "de", "es", "fr", "it", "pt", "ru"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
|
@ -35,7 +35,7 @@ import java.net.URL;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
@ -45,11 +45,10 @@ class Database {
|
||||||
private static TextView log = null;
|
private static TextView log = null;
|
||||||
private static SharedPreferences prefs = null;
|
private static SharedPreferences prefs = null;
|
||||||
private static File databasePath = null;
|
private static File databasePath = null;
|
||||||
private static ThreadPoolExecutor threadPoolExecutor = null;
|
|
||||||
private static boolean databaseFullyLoaded = false;
|
private static boolean databaseFullyLoaded = false;
|
||||||
private static boolean databaseCurrentlyLoading = false;
|
private static boolean databaseCurrentlyLoading = false;
|
||||||
|
|
||||||
public final static HashSet<SignatureDatabase> signatureDatabases = new HashSet<>();
|
public final static ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases = new ConcurrentLinkedQueue<>();
|
||||||
public final static String baseURL = "https://divested.dev/MalwareScannerSignatures/";
|
public final static String baseURL = "https://divested.dev/MalwareScannerSignatures/";
|
||||||
public final static String baseURLOnion = "https://hypatiagbf5vp3ba.onion/MalwareScannerSignatures/"; //TODO: Setup the .onion
|
public final static String baseURLOnion = "https://hypatiagbf5vp3ba.onion/MalwareScannerSignatures/"; //TODO: Setup the .onion
|
||||||
|
|
||||||
|
@ -61,7 +60,6 @@ class Database {
|
||||||
|
|
||||||
public Database(TextView log) {
|
public Database(TextView log) {
|
||||||
Database.log = log;
|
Database.log = log;
|
||||||
threadPoolExecutor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(Utils.getMaxThreads());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean areDatabasesAvailable() {
|
public static boolean areDatabasesAvailable() {
|
||||||
|
@ -76,12 +74,12 @@ class Database {
|
||||||
return signaturesMD5.size() + signaturesSHA1.size() + signaturesSHA256.size();
|
return signaturesMD5.size() + signaturesSHA1.size() + signaturesSHA256.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateDatabase(Context context, HashSet<SignatureDatabase> signatureDatabases) {
|
public static void updateDatabase(Context context, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) {
|
||||||
initDatabase(context);
|
initDatabase(context);
|
||||||
log.append(context.getString(R.string.main_database_updating, signatureDatabases.size() + "") + "\n");
|
log.append(context.getString(R.string.main_database_updating, signatureDatabases.size() + "") + "\n");
|
||||||
for (SignatureDatabase signatureDatabase : signatureDatabases) {
|
for (SignatureDatabase signatureDatabase : signatureDatabases) {
|
||||||
boolean onionRouting = prefs.getBoolean("ONION_ROUTING", false);
|
boolean onionRouting = prefs.getBoolean("ONION_ROUTING", false);
|
||||||
new Downloader().executeOnExecutor(threadPoolExecutor, onionRouting, signatureDatabase.getUrl(), databasePath + "/" + signatureDatabase.getName());
|
new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, signatureDatabase.getUrl(), databasePath + "/" + signatureDatabase.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +112,7 @@ class Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadDatabase(Context context, boolean forceReload, HashSet<SignatureDatabase> signatureDatabases) {
|
public static void loadDatabase(Context context, boolean forceReload, ConcurrentLinkedQueue<SignatureDatabase> signatureDatabases) {
|
||||||
if ((!isDatabaseLoaded() || forceReload) && !databaseCurrentlyLoading) {
|
if ((!isDatabaseLoaded() || forceReload) && !databaseCurrentlyLoading) {
|
||||||
databaseFullyLoaded = false;
|
databaseFullyLoaded = false;
|
||||||
databaseCurrentlyLoading = true;
|
databaseCurrentlyLoading = true;
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.Manifest;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
|
@ -33,7 +32,6 @@ import android.os.Environment;
|
||||||
import android.text.method.ScrollingMovementMethod;
|
import android.text.method.ScrollingMovementMethod;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
@ -48,7 +46,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@ -91,35 +88,30 @@ public class MainActivity extends AppCompatActivity {
|
||||||
prefs = getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
|
prefs = getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
|
||||||
|
|
||||||
final FloatingActionButton fab = findViewById(R.id.fab);
|
final FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
fab.setOnClickListener(view -> {
|
||||||
@Override
|
if (!malwareScanner.running) {
|
||||||
public void onClick(View view) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (!malwareScanner.running) {
|
fab.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.red)));
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
fab.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.red)));
|
|
||||||
}
|
|
||||||
startScanner();
|
|
||||||
Utils.getThreadPoolExecutor().execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (malwareScanner.running) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
fab.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.light_blue)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logView.append("\n" + getString(R.string.main_cancelling_scan) + "\n\n");
|
|
||||||
malwareScanner.cancel(true);
|
|
||||||
malwareScanner.running = false;
|
|
||||||
}
|
}
|
||||||
|
startScanner();
|
||||||
|
//TODO: This might not receive an available thread in time
|
||||||
|
Utils.getThreadPoolExecutor().execute(() -> {
|
||||||
|
while (malwareScanner.running) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
fab.setBackgroundTintList(ColorStateList.valueOf(getColor(R.color.light_blue)));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logView.append("\n" + getString(R.string.main_cancelling_scan) + "\n\n");
|
||||||
|
malwareScanner.cancel(true);
|
||||||
|
malwareScanner.running = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -146,18 +138,15 @@ public class MainActivity extends AppCompatActivity {
|
||||||
Dialog creditsDialog;
|
Dialog creditsDialog;
|
||||||
AlertDialog.Builder creditsBuilder = new AlertDialog.Builder(this);
|
AlertDialog.Builder creditsBuilder = new AlertDialog.Builder(this);
|
||||||
creditsBuilder.setTitle(getString(R.string.lblFullCredits));
|
creditsBuilder.setTitle(getString(R.string.lblFullCredits));
|
||||||
creditsBuilder.setItems(R.array.fullCredits, new DialogInterface.OnClickListener() {
|
creditsBuilder.setItems(R.array.fullCredits, (dialog, which) -> {
|
||||||
@Override
|
//do nothing
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
//do nothing
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
creditsDialog = creditsBuilder.create();
|
creditsDialog = creditsBuilder.create();
|
||||||
creditsDialog.show();
|
creditsDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String localizeDBDescription(String desc) {
|
private String localizeDBDescription(String desc) {
|
||||||
String localDesc = desc
|
return desc
|
||||||
.replaceAll("AUTHOR", getString(R.string.db_desc_author))
|
.replaceAll("AUTHOR", getString(R.string.db_desc_author))
|
||||||
.replaceAll("LICENSE", getString(R.string.db_desc_license))
|
.replaceAll("LICENSE", getString(R.string.db_desc_license))
|
||||||
.replaceAll("SIZE_SMALL", getString(R.string.db_desc_size_small))
|
.replaceAll("SIZE_SMALL", getString(R.string.db_desc_size_small))
|
||||||
|
@ -165,7 +154,6 @@ public class MainActivity extends AppCompatActivity {
|
||||||
.replaceAll("SIZE_LARGE", getString(R.string.db_desc_size_large))
|
.replaceAll("SIZE_LARGE", getString(R.string.db_desc_size_large))
|
||||||
.replaceAll("SIZE", getString(R.string.db_desc_size))
|
.replaceAll("SIZE", getString(R.string.db_desc_size))
|
||||||
.replaceAll("SOURCE", getString(R.string.db_desc_source));
|
.replaceAll("SOURCE", getString(R.string.db_desc_source));
|
||||||
return localDesc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectDatabases() {
|
private void selectDatabases() {
|
||||||
|
@ -186,21 +174,13 @@ public class MainActivity extends AppCompatActivity {
|
||||||
AlertDialog.Builder databaseBuilder = new AlertDialog.Builder(this);
|
AlertDialog.Builder databaseBuilder = new AlertDialog.Builder(this);
|
||||||
databaseBuilder.setTitle(R.string.lblSelectDatabasesTitle);
|
databaseBuilder.setTitle(R.string.lblSelectDatabasesTitle);
|
||||||
|
|
||||||
databaseBuilder.setMultiChoiceItems(databases, databaseDefaults, new DialogInterface.OnMultiChoiceClickListener() {
|
databaseBuilder.setMultiChoiceItems(databases, databaseDefaults, (dialogInterface, i, selected) -> databaseDefaults[i] = selected);
|
||||||
@Override
|
databaseBuilder.setPositiveButton("OK", (dialogInterface, i) -> {
|
||||||
public void onClick(DialogInterface dialogInterface, int i, boolean selected) {
|
prefs.edit().putBoolean("SIGNATURES_CLAMAV-ANDROID", databaseDefaults[0]).apply();
|
||||||
databaseDefaults[i] = selected;
|
prefs.edit().putBoolean("SIGNATURES_CLAMAV-MAIN", databaseDefaults[1]).apply();
|
||||||
}
|
prefs.edit().putBoolean("SIGNATURES_CLAMAV-DAILY", databaseDefaults[2]).apply();
|
||||||
});
|
prefs.edit().putBoolean("SIGNATURES_ESET", databaseDefaults[3]).apply();
|
||||||
databaseBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
prefs.edit().putBoolean("SIGNATURES_TARGETEDTHREATS", databaseDefaults[4]).apply();
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
prefs.edit().putBoolean("SIGNATURES_CLAMAV-ANDROID", databaseDefaults[0]).apply();
|
|
||||||
prefs.edit().putBoolean("SIGNATURES_CLAMAV-MAIN", databaseDefaults[1]).apply();
|
|
||||||
prefs.edit().putBoolean("SIGNATURES_CLAMAV-DAILY", databaseDefaults[2]).apply();
|
|
||||||
prefs.edit().putBoolean("SIGNATURES_ESET", databaseDefaults[3]).apply();
|
|
||||||
prefs.edit().putBoolean("SIGNATURES_TARGETEDTHREATS", databaseDefaults[4]).apply();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
databaseDialog = databaseBuilder.create();
|
databaseDialog = databaseBuilder.create();
|
||||||
|
@ -271,9 +251,10 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private void startScanner() {
|
private void startScanner() {
|
||||||
malwareScanner = new MalwareScanner(this, this, true);
|
malwareScanner = new MalwareScanner(this, this, true);
|
||||||
Set<File> filesToScan = new HashSet<>();
|
malwareScanner.running = true;
|
||||||
|
HashSet<File> filesToScan = new HashSet<>();
|
||||||
if (scanSystem) {
|
if (scanSystem) {
|
||||||
filesToScan.addAll(Utils.getFilesRecursive(Environment.getRootDirectory()));
|
filesToScan.add(Environment.getRootDirectory());
|
||||||
}
|
}
|
||||||
if (scanApps) {
|
if (scanApps) {
|
||||||
for (ApplicationInfo packageInfo : getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA)) {
|
for (ApplicationInfo packageInfo : getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA)) {
|
||||||
|
@ -281,25 +262,19 @@ public class MainActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (scanInternal) {
|
if (scanInternal) {
|
||||||
filesToScan.addAll(Utils.getFilesRecursive(Environment.getExternalStorageDirectory()));
|
filesToScan.add(Environment.getExternalStorageDirectory());
|
||||||
}
|
}
|
||||||
if (scanExternal) {
|
if (scanExternal) {
|
||||||
filesToScan.addAll(Utils.getFilesRecursive(new File("/storage")));
|
filesToScan.add(new File("/storage"));
|
||||||
}
|
}
|
||||||
malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan);
|
malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan);
|
||||||
malwareScanner.running = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDatabase() {
|
private void updateDatabase() {
|
||||||
new Database((TextView) findViewById(R.id.txtLogOutput));
|
new Database((TextView) findViewById(R.id.txtLogOutput));
|
||||||
Database.updateDatabase(this, Database.signatureDatabases);
|
Database.updateDatabase(this, Database.signatureDatabases);
|
||||||
if (Database.isDatabaseLoaded()) {
|
if (Database.isDatabaseLoaded()) {
|
||||||
Utils.getThreadPoolExecutor().execute(new Runnable() {
|
Utils.getThreadPoolExecutor().execute(() -> Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,13 +37,15 @@ import java.math.BigInteger;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
|
|
||||||
class MalwareScanner extends AsyncTask<Set<File>, Object, String> {
|
class MalwareScanner extends AsyncTask<HashSet<File>, Object, String> {
|
||||||
|
|
||||||
private Context context = null;
|
private final Context context;
|
||||||
private TextView logOutput = null;
|
private TextView logOutput = null;
|
||||||
private boolean userFacing = false;
|
private boolean userFacing = false;
|
||||||
private NotificationManager notificationManager = null;
|
private NotificationManager notificationManager = null;
|
||||||
|
@ -100,15 +102,22 @@ class MalwareScanner extends AsyncTask<Set<File>, Object, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final String doInBackground(Set<File>[] filesToScan) {
|
protected final String doInBackground(HashSet<File>[] filesToScan) {
|
||||||
running = true;
|
running = true;
|
||||||
|
ConcurrentSkipListSet<File> filesToScanReal = new ConcurrentSkipListSet<>(); //TODO: Reduce this?
|
||||||
|
for (Set<File> fileArray : filesToScan) {
|
||||||
|
for (File file : fileArray) {
|
||||||
|
filesToScanReal.addAll(Utils.getFilesRecursive(file)); //TODO: Inline this, hash files as they are found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filesToScan = null;
|
||||||
|
|
||||||
//Pre
|
//Pre
|
||||||
fileHashesMD5.clear();
|
fileHashesMD5.clear();
|
||||||
fileHashesSHA1.clear();
|
fileHashesSHA1.clear();
|
||||||
fileHashesSHA256.clear();
|
fileHashesSHA256.clear();
|
||||||
|
|
||||||
publishProgress("\t" + context.getString(R.string.main_files_pending_scan, NumberFormat.getInstance().format(filesToScan[0].size()) + "") + "\n", true);
|
publishProgress("\t" + context.getString(R.string.main_files_pending_scan, NumberFormat.getInstance().format(filesToScanReal.size()) + "") + "\n", true);
|
||||||
|
|
||||||
Database.loadDatabase(context, false, Database.signatureDatabases);
|
Database.loadDatabase(context, false, Database.signatureDatabases);
|
||||||
int delayCount = 0;
|
int delayCount = 0;
|
||||||
|
@ -132,14 +141,14 @@ class MalwareScanner extends AsyncTask<Set<File>, Object, String> {
|
||||||
publishProgress("\t" + context.getString(R.string.main_hashing_files), true);
|
publishProgress("\t" + context.getString(R.string.main_hashing_files), true);
|
||||||
publishProgress("\t", true);
|
publishProgress("\t", true);
|
||||||
int fileScannedCount = 0;
|
int fileScannedCount = 0;
|
||||||
int percentIncrement = (filesToScan[0].size() / 20);
|
int percentIncrement = (filesToScanReal.size() / 20);
|
||||||
if (percentIncrement < 1) { //Prevent divide by zero
|
if (percentIncrement < 1) { //Prevent divide by zero
|
||||||
percentIncrement = 1;
|
percentIncrement = 1;
|
||||||
}
|
}
|
||||||
String spinnerCur = " ~ ";
|
String spinnerCur = " ~ ";
|
||||||
long totalBytesHashed = 0;
|
long totalBytesHashed = 0;
|
||||||
long hashStartTime = SystemClock.elapsedRealtime();
|
long hashStartTime = SystemClock.elapsedRealtime();
|
||||||
for (File file : filesToScan[0]) {
|
for (File file : filesToScanReal) {
|
||||||
if (this.isCancelled()) { //Allow quicker cancels
|
if (this.isCancelled()) { //Allow quicker cancels
|
||||||
//publishProgress("\t" + context.getString(R.string.main_cancelled_scan), true);
|
//publishProgress("\t" + context.getString(R.string.main_cancelled_scan), true);
|
||||||
running = false;
|
running = false;
|
||||||
|
@ -147,6 +156,7 @@ class MalwareScanner extends AsyncTask<Set<File>, Object, String> {
|
||||||
}
|
}
|
||||||
totalBytesHashed += file.length();
|
totalBytesHashed += file.length();
|
||||||
getFileHashes(file);
|
getFileHashes(file);
|
||||||
|
filesToScanReal.remove(file);
|
||||||
fileScannedCount++;
|
fileScannedCount++;
|
||||||
if ((fileScannedCount % percentIncrement) == 0) {
|
if ((fileScannedCount % percentIncrement) == 0) {
|
||||||
publishProgress(spinnerCur, true);
|
publishProgress(spinnerCur, true);
|
||||||
|
@ -157,6 +167,7 @@ class MalwareScanner extends AsyncTask<Set<File>, Object, String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
filesToScanReal.clear();
|
||||||
publishProgress(" !\n\t" + context.getString(R.string.main_hashing_done) + "\n", true);
|
publishProgress(" !\n\t" + context.getString(R.string.main_hashing_done) + "\n", true);
|
||||||
|
|
||||||
//Check the hashes
|
//Check the hashes
|
||||||
|
@ -168,14 +179,14 @@ class MalwareScanner extends AsyncTask<Set<File>, Object, String> {
|
||||||
fileHashesMD5.clear();
|
fileHashesMD5.clear();
|
||||||
fileHashesSHA1.clear();
|
fileHashesSHA1.clear();
|
||||||
fileHashesSHA256.clear();
|
fileHashesSHA256.clear();
|
||||||
Utils.FILES_SCANNED += filesToScan[0].size();
|
Utils.FILES_SCANNED.getAndAdd(fileScannedCount);
|
||||||
if(userFacing || Utils.FILES_SCANNED % 40 == 0) {
|
if (userFacing || Utils.FILES_SCANNED.get() % 40 == 0) {
|
||||||
System.gc(); //GC can be expensive, don't run it too often.
|
System.gc(); //GC can be expensive, don't run it too often.
|
||||||
}
|
}
|
||||||
if(userFacing) {
|
if (userFacing) {
|
||||||
long secondsSpent = ((SystemClock.elapsedRealtime() - scanStartTime) / 1000L);
|
long secondsSpent = ((SystemClock.elapsedRealtime() - scanStartTime) / 1000L);
|
||||||
long secondsSpentHasing = ((SystemClock.elapsedRealtime() - hashStartTime) / 1000L);
|
long secondsSpentHashing = ((SystemClock.elapsedRealtime() - hashStartTime) / 1000L);
|
||||||
long MBS = totalBytesHashed / 1000 / 1000 / secondsSpentHasing;
|
long MBS = totalBytesHashed / 1000 / 1000 / secondsSpentHashing;
|
||||||
publishProgress(context.getString(R.string.main_scanning_done, secondsSpent + "", MBS + "") + "\n\n\n\n", true);
|
publishProgress(context.getString(R.string.main_scanning_done, secondsSpent + "", MBS + "") + "\n\n\n\n", true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,6 @@ import androidx.core.app.NotificationCompat;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
public class MalwareScannerService extends Service {
|
public class MalwareScannerService extends Service {
|
||||||
|
@ -55,21 +54,12 @@ public class MalwareScannerService extends Service {
|
||||||
malwareMonitors.clear();
|
malwareMonitors.clear();
|
||||||
addMalwareMonitor(Environment.getExternalStorageDirectory().toString());
|
addMalwareMonitor(Environment.getExternalStorageDirectory().toString());
|
||||||
|
|
||||||
threadPoolExecutor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(Utils.getMaxThreads() + malwareMonitors.size());
|
int threadCount = Utils.getMaxThreads() + malwareMonitors.size();
|
||||||
threadPoolExecutor.execute(new Runnable() {
|
threadPoolExecutor = Utils.getNewThreadPoolExecutor(threadCount);
|
||||||
@Override
|
threadPoolExecutor.execute(() -> Database.loadDatabase(getApplicationContext(), false, Database.signatureDatabases));
|
||||||
public void run() {
|
|
||||||
Database.loadDatabase(getApplicationContext(), false, Database.signatureDatabases);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (final RecursiveFileObserver malwareMonitor : malwareMonitors) {
|
for (final RecursiveFileObserver malwareMonitor : malwareMonitors) {
|
||||||
threadPoolExecutor.execute(new Runnable() {
|
threadPoolExecutor.execute(malwareMonitor::startWatching);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
malwareMonitor.startWatching();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
|
@ -19,8 +19,8 @@ package us.spotco.malwarescanner;
|
||||||
|
|
||||||
class SignatureDatabase {
|
class SignatureDatabase {
|
||||||
|
|
||||||
private String url = null;
|
private final String url;
|
||||||
private String name = null;
|
private final String name;
|
||||||
|
|
||||||
public SignatureDatabase(String url) {
|
public SignatureDatabase(String url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
|
|
@ -27,8 +27,10 @@ import android.os.Build;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
|
|
||||||
|
@ -38,43 +40,50 @@ class Utils {
|
||||||
|
|
||||||
public final static int MAX_HASH_LENGTH = 12;
|
public final static int MAX_HASH_LENGTH = 12;
|
||||||
|
|
||||||
public static int FILES_SCANNED = 0;
|
public static final AtomicInteger FILES_SCANNED = new AtomicInteger();
|
||||||
private static ThreadPoolExecutor threadPoolExecutor = null;
|
private static ThreadPoolExecutor threadPoolExecutor = null;
|
||||||
|
|
||||||
public static ThreadPoolExecutor getThreadPoolExecutor() {
|
public static ThreadPoolExecutor getThreadPoolExecutor() {
|
||||||
if (threadPoolExecutor == null) {
|
if (threadPoolExecutor == null) {
|
||||||
threadPoolExecutor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(getMaxThreads());
|
threadPoolExecutor = getNewThreadPoolExecutor(getMaxThreads());
|
||||||
}
|
}
|
||||||
return threadPoolExecutor;
|
return threadPoolExecutor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ThreadPoolExecutor getNewThreadPoolExecutor(int threads) {
|
||||||
|
return new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(16), new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
}
|
||||||
|
|
||||||
public static int getMaxThreads() {
|
public static int getMaxThreads() {
|
||||||
int maxTheads = Runtime.getRuntime().availableProcessors();
|
int maxThreads = Runtime.getRuntime().availableProcessors();
|
||||||
if (maxTheads > 4) {
|
if (maxThreads > 4) {
|
||||||
maxTheads = 4;
|
maxThreads = 4;
|
||||||
}
|
}
|
||||||
return maxTheads;
|
if(maxThreads < 2) {
|
||||||
|
maxThreads = 2;
|
||||||
|
}
|
||||||
|
return maxThreads;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HashSet<File> getFilesRecursive(File root) {
|
public static HashSet<File> getFilesRecursive(File root) {
|
||||||
HashSet<File> filesAll = new HashSet<>();
|
HashSet<File> filesAll = new HashSet<>();
|
||||||
|
if (root.isFile()) { //TODO: Skip this
|
||||||
File[] files = root.listFiles();
|
filesAll.add(root);
|
||||||
if (files != null && files.length > 0) {
|
return filesAll;
|
||||||
for (File f : files) {
|
} else {
|
||||||
if (f.isDirectory()) {
|
File[] files = root.listFiles();
|
||||||
HashSet<File> filesTmp = getFilesRecursive(f);
|
if (files != null && files.length > 0) {
|
||||||
if (filesTmp != null) {
|
for (File f : files) {
|
||||||
filesAll.addAll(filesTmp);
|
if (f.isDirectory()) {
|
||||||
}
|
filesAll.addAll(getFilesRecursive(f));
|
||||||
} else {
|
} else {
|
||||||
if (f.length() <= MAX_SCAN_SIZE && f.canRead()) {//Exclude files larger than limit for performance
|
if (f.length() <= MAX_SCAN_SIZE && f.canRead()) {//Exclude files larger than limit for performance
|
||||||
filesAll.add(f);
|
filesAll.add(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filesAll;
|
return filesAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,26 +139,17 @@ class Utils {
|
||||||
|
|
||||||
//Credit: https://www.geekality.net/2013/04/30/java-simple-check-to-see-if-a-server-is-listening-on-a-port/
|
//Credit: https://www.geekality.net/2013/04/30/java-simple-check-to-see-if-a-server-is-listening-on-a-port/
|
||||||
public static boolean isPortListening(String host, int port) {
|
public static boolean isPortListening(String host, int port) {
|
||||||
Socket s = null;
|
try (Socket s = new Socket(host, port)) {
|
||||||
try {
|
s.close();
|
||||||
s = new Socket(host, port);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
|
||||||
if (s != null) {
|
|
||||||
try {
|
|
||||||
s.close();
|
|
||||||
} catch (Exception e1) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean waitUntilOrbotIsAvailable() {
|
public static void waitUntilOrbotIsAvailable() {
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
boolean listening;
|
while (!isPortListening("127.0.0.1", 9050) && tries <= 60) {
|
||||||
while (!(listening = isPortListening("127.0.0.1", 9050)) && tries <= 60) {
|
|
||||||
tries++;
|
tries++;
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
|
@ -157,7 +157,6 @@ class Utils {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return listening;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Context getContext() {
|
public static Context getContext() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue