From c3bfcebb8c620314d8acc923ca4ef90808651eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 14:45:42 +0300 Subject: [PATCH 01/25] Update Database Links (again) (#36) * change cloudflare links to github links *change default link to codeberg * warn users if using old database urls * also warn right after update --- .../spotco/malwarescanner/MainActivity.java | 12 ++++++--- .../java/us/spotco/malwarescanner/Utils.java | 26 +++++++++++++++++-- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 61e7208..39c6b02 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -83,6 +83,8 @@ protected final void onCreate(Bundle savedInstanceState) { logView.append(getString(R.string.app_copyright) + "\n"); logView.append(getString(R.string.app_license) + "\n"); logView.append(getString(R.string.app_version, buildVersionName) + "\n\n"); + + logView.append(Utils.checkOldDatabase(this) + "\n"); malwareScanner = new MalwareScanner(this, this, true); @@ -196,13 +198,14 @@ public final boolean onOptionsItemSelected(MenuItem item) { newServer += "/"; } prefs.edit().putString("DATABASE_SERVER", newServer).apply(); + logView.append(Utils.checkOldDatabase(this) + "\n"); }); builderServerOverride.setNegativeButton(getString(R.string.lblResetPrimary), (dialog, which) -> { prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT).apply(); dialog.cancel(); }); - builderServerOverride.setNeutralButton(getString(R.string.lblResetCloudflare), (dialog, which) -> { - prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_CLOUDFLARE).apply(); + builderServerOverride.setNeutralButton(getString(R.string.lblResetGitHub), (dialog, which) -> { + prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_GITHUB).apply(); dialog.cancel(); }); builderServerOverride.show(); @@ -220,7 +223,10 @@ public final boolean onOptionsItemSelected(MenuItem item) { inputKey.setInputType(InputType.TYPE_CLASS_TEXT); inputKey.setText(Utils.getSigningKey(this)); builderKey.setView(inputKey); - builderKey.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> prefs.edit().putString("SIGNING_KEY", inputKey.getText().toString()).apply()); + builderKey.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> { + prefs.edit().putString("SIGNING_KEY", inputKey.getText().toString()).apply(); + logView.append(Utils.checkOldDatabase(this) + "\n"); + }); builderKey.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> { prefs.edit().putString("SIGNING_KEY", Utils.SIGNING_KEY_DEFAULT).apply(); dialog.cancel(); diff --git a/app/src/main/java/us/spotco/malwarescanner/Utils.java b/app/src/main/java/us/spotco/malwarescanner/Utils.java index 8ba7f3b..5211123 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Utils.java +++ b/app/src/main/java/us/spotco/malwarescanner/Utils.java @@ -38,15 +38,23 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.Arrays; class Utils { private static Context context = null; public final static int MAX_SCAN_SIZE = (1000 * 1000) * 500; //500MB public final static int MAX_SCAN_SIZE_REALTIME = (1000 * 1000) * 250; //250MB - public final static String DATABASE_URL_DEFAULT = "https://raw.githubusercontent.com/MaintainTeam/HypatiaDatabases/refs/heads/main/"; - public final static String DATABASE_URL_CLOUDFLARE = "https://eeyo.re/MalwareScannerSignatures/"; + public final static String DATABASE_URL_DEFAULT = "https://maintainteam.codeberg.page/HypatiaDatabases/"; + public final static String DATABASE_URL_GITHUB = "https://maintainteam.github.io/HypatiaDatabases/"; public final static String SIGNING_KEY_DEFAULT = "5298C0C0C3E73288"; + public final static String[] OLD_DATABASES = { + "https://raw.githubusercontent.com/MaintainTeam/HypatiaDatabases/refs/heads/main/", + "https://maintainteam.github.io/HypatiaDatabasesTests/" + }; + public final static String[] OLD_SIGNING_KEYS = { + "C335CDA7A1C0F66B", + }; public static final AtomicInteger FILES_SCANNED = new AtomicInteger(); public final static ConcurrentHashMap MATCHED_FILES_TIME = new ConcurrentHashMap<>(); @@ -222,4 +230,18 @@ public static void writeStringToFile(File fileOut, String s) { e.printStackTrace(); } } + + public static String checkOldDatabase(Context context) { + SharedPreferences prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE); + String saved_database = prefs.getString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT); + String saved_key = prefs.getString("SIGNING_KEY", Utils.SIGNING_KEY_DEFAULT); + + if (Arrays.asList(OLD_DATABASES).contains(saved_database)) { + return "WARNING: YOU ARE USING AN OLD DATABASE URL\n- Change Database server override in menu"; + } + if (Arrays.asList(OLD_SIGNING_KEYS).contains(saved_key)) { + return "WARNING: YOU ARE USING AN OLD SIGNING KEY\n- Change Database signing key in menu"; + } + return "Database/Key URLs are not deprecated - OK"; + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a54cefa..9fb8dbb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,7 +76,7 @@ Skipping action, an update is running! Wrote test files! Primary Servers - Cloudflare Mirror + GitHub Mirror Hypatia Link Scanner Extracts domains from all screen text content and checks them against a blocklist Malicious Link Detected: From 8cf050c97b95c84e49c7b0df4a37aed46571ce12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 21:29:33 +0300 Subject: [PATCH 02/25] update license years and initialize sharescanner --- .../spotco/malwarescanner/MainActivity.java | 1 + .../spotco/malwarescanner/ShareScanner.java | 23 +++++++++++++++++++ .../java/us/spotco/malwarescanner/Utils.java | 1 + 3 files changed, 25 insertions(+) create mode 100644 app/src/main/java/us/spotco/malwarescanner/ShareScanner.java diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 39c6b02..6c5c3e0 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -1,6 +1,7 @@ /* Hypatia: A realtime malware scanner for Android Copyright (c) 2017-2018 Divested Computing Group +Copyleft (🄯) 2025 MaintainTeam Organization This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java new file mode 100644 index 0000000..57da1df --- /dev/null +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -0,0 +1,23 @@ +/* +Hypatia: A realtime malware scanner for Android +Copyleft (🄯) 2025 MaintainTeam Organization + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +package us.spotco.malwarescanner; + +class ShareScanner { + + +} \ No newline at end of file diff --git a/app/src/main/java/us/spotco/malwarescanner/Utils.java b/app/src/main/java/us/spotco/malwarescanner/Utils.java index 5793177..bcec9ea 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Utils.java +++ b/app/src/main/java/us/spotco/malwarescanner/Utils.java @@ -1,6 +1,7 @@ /* Hypatia: A realtime malware scanner for Android Copyright (c) 2017-2018 Divested Computing Group +Copyleft (🄯) 2025 MaintainTeam Organization This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by From 3734bbc6ad86eafd2c77e92b92bcd26c458a23de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 21:30:22 +0300 Subject: [PATCH 03/25] add share intent to manifest --- app/src/main/AndroidManifest.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 278356d..b1b8fce 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,6 +51,18 @@ + + + + + + + + + + + Date: Sat, 1 Mar 2025 21:54:37 +0300 Subject: [PATCH 04/25] WIP: share implementation --- .../spotco/malwarescanner/ShareScanner.java | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index 57da1df..fd82fc9 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -17,7 +17,29 @@ */ package us.spotco.malwarescanner; -class ShareScanner { +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + + +import java.io.File; +import java.util.HashSet; + +public class ShareScanner extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + logView = findViewById(R.id.txtLogOutput); + + Intent intent = getIntent(); + String action = intent.getAction(); + String type = intent.getType(); + if ("android.intent.action.SEND".equals(action) && type != null && "text/plain".equals(type)) { + publishProgress(intent.getStringExtra("android.intent.extra.TEXT") + "\n", true); + } + + } - } \ No newline at end of file From 4e62215b3b04b96407a46ed28f17922b61079c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 22:03:17 +0300 Subject: [PATCH 05/25] WIP fix manifest --- app/src/main/AndroidManifest.xml | 3 ++- app/src/main/java/us/spotco/malwarescanner/ShareScanner.java | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b1b8fce..ea109c5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,7 +52,8 @@ + android:name=".ShareScanner" + android:exported="true"> diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index fd82fc9..a7c7978 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.os.Bundle; - import java.io.File; import java.util.HashSet; From 2ba1bd2b29699a72ee16a5cc1f8df314940625cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 22:06:09 +0300 Subject: [PATCH 06/25] WIP --- app/src/main/java/us/spotco/malwarescanner/ShareScanner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index a7c7978..b624558 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -18,6 +18,7 @@ package us.spotco.malwarescanner; import android.app.Activity; +import android.os.AsyncTask; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -30,7 +31,6 @@ public class ShareScanner extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - logView = findViewById(R.id.txtLogOutput); Intent intent = getIntent(); String action = intent.getAction(); From e2a50ae2e4070462c62dc8309532d71453bf8749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 22:16:10 +0300 Subject: [PATCH 07/25] WIP --- app/src/main/AndroidManifest.xml | 6 +----- .../main/java/us/spotco/malwarescanner/ShareScanner.java | 8 ++++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea109c5..ac645e1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,11 +57,7 @@ - - - - - + diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index b624558..1002d24 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -18,25 +18,29 @@ package us.spotco.malwarescanner; import android.app.Activity; -import android.os.AsyncTask; +// import android.os.AsyncTask; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.widget.TextView; import java.io.File; import java.util.HashSet; public class ShareScanner extends Activity { + private TextView logView; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + logView = findViewById(R.id.txtLogOutput); Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); if ("android.intent.action.SEND".equals(action) && type != null && "text/plain".equals(type)) { - publishProgress(intent.getStringExtra("android.intent.extra.TEXT") + "\n", true); + logView.append(intent.getStringExtra("android.intent.extra.TEXT") + "\n"); } } From 8fcf220a4ee174f239a7af8785895a363b4c2cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 23:03:46 +0300 Subject: [PATCH 08/25] WIP --- app/build.gradle | 1 + .../spotco/malwarescanner/MainActivity.java | 31 ++++++++++---- .../spotco/malwarescanner/ShareScanner.java | 42 ++++++++++++++----- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5ee6801..98dbaa8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,6 +55,7 @@ dependencies { implementation 'commons-io:commons-io:2.5' implementation 'org.bouncycastle:bcpg-jdk15to18:1.77' implementation 'com.google.guava:guava:33.0.0-jre' + implementation 'androidx.appcompat:appcompat:1.6.1' } static String getGitWorkingBranch() { diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 6c5c3e0..737c215 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -84,7 +84,7 @@ protected final void onCreate(Bundle savedInstanceState) { logView.append(getString(R.string.app_copyright) + "\n"); logView.append(getString(R.string.app_license) + "\n"); logView.append(getString(R.string.app_version, buildVersionName) + "\n\n"); - + logView.append(Utils.checkOldDatabase(this) + "\n"); malwareScanner = new MalwareScanner(this, this, true); @@ -111,10 +111,12 @@ public final boolean onCreateOptionsMenu(Menu menu) { private void requestPermissions() { if (SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_EXTERNAL_STORAGE); + requestPermissions(new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, + REQUEST_PERMISSION_EXTERNAL_STORAGE); } if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_EXTERNAL_STORAGE); + requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, + REQUEST_PERMISSION_EXTERNAL_STORAGE); } } if (SDK_INT >= Build.VERSION_CODES.R) { @@ -132,7 +134,7 @@ private void showCredits() { AlertDialog.Builder creditsBuilder = new AlertDialog.Builder(this); creditsBuilder.setTitle(getString(R.string.lblFullCredits)); creditsBuilder.setItems(R.array.fullCredits, (dialog, which) -> { - //do nothing + // do nothing }); creditsDialog = creditsBuilder.create(); creditsDialog.show(); @@ -280,7 +282,8 @@ public final boolean onOptionsItemSelected(MenuItem item) { if (!prevDomains) { Database.changedConfig = true; } - startActivityForResult(new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS), 0); + startActivityForResult( + new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS), 0); }) .setNegativeButton(getString(android.R.string.no), (dialog, which) -> { prefs.edit().putBoolean("DOMAINS", false).apply(); @@ -370,7 +373,8 @@ private void startScanner() { filesToScan.add(new File("/vendor_dlkm")); } if (scanApps) { - for (ApplicationInfo packageInfo : getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA)) { + for (ApplicationInfo packageInfo : getPackageManager() + .getInstalledApplications(PackageManager.GET_META_DATA)) { if (packageInfo != null) { if (packageInfo.sourceDir != null) { filesToScan.add(new File(packageInfo.sourceDir)); @@ -400,7 +404,8 @@ private void startScanner() { } PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); - PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Hypatia::ManualScanLock"); + PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "Hypatia::ManualScanLock"); wakeLock.acquire(10 * 60 * 1000L); /* 10 minutes */ malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); new Thread(() -> { @@ -424,7 +429,8 @@ private void updateDatabase() { new Database(findViewById(R.id.txtLogOutput)); if (!Database.isDatabaseLoading()) { PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); - PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Hypatia::UpdateLock"); + PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + "Hypatia::UpdateLock"); wakeLock.acquire(3 * 60 * 1000L); /* 3 minutes */ Database.updateDatabase(this, Database.signatureDatabases); Utils.getThreadPoolExecutor().execute(() -> { @@ -477,4 +483,13 @@ private void updateScanButton(boolean running) { } } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent.hasExtra("scan_result")) { + String result = intent.getStringExtra("scan_result"); + logView.append("Share Scan Successfull: " + result + "\n"); + } + } + } diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index 1002d24..6f1cb45 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -18,31 +18,51 @@ package us.spotco.malwarescanner; import android.app.Activity; -// import android.os.AsyncTask; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.widget.TextView; +import androidx.appcompat.app.AppCompatActivity; +import android.net.Uri; +import java.io.InputStream; +import java.io.IOException; import java.io.File; import java.util.HashSet; -public class ShareScanner extends Activity { - - private TextView logView; +public class ShareScanner extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - logView = findViewById(R.id.txtLogOutput); + setContentView(R.layout.activity_sharescanner); + // HOW TO GET MAIN SCREEN CONTENT ? - Intent intent = getIntent(); - String action = intent.getAction(); - String type = intent.getType(); - if ("android.intent.action.SEND".equals(action) && type != null && "text/plain".equals(type)) { - logView.append(intent.getStringExtra("android.intent.extra.TEXT") + "\n"); - } + handleIncomingIntent(getIntent()); + } + private void handleIncomingIntent(Intent intent) { + if (Intent.ACTION_SEND.equals(intent.getAction()) && intent.getType() != null) { + Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + if (fileUri != null) { + String scanResult = scanFile(fileUri); + Intent mainIntent = new Intent(this, MainActivity.class); + mainIntent.putExtra("scan_result", scanResult); + mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(mainIntent); + finish(); + } + } } + private String scanFile(Uri fileUri) { + try { + // InputStream inputStream = getContentResolver().openInputStream(fileUri); + // return YourScanner.scan(inputStream); + return "SCAN RESULT"; + + } catch (IOException e) { + return "Scanner Error: " + e.getMessage(); + } + } } \ No newline at end of file From 6142b28554387cbb53162a0124fcca53dd60e745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 23:31:49 +0300 Subject: [PATCH 09/25] update gradle --- gradle/verification-metadata.xml | 1444 ++++++++++++++++++++-- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 38 +- gradlew.bat | 23 +- 5 files changed, 1407 insertions(+), 102 deletions(-) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 016cbf6..d7803b0 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1,5 +1,5 @@ - + true true @@ -7,79 +7,79 @@ - + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -101,17 +101,21 @@ + + + + @@ -130,9 +134,11 @@ + + @@ -160,6 +166,14 @@ + + + + + + + + @@ -184,27 +198,41 @@ + + + + + + + + + + + + + + @@ -218,22 +246,27 @@ + + + + + @@ -271,17 +304,21 @@ + + + + @@ -290,30 +327,37 @@ + + + + + + + @@ -327,17 +371,21 @@ + + + + @@ -351,9 +399,11 @@ + + @@ -375,33 +425,41 @@ + + + + + + + + @@ -428,22 +486,27 @@ + + + + + @@ -457,17 +520,21 @@ + + + + @@ -491,9 +558,11 @@ + + @@ -512,9 +581,11 @@ + + @@ -528,17 +599,21 @@ + + + + @@ -573,9 +648,11 @@ + + @@ -586,30 +663,37 @@ + + + + + + + @@ -623,33 +707,41 @@ + + + + + + + + @@ -663,105 +755,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -783,121 +901,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -927,9 +1075,11 @@ + + @@ -967,68 +1117,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1045,9 +1300,68 @@ + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1058,150 +1372,1124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - + + - + + - + + - + + - + + + + + + + + + + + + + + + + + + + + - + + - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch literal 43504 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 TZiJi*F`$tkWLn) literal 60756 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 /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 53a6b23..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -42,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From 748bf80000b2878f04ebf5b946d6083cbce3958a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 23:38:33 +0300 Subject: [PATCH 10/25] update java to 21 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe21fc3..3da5d0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: "temurin" cache: 'gradle' From 3f30c2cc5aace7754d9981f4a06d97a37a5ebfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sat, 1 Mar 2025 23:41:43 +0300 Subject: [PATCH 11/25] change checksum --- app/build.gradle | 11 +- app/src/main/AndroidManifest.xml | 3 +- .../spotco/malwarescanner/ShareScanner.java | 10 +- build.gradle | 2 +- gradle.properties | 2 + gradle/verification-metadata.xml | 1019 +++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- 7 files changed, 1039 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 98dbaa8..f8530a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,8 @@ apply plugin: 'com.android.application' android { compileSdkVersion 33 + namespace 'us.spotco.malwarescanner' + defaultConfig { applicationId "org.maintainteam.hypatia" resValue "string", "app_name", "Hypatia" @@ -28,7 +30,6 @@ android { resValue "string", "app_name", "Hypatia " + workingBranch } - minifyEnabled true zipAlignEnabled true } @@ -49,6 +50,14 @@ android { exclude 'org/bouncycastle/x509/CertPathReviewerMessages.properties' exclude 'org/bouncycastle/x509/CertPathReviewerMessages_de.properties' } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + encoding 'utf-8' + } + buildFeatures { + buildConfig = true + } } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ac645e1..8df285e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index 6f1cb45..5da7873 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -35,7 +35,7 @@ public class ShareScanner extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_sharescanner); + // setContentView(R.layout.activity_sharescanner); // HOW TO GET MAIN SCREEN CONTENT ? handleIncomingIntent(getIntent()); @@ -56,13 +56,13 @@ private void handleIncomingIntent(Intent intent) { } private String scanFile(Uri fileUri) { - try { + // try { // InputStream inputStream = getContentResolver().openInputStream(fileUri); // return YourScanner.scan(inputStream); return "SCAN RESULT"; - } catch (IOException e) { - return "Scanner Error: " + e.getMessage(); - } + // } catch (IOException e) { + // return "Scanner Error: " + e.getMessage(); + // } } } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 23ab4b2..04bbaf4 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' + classpath 'com.android.tools.build:gradle:8.7.1' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle.properties b/gradle.properties index 11e2709..9484d81 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,8 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. android.enableJetifier=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index d7803b0..409472b 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -379,6 +379,14 @@ + + + + + + + + @@ -389,6 +397,14 @@ + + + + + + + + @@ -763,6 +779,14 @@ + + + + + + + + @@ -773,6 +797,14 @@ + + + + + + + + @@ -783,6 +815,14 @@ + + + + + + + + @@ -793,6 +833,14 @@ + + + + + + + + @@ -803,6 +851,14 @@ + + + + + + + + @@ -813,6 +869,14 @@ + + + + + + + + @@ -823,6 +887,14 @@ + + + + + + + + @@ -833,6 +905,14 @@ + + + + + + + + @@ -843,6 +923,14 @@ + + + + + + + + @@ -853,6 +941,14 @@ + + + + + + + + @@ -863,6 +959,14 @@ + + + + + + + + @@ -873,6 +977,14 @@ + + + + + + + + @@ -883,6 +995,14 @@ + + + + + + + + @@ -891,6 +1011,14 @@ + + + + + + + + @@ -909,6 +1037,14 @@ + + + + + + + + @@ -919,6 +1055,14 @@ + + + + + + + + @@ -929,6 +1073,14 @@ + + + + + + + + @@ -939,6 +1091,14 @@ + + + + + + + + @@ -949,6 +1109,14 @@ + + + + + + + + @@ -959,6 +1127,14 @@ + + + + + + + + @@ -969,6 +1145,22 @@ + + + + + + + + + + + + + + + + @@ -989,6 +1181,14 @@ + + + + + + + + @@ -999,6 +1199,22 @@ + + + + + + + + + + + + + + + + @@ -1009,6 +1225,14 @@ + + + + + + + + @@ -1029,6 +1253,14 @@ + + + + + + + + @@ -1039,6 +1271,14 @@ + + + + + + + + @@ -1049,6 +1289,22 @@ + + + + + + + + + + + + + + + + @@ -1083,6 +1339,14 @@ + + + + + + + + @@ -1125,6 +1389,14 @@ + + + + + + + + @@ -1135,6 +1407,22 @@ + + + + + + + + + + + + + + + + @@ -1145,6 +1433,22 @@ + + + + + + + + + + + + + + + + @@ -1155,6 +1459,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1165,6 +1501,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1175,6 +1543,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1185,6 +1617,22 @@ + + + + + + + + + + + + + + + + @@ -1195,6 +1643,22 @@ + + + + + + + + + + + + + + + + @@ -1308,6 +1772,14 @@ + + + + + + + + @@ -1335,6 +1807,14 @@ + + + + + + + + @@ -1343,6 +1823,11 @@ + + + + + @@ -1356,6 +1841,14 @@ + + + + + + + + @@ -1364,6 +1857,11 @@ + + + + + @@ -1372,6 +1870,14 @@ + + + + + + + + @@ -1398,11 +1904,21 @@ + + + + + + + + + + @@ -1455,6 +1971,14 @@ + + + + + + + + @@ -1474,6 +1998,11 @@ + + + + + @@ -1523,6 +2052,11 @@ + + + + + @@ -1531,6 +2065,14 @@ + + + + + + + + @@ -1539,11 +2081,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1554,6 +2141,22 @@ + + + + + + + + + + + + + + + + @@ -1666,6 +2269,14 @@ + + + + + + + + @@ -1698,6 +2309,14 @@ + + + + + + + + @@ -1706,6 +2325,14 @@ + + + + + + + + @@ -1714,6 +2341,14 @@ + + + + + + + + @@ -1722,6 +2357,14 @@ + + + + + + + + @@ -1730,6 +2373,14 @@ + + + + + + + + @@ -1738,6 +2389,14 @@ + + + + + + + + @@ -1746,6 +2405,14 @@ + + + + + + + + @@ -1754,6 +2421,14 @@ + + + + + + + + @@ -1762,6 +2437,14 @@ + + + + + + + + @@ -1770,6 +2453,14 @@ + + + + + + + + @@ -1778,6 +2469,14 @@ + + + + + + + + @@ -1786,6 +2485,14 @@ + + + + + + + + @@ -1794,6 +2501,14 @@ + + + + + + + + @@ -1802,6 +2517,14 @@ + + + + + + + + @@ -1810,11 +2533,24 @@ + + + + + + + + + + + + + @@ -1823,6 +2559,14 @@ + + + + + + + + @@ -1831,6 +2575,22 @@ + + + + + + + + + + + + + + + + @@ -1847,6 +2607,14 @@ + + + + + + + + @@ -1876,6 +2644,14 @@ + + + + + + + + @@ -1892,6 +2668,11 @@ + + + + + @@ -1957,6 +2738,16 @@ + + + + + + + + + + @@ -1971,6 +2762,14 @@ + + + + + + + + @@ -2004,6 +2803,24 @@ + + + + + + + + + + + + + + + + + + @@ -2012,6 +2829,11 @@ + + + + + @@ -2028,6 +2850,11 @@ + + + + + @@ -2047,6 +2874,14 @@ + + + + + + + + @@ -2063,6 +2898,14 @@ + + + + + + + + @@ -2079,6 +2922,14 @@ + + + + + + + + @@ -2095,11 +2946,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -2124,17 +2999,35 @@ + + + + + + + + + + + + + + + + + + @@ -2228,6 +3121,14 @@ + + + + + + + + @@ -2257,6 +3158,14 @@ + + + + + + + + @@ -2265,6 +3174,14 @@ + + + + + + + + @@ -2281,6 +3198,14 @@ + + + + + + + + @@ -2297,6 +3222,11 @@ + + + + + @@ -2313,6 +3243,14 @@ + + + + + + + + @@ -2329,6 +3267,14 @@ + + + + + + + + @@ -2342,6 +3288,11 @@ + + + + + @@ -2352,6 +3303,11 @@ + + + + + @@ -2368,6 +3324,14 @@ + + + + + + + + @@ -2389,6 +3353,16 @@ + + + + + + + + + + @@ -2403,6 +3377,11 @@ + + + + + @@ -2411,6 +3390,14 @@ + + + + + + + + @@ -2419,6 +3406,14 @@ + + + + + + + + @@ -2427,6 +3422,14 @@ + + + + + + + + @@ -2435,6 +3438,14 @@ + + + + + + + + @@ -2443,6 +3454,14 @@ + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5509ac3..68e8816 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4 +distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true From 4b08172b0c95c68aca24b6f954589ead808e16df Mon Sep 17 00:00:00 2001 From: Aliberk Sandikci Date: Sat, 1 Mar 2025 22:49:20 +0000 Subject: [PATCH 12/25] WIP --- app/build.gradle | 3 + gradle/verification-metadata.xml | 105 +++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/app/build.gradle b/app/build.gradle index f8530a6..1a6d07b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,6 +9,7 @@ android { resValue "string", "app_name", "Hypatia" minSdkVersion 16 targetSdkVersion 32 + multiDexEnabled true versionCode 314 versionName "3.14" resConfigs 'en', 'ar', 'bg', 'cs', 'de', 'es', 'et', 'fi', 'fr', 'gl', 'hr', 'in', 'it', 'ja', 'pt', 'pt-rBR', 'ro', 'ru', 'sk', 'ta', 'tr', 'uk', 'zh-rCN', 'zh-rTW' @@ -65,6 +66,8 @@ dependencies { implementation 'org.bouncycastle:bcpg-jdk15to18:1.77' implementation 'com.google.guava:guava:33.0.0-jre' implementation 'androidx.appcompat:appcompat:1.6.1' + def multidex_version = "2.0.1" + implementation "androidx.multidex:multidex:$multidex_version" } static String getGitWorkingBranch() { diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 409472b..a132921 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -641,6 +641,22 @@ + + + + + + + + + + + + + + + + @@ -877,6 +893,14 @@ + + + + + + + + @@ -1313,6 +1337,14 @@ + + + + + + + + @@ -1321,6 +1353,14 @@ + + + + + + + + @@ -1329,6 +1369,14 @@ + + + + + + + + @@ -1355,6 +1403,14 @@ + + + + + + + + @@ -1363,6 +1419,14 @@ + + + + + + + + @@ -1371,6 +1435,14 @@ + + + + + + + + @@ -1379,6 +1451,14 @@ + + + + + + + + @@ -2257,6 +2337,9 @@ + + + @@ -2821,6 +2904,14 @@ + + + + + + + + @@ -2991,6 +3082,14 @@ + + + + + + + + @@ -3354,11 +3453,17 @@ + + + + + + From 0cb07a17eb59b3fb6fd363eaa3039f7992a38810 Mon Sep 17 00:00:00 2001 From: Aliberk Sandikci Date: Sat, 1 Mar 2025 23:07:12 +0000 Subject: [PATCH 13/25] disable checks for now --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9484d81..b2484da 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,5 +20,5 @@ org.gradle.jvmargs=-Xmx1536m # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -org.gradle.dependency.verification=strict +org.gradle.dependency.verification=lenient #org.gradle.dependency.verification.console=verbose From e1fb320cf82e1be62b85d3083746c5c6b86b2ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sun, 2 Mar 2025 03:35:10 +0300 Subject: [PATCH 14/25] WIP TEST --- app/src/main/AndroidManifest.xml | 1 + .../spotco/malwarescanner/MainActivity.java | 25 ++++++++++++------- .../spotco/malwarescanner/ShareScanner.java | 7 ++++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8df285e..0e46a79 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ android:excludeFromRecents="true" /> diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 737c215..8ec474b 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -45,6 +45,8 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; + import java.io.File; import java.util.HashSet; @@ -96,6 +98,20 @@ protected final void onCreate(Bundle savedInstanceState) { Utils.considerStartService(this); } + @Override + protected final void onNewIntent(Intent intent) { + Log.w("Hypatia", "111"); + super.onNewIntent(intent); + Log.w("Hypatia", "222"); + if (intent.hasExtra("scan_result")) { + Log.w("Hypatia", "333"); + String result = intent.getStringExtra("scan_result"); + Log.w("Hypatia", "444"); + logView.append("Share Scan Successfull: " + result + "\n"); + Log.w("Hypatia", "555"); + } + } + @Override public final boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); @@ -483,13 +499,4 @@ private void updateScanButton(boolean running) { } } - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - if (intent.hasExtra("scan_result")) { - String result = intent.getStringExtra("scan_result"); - logView.append("Share Scan Successfull: " + result + "\n"); - } - } - } diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index 5da7873..df91143 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -26,11 +26,13 @@ import android.net.Uri; import java.io.InputStream; import java.io.IOException; +import android.util.Log; + import java.io.File; import java.util.HashSet; -public class ShareScanner extends AppCompatActivity { +public class ShareScanner extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -46,9 +48,10 @@ private void handleIncomingIntent(Intent intent) { Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); if (fileUri != null) { String scanResult = scanFile(fileUri); + Log.w("Hypatia", scanResult); Intent mainIntent = new Intent(this, MainActivity.class); mainIntent.putExtra("scan_result", scanResult); - mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); startActivity(mainIntent); finish(); } From 5bde35381318898e1c1d323b3da6226253ef1c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sun, 2 Mar 2025 20:01:39 +0300 Subject: [PATCH 15/25] update sdk versions * drop android 4 support better maintenance --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1a6d07b..467be90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,14 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 33 + compileSdkVersion 34 namespace 'us.spotco.malwarescanner' defaultConfig { applicationId "org.maintainteam.hypatia" resValue "string", "app_name", "Hypatia" - minSdkVersion 16 - targetSdkVersion 32 + minSdkVersion 21 + targetSdkVersion 34 multiDexEnabled true versionCode 314 versionName "3.14" From 593c7cb07dd7baafb03728ffa728e939b367a5d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sun, 2 Mar 2025 21:42:43 +0300 Subject: [PATCH 16/25] appcompat and theme --- app/build.gradle | 3 --- app/proguard-rules.pro | 3 +++ app/src/main/AndroidManifest.xml | 2 +- app/src/main/java/us/spotco/malwarescanner/MainActivity.java | 5 ++--- .../main/java/us/spotco/malwarescanner/MalwareScanner.java | 1 + .../us/spotco/malwarescanner/NotificationPromptActivity.java | 3 ++- app/src/main/java/us/spotco/malwarescanner/ShareScanner.java | 4 ++-- app/src/main/res/values/strings.xml | 2 +- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 467be90..154f93c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,6 @@ android { resValue "string", "app_name", "Hypatia" minSdkVersion 21 targetSdkVersion 34 - multiDexEnabled true versionCode 314 versionName "3.14" resConfigs 'en', 'ar', 'bg', 'cs', 'de', 'es', 'et', 'fi', 'fr', 'gl', 'hr', 'in', 'it', 'ja', 'pt', 'pt-rBR', 'ro', 'ru', 'sk', 'ta', 'tr', 'uk', 'zh-rCN', 'zh-rTW' @@ -66,8 +65,6 @@ dependencies { implementation 'org.bouncycastle:bcpg-jdk15to18:1.77' implementation 'com.google.guava:guava:33.0.0-jre' implementation 'androidx.appcompat:appcompat:1.6.1' - def multidex_version = "2.0.1" - implementation "androidx.multidex:multidex:$multidex_version" } static String getGitWorkingBranch() { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 95c6362..2ada171 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -24,3 +24,6 @@ # Disable obfuscation -dontobfuscate + +-keep class androidx.appcompat.app.** { *; } +-keep class androidx.appcompat.widget.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0e46a79..542b0f4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@android:style/Theme.DeviceDefault"> + android:theme="@style/Theme.AppCompat.DayNight"> Hashing files… Calculated hashes for all files Checked all %s hashes against signature databases - Scan completed in %s seconds @ %sMB/s! + Scan completed in %1$s seconds @ %2$sMB/s! %s files scanned Scan Control Skipping action, a scan is running! From a0ae0713172e24bcea7b7b99b6b6d50827f418a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Sun, 2 Mar 2025 23:12:13 +0300 Subject: [PATCH 17/25] AppCompat & Toolbar Setup --- app/src/main/AndroidManifest.xml | 2 +- .../java/us/spotco/malwarescanner/MainActivity.java | 10 ++++++---- app/src/main/res/layout/content_main.xml | 11 +++++++++++ app/src/main/res/menu/menu_main.xml | 3 ++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 542b0f4..5e705ba 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.AppCompat.DayNight"> + android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"> = Build.VERSION_CODES.Q) { - setTheme(android.R.style.Theme_DeviceDefault_DayNight); - } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); } @@ -78,6 +77,9 @@ protected final void onCreate(Bundle savedInstanceState) { Utils.setContext(getApplicationContext()); setContentView(R.layout.content_main); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + logView = findViewById(R.id.txtLogOutput); logView.setMovementMethod(new ScrollingMovementMethod()); logView.setTextIsSelectable(true); diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index dbe3040..5c31f29 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -1,9 +1,20 @@ + + + @@ -6,7 +7,7 @@ android:id="@+id/btnStartScan" android:title="@string/scan_control" android:icon="@android:drawable/ic_media_play" - android:showAsAction="always" /> + app:showAsAction="always" /> Date: Mon, 3 Mar 2025 03:29:37 +0300 Subject: [PATCH 18/25] WIP --- .../spotco/malwarescanner/MainActivity.java | 25 +++-- .../spotco/malwarescanner/MalwareScanner.java | 3 +- .../spotco/malwarescanner/ShareScanner.java | 102 ++++++++++++++---- 3 files changed, 103 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index f550aa4..1694dc2 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -53,6 +53,7 @@ public class MainActivity extends AppCompatActivity { + private static MainActivity instance; private SharedPreferences prefs = null; private MalwareScanner malwareScanner = null; @@ -74,6 +75,7 @@ protected final void onCreate(Bundle savedInstanceState) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); } super.onCreate(savedInstanceState); + instance = this; Utils.setContext(getApplicationContext()); setContentView(R.layout.content_main); @@ -97,22 +99,29 @@ protected final void onCreate(Bundle savedInstanceState) { requestPermissions(); Utils.considerStartService(this); + + handleIntent(getIntent()); } @Override protected final void onNewIntent(Intent intent) { - Log.w("Hypatia", "111"); super.onNewIntent(intent); - Log.w("Hypatia", "222"); - if (intent.hasExtra("scan_result")) { - Log.w("Hypatia", "333"); - String result = intent.getStringExtra("scan_result"); - Log.w("Hypatia", "444"); - logView.append("Share Scan Successfull: " + result + "\n"); - Log.w("Hypatia", "555"); + + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + if (intent != null && intent.hasExtra("SHARE_SCAN_RESULT")) { + String result = intent.getStringExtra("SHARE_SCAN_RESULT"); + logView.append("Share Scan Result: " + result + "\n"); } } + public static MainActivity getInstance() { + return instance; + // REVIEW Potential security risk ?! + } + @Override public final boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java index 05b4cce..85cd51b 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java @@ -30,6 +30,7 @@ import android.os.Environment; import android.os.SystemClock; import android.widget.TextView; +import android.util.Log; import com.google.common.hash.BloomFilter; @@ -222,7 +223,7 @@ protected final String doInBackground(HashSet[] filesToScan) { spinnerCur = " = "; } } - //Log.d("Hypatia", "Scanning " + file); + Log.d("Hypatia", "Scanning " + file); } filesToScanReal.clear(); publishProgress("\n\t" + context.getString(R.string.main_hashing_done) + "\n", true); diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index a1ac215..c5ce3ac 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -27,18 +27,22 @@ import java.io.IOException; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; - +import android.provider.MediaStore; +import android.database.Cursor; +import android.provider.OpenableColumns; +import android.os.Environment; +import java.io.FileOutputStream; import java.io.File; import java.util.HashSet; public class ShareScanner extends AppCompatActivity { + private MalwareScanner malwareScanner = null; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // setContentView(R.layout.activity_sharescanner); - // HOW TO GET MAIN SCREEN CONTENT ? handleIncomingIntent(getIntent()); } @@ -46,26 +50,88 @@ protected void onCreate(Bundle savedInstanceState) { private void handleIncomingIntent(Intent intent) { if (Intent.ACTION_SEND.equals(intent.getAction()) && intent.getType() != null) { Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + String userResult; + Intent mainIntent = new Intent(this, MainActivity.class); + if (fileUri != null) { - String scanResult = scanFile(fileUri); - Log.w("Hypatia", scanResult); - Intent mainIntent = new Intent(this, MainActivity.class); - mainIntent.putExtra("scan_result", scanResult); - mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); - startActivity(mainIntent); - finish(); + userResult = scanFile(fileUri); + } else if ("text/plain".equals(intent.getType())) { + userResult = "This is not a scannable file - probably just a string: " + + intent.getStringExtra(Intent.EXTRA_TEXT); + // TODO feat: Link Scanner? + // TODO feat: get string input (hash) directly to scan + } else { + userResult = "THIS IS NOT A SCANNABLE THING"; } + mainIntent.putExtra("SHARE_SCAN_RESULT", userResult); + mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // REVIEW FLAGS + startActivity(mainIntent); + finish(); } } private String scanFile(Uri fileUri) { - // try { - // InputStream inputStream = getContentResolver().openInputStream(fileUri); - // return YourScanner.scan(inputStream); - return "SCAN RESULT"; - - // } catch (IOException e) { - // return "Scanner Error: " + e.getMessage(); - // } + try { + malwareScanner = new MalwareScanner(MainActivity.getInstance(), MainActivity.getInstance(), true); + malwareScanner.running = true; + HashSet filesToScan = new HashSet<>(); + + File file = uriToFile(this, fileUri); + Log.d("Hypatia", "Shared file saved in cache to scan: " + file.getAbsolutePath()); + + filesToScan.add(file); + malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); + return "STARTED SCAN, IF ANYTHING FOUND YOU SHOUD DELETE SOURCE MANUALLY (Notification Panel Won't work)"; + + } catch (RuntimeException e) { + Log.d("Hypatia", e.getMessage()); + return "Scanner Error: " + e.getMessage(); + } + } + + public File uriToFile(Context context, Uri uri) { + String fileName = getFileName(context, uri); + File file = createTempFile(context, fileName); + + try (InputStream inputStream = context.getContentResolver().openInputStream(uri); + FileOutputStream outputStream = new FileOutputStream(file)) { + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + return file; + } + + private String getFileName(Context context, Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; + } + + private File createTempFile(Context context, String fileName) { + File storageDir = context.getExternalCacheDir(); + if (storageDir == null) { + storageDir = context.getCacheDir(); + } + return new File(storageDir, fileName); } } \ No newline at end of file From fc9f9741ecb651051024b6403b64d8d1f1921df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Mon, 3 Mar 2025 23:17:09 +0300 Subject: [PATCH 19/25] Implement a new Logger --- app/src/main/AndroidManifest.xml | 1 + .../us/spotco/malwarescanner/Database.java | 22 ++-- .../spotco/malwarescanner/EventReceiver.java | 2 +- .../us/spotco/malwarescanner/Hypatia.java | 35 ++++++ .../spotco/malwarescanner/HypatiaLogger.java | 118 ++++++++++++++++++ .../spotco/malwarescanner/MainActivity.java | 76 +++++------ .../spotco/malwarescanner/MalwareScanner.java | 10 +- .../malwarescanner/MalwareScannerService.java | 8 +- .../spotco/malwarescanner/ShareScanner.java | 12 +- 9 files changed, 213 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/us/spotco/malwarescanner/Hypatia.java create mode 100644 app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5e705ba..14d19ce 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ 0) { - new MalwareScanner(null, context, false).executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); + new MalwareScanner(context, false).executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); } } } diff --git a/app/src/main/java/us/spotco/malwarescanner/Hypatia.java b/app/src/main/java/us/spotco/malwarescanner/Hypatia.java new file mode 100644 index 0000000..60f073e --- /dev/null +++ b/app/src/main/java/us/spotco/malwarescanner/Hypatia.java @@ -0,0 +1,35 @@ +/* +Hypatia: A realtime malware scanner for Android +Copyleft (🄯) 2025 MaintainTeam Organization + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +package us.spotco.malwarescanner; + +import android.app.Application; +import android.content.Context; + +public class Hypatia extends Application { + private static Hypatia instance; + + @Override + public void onCreate() { + super.onCreate(); + instance = this; + } + + public static Context getAppContext() { + return instance.getApplicationContext(); + } +} \ No newline at end of file diff --git a/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java b/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java new file mode 100644 index 0000000..1355fe2 --- /dev/null +++ b/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java @@ -0,0 +1,118 @@ +/* +Hypatia: A realtime malware scanner for Android +Copyleft (🄯) 2025 MaintainTeam Organization + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package us.spotco.malwarescanner; + +import android.widget.TextView; +import android.text.method.ScrollingMovementMethod; + +import android.util.Log; +import android.os.Build; + +import java.lang.ref.WeakReference; + +/** + * Log Types + */ +enum LogType { + VERBOSE, + DEBUG, + NONEWLINE, + ONLYNEWLINE, + DEFAULT, + INFO, + WARNING, + ERROR; +} + +/** + * Logs process outputs to user interface + * TODO use a separate log page to list this logs, not MainActivity + * + * @author Aliberk Sandıkçı + * + */ +public class HypatiaLogger { + + private static WeakReference logViewRef; + + /** + * Initializes logger listed in manifest. Uses WeakReference + */ + public static void InitializeLogger(TextView textView) { + logViewRef = new WeakReference<>(textView); + textView.setMovementMethod(new ScrollingMovementMethod()); + textView.setTextIsSelectable(true); + } + + /** + * Logs process outputs to user interface + * prints out also verbose/debug if app is debug version + * + * @param content log content + * @param type log type + */ + public static void Log(String content, LogType type) { + Boolean userFacing = true; + switch (type) { + case VERBOSE: + android.util.Log.v("Hypatia", content); + content = "VERBOSE: " + content; + if (!BuildConfig.DEBUG) { + userFacing = false; + } + break; + case DEBUG: + android.util.Log.d("Hypatia", content); + content = "DEBUG: " + content; + if (!BuildConfig.DEBUG) { + userFacing = false; + } + break; + case INFO: + android.util.Log.i("Hypatia", content); + content = "INFO: " + content; + break; + case WARNING: + android.util.Log.w("Hypatia", content); + content = "WARNING: " + content; + break; + case ERROR: + android.util.Log.e("Hypatia", content); + content = "ERROR: " + content; + break; + default: + android.util.Log.d("Hypatia", content); + break; + } + if (userFacing && logViewRef != null && logViewRef.get() != null) { + TextView currentLogView = logViewRef.get(); + currentLogView.append(content + (type == LogType.NONEWLINE ? "" : "\n")); + } + } + + /** + * Overloader of actual {@code Log()} function + * + * @param content log content + */ + public static void Log(String content) { + Log(content, LogType.DEFAULT); + } + +} \ No newline at end of file diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 1694dc2..4fc1897 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -36,13 +36,11 @@ import android.os.PowerManager; import android.provider.Settings; import android.text.InputType; -import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.WindowManager; import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; @@ -53,11 +51,9 @@ public class MainActivity extends AppCompatActivity { - private static MainActivity instance; private SharedPreferences prefs = null; private MalwareScanner malwareScanner = null; - private TextView logView; private Menu menu; private static final String buildVersionName = BuildConfig.VERSION_NAME; @@ -75,24 +71,21 @@ protected final void onCreate(Bundle savedInstanceState) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); } super.onCreate(savedInstanceState); - instance = this; - Utils.setContext(getApplicationContext()); + Utils.setContext(Hypatia.getAppContext()); setContentView(R.layout.content_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - logView = findViewById(R.id.txtLogOutput); - logView.setMovementMethod(new ScrollingMovementMethod()); - logView.setTextIsSelectable(true); + HypatiaLogger.InitializeLogger(findViewById(R.id.txtLogOutput)); - logView.append(getString(R.string.app_copyright) + "\n"); - logView.append(getString(R.string.app_license) + "\n"); - logView.append(getString(R.string.app_version, buildVersionName) + "\n\n"); + HypatiaLogger.Log(getString(R.string.app_copyright)); + HypatiaLogger.Log(getString(R.string.app_license)); + HypatiaLogger.Log(getString(R.string.app_version, buildVersionName)); - logView.append(Utils.checkOldDatabase(this) + "\n"); + HypatiaLogger.Log(Utils.checkOldDatabase(this)); - malwareScanner = new MalwareScanner(this, this, true); + malwareScanner = new MalwareScanner(this, true); prefs = getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE); @@ -113,15 +106,10 @@ protected final void onNewIntent(Intent intent) { private void handleIntent(Intent intent) { if (intent != null && intent.hasExtra("SHARE_SCAN_RESULT")) { String result = intent.getStringExtra("SHARE_SCAN_RESULT"); - logView.append("Share Scan Result: " + result + "\n"); + HypatiaLogger.Log("single file scan has been started", LogType.INFO); } } - public static MainActivity getInstance() { - return instance; - // REVIEW Potential security risk ?! - } - @Override public final boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); @@ -186,13 +174,13 @@ public final boolean onOptionsItemSelected(MenuItem item) { break; case R.id.mnuUpdateDatabase: if (Database.hasDownloadsRunning()) { - logView.append(getString(R.string.lblUpdateRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblUpdateRunning)); } else if (malwareScanner.running) { - logView.append(getString(R.string.lblScanRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblScanRunning)); } else if (!Utils.isNetworkAvailable(this)) { - logView.append(getString(R.string.lblNoNetwork) + "\n"); + HypatiaLogger.Log(getString(R.string.lblNoNetwork)); } else if (Database.isDatabaseLoading()) { - logView.append(getString(R.string.lblDatabaseLoading) + "\n"); + HypatiaLogger.Log(getString(R.string.lblDatabaseLoading)); } else if (Utils.isConnectionMetered(this)) { int amt = prefs.getBoolean("SIGNATURES_EXTENDED", false) ? 200 : 50; new AlertDialog.Builder(this) @@ -211,9 +199,9 @@ public final boolean onOptionsItemSelected(MenuItem item) { break; case R.id.mnuDatabaseServer: if (Database.hasDownloadsRunning()) { - logView.append(getString(R.string.lblUpdateRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblUpdateRunning) + "\n"); } else if (Database.isDatabaseLoading()) { - logView.append(getString(R.string.lblDatabaseLoading) + "\n"); + HypatiaLogger.Log(getString(R.string.lblDatabaseLoading) + "\n"); } else { AlertDialog.Builder builderServerOverride = new AlertDialog.Builder(this); builderServerOverride.setTitle(getString(R.string.lblDatabaseServer)); @@ -227,7 +215,7 @@ public final boolean onOptionsItemSelected(MenuItem item) { newServer += "/"; } prefs.edit().putString("DATABASE_SERVER", newServer).apply(); - logView.append(Utils.checkOldDatabase(this) + "\n"); + HypatiaLogger.Log(Utils.checkOldDatabase(this) + "\n"); }); builderServerOverride.setNegativeButton(getString(R.string.lblResetPrimary), (dialog, which) -> { prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT).apply(); @@ -242,9 +230,9 @@ public final boolean onOptionsItemSelected(MenuItem item) { break; case R.id.mnuSigningKey: if (Database.hasDownloadsRunning()) { - logView.append(getString(R.string.lblUpdateRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblUpdateRunning) + "\n"); } else if (Database.isDatabaseLoading()) { - logView.append(getString(R.string.lblDatabaseLoading) + "\n"); + HypatiaLogger.Log(getString(R.string.lblDatabaseLoading) + "\n"); } else { AlertDialog.Builder builderKey = new AlertDialog.Builder(this); builderKey.setTitle(getString(R.string.lblSigningKey)); @@ -254,7 +242,7 @@ public final boolean onOptionsItemSelected(MenuItem item) { builderKey.setView(inputKey); builderKey.setPositiveButton(getString(R.string.lblOverride), (dialog, which) -> { prefs.edit().putString("SIGNING_KEY", inputKey.getText().toString()).apply(); - logView.append(Utils.checkOldDatabase(this) + "\n"); + HypatiaLogger.Log(Utils.checkOldDatabase(this) + "\n"); }); builderKey.setNegativeButton(getString(R.string.lblReset), (dialog, which) -> { prefs.edit().putString("SIGNING_KEY", Utils.SIGNING_KEY_DEFAULT).apply(); @@ -265,9 +253,9 @@ public final boolean onOptionsItemSelected(MenuItem item) { break; case R.id.toggleExtended: if (Database.hasDownloadsRunning()) { - logView.append(getString(R.string.lblUpdateRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblUpdateRunning) + "\n"); } else if (Database.isDatabaseLoading()) { - logView.append(getString(R.string.lblDatabaseLoading) + "\n"); + HypatiaLogger.Log(getString(R.string.lblDatabaseLoading) + "\n"); } else { boolean prevExtended = prefs.getBoolean("SIGNATURES_EXTENDED", false); new AlertDialog.Builder(this) @@ -293,9 +281,9 @@ public final boolean onOptionsItemSelected(MenuItem item) { break; case R.id.toggleLinkScanner: if (Database.hasDownloadsRunning()) { - logView.append(getString(R.string.lblUpdateRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblUpdateRunning) + "\n"); } else if (Database.isDatabaseLoading()) { - logView.append(getString(R.string.lblDatabaseLoading) + "\n"); + HypatiaLogger.Log(getString(R.string.lblDatabaseLoading) + "\n"); } else { boolean prevDomains = prefs.getBoolean("DOMAINS", false); new AlertDialog.Builder(this) @@ -323,9 +311,9 @@ public final boolean onOptionsItemSelected(MenuItem item) { break; case R.id.toggleRealtime: if (malwareScanner.running) { - logView.append(getString(R.string.lblScanRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblScanRunning) + "\n"); } else { - Intent realtimeScanner = new Intent(getApplicationContext(), MalwareScannerService.class); + Intent realtimeScanner = new Intent(Hypatia.getAppContext(), MalwareScannerService.class); if (!item.isChecked()) { prefs.edit().putBoolean("autostart", true).apply(); Utils.considerStartService(this); @@ -361,15 +349,15 @@ public final boolean onOptionsItemSelected(MenuItem item) { case R.id.btnStartScan: if (!malwareScanner.running) { if (Database.hasDownloadsRunning()) { - logView.append(getString(R.string.lblUpdateRunning) + "\n"); + HypatiaLogger.Log(getString(R.string.lblUpdateRunning) + "\n"); } else if (Database.isDatabaseLoading()) { - logView.append(getString(R.string.lblDatabaseLoading) + "\n"); + HypatiaLogger.Log(getString(R.string.lblDatabaseLoading) + "\n"); } else { updateScanButton(true); startScanner(); } } else { - logView.append("\n" + getString(R.string.main_cancelling_scan) + "\n\n"); + HypatiaLogger.Log("\n" + getString(R.string.main_cancelling_scan) + "\n\n"); malwareScanner.cancel(true); malwareScanner.running = false; } @@ -378,7 +366,7 @@ public final boolean onOptionsItemSelected(MenuItem item) { } private void startScanner() { - malwareScanner = new MalwareScanner(this, this, true); + malwareScanner = new MalwareScanner(this, true); malwareScanner.running = true; HashSet filesToScan = new HashSet<>(); if (scanSystem) { @@ -450,7 +438,7 @@ private void startScanner() { private void updateDatabase() { if (prefs.getBoolean("ONION_ROUTING", false)) { Utils.requestStartOrbot(this); - logView.append(getString(R.string.lblOnionRoutingEnabledHint) + "\n"); + HypatiaLogger.Log(getString(R.string.lblOnionRoutingEnabledHint) + "\n"); } new Database(findViewById(R.id.txtLogOutput)); if (!Database.isDatabaseLoading()) { @@ -471,11 +459,11 @@ private void updateDatabase() { } catch (InterruptedException e) { throw new RuntimeException(e); } - runOnUiThread(() -> logView.append(getString(R.string.lblDatabasesUpdated) + "\n")); + runOnUiThread(() -> HypatiaLogger.Log(getString(R.string.lblDatabasesUpdated) + "\n")); if (Database.isDatabaseLoaded()) { if (Database.changedDownload || Database.changedConfig) { Log.d("Hypatia", "Really reloading database!"); - Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases); + Database.loadDatabase(Hypatia.getAppContext(), true, Database.signatureDatabases); } else { Log.d("Hypatia", "Database not changed, skipping reload!"); } @@ -484,7 +472,7 @@ private void updateDatabase() { } }); } else { - logView.append(getString(R.string.lblDatabaseLoading) + "\n"); + HypatiaLogger.Log(getString(R.string.lblDatabaseLoading) + "\n"); } } diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java index 85cd51b..163fdee 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java @@ -17,7 +17,6 @@ */ package us.spotco.malwarescanner; -import android.app.Activity; import androidx.appcompat.app.AppCompatActivity; import android.app.Notification; import android.app.NotificationChannel; @@ -60,12 +59,9 @@ class MalwareScanner extends AsyncTask, Object, String> { public boolean running = false; private int amtMatchedFiles = 0; - public MalwareScanner(Activity activity, Context context, boolean userFacing) { + public MalwareScanner(Context context, boolean userFacing) { this.context = context; this.userFacing = userFacing; - if (activity != null) { - logOutput = activity.findViewById(R.id.txtLogOutput); - } notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel detectionChannel = new NotificationChannel("DETECTION", context.getString(R.string.lblNotificationMalwareDetectionTitle), NotificationManager.IMPORTANCE_HIGH); @@ -138,9 +134,9 @@ private void logResult(String result, String hashsum) { //Show it! notificationManager.notify(notificationId, mBuilder.build()); } else if (userFacing) { - logOutput.append(result); + HypatiaLogger.Log(result, LogType.NONEWLINE); if (!(result.length() <= 3)) { - logOutput.append("\n"); + HypatiaLogger.Log("\n", LogType.NONEWLINE); } } } diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java index fa35081..2731225 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java +++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java @@ -48,19 +48,19 @@ public final IBinder onBind(Intent intent) { @Override public final int onStartCommand(Intent intent, int flags, int startId) { - Utils.setContext(getApplicationContext()); + Utils.setContext(Hypatia.getAppContext()); malwareMonitors.clear(); addMalwareMonitor(Environment.getExternalStorageDirectory().toString()); int threadCount = Utils.getMaxThreads() + malwareMonitors.size(); threadPoolExecutor = Utils.getNewThreadPoolExecutor(threadCount); - threadPoolExecutor.execute(() -> Database.loadDatabase(getApplicationContext(), false, Database.signatureDatabases)); + threadPoolExecutor.execute(() -> Database.loadDatabase(Hypatia.getAppContext(), false, Database.signatureDatabases)); for (final RecursiveFileObserver malwareMonitor : malwareMonitors) { threadPoolExecutor.execute(malwareMonitor::startWatching); } - notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager = (NotificationManager) Hypatia.getAppContext().getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel foregroundChannel = new NotificationChannel("FOREGROUND", getString(R.string.lblNotificationRealtimeTitle), NotificationManager.IMPORTANCE_LOW); foregroundChannel.setDescription(getString(R.string.lblNotificationRealtimeDescription)); @@ -82,7 +82,7 @@ public void onEvent(int event, String path) { if (file.exists() && file.length() <= Utils.MAX_SCAN_SIZE_REALTIME) { HashSet filesToScan = new HashSet<>(); filesToScan.add(file); - new MalwareScanner(null, getApplicationContext(), false).executeOnExecutor(threadPoolExecutor, filesToScan); + new MalwareScanner(Hypatia.getAppContext(), false).executeOnExecutor(threadPoolExecutor, filesToScan); } updateForegroundNotification(); break; diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index c5ce3ac..a22aa72 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -51,10 +51,10 @@ private void handleIncomingIntent(Intent intent) { if (Intent.ACTION_SEND.equals(intent.getAction()) && intent.getType() != null) { Uri fileUri = intent.getParcelableExtra(Intent.EXTRA_STREAM); String userResult; - Intent mainIntent = new Intent(this, MainActivity.class); + Intent mainIntent = new Intent(Hypatia.getAppContext(), MainActivity.class); if (fileUri != null) { - userResult = scanFile(fileUri); + userResult = "Single file scan started"; } else if ("text/plain".equals(intent.getType())) { userResult = "This is not a scannable file - probably just a string: " + intent.getStringExtra(Intent.EXTRA_TEXT); @@ -67,16 +67,20 @@ private void handleIncomingIntent(Intent intent) { mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // REVIEW FLAGS startActivity(mainIntent); finish(); + + if (fileUri != null) { + userResult = scanFile(fileUri); + } } } private String scanFile(Uri fileUri) { try { - malwareScanner = new MalwareScanner(MainActivity.getInstance(), MainActivity.getInstance(), true); + malwareScanner = new MalwareScanner(Hypatia.getAppContext(), true); malwareScanner.running = true; HashSet filesToScan = new HashSet<>(); - File file = uriToFile(this, fileUri); + File file = uriToFile(Hypatia.getAppContext(), fileUri); Log.d("Hypatia", "Shared file saved in cache to scan: " + file.getAbsolutePath()); filesToScan.add(file); From 9742e7e6783a7bf1f226d9cd7cdb75a66cbc429d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Mon, 3 Mar 2025 23:20:40 +0300 Subject: [PATCH 20/25] add nix file to use android sdk --- android-env.nix | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 android-env.nix diff --git a/android-env.nix b/android-env.nix new file mode 100644 index 0000000..9044843 --- /dev/null +++ b/android-env.nix @@ -0,0 +1,27 @@ +# see https://github.com/tadfisher/android-nixpkgs +{ pkgs ? import { config.android_sdk.accept_license = true; } }: + +let + android-nixpkgs = pkgs.callPackage { + # Default; can also choose "beta", "preview", or "canary". + channel = "stable"; + }; + + androidSdk = android-nixpkgs.sdk (sdkPkgs: with sdkPkgs; [ + cmdline-tools-latest + build-tools-34-0-0 + platform-tools + platforms-android-34 + # emulator + ]); + +in +pkgs.mkShell { + buildInputs = with pkgs; [ + androidSdk + glibc + ]; + + # Override the aapt2 that gradle uses with the nix-shipped version + GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${androidSdk}/share/android-sdk/build-tools/34.0.0/aapt2"; +} From e3d37746bd4f290bbfe74917c46c5a864200237d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Tue, 4 Mar 2025 00:12:19 +0300 Subject: [PATCH 21/25] fix: notification panel buttons not workin --- app/src/main/AndroidManifest.xml | 2 +- .../malwarescanner/NotificationPromptActivity.java | 4 ---- app/src/main/res/values/styles.xml | 9 +++++++++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/values/styles.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 14d19ce..5e25eac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,7 +35,7 @@ android:name=".NotificationPromptActivity" android:exported="false" android:launchMode="singleInstance" - android:theme="@android:style/Theme.Translucent.NoTitleBar" + android:theme="@style/Theme.AppCompat.Translucent" android:layout_width="fill_parent" android:layout_height="wrap_content" android:noHistory="true" diff --git a/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java b/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java index ab3820e..5ce0476 100644 --- a/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java @@ -40,10 +40,6 @@ public class NotificationPromptActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getWindow().setLayout( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ); Intent intent = getIntent(); Context context = this; switch (Objects.requireNonNull(intent.getAction())) { diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..7c39ffc --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file From 22c19393ea3ae8d08aafd37aea4b7f65da7bdfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Tue, 4 Mar 2025 03:45:24 +0300 Subject: [PATCH 22/25] WIP --- .../us/spotco/malwarescanner/Database.java | 2 +- .../spotco/malwarescanner/EventReceiver.java | 1 + .../us/spotco/malwarescanner/Hypatia.java | 29 +++ .../spotco/malwarescanner/HypatiaLogger.java | 117 ++++++++++--- .../spotco/malwarescanner/MainActivity.java | 33 +++- .../spotco/malwarescanner/MalwareScanner.java | 45 ++++- .../malwarescanner/MalwareScannerService.java | 1 + .../NotificationPromptActivity.java | 8 +- .../spotco/malwarescanner/ShareScanner.java | 165 ++++++++++++++---- app/src/main/res/values/strings.xml | 2 +- 10 files changed, 323 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/us/spotco/malwarescanner/Database.java b/app/src/main/java/us/spotco/malwarescanner/Database.java index 2f69a0a..2e8e8f6 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Database.java +++ b/app/src/main/java/us/spotco/malwarescanner/Database.java @@ -176,7 +176,7 @@ public static void loadDatabase(Context context, boolean forceReload, Concurrent } databaseLoading.close(); } else { - Log.w("Hypatia", "Failed to validate database"); + HypatiaLogger.Log("Failed to validate database, please double check signing key", LogType.WARNING); } } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/us/spotco/malwarescanner/EventReceiver.java b/app/src/main/java/us/spotco/malwarescanner/EventReceiver.java index 5950068..d13a3a8 100644 --- a/app/src/main/java/us/spotco/malwarescanner/EventReceiver.java +++ b/app/src/main/java/us/spotco/malwarescanner/EventReceiver.java @@ -60,6 +60,7 @@ private static void scanApp(Context context, String appID) { e.printStackTrace(); } if (filesToScan.size() > 0) { + MalwareScanner.isCache = false; new MalwareScanner(context, false).executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); } } diff --git a/app/src/main/java/us/spotco/malwarescanner/Hypatia.java b/app/src/main/java/us/spotco/malwarescanner/Hypatia.java index 60f073e..d0fb306 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Hypatia.java +++ b/app/src/main/java/us/spotco/malwarescanner/Hypatia.java @@ -19,8 +19,15 @@ import android.app.Application; import android.content.Context; +import android.content.Intent; + +import android.widget.TextView; +import androidx.annotation.NonNull; +import java.lang.ref.WeakReference; +import android.util.Log; public class Hypatia extends Application { + private WeakReference mainActivityRef; private static Hypatia instance; @Override @@ -29,6 +36,28 @@ public void onCreate() { instance = this; } + public static Hypatia getInstance() { + return instance; + } + + public void setMainActivity(@NonNull MainActivity activity) { + mainActivityRef = new WeakReference<>(activity); + if (activity != null) { + HypatiaLogger.getInstance().setLogView(activity.getLogView()); + } else { + Log.e("Hypatia", "COULDN'T START HYPATIA LOGGER"); + } + } + + public void ensureMainActivity() { + if (mainActivityRef == null || mainActivityRef.get() == null) { + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + startActivity(intent); + } + } + + // REVIEW Is it really needed ??? public static Context getAppContext() { return instance.getApplicationContext(); } diff --git a/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java b/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java index 1355fe2..147fa04 100644 --- a/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java +++ b/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java @@ -18,18 +18,27 @@ package us.spotco.malwarescanner; -import android.widget.TextView; -import android.text.method.ScrollingMovementMethod; +import android.content.Context; -import android.util.Log; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import android.text.method.ScrollingMovementMethod; +import android.widget.TextView; import java.lang.ref.WeakReference; +import java.util.concurrent.ConcurrentLinkedQueue; +import android.util.Log; + +import androidx.annotation.NonNull; /** * Log Types */ enum LogType { + VERYVERBOSE, VERBOSE, DEBUG, NONEWLINE, @@ -39,6 +48,7 @@ enum LogType { WARNING, ERROR; } +// TODO add more DEBUG types??: DEBUG-ERROR, DEBUG-WARNING etc? /** * Logs process outputs to user interface @@ -49,70 +59,127 @@ enum LogType { */ public class HypatiaLogger { - private static WeakReference logViewRef; - - /** - * Initializes logger listed in manifest. Uses WeakReference - */ - public static void InitializeLogger(TextView textView) { - logViewRef = new WeakReference<>(textView); - textView.setMovementMethod(new ScrollingMovementMethod()); - textView.setTextIsSelectable(true); - } + private static HypatiaLogger instance; + private final ConcurrentLinkedQueue logQueue = new ConcurrentLinkedQueue<>(); + private WeakReference logViewRef; + private final Handler uiHandler = new Handler(Looper.getMainLooper()); /** * Logs process outputs to user interface * prints out also verbose/debug if app is debug version - * + * * @param content log content * @param type log type */ public static void Log(String content, LogType type) { Boolean userFacing = true; + + // toggle these to show/hide DEBUG/VERBOSE in debug app builds + Boolean DEBUG_ENDUSER = true; // default: true + Boolean ACTIVATE_VERYVERBOSE = false; // default: false switch (type) { + case VERYVERBOSE: + if (ACTIVATE_VERYVERBOSE) { + android.util.Log.v("Hypatia Logger", content); + } + break; case VERBOSE: - android.util.Log.v("Hypatia", content); + android.util.Log.v("Hypatia Logger", content); content = "VERBOSE: " + content; - if (!BuildConfig.DEBUG) { + if (!BuildConfig.DEBUG || DEBUG_ENDUSER) { userFacing = false; } break; case DEBUG: - android.util.Log.d("Hypatia", content); + android.util.Log.d("Hypatia Logger", content); content = "DEBUG: " + content; - if (!BuildConfig.DEBUG) { + if (!BuildConfig.DEBUG || DEBUG_ENDUSER) { userFacing = false; } break; case INFO: - android.util.Log.i("Hypatia", content); + android.util.Log.i("Hypatia Logger", content); content = "INFO: " + content; break; case WARNING: - android.util.Log.w("Hypatia", content); + android.util.Log.w("Hypatia Logger", content); content = "WARNING: " + content; break; case ERROR: - android.util.Log.e("Hypatia", content); + android.util.Log.e("Hypatia Logger", content); content = "ERROR: " + content; break; default: - android.util.Log.d("Hypatia", content); + android.util.Log.d("Hypatia Logger", content); break; } - if (userFacing && logViewRef != null && logViewRef.get() != null) { - TextView currentLogView = logViewRef.get(); - currentLogView.append(content + (type == LogType.NONEWLINE ? "" : "\n")); + try { + if (userFacing && (type != LogType.VERYVERBOSE)) { + HypatiaLogger.getInstance().logUtil(content + (type == LogType.NONEWLINE ? "" : "\n")); + } + } catch (Exception e) { + Log.d("Hypatia LOGGER", "LOGGER DO NOT WORK FOR SOME REASON !!!"); + e.printStackTrace(); } + } /** * Overloader of actual {@code Log()} function * + * @see #Log(String, LogType) * @param content log content */ public static void Log(String content) { Log(content, LogType.DEFAULT); } + /* + * ////////////////////////////////////////////////////////////////////////// + * Utilities to ensure Logger working correctly and getting logs from + * multiple sources + * ////////////////////////////////////////////////////////////////////////// + */ + + private HypatiaLogger() { + } + + public static HypatiaLogger getInstance() { + if (instance == null) { + synchronized (HypatiaLogger.class) { + if (instance == null) { + instance = new HypatiaLogger(); + } + } + } + return instance; + } + + public void setLogView(TextView textView) { + logViewRef = new WeakReference<>(textView); + flushQueue(); + } + + private void checkMainActivity() { + if (logViewRef == null || logViewRef.get() == null) { + Hypatia.getInstance().ensureMainActivity(); + } + } + + public void logUtil(String message) { + logQueue.add(message); + uiHandler.post(this::flushQueue); + checkMainActivity(); + } + + private void flushQueue() { + if (logViewRef != null && logViewRef.get() != null) { + TextView tv = logViewRef.get(); + while (!logQueue.isEmpty()) { + String log = logQueue.poll(); + tv.append(log); + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 4fc1897..e7f5068 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -37,10 +37,13 @@ import android.provider.Settings; import android.text.InputType; import android.util.Log; +import android.text.method.ScrollingMovementMethod; import android.view.Menu; import android.view.MenuItem; import android.view.WindowManager; import android.widget.EditText; +import android.widget.TextView; + import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; @@ -63,6 +66,8 @@ public class MainActivity extends AppCompatActivity { private boolean scanInternal = true; private boolean scanExternal = false; + private TextView logView; + private static final int REQUEST_PERMISSION_EXTERNAL_STORAGE = 0; @Override @@ -77,7 +82,10 @@ protected final void onCreate(Bundle savedInstanceState) { Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - HypatiaLogger.InitializeLogger(findViewById(R.id.txtLogOutput)); + logView = findViewById(R.id.txtLogOutput); + logView.setMovementMethod(new ScrollingMovementMethod()); + + HypatiaLogger.getInstance().setLogView(logView); HypatiaLogger.Log(getString(R.string.app_copyright)); HypatiaLogger.Log(getString(R.string.app_license)); @@ -96,6 +104,10 @@ protected final void onCreate(Bundle savedInstanceState) { handleIntent(getIntent()); } + public TextView getLogView() { + return logView; + } + @Override protected final void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -106,10 +118,16 @@ protected final void onNewIntent(Intent intent) { private void handleIntent(Intent intent) { if (intent != null && intent.hasExtra("SHARE_SCAN_RESULT")) { String result = intent.getStringExtra("SHARE_SCAN_RESULT"); - HypatiaLogger.Log("single file scan has been started", LogType.INFO); + HypatiaLogger.Log(result, LogType.INFO); } } + @Override + protected void onDestroy() { + Hypatia.getInstance().setMainActivity(null); + super.onDestroy(); + } + @Override public final boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); @@ -421,6 +439,7 @@ private void startScanner() { PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Hypatia::ManualScanLock"); wakeLock.acquire(10 * 60 * 1000L); /* 10 minutes */ + MalwareScanner.isCache = false; malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); new Thread(() -> { try { @@ -450,10 +469,10 @@ private void updateDatabase() { Utils.getThreadPoolExecutor().execute(() -> { try { Thread.sleep(500); - Log.d("Hypatia", "Considering database reload!"); + HypatiaLogger.Log("Considering database reload!", LogType.DEBUG); while (Database.hasDownloadsRunning()) { Thread.sleep(500); - Log.d("Hypatia", "Download in progress, waiting!"); + HypatiaLogger.Log("Download in progress, waiting!", LogType.DEBUG); } wakeLock.release(); } catch (InterruptedException e) { @@ -462,13 +481,13 @@ private void updateDatabase() { runOnUiThread(() -> HypatiaLogger.Log(getString(R.string.lblDatabasesUpdated) + "\n")); if (Database.isDatabaseLoaded()) { if (Database.changedDownload || Database.changedConfig) { - Log.d("Hypatia", "Really reloading database!"); + HypatiaLogger.Log("Really reloading database!", LogType.DEBUG); Database.loadDatabase(Hypatia.getAppContext(), true, Database.signatureDatabases); } else { - Log.d("Hypatia", "Database not changed, skipping reload!"); + HypatiaLogger.Log("Database not changed, skipping reload!", LogType.DEBUG); } } else { - Log.d("Hypatia", "Database not loaded, skipping reload!"); + HypatiaLogger.Log("Database not loaded, skipping reload!", LogType.DEBUG); } }); } else { diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java index 163fdee..17f2d18 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScanner.java @@ -58,6 +58,7 @@ class MalwareScanner extends AsyncTask, Object, String> { private final HashMap fileHashesSHA256 = new HashMap<>(); public boolean running = false; private int amtMatchedFiles = 0; + public static boolean isCache = false; public MalwareScanner(Context context, boolean userFacing) { this.context = context; @@ -106,7 +107,7 @@ private void logResult(String result, String hashsum) { PendingIntent lookupIntentPending = PendingIntent.getActivity(context, notificationId + 1, lookupIntent, PendingIntent.FLAG_IMMUTABLE); mBuilder.addAction(android.R.drawable.ic_dialog_map, context.getText(R.string.lookupVT), lookupIntentPending); //Delete action - if (malwareDetect[1].startsWith("~/")) { + if (malwareDetect[1].startsWith("~/") && !isCache) { Intent deleteIntent = new Intent(context, NotificationPromptActivity.class); deleteIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); deleteIntent.setAction("us.spotco.malwarescanner.DELETE_FILE"); @@ -147,14 +148,25 @@ protected final void onPreExecute() { logResult(context.getString(R.string.main_starting_scan), null); } + /** + * Runs when executeOnExecutor() method called + * Handles the main scanning job + * + * @param filesToScan HashSet of files to scan + * @return + */ @Override protected final String doInBackground(HashSet[] filesToScan) { + if (isCache) { + HypatiaLogger.Log("Cache File Scan Started", LogType.DEBUG); + } + running = true; int startCount = amtMatchedFiles; - ConcurrentSkipListSet filesToScanReal = new ConcurrentSkipListSet<>(); //TODO: Reduce this? + ConcurrentSkipListSet filesToScanReal = new ConcurrentSkipListSet<>(); //TODO: (from divestos) Reduce this? for (Set fileArray : filesToScan) { for (File file : fileArray) { - filesToScanReal.addAll(Utils.getFilesRecursive(file)); //TODO: Inline this, hash files as they are found + filesToScanReal.addAll(Utils.getFilesRecursive(file)); //TODO: (from divestos) Inline this, hash files as they are found } } @@ -219,7 +231,7 @@ protected final String doInBackground(HashSet[] filesToScan) { spinnerCur = " = "; } } - Log.d("Hypatia", "Scanning " + file); + HypatiaLogger.Log("Scanning " + file, LogType.VERYVERBOSE); } filesToScanReal.clear(); publishProgress("\n\t" + context.getString(R.string.main_hashing_done) + "\n", true); @@ -239,6 +251,18 @@ protected final String doInBackground(HashSet[] filesToScan) { publishProgress("\n\t" + context.getString(R.string.detections_found) + "\n", true); } + //Delete if cache + if (isCache) { + try { + File cacheFile = filesToScan[0].stream().findFirst().get(); + cacheFile.delete(); + } catch (Exception e) { + HypatiaLogger.Log("Cache file couldn't deleted", LogType.WARNING); + e.printStackTrace(); + } + + } + //Post fileHashesMD5.clear(); fileHashesSHA1.clear(); @@ -254,7 +278,11 @@ protected final String doInBackground(HashSet[] filesToScan) { if (secondsSpentHashing > 0) { MBS = totalBytesHashed / 1000 / 1000 / secondsSpentHashing; } - publishProgress(context.getString(R.string.main_scanning_done, String.valueOf(secondsSpent), String.valueOf(MBS)) + "\n\n\n\n", true); + publishProgress(context.getString(R.string.main_scanning_done, String.valueOf(secondsSpent), String.valueOf(MBS)) + (isCache ? "\nNote:scan speed can be wrong in share scans\n\n" : "\n\n\n\n") , true); + + if(isCache) { + HypatiaLogger.Log("You Should Manually Remove & Quarantine the file !!!\n\n", LogType.ERROR);; + } } } else { publishProgress("\t" + context.getString(R.string.main_no_database_available), true); @@ -280,10 +308,13 @@ private void checkSignature(String hashType, HashMap signaturesToC && !file.getValue().equals("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")) { if (signatureDatabase.mightContain(file.getValue())) { amtMatchedFiles++; - //Log.d("Hypatia", "Match: " + fileHashesSHA256.get(file.getKey())); + HypatiaLogger.Log("Match: " + fileHashesSHA256.get(file.getKey()), LogType.VERBOSE); + HypatiaLogger.Log("Match In: " + file.getKey().toString().replaceAll(Environment.getExternalStorageDirectory().toString(), "~"), LogType.VERBOSE); + HypatiaLogger.Log("NOTIFICATION OUTPUT: " + file.getKey().toString().replaceAll(Environment.getExternalStorageDirectory().toString(), "~").replaceFirst("~", Environment.getExternalStorageDirectory().toString()), LogType.VERBOSE); + publishProgress("Potential match in " + file.getKey().toString().replaceAll(Environment.getExternalStorageDirectory().toString(), "~"), false, fileHashesSHA256.get(file.getKey())); } else { - //Log.d("Hypatia", "No match for " + file.getValue()); + HypatiaLogger.Log("No match for " + file.getValue(), LogType.VERYVERBOSE); } } } diff --git a/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java b/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java index 2731225..998cacc 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java +++ b/app/src/main/java/us/spotco/malwarescanner/MalwareScannerService.java @@ -82,6 +82,7 @@ public void onEvent(int event, String path) { if (file.exists() && file.length() <= Utils.MAX_SCAN_SIZE_REALTIME) { HashSet filesToScan = new HashSet<>(); filesToScan.add(file); + MalwareScanner.isCache = false; new MalwareScanner(Hypatia.getAppContext(), false).executeOnExecutor(threadPoolExecutor, filesToScan); } updateForegroundNotification(); diff --git a/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java b/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java index 5ce0476..e602dce 100644 --- a/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/NotificationPromptActivity.java @@ -60,7 +60,7 @@ protected void onCreate(Bundle savedInstanceState) { lookupDialog.setOnDismissListener((dialog) -> finish()); lookupDialog.show(); } else { - Log.d("Hypatia", "Invalid hash"); + HypatiaLogger.Log("Invalid hash", LogType.DEBUG); finish(); } break; @@ -89,7 +89,7 @@ protected void onCreate(Bundle savedInstanceState) { finish(); } } else { - Log.d("Hypatia", "Ignoring DELETE_FILE request from external package: " + intent.getPackage()); + HypatiaLogger.Log("Ignoring DELETE_FILE request from external package: " + intent.getPackage(), LogType.DEBUG); finish(); } break; @@ -114,7 +114,7 @@ protected void onCreate(Bundle savedInstanceState) { finish(); } } else { - Log.d("Hypatia", "Ignoring DELETE_FILE request from external package: " + intent.getPackage()); + HypatiaLogger.Log("Ignoring DELETE_FILE request from external package: " + intent.getPackage(), LogType.DEBUG); finish(); } break; @@ -130,7 +130,7 @@ private static void clearNotification(Context context, int id) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); if (id != 0) { notificationManager.cancel(id); - Log.d("Hypatia", "Canceled notification"); + HypatiaLogger.Log("Canceled notification", LogType.DEBUG); } } diff --git a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java index a22aa72..5d802b1 100644 --- a/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java +++ b/app/src/main/java/us/spotco/malwarescanner/ShareScanner.java @@ -20,25 +20,27 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.os.Bundle; -import android.widget.TextView; +import android.database.Cursor; import android.net.Uri; -import java.io.InputStream; -import java.io.IOException; -import android.util.Log; -import androidx.appcompat.app.AppCompatActivity; +import android.os.Bundle; +import android.provider.DocumentsContract; import android.provider.MediaStore; -import android.database.Cursor; import android.provider.OpenableColumns; -import android.os.Environment; -import java.io.FileOutputStream; +import android.util.Log; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.FileOutputStream; import java.util.HashSet; public class ShareScanner extends AppCompatActivity { private MalwareScanner malwareScanner = null; + private String shareScanType; @Override protected void onCreate(Bundle savedInstanceState) { @@ -54,7 +56,7 @@ private void handleIncomingIntent(Intent intent) { Intent mainIntent = new Intent(Hypatia.getAppContext(), MainActivity.class); if (fileUri != null) { - userResult = "Single file scan started"; + userResult = scanFile(fileUri); } else if ("text/plain".equals(intent.getType())) { userResult = "This is not a scannable file - probably just a string: " + intent.getStringExtra(Intent.EXTRA_TEXT); @@ -63,53 +65,146 @@ private void handleIncomingIntent(Intent intent) { } else { userResult = "THIS IS NOT A SCANNABLE THING"; } + mainIntent.putExtra("SHARE_SCAN_RESULT", userResult); - mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // REVIEW FLAGS + mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(mainIntent); finish(); - - if (fileUri != null) { - userResult = scanFile(fileUri); - } } } private String scanFile(Uri fileUri) { try { - malwareScanner = new MalwareScanner(Hypatia.getAppContext(), true); - malwareScanner.running = true; - HashSet filesToScan = new HashSet<>(); + Context context = Hypatia.getAppContext(); + File file = getFileFromUri(context, fileUri); - File file = uriToFile(Hypatia.getAppContext(), fileUri); - Log.d("Hypatia", "Shared file saved in cache to scan: " + file.getAbsolutePath()); + if (file == null || !file.exists()) { + return "Error: File not accessible"; + } + malwareScanner = new MalwareScanner(context, true); + malwareScanner.running = true; + HashSet filesToScan = new HashSet<>(); filesToScan.add(file); + + HypatiaLogger.Log("Scanning file at: " + file.getAbsolutePath(), LogType.DEBUG); malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan); - return "STARTED SCAN, IF ANYTHING FOUND YOU SHOUD DELETE SOURCE MANUALLY (Notification Panel Won't work)"; + return "Scan started for: " + file.getName(); } catch (RuntimeException e) { - Log.d("Hypatia", e.getMessage()); + Log.e("Hypatia", "Scan error: " + e.getMessage()); return "Scanner Error: " + e.getMessage(); } } - public File uriToFile(Context context, Uri uri) { - String fileName = getFileName(context, uri); - File file = createTempFile(context, fileName); + private File getFileFromUri(Context context, Uri uri) { + String filePath = getRealPathFromUri(context, uri); + if (filePath != null) { + MalwareScanner.isCache = false; + return new File(filePath); + } else { + HypatiaLogger.Log("Unsupported URI, falling back to cache-system", LogType.DEBUG); + MalwareScanner.isCache = true; + return handleUnsupportedUri(context, uri); + } + } - try (InputStream inputStream = context.getContentResolver().openInputStream(uri); - FileOutputStream outputStream = new FileOutputStream(file)) { - byte[] buffer = new byte[1024]; - int length; - while ((length = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, length); + /** + * Get real path from an uri. probably returns null for share on Android 10+ + * + * @see #handleUnsupportedUri() + * + * @param context + * @param uri uri + * @return real path + */ + private String getRealPathFromUri(Context context, Uri uri) { + // File scheme + if ("file".equals(uri.getScheme())) { + return uri.getPath(); + } + + // Document provider + if (DocumentsContract.isDocumentUri(context, uri)) { + String docId = DocumentsContract.getDocumentId(uri); + String[] split = docId.split(":"); + String type = split[0]; + + Uri contentUri = null; + switch (type) { + case "image": + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + break; + case "video": + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + break; + case "audio": + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + break; + case "download": + return getDataColumn(context, uri, null, null); } - } catch (IOException e) { - e.printStackTrace(); - return null; + + if (contentUri != null) { + String selection = "_id=?"; + String[] selectionArgs = new String[] { split[1] }; + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(context, uri, null, null); } - return file; + return null; + } + + private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + String column = MediaStore.MediaColumns.DATA; + String[] projection = { column }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + int columnIndex = cursor.getColumnIndexOrThrow(column); + return cursor.getString(columnIndex); + } + } catch (Exception e) { + Log.e("Hypatia", "Data column error", e); + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + /** + * fallback for unsupported uris. reads share as InputStream and write copies to + * cache + * + * @param context + * @param uri + * @return hopefully a file + */ + private File handleUnsupportedUri(Context context, Uri uri) { + try { + InputStream input = context.getContentResolver().openInputStream(uri); + File file = createTempFile(context, getFileName(context, uri)); + FileOutputStream output = new FileOutputStream(file); + + byte[] buffer = new byte[4 * 1024]; + int read; + while ((read = input.read(buffer)) != -1) { + output.write(buffer, 0, read); + } + output.flush(); + return file; + + } catch (Exception e) { + Log.e("Hypatia", "File copy error: " + e.getMessage()); + return null; + } } private String getFileName(Context context, Uri uri) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 45dcb0b..07a1fc9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - Copyright 2017-2024 Divested Computing Group, 2025 MaintainTeam + Copyright 2017-2024 Divested Computing Group\nCopyleft 2025 MaintainTeam Organization License: GPL-3.0 Version: %s Powered by ClamAV style signatures From 37eb7a67a8d7c0fa55a4253c4485c84224443683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Wed, 5 Mar 2025 19:21:44 +0300 Subject: [PATCH 23/25] throw warning if signing key differ --- app/src/main/java/us/spotco/malwarescanner/Database.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/us/spotco/malwarescanner/Database.java b/app/src/main/java/us/spotco/malwarescanner/Database.java index 2e8e8f6..60301bf 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Database.java +++ b/app/src/main/java/us/spotco/malwarescanner/Database.java @@ -179,6 +179,7 @@ public static void loadDatabase(Context context, boolean forceReload, Concurrent HypatiaLogger.Log("Failed to validate database, please double check signing key", LogType.WARNING); } } catch (Exception e) { + HypatiaLogger.Log("There was an error while checking database validation, please double check signing key", LogType.WARNING); e.printStackTrace(); } } From 1d3c8cbf506f63f13811b26eb99e0332565d3d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Wed, 5 Mar 2025 21:55:49 +0300 Subject: [PATCH 24/25] database download conditions update - make logs selectable again - database.java refactoring - logger update for debug --- .../us/spotco/malwarescanner/Database.java | 169 +++++++++++++----- .../spotco/malwarescanner/HypatiaLogger.java | 10 +- .../spotco/malwarescanner/MainActivity.java | 11 +- 3 files changed, 141 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/us/spotco/malwarescanner/Database.java b/app/src/main/java/us/spotco/malwarescanner/Database.java index 60301bf..1f630ce 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Database.java +++ b/app/src/main/java/us/spotco/malwarescanner/Database.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.Proxy; @@ -55,6 +56,7 @@ class Database { public static long signaturesCount = 0; public static boolean changedDownload = false; public static boolean changedConfig = false; + public static boolean changedUri = false; private static final DateFormat dateFormat = DateFormat.getDateInstance(); @@ -90,17 +92,22 @@ public static boolean hasDownloadsRunning() { public static void updateDatabase(Context context, ConcurrentLinkedQueue signatureDatabases) { initDatabase(context); - log.append(context.getString(R.string.main_database_updating, String.valueOf(signatureDatabases.size())) + "\n"); + log.append( + context.getString(R.string.main_database_updating, String.valueOf(signatureDatabases.size())) + "\n"); if (!Utils.getDatabaseURL(context).equals(Utils.DATABASE_URL_DEFAULT)) { log.append(context.getString(R.string.main_database_override, Utils.getDatabaseURL(context)) + "\n"); } changedDownload = false; boolean onionRouting = prefs.getBoolean("ONION_ROUTING", false); - new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, Utils.getDatabaseURL(context) + "gpg.key", databasePath + "/gpg.key", Utils.getDatabaseURL(context)); + new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, + Utils.getDatabaseURL(context) + "gpg.key", databasePath + "/gpg.key", Utils.getDatabaseURL(context)); for (SignatureDatabase signatureDatabase : signatureDatabases) { - new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, signatureDatabase.getUrl(), databasePath + "/" + signatureDatabase.getName(), signatureDatabase.getBaseUrl()); - new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, signatureDatabase.getUrl() + ".sig", databasePath + "/" + signatureDatabase.getName() + ".sig", signatureDatabase.getBaseUrl()); + new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, signatureDatabase.getUrl(), + databasePath + "/" + signatureDatabase.getName(), signatureDatabase.getBaseUrl()); + new Downloader().executeOnExecutor(Utils.getThreadPoolExecutor(), onionRouting, + signatureDatabase.getUrl() + ".sig", databasePath + "/" + signatureDatabase.getName() + ".sig", + signatureDatabase.getBaseUrl()); } } @@ -122,13 +129,15 @@ private static void initDatabase(Context context) { } } - public static void loadDatabase(Context context, boolean forceReload, ConcurrentLinkedQueue signatureDatabases) { + public static void loadDatabase(Context context, boolean forceReload, + ConcurrentLinkedQueue signatureDatabases) { if ((!isDatabaseLoaded() || forceReload) && !databaseCurrentlyLoading) { databaseFullyLoaded = false; databaseCurrentlyLoading = true; initDatabase(context); signaturesCount = 0; changedConfig = false; + changedUri = false; signaturesMD5Extended = null; domains = null; File publicKey = new File(databasePath + "/gpg.key"); @@ -141,45 +150,64 @@ public static void loadDatabase(Context context, boolean forceReload, Concurrent try { boolean validated = verifier.verify(databaseLocation, databaseSigLocation, publicKey); if (validated) { - HypatiaLogger.Log("Successfully validated database: " + databaseLocation.getName(), LogType.DEBUG); + HypatiaLogger.Log("Successfully validated database: " + databaseLocation.getName(), + LogType.DEBUG); FileInputStream databaseLoading = new FileInputStream(databaseLocation); switch (databaseLocation.getName()) { case "hypatia-md5-bloom.bin": HypatiaLogger.Log("Processing md5", LogType.DEBUG); - signaturesMD5 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(Charsets.US_ASCII)); + signaturesMD5 = BloomFilter.readFrom(databaseLoading, + Funnels.stringFunnel(Charsets.US_ASCII)); signaturesCount += signaturesMD5.approximateElementCount(); - HypatiaLogger.Log("Loaded md5 with " + signaturesMD5.approximateElementCount() + " entries", LogType.DEBUG); + HypatiaLogger.Log( + "Loaded md5 with " + signaturesMD5.approximateElementCount() + " entries", + LogType.DEBUG); break; case "hypatia-sha1-bloom.bin": HypatiaLogger.Log("Processing sha1", LogType.DEBUG); - signaturesSHA1 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(Charsets.US_ASCII)); + signaturesSHA1 = BloomFilter.readFrom(databaseLoading, + Funnels.stringFunnel(Charsets.US_ASCII)); signaturesCount += signaturesSHA1.approximateElementCount(); - HypatiaLogger.Log("Loaded sha1 with " + signaturesSHA1.approximateElementCount() + " entries", LogType.DEBUG); + HypatiaLogger.Log( + "Loaded sha1 with " + signaturesSHA1.approximateElementCount() + " entries", + LogType.DEBUG); break; case "hypatia-sha256-bloom.bin": HypatiaLogger.Log("Processing sha256", LogType.DEBUG); - signaturesSHA256 = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(Charsets.US_ASCII)); + signaturesSHA256 = BloomFilter.readFrom(databaseLoading, + Funnels.stringFunnel(Charsets.US_ASCII)); signaturesCount += signaturesSHA256.approximateElementCount(); - HypatiaLogger.Log("Loaded sha256 with " + signaturesSHA256.approximateElementCount() + " entries", LogType.DEBUG); + HypatiaLogger.Log("Loaded sha256 with " + signaturesSHA256.approximateElementCount() + + " entries", LogType.DEBUG); break; case "hypatia-md5-extended-bloom.bin": HypatiaLogger.Log("Processing md5 extended", LogType.DEBUG); - signaturesMD5Extended = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(Charsets.US_ASCII)); + signaturesMD5Extended = BloomFilter.readFrom(databaseLoading, + Funnels.stringFunnel(Charsets.US_ASCII)); signaturesCount += signaturesMD5Extended.approximateElementCount(); - HypatiaLogger.Log("Loaded md5 extended with " + signaturesMD5Extended.approximateElementCount() + " entries", LogType.DEBUG); + HypatiaLogger.Log( + "Loaded md5 extended with " + + signaturesMD5Extended.approximateElementCount() + " entries", + LogType.DEBUG); break; case "hypatia-domains-bloom.bin": HypatiaLogger.Log("Processing domains", LogType.DEBUG); - domains = BloomFilter.readFrom(databaseLoading, Funnels.stringFunnel(Charsets.US_ASCII)); - HypatiaLogger.Log("Loaded domains with " + domains.approximateElementCount() + " entries", LogType.DEBUG); + domains = BloomFilter.readFrom(databaseLoading, + Funnels.stringFunnel(Charsets.US_ASCII)); + HypatiaLogger.Log( + "Loaded domains with " + domains.approximateElementCount() + " entries", + LogType.DEBUG); break; } databaseLoading.close(); } else { - HypatiaLogger.Log("Failed to validate database, please double check signing key", LogType.WARNING); + HypatiaLogger.Log("Failed to validate database, please double check signing key", + LogType.WARNING); } } catch (Exception e) { - HypatiaLogger.Log("There was an error while checking database validation, please double check signing key", LogType.WARNING); + HypatiaLogger.Log( + "There was an error while checking database validation, please double check signing key", + LogType.WARNING); e.printStackTrace(); } } @@ -227,37 +255,73 @@ protected String doInBackground(Object... objects) { connection.setReadTimeout(30000); connection.addRequestProperty("User-Agent", "Hypatia"); String lastModifiedLocal = ""; + Date lastModifLocalDate = new Date(out.lastModified()); if (out.exists()) { connection.setIfModifiedSince(out.lastModified()); - lastModifiedLocal = Utils.getContext().getString(R.string.main_database_not_modified_since, dateFormat.format(new Date(out.lastModified()))); + lastModifiedLocal = Utils.getContext().getString(R.string.main_database_not_modified_since, + dateFormat.format(lastModifLocalDate)); } connection.connect(); - String lastModifiedServer = dateFormat.format(new Date(connection.getLastModified())); + Date lastModifServerDate = new Date(connection.getLastModified()); + String lastModifiedServer = dateFormat.format(lastModifServerDate); int res = connection.getResponseCode(); + HypatiaLogger.Log( + "S:" + dateFormat.format(lastModifServerDate) + " - L:" + dateFormat.format(lastModifLocalDate) + + " | resCode: " + res, + LogType.DEBUG); + if (res != 304) { if (res == 200) { - FileOutputStream fileOutputStream = new FileOutputStream(outNew); - - final byte[] data = new byte[1024]; - int count; - while ((count = connection.getInputStream().read(data, 0, 1024)) != -1) { - fileOutputStream.write(data, 0, count); + if (changedUri) { + HypatiaLogger.Log("Downloading databases regarding of servers right/wrong answer & file dates because Uri has been changed recently", LogType.DEBUG); + downloadNewDatabasesFile(connection, out, outNew); + publishProgress(url.replaceAll(baseURL, "") + + "\n\t" + Utils.getContext().getString(R.string.main_database_download_success) + + "\n\t" + + Utils.getContext().getString(R.string.main_database_released_on, + lastModifiedServer) + + "\n"); + } else if (!(lastModifServerDate.after(lastModifLocalDate))) { + HypatiaLogger.Log("Server replied with 200 even sources are not newer", LogType.DEBUG); + publishProgress(url.replaceAll(baseURL, "") + + "\n\t" + Utils.getContext().getString(R.string.main_database_not_changed) + " " + + lastModifiedLocal + "\n"); + } else { + downloadNewDatabasesFile(connection, out, outNew); + publishProgress(url.replaceAll(baseURL, "") + + "\n\t" + Utils.getContext().getString(R.string.main_database_download_success) + + "\n\t" + + Utils.getContext().getString(R.string.main_database_released_on, + lastModifiedServer) + + "\n"); } - - fileOutputStream.close(); - outNew.renameTo(out); //Move the new file into place - changedDownload = true; - - publishProgress(url.replaceAll(baseURL, "") - + "\n\t" + Utils.getContext().getString(R.string.main_database_download_success) - + "\n\t" + Utils.getContext().getString(R.string.main_database_released_on, lastModifiedServer) + "\n"); } else { publishProgress(url.replaceAll(baseURL, "") - + "\n\t" + Utils.getContext().getString(R.string.main_database_download_error, String.valueOf(res)) + "\n"); + + "\n\t" + Utils.getContext().getString(R.string.main_database_download_error, + String.valueOf(res)) + + "\n"); } + } else if (lastModifServerDate.after(lastModifLocalDate)) { + HypatiaLogger.Log("Server replied with 304 even sources are newer", LogType.DEBUG); + downloadNewDatabasesFile(connection, out, outNew); + publishProgress(url.replaceAll(baseURL, "") + + "\n\t" + Utils.getContext().getString(R.string.main_database_download_success) + + "\n\t" + + Utils.getContext().getString(R.string.main_database_released_on, lastModifiedServer) + + "\n"); + } else if (changedUri) { + HypatiaLogger.Log("Maybe not changed but reloading because of database uri changed recently", + LogType.DEBUG); + downloadNewDatabasesFile(connection, out, outNew); + publishProgress(url.replaceAll(baseURL, "") + + "\n\t" + Utils.getContext().getString(R.string.main_database_download_success) + + "\n\t" + + Utils.getContext().getString(R.string.main_database_released_on, lastModifiedServer) + + "\n"); } else { publishProgress(url.replaceAll(baseURL, "") - + "\n\t" + Utils.getContext().getString(R.string.main_database_not_changed) + " " + lastModifiedLocal + "\n"); + + "\n\t" + Utils.getContext().getString(R.string.main_database_not_changed) + " " + + lastModifiedLocal + "\n"); } connection.disconnect(); } catch (Exception e) { @@ -277,14 +341,35 @@ protected final void onProgressUpdate(String... progress) { } } + public static void downloadNewDatabasesFile(HttpURLConnection connection, File out, File outNew) + throws IOException { + try { + FileOutputStream fileOutputStream = new FileOutputStream(outNew); + + final byte[] data = new byte[1024]; + int count; + while ((count = connection.getInputStream().read(data, 0, 1024)) != -1) { + fileOutputStream.write(data, 0, count); + } + + fileOutputStream.close(); + outNew.renameTo(out); // Move the new file into place + changedDownload = true; + } catch (IOException e) { + throw e; + } + + } + public static boolean selfTest() { if (signaturesMD5 != null && signaturesSHA1 != null && signaturesSHA256 != null) { - return signaturesMD5.mightContain("903616d0dbe074aa363d2d49c03f7362") //HypatiaHypatiaHypatia - && signaturesMD5.mightContain("faf325d9d4b2a6c9457405eb31870b22") //HypatiaHypatiaHypatia-MD5 - && signaturesSHA1.mightContain("fc4a3e802894cc2229be77ec6f082d1aab744e54") //HypatiaHypatiaHypatia - && signaturesSHA1.mightContain("04445ab9332cdca0031184165eb88a0a9dcec4ed") //HypatiaHypatiaHypatia-SHA1 - && signaturesSHA256.mightContain("df44844a0e99ddd935e8419257440a2ca7ef3243435a67416fcbb6cd3ae560c3") //HypatiaHypatiaHypatia - && signaturesSHA256.mightContain("3be403a831795482e82e2f9bc3e57ca34744aca3756e297b056b9f9c41830eaf"); //HypatiaHypatiaHypatia-SHA256 + return signaturesMD5.mightContain("903616d0dbe074aa363d2d49c03f7362") // HypatiaHypatiaHypatia + && signaturesMD5.mightContain("faf325d9d4b2a6c9457405eb31870b22") // HypatiaHypatiaHypatia-MD5 + && signaturesSHA1.mightContain("fc4a3e802894cc2229be77ec6f082d1aab744e54") // HypatiaHypatiaHypatia + && signaturesSHA1.mightContain("04445ab9332cdca0031184165eb88a0a9dcec4ed") // HypatiaHypatiaHypatia-SHA1 + && signaturesSHA256.mightContain("df44844a0e99ddd935e8419257440a2ca7ef3243435a67416fcbb6cd3ae560c3") // HypatiaHypatiaHypatia + && signaturesSHA256 + .mightContain("3be403a831795482e82e2f9bc3e57ca34744aca3756e297b056b9f9c41830eaf"); // HypatiaHypatiaHypatia-SHA256 } return false; } diff --git a/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java b/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java index 147fa04..260153e 100644 --- a/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java +++ b/app/src/main/java/us/spotco/malwarescanner/HypatiaLogger.java @@ -74,9 +74,9 @@ public class HypatiaLogger { public static void Log(String content, LogType type) { Boolean userFacing = true; - // toggle these to show/hide DEBUG/VERBOSE in debug app builds - Boolean DEBUG_ENDUSER = true; // default: true - Boolean ACTIVATE_VERYVERBOSE = false; // default: false + // toggle these to show/hide DEBUG/VERBOSE and VERYVERBOSE in debug app builds + Boolean ENABLE_DEBUG = false; + Boolean ACTIVATE_VERYVERBOSE = false; switch (type) { case VERYVERBOSE: if (ACTIVATE_VERYVERBOSE) { @@ -86,14 +86,14 @@ public static void Log(String content, LogType type) { case VERBOSE: android.util.Log.v("Hypatia Logger", content); content = "VERBOSE: " + content; - if (!BuildConfig.DEBUG || DEBUG_ENDUSER) { + if (!BuildConfig.DEBUG || !ENABLE_DEBUG) { userFacing = false; } break; case DEBUG: android.util.Log.d("Hypatia Logger", content); content = "DEBUG: " + content; - if (!BuildConfig.DEBUG || DEBUG_ENDUSER) { + if (!BuildConfig.DEBUG || !ENABLE_DEBUG) { userFacing = false; } break; diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index e7f5068..9bf6c1f 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -84,6 +84,7 @@ protected final void onCreate(Bundle savedInstanceState) { logView = findViewById(R.id.txtLogOutput); logView.setMovementMethod(new ScrollingMovementMethod()); + logView.setTextIsSelectable(true); HypatiaLogger.getInstance().setLogView(logView); @@ -232,14 +233,17 @@ public final boolean onOptionsItemSelected(MenuItem item) { if (!newServer.endsWith("/")) { newServer += "/"; } + Database.changedUri = true; prefs.edit().putString("DATABASE_SERVER", newServer).apply(); HypatiaLogger.Log(Utils.checkOldDatabase(this) + "\n"); }); builderServerOverride.setNegativeButton(getString(R.string.lblResetPrimary), (dialog, which) -> { + Database.changedUri = true; prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_DEFAULT).apply(); dialog.cancel(); }); builderServerOverride.setNeutralButton(getString(R.string.lblResetGitHub), (dialog, which) -> { + Database.changedUri = true; prefs.edit().putString("DATABASE_SERVER", Utils.DATABASE_URL_GITHUB).apply(); dialog.cancel(); }); @@ -478,9 +482,12 @@ private void updateDatabase() { } catch (InterruptedException e) { throw new RuntimeException(e); } - runOnUiThread(() -> HypatiaLogger.Log(getString(R.string.lblDatabasesUpdated) + "\n")); + runOnUiThread(() -> { + HypatiaLogger.Log(getString(R.string.lblDatabasesUpdated) + "\n"); + Database.changedUri = false; + }); if (Database.isDatabaseLoaded()) { - if (Database.changedDownload || Database.changedConfig) { + if (Database.changedDownload || Database.changedConfig || Database.changedUri) { HypatiaLogger.Log("Really reloading database!", LogType.DEBUG); Database.loadDatabase(Hypatia.getAppContext(), true, Database.signatureDatabases); } else { From 841c0d72c5aa774dafc8431a2a2faab5a6520d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliberk=20Sand=C4=B1k=C3=A7=C4=B1?= Date: Thu, 6 Mar 2025 21:36:03 +0300 Subject: [PATCH 25/25] database conditions update and add disclaimer --- android-env.nix | 1 + .../main/java/us/spotco/malwarescanner/Database.java | 11 ++++++++++- .../java/us/spotco/malwarescanner/MainActivity.java | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/android-env.nix b/android-env.nix index 9044843..75df4c2 100644 --- a/android-env.nix +++ b/android-env.nix @@ -1,4 +1,5 @@ # see https://github.com/tadfisher/android-nixpkgs +# run with `nix-shell android-env.nix` { pkgs ? import { config.android_sdk.accept_license = true; } }: let diff --git a/app/src/main/java/us/spotco/malwarescanner/Database.java b/app/src/main/java/us/spotco/malwarescanner/Database.java index 1f630ce..23a6f09 100644 --- a/app/src/main/java/us/spotco/malwarescanner/Database.java +++ b/app/src/main/java/us/spotco/malwarescanner/Database.java @@ -270,10 +270,19 @@ protected String doInBackground(Object... objects) { + " | resCode: " + res, LogType.DEBUG); + // REVIEW these lines + if (res == 304 && dateFormat.format(lastModifServerDate).equalsIgnoreCase("Jan 1, 1970")) { + lastModifServerDate = lastModifLocalDate; + HypatiaLogger.Log( + "res code 304 and last modified server date Jan 1, 1970. Last modified server date has been updated to local date", + LogType.DEBUG); + } if (res != 304) { if (res == 200) { if (changedUri) { - HypatiaLogger.Log("Downloading databases regarding of servers right/wrong answer & file dates because Uri has been changed recently", LogType.DEBUG); + HypatiaLogger.Log( + "Downloading databases regarding of servers right/wrong answer & file dates because Uri has been changed recently", + LogType.DEBUG); downloadNewDatabasesFile(connection, out, outNew); publishProgress(url.replaceAll(baseURL, "") + "\n\t" + Utils.getContext().getString(R.string.main_database_download_success) diff --git a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java index 9bf6c1f..b51848e 100644 --- a/app/src/main/java/us/spotco/malwarescanner/MainActivity.java +++ b/app/src/main/java/us/spotco/malwarescanner/MainActivity.java @@ -88,6 +88,7 @@ protected final void onCreate(Bundle savedInstanceState) { HypatiaLogger.getInstance().setLogView(logView); + HypatiaLogger.Log(getString(R.string.app_disclaimer) + "\n\n\n"); HypatiaLogger.Log(getString(R.string.app_copyright)); HypatiaLogger.Log(getString(R.string.app_license)); HypatiaLogger.Log(getString(R.string.app_version, buildVersionName)); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 07a1fc9..7409c3a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ + This is only a malware scanner which compares files with previously found hashes. Not a magic wand that keeps your entire android super secure. You should be careful what you download/click !!! Copyright 2017-2024 Divested Computing Group\nCopyleft 2025 MaintainTeam Organization License: GPL-3.0 Version: %s