mirror of
https://github.com/MaintainTeam/Hypatia.git
synced 2025-03-01 05:48:23 +03:00
Start of overhaul, scaffolding for new interface
This commit is contained in:
parent
716b58bd16
commit
2b8de41221
30 changed files with 338 additions and 1234 deletions
46
.idea/assetWizardSettings.xml
generated
Normal file
46
.idea/assetWizardSettings.xml
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WizardSettings">
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorAssetStep">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipartAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="url" value="jar:file:/app/extra/plugins/android/lib/android.jar!/images/material_design_icons/image/ic_looks_black_24dp.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="outputName" value="ic_looks_black_24dp" />
|
||||
<entry key="sourceFile" value="$USER_HOME$" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
BIN
.idea/caches/build_file_checksums.ser
generated
BIN
.idea/caches/build_file_checksums.ser
generated
Binary file not shown.
|
@ -6,9 +6,10 @@ android {
|
|||
applicationId "us.spotco.malwarescanner"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 26
|
||||
versionCode 39
|
||||
versionName "2.9"
|
||||
versionCode 40
|
||||
versionName "3.0"
|
||||
resConfigs "en"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
|
@ -31,4 +32,7 @@ dependencies {
|
|||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'com.android.support:appcompat-v7:26.1.0'
|
||||
implementation 'com.android.support:design:26.1.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
implementation 'com.android.support:support-vector-drawable:26.1.0'
|
||||
implementation 'com.android.support:support-v4:26.1.0'
|
||||
}
|
||||
|
|
|
@ -10,15 +10,13 @@
|
|||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:largeHeap="true">
|
||||
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:label="@string/title_activity_main"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<intent-filter>
|
||||
|
@ -26,29 +24,6 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".MalwareScannerService"
|
||||
android:label="Realtime Malware Scanner"
|
||||
android:enabled="true"
|
||||
android:exported="false"/>
|
||||
|
||||
<receiver
|
||||
android:name=".EventReceiver"
|
||||
android:label="Event Handler"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
<action android:name="android.intent.action.PACKAGE_ADDED" />
|
||||
<data android:scheme="package" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -1,226 +0,0 @@
|
|||
/*
|
||||
Hypatia: An realtime malware scanner for Android
|
||||
Copyright (c) 2017-2018 Divested Computing, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package us.spotco.malwarescanner;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
class Database {
|
||||
|
||||
private static TextView log = null;
|
||||
private static SharedPreferences prefs = null;
|
||||
private static File databasePath = null;
|
||||
|
||||
public final static HashSet<SignatureDatabase> signatureDatabases = new HashSet<>();
|
||||
public final static String baseURL = "https://spotco.us/MalwareScannerSignatures/";
|
||||
public final static String baseURLOnion = "https://hypatiagbf5vp3ba.onion/MalwareScannerSignatures/"; //TODO: Setup the .onion
|
||||
|
||||
public final static HashMap<String, String> signaturesMD5 = new HashMap<>();
|
||||
public final static HashMap<String, String> signaturesSHA1 = new HashMap<>();
|
||||
public final static HashMap<String, String> signaturesSHA256 = new HashMap<>();
|
||||
|
||||
public Database(TextView log) {
|
||||
Database.log = log;
|
||||
}
|
||||
|
||||
public static boolean areDatabasesAvailable() {
|
||||
return databasePath != null && databasePath.listFiles().length > 0;
|
||||
}
|
||||
|
||||
public static boolean isDatabaseLoaded() {
|
||||
return signaturesMD5.size() > 0 && signaturesSHA1.size() > 0 && signaturesSHA256.size() > 0;
|
||||
}
|
||||
|
||||
public static int getSignatureCount() {
|
||||
return signaturesMD5.size() + signaturesSHA1.size() + signaturesSHA256.size();
|
||||
}
|
||||
|
||||
public static void updateDatabase(Context context, HashSet<SignatureDatabase> signatureDatabases) {
|
||||
initDatabase(context);
|
||||
for (SignatureDatabase signatureDatabase : signatureDatabases) {
|
||||
boolean onionRouting = prefs.getBoolean("ONION_ROUTING", false);
|
||||
new Downloader().execute(onionRouting, signatureDatabase.getUrl(), databasePath + "/" + signatureDatabase.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static void initDatabase(Context context) {
|
||||
databasePath = new File(context.getFilesDir() + "/signatures/");
|
||||
databasePath.mkdir();
|
||||
|
||||
signatureDatabases.clear();
|
||||
|
||||
prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
|
||||
if (prefs.getBoolean("SIGNATURES_EXTENDED", false)) {
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "bofhland_malware_attach.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "crdfam.clamav.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "doppelstern.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "hackingteam.hsb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "malware.expert.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "malwarehash.hsb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "porcupine.hsb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "rfxn.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "rogue.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "spamattach.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "spamimg.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "winnow.attachments.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "winnow_bad_cw.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "winnow_extended_malware.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "winnow_malware.hdb.gz"));
|
||||
}
|
||||
if (prefs.getBoolean("SIGNATURES_CLAMAV-MAIN", false)) {
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "main.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "main.hsb.gz"));
|
||||
}
|
||||
if (prefs.getBoolean("SIGNATURES_CLAMAV-DAILY", false)) {
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "daily.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "daily.hsb.gz"));
|
||||
}
|
||||
if (prefs.getBoolean("SIGNATURES_CLAMAV-ANDROID", true)) {
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "Android.hdb.gz"));
|
||||
signatureDatabases.add(new SignatureDatabase(baseURL + "Android.hsb.gz"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadDatabase(Context context, boolean ignoreifLoaded, HashSet<SignatureDatabase> signatureDatabases) {
|
||||
if (!isDatabaseLoaded() || !ignoreifLoaded && isDatabaseLoaded()) {
|
||||
initDatabase(context);
|
||||
signaturesMD5.clear();
|
||||
signaturesSHA1.clear();
|
||||
signaturesSHA256.clear();
|
||||
System.gc();
|
||||
for (SignatureDatabase database : signatureDatabases) {
|
||||
File databaseLocation = new File(databasePath + "/" + database.getName());
|
||||
if (databaseLocation.exists()) {
|
||||
try {
|
||||
BufferedReader reader;
|
||||
if (databaseLocation.getName().endsWith(".gz")) {
|
||||
reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(databaseLocation))));
|
||||
} else {
|
||||
reader = new BufferedReader(new FileReader(databaseLocation));
|
||||
}
|
||||
String line;
|
||||
if (database.getName().contains(".hdb")) {//.hdb format: md5, size, name
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String[] lineS = line.split(":");
|
||||
if (Utils.TRIM_VARIANT_NUMBER) {
|
||||
lineS[2] = lineS[2].split("-")[0];
|
||||
}
|
||||
signaturesMD5.put(lineS[0].substring(0, Utils.MAX_HASH_LENGTH), lineS[2]);
|
||||
}
|
||||
} else if (database.getName().contains(".hsb")) {//.hsb format: sha256, size, name
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String[] lineS = line.split(":");
|
||||
if (Utils.TRIM_VARIANT_NUMBER) {
|
||||
lineS[2] = lineS[2].split("-")[0];
|
||||
}
|
||||
if (lineS[0].length() == 32) {
|
||||
signaturesSHA1.put(lineS[0].substring(0, Utils.MAX_HASH_LENGTH), lineS[2]);
|
||||
} else {
|
||||
signaturesSHA256.put(lineS[0].substring(0, Utils.MAX_HASH_LENGTH), lineS[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
signaturesMD5.put("44d88612fea8a8f36de82e1278abb02f".substring(0, Utils.MAX_HASH_LENGTH), "Eicar-Test-Signature");
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Downloader extends AsyncTask<Object, String, String> {
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Object... objects) {
|
||||
boolean onionRouting = (boolean) objects[0];
|
||||
String url = (String) objects[1];
|
||||
File out = new File((String) objects[2]);
|
||||
publishProgress("Downloading " + url.replaceAll(baseURL, ""));
|
||||
try {
|
||||
HttpURLConnection connection;
|
||||
if (onionRouting) {
|
||||
Utils.waitUntilOrbotIsAvailable();
|
||||
//url = url.replaceAll(baseURL, baseURLOnion); //TODO: Setup the .onion
|
||||
Proxy orbot = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", 9050));
|
||||
connection = (HttpURLConnection) new URL(url).openConnection(orbot);
|
||||
} else {
|
||||
connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
}
|
||||
connection.setConnectTimeout(90000);
|
||||
connection.setReadTimeout(30000);
|
||||
connection.addRequestProperty("User-Agent", "Hypatia");
|
||||
if (out.exists()) {
|
||||
connection.setIfModifiedSince(out.lastModified());
|
||||
}
|
||||
connection.connect();
|
||||
int res = connection.getResponseCode();
|
||||
if (res != 304) {
|
||||
if (res == 200) {
|
||||
if (out.exists()) {
|
||||
out.delete();
|
||||
}
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(out);
|
||||
|
||||
final byte data[] = new byte[1024];
|
||||
int count;
|
||||
while ((count = connection.getInputStream().read(data, 0, 1024)) != -1) {
|
||||
fileOutputStream.write(data, 0, count);
|
||||
}
|
||||
|
||||
fileOutputStream.close();
|
||||
publishProgress("Successfully downloaded\n");
|
||||
} else {
|
||||
publishProgress("File not downloaded, response code " + res + "\n");
|
||||
}
|
||||
} else {
|
||||
publishProgress("File not changed\n");
|
||||
}
|
||||
connection.disconnect();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
out.delete();
|
||||
publishProgress("Failed to download, check logcat\n");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onProgressUpdate(String... progress) {
|
||||
log.append(progress[0] + "\n");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package us.spotco.malwarescanner;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
*/
|
||||
public class DatabaseFragment extends Fragment {
|
||||
|
||||
|
||||
public DatabaseFragment() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_database, container, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
Hypatia: An realtime malware scanner for Android
|
||||
Copyright (c) 2017-2018 Divested Computing, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package us.spotco.malwarescanner;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class EventReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public final void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
|
||||
Utils.considerStartService(context);
|
||||
}
|
||||
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)) {
|
||||
if (intent.getDataString().contains(context.getPackageName())) {
|
||||
Utils.considerStartService(context); //We've been updated, restart service
|
||||
} else {
|
||||
//scanApp(context, intent.getDataString());//An app was updated, scan it
|
||||
}
|
||||
}
|
||||
if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
|
||||
//scanApp(context, intent.getDataString());//An app was installed, scan it
|
||||
}
|
||||
}
|
||||
|
||||
private static void scanApp(Context context, String appID) {
|
||||
if (Utils.isServiceRunning(MalwareScannerService.class, context)) {
|
||||
HashSet<File> filesToScan = new HashSet<>();
|
||||
try {
|
||||
filesToScan.add(new File(context.getPackageManager().getApplicationInfo(appID, PackageManager.GET_META_DATA).sourceDir));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (filesToScan.size() > 0) {
|
||||
new MalwareScanner(null, context, false).executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
25
app/src/main/java/us/spotco/malwarescanner/LogFragment.java
Normal file
25
app/src/main/java/us/spotco/malwarescanner/LogFragment.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
package us.spotco.malwarescanner;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
*/
|
||||
public class LogFragment extends Fragment {
|
||||
|
||||
public LogFragment() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_log, container, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,232 +1,66 @@
|
|||
/*
|
||||
Hypatia: An realtime malware scanner for Android
|
||||
Copyright (c) 2017-2018 Divested Computing, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package us.spotco.malwarescanner;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.view.Menu;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.BottomNavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
public class MainActivity extends FragmentActivity {
|
||||
|
||||
private SharedPreferences prefs = null;
|
||||
private MalwareScanner malwareScanner = null;
|
||||
private final FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
private Fragment activeFragment = null;
|
||||
private final Fragment scannerFragment = new ScannerFragment();
|
||||
private final Fragment logFragment = new LogFragment();
|
||||
private final Fragment databaseFragment = new DatabaseFragment();
|
||||
private final Fragment settingsFragment = new SettingsFragment();
|
||||
|
||||
private TextView logView;
|
||||
|
||||
private boolean scanSystem = false;
|
||||
private boolean scanApps = true;
|
||||
private boolean scanInternal = true;
|
||||
private boolean scanExternal = false;
|
||||
|
||||
private static final int REQUEST_PERMISSION_EXTERNAL_STORAGE = 0;
|
||||
|
||||
@Override
|
||||
protected final void onCreate(Bundle savedInstanceState) {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
|
||||
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
|
||||
|
||||
logView = findViewById(R.id.txtLogOutput);
|
||||
logView.setMovementMethod(new ScrollingMovementMethod());
|
||||
logView.append("Copyright 2017 Divested Computing, Inc.\n");
|
||||
logView.append("License: GPLv3\n");
|
||||
logView.append("Powered by ClamAV signatures\n\n");
|
||||
|
||||
malwareScanner = new MalwareScanner(this, this, true);
|
||||
fragmentManager.beginTransaction().add(R.id.main_fragment, logFragment, "History").hide(logFragment).commit();
|
||||
fragmentManager.beginTransaction().add(R.id.main_fragment, databaseFragment, "Databases").hide(databaseFragment).commit();
|
||||
fragmentManager.beginTransaction().add(R.id.main_fragment, settingsFragment, "Settings").hide(settingsFragment).commit();
|
||||
fragmentManager.beginTransaction().add(R.id.main_fragment, scannerFragment, "Scanner").commit();
|
||||
activeFragment = scannerFragment;
|
||||
}
|
||||
|
||||
prefs = getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
|
||||
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
|
||||
= new BottomNavigationView.OnNavigationItemSelectedListener() {
|
||||
|
||||
FloatingActionButton fab = findViewById(R.id.fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (!malwareScanner.getStatus().equals(AsyncTask.Status.RUNNING)) {
|
||||
startScanner();
|
||||
} else {
|
||||
malwareScanner.cancel(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.navigation_scanner:
|
||||
fragmentManager.beginTransaction().hide(activeFragment).show(scannerFragment).commit();
|
||||
activeFragment = scannerFragment;
|
||||
return true;
|
||||
case R.id.navigation_log:
|
||||
fragmentManager.beginTransaction().hide(activeFragment).show(logFragment).commit();
|
||||
activeFragment = logFragment;
|
||||
return true;
|
||||
case R.id.navigation_databases:
|
||||
fragmentManager.beginTransaction().hide(activeFragment).show(databaseFragment).commit();
|
||||
activeFragment = databaseFragment;
|
||||
return true;
|
||||
case R.id.navigation_settings:
|
||||
fragmentManager.beginTransaction().hide(activeFragment).show(settingsFragment).commit();
|
||||
activeFragment = settingsFragment;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
requestPermissions();
|
||||
|
||||
Utils.considerStartService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||
menu.findItem(R.id.toggleRealtime).setChecked(Utils.isServiceRunning(MalwareScannerService.class, this));
|
||||
menu.findItem(R.id.toggleOnionRouting).setChecked(prefs.getBoolean("ONION_ROUTING", false));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_PERMISSION_EXTERNAL_STORAGE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void selectDatabases() {
|
||||
final String[] databases = {"ClamAV: Android Only (GPLv2)", "ClamAV: Main (GPLv2)", "ClamAV: Daily [LARGE] (GPLv2)", "3rd Parties: Extended"};
|
||||
final boolean[] databaseDefaults = {
|
||||
prefs.getBoolean("SIGNATURES_CLAMAV-ANDROID", true),
|
||||
prefs.getBoolean("SIGNATURES_CLAMAV-MAIN", false),
|
||||
prefs.getBoolean("SIGNATURES_CLAMAV-DAILY", false),
|
||||
prefs.getBoolean("SIGNATURES_EXTENDED", false)};
|
||||
|
||||
Dialog dialog;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(R.string.lblSelectDatabasesTitle);
|
||||
|
||||
builder.setMultiChoiceItems(databases, databaseDefaults, new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i, boolean selected) {
|
||||
databaseDefaults[i] = selected;
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
prefs.edit().putBoolean("SIGNATURES_CLAMAV-ANDROID", databaseDefaults[0]).apply();
|
||||
prefs.edit().putBoolean("SIGNATURES_CLAMAV-MAIN", databaseDefaults[1]).apply();
|
||||
prefs.edit().putBoolean("SIGNATURES_CLAMAV-DAILY", databaseDefaults[2]).apply();
|
||||
prefs.edit().putBoolean("SIGNATURES_EXTENDED", databaseDefaults[3]).apply();
|
||||
}
|
||||
});
|
||||
|
||||
dialog = builder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.toggleOnionRouting:
|
||||
if (!item.isChecked()) {
|
||||
if (Utils.isOrbotInstalled(this)) {
|
||||
prefs.edit().putBoolean("ONION_ROUTING", !item.isChecked()).apply();
|
||||
item.setChecked(true);
|
||||
} else {
|
||||
prefs.edit().putBoolean("ONION_ROUTING", false).apply();
|
||||
item.setChecked(false);
|
||||
Toast.makeText(this, R.string.lblOnionRoutingNotInstalled, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
prefs.edit().putBoolean("ONION_ROUTING", false).apply();
|
||||
item.setChecked(false);
|
||||
}
|
||||
break;
|
||||
case R.id.mnuUpdateDatabase:
|
||||
if (prefs.getBoolean("ONION_ROUTING", false)) {
|
||||
Utils.requestStartOrbot(this);
|
||||
logView.append("Downloading over Tor, this may take a while...\n");
|
||||
}
|
||||
updateDatabase();
|
||||
break;
|
||||
case R.id.mnuSelectDatabases:
|
||||
selectDatabases();
|
||||
break;
|
||||
case R.id.toggleRealtime:
|
||||
Intent realtimeScanner = new Intent(getApplicationContext(), MalwareScannerService.class);
|
||||
if (!item.isChecked()) {
|
||||
prefs.edit().putBoolean("autostart", true).apply();
|
||||
Utils.considerStartService(this);
|
||||
} else {
|
||||
stopService(realtimeScanner);
|
||||
prefs.edit().putBoolean("autostart", false).apply();
|
||||
}
|
||||
item.setChecked(!item.isChecked());
|
||||
break;
|
||||
case R.id.mnuScanSystem:
|
||||
scanSystem = !item.isChecked();
|
||||
item.setChecked(scanSystem);
|
||||
break;
|
||||
case R.id.mnuScanApps:
|
||||
scanApps = !item.isChecked();
|
||||
item.setChecked(scanApps);
|
||||
break;
|
||||
case R.id.mnuScanInternal:
|
||||
scanInternal = !item.isChecked();
|
||||
item.setChecked(scanInternal);
|
||||
break;
|
||||
case R.id.mnuScanExternal:
|
||||
scanExternal = !item.isChecked();
|
||||
item.setChecked(scanExternal);
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void startScanner() {
|
||||
malwareScanner = new MalwareScanner(this, this, true);
|
||||
Set<File> filesToScan = new HashSet<>();
|
||||
if (scanSystem) {
|
||||
filesToScan.addAll(Utils.getFilesRecursive(Environment.getRootDirectory()));
|
||||
}
|
||||
if (scanApps) {
|
||||
for (ApplicationInfo packageInfo : getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA)) {
|
||||
filesToScan.add(new File(packageInfo.sourceDir));
|
||||
}
|
||||
}
|
||||
if (scanInternal) {
|
||||
filesToScan.addAll(Utils.getFilesRecursive(Environment.getExternalStorageDirectory()));
|
||||
}
|
||||
if (scanExternal) {
|
||||
filesToScan.addAll(Utils.getFilesRecursive(new File("/storage")));
|
||||
}
|
||||
malwareScanner.executeOnExecutor(Utils.getThreadPoolExecutor(), filesToScan);
|
||||
}
|
||||
|
||||
private void updateDatabase() {
|
||||
new Database((TextView) findViewById(R.id.txtLogOutput));
|
||||
Database.updateDatabase(this, Database.signatureDatabases);
|
||||
if (Database.isDatabaseLoaded()) {
|
||||
Database.loadDatabase(this, false, Database.signatureDatabases);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
/*
|
||||
Hypatia: An realtime malware scanner for Android
|
||||
Copyright (c) 2017-2018 Divested Computing, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package us.spotco.malwarescanner;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
class MalwareScanner extends AsyncTask<Set<File>, Object, String> {
|
||||
|
||||
private Context context = null;
|
||||
private TextView logOutput = null;
|
||||
private boolean userFacing = false;
|
||||
private NotificationManager notificationManager = null;
|
||||
private long scanTime = 0;
|
||||
private final HashMap<String, File> fileHashesMD5 = new HashMap<>();
|
||||
private final HashMap<String, File> fileHashesSHA1 = new HashMap<>();
|
||||
private final HashMap<String, File> fileHashesSHA256 = new HashMap<>();
|
||||
|
||||
public MalwareScanner(Activity activity, Context context, boolean userFacing) {
|
||||
this.context = context;
|
||||
this.userFacing = userFacing;
|
||||
if (activity != null) {
|
||||
logOutput = activity.findViewById(R.id.txtLogOutput);
|
||||
} else {
|
||||
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);
|
||||
detectionChannel.setDescription(context.getString(R.string.lblNotificationMalwareDetectionDescription));
|
||||
notificationManager.createNotificationChannel(detectionChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void logResult(String result, boolean userFacingOnly) {
|
||||
if (userFacing) {
|
||||
logOutput.append(result + "\n");
|
||||
} else if (!userFacingOnly) {
|
||||
String[] malwareDetect = result.split(" in ");
|
||||
NotificationCompat.Builder mBuilder =
|
||||
new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(context.getText(R.string.lblNotificationRealtimeDetection) + " " + malwareDetect[0])
|
||||
.setContentText(malwareDetect[1])
|
||||
.setPriority(Notification.PRIORITY_MAX)
|
||||
.setDefaults(Notification.DEFAULT_VIBRATE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
mBuilder.setVisibility(Notification.VISIBILITY_SECRET);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
mBuilder.setChannelId("DETECTION");
|
||||
}
|
||||
notificationManager.notify(new Random().nextInt(), mBuilder.build());
|
||||
//Log.d("Hypatia", result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onPreExecute() {
|
||||
scanTime = SystemClock.elapsedRealtime();
|
||||
logResult("Starting scan...", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final String doInBackground(Set<File>[] filesToScan) {
|
||||
//Pre
|
||||
fileHashesMD5.clear();
|
||||
fileHashesSHA1.clear();
|
||||
fileHashesSHA256.clear();
|
||||
|
||||
publishProgress(filesToScan[0].size() + " files pending scan\n", true);
|
||||
|
||||
Database.loadDatabase(context, true, Database.signatureDatabases);
|
||||
if (Database.getSignatureCount() >= 0) {
|
||||
publishProgress("Loaded database with " + Database.getSignatureCount() + " signatures\n", true);
|
||||
|
||||
//Get file hashes
|
||||
publishProgress("Hashing files...", true);
|
||||
for (File file : filesToScan[0]) {
|
||||
getFileHashes(file);
|
||||
}
|
||||
publishProgress("Calculated MD5/SHA-1/SHA-256 hashes for all files\n", true);
|
||||
|
||||
//Check the hashes
|
||||
checkSignature("MD5", fileHashesMD5, Database.signaturesMD5);
|
||||
checkSignature("SHA-1", fileHashesSHA1, Database.signaturesSHA1);
|
||||
checkSignature("SHA-256", fileHashesSHA256, Database.signaturesSHA256);
|
||||
|
||||
//Post
|
||||
fileHashesMD5.clear();
|
||||
fileHashesSHA1.clear();
|
||||
fileHashesSHA256.clear();
|
||||
System.gc();
|
||||
Utils.FILES_SCANNED += filesToScan[0].size();
|
||||
Log.d("Thiea", "Scan completed in " + (SystemClock.elapsedRealtime() - scanTime) + " ms!");
|
||||
publishProgress("Scan completed in " + ((SystemClock.elapsedRealtime() - scanTime) / 1000) + " seconds!\n\n\n\n", true);
|
||||
} else {
|
||||
publishProgress("No database available, not scanning...", true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onProgressUpdate(Object... objects) {
|
||||
logResult((String) objects[0], (boolean) objects[1]);
|
||||
}
|
||||
|
||||
private void checkSignature(String hashType, HashMap<String, File> signaturesToCheck, HashMap<String, String> signatureDatabase) {
|
||||
if (signatureDatabase.size() > 0) {
|
||||
for (Map.Entry<String, File> file : signaturesToCheck.entrySet()) {
|
||||
if (signatureDatabase.containsKey(file.getKey())) {
|
||||
String result = signatureDatabase.get(file.getKey());
|
||||
publishProgress(result + " in " + file.getValue().toString().replaceAll(Environment.getExternalStorageDirectory().toString(), "~"), false);
|
||||
}
|
||||
}
|
||||
publishProgress("Checked all " + hashType + " hashes against signature databases\n", true);
|
||||
} else {
|
||||
publishProgress("No " + hashType + " signatures available\n", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void getFileHashes(File file) {
|
||||
try {
|
||||
InputStream fis = new FileInputStream(file);
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int numRead;
|
||||
|
||||
MessageDigest digestMD5 = MessageDigest.getInstance("MD5");
|
||||
MessageDigest digestSHA1 = MessageDigest.getInstance("SHA-1");
|
||||
MessageDigest digestSHA256 = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
do {
|
||||
numRead = fis.read(buffer);
|
||||
if (numRead > 0) {
|
||||
digestMD5.update(buffer, 0, numRead);
|
||||
digestSHA1.update(buffer, 0, numRead);
|
||||
digestSHA256.update(buffer, 0, numRead);
|
||||
}
|
||||
} while (numRead != -1);
|
||||
|
||||
fis.close();
|
||||
|
||||
fileHashesMD5.put(String.format("%032x", new BigInteger(1, digestMD5.digest())).substring(0, Utils.MAX_HASH_LENGTH), file);
|
||||
fileHashesSHA1.put(String.format("%032x", new BigInteger(1, digestSHA1.digest())).substring(0, Utils.MAX_HASH_LENGTH), file);
|
||||
fileHashesSHA256.put(String.format("%064x", new BigInteger(1, digestSHA256.digest())).substring(0, Utils.MAX_HASH_LENGTH), file);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
Hypatia: An realtime malware scanner for Android
|
||||
Copyright (c) 2017-2018 Divested Computing, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package us.spotco.malwarescanner;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.FileObserver;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
public class MalwareScannerService extends Service {
|
||||
|
||||
private final HashSet<RecursiveFileObserver> malwareMonitors = new HashSet<>();
|
||||
private ThreadPoolExecutor threadPoolExecutor = null;
|
||||
private NotificationCompat.Builder foregroundNotification = null;
|
||||
private NotificationManager notificationManager = null;
|
||||
|
||||
@Override
|
||||
public final IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int onStartCommand(Intent intent, int flags, int startId) {
|
||||
malwareMonitors.clear();
|
||||
addMalwareMonitor(Environment.getExternalStorageDirectory().toString());
|
||||
|
||||
threadPoolExecutor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(Utils.getMaxThreads() + malwareMonitors.size());
|
||||
threadPoolExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Database.loadDatabase(getApplicationContext(), true, Database.signatureDatabases);
|
||||
}
|
||||
});
|
||||
|
||||
for (final RecursiveFileObserver malwareMonitor : malwareMonitors) {
|
||||
threadPoolExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
malwareMonitor.startWatching();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
notificationManager = (NotificationManager) getApplicationContext().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));
|
||||
foregroundChannel.setShowBadge(false);
|
||||
notificationManager.createNotificationChannel(foregroundChannel);
|
||||
}
|
||||
setForeground();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void addMalwareMonitor(String monitorPath) {
|
||||
malwareMonitors.add(new RecursiveFileObserver(monitorPath) {
|
||||
@Override
|
||||
public void onEvent(int event, String path) {
|
||||
switch (event) {
|
||||
case FileObserver.MOVED_TO:
|
||||
case FileObserver.CLOSE_WRITE:
|
||||
File file = new File(path);
|
||||
if (file.exists() && /*file.length() > 0 &&*/ file.length() <= Utils.MAX_SCAN_SIZE_REALTIME) {
|
||||
HashSet<File> filesToScan = new HashSet<>();
|
||||
filesToScan.add(file);
|
||||
new MalwareScanner(null, getApplicationContext(), false).executeOnExecutor(threadPoolExecutor, filesToScan);
|
||||
}
|
||||
updateForegroundNotification();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDestroy() {
|
||||
for (RecursiveFileObserver malwareMonitor : malwareMonitors) {
|
||||
malwareMonitor.stopWatching();
|
||||
}
|
||||
malwareMonitors.clear();
|
||||
System.gc();
|
||||
//Toast.makeText(this, "Hypatia: Realtime Scanning Stopped", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void setForeground() {
|
||||
foregroundNotification =
|
||||
new NotificationCompat.Builder(this)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(getText(R.string.lblNotificationRealtimeTitle))
|
||||
.setContentText(getText(R.string.lblNotificationRealtimeText))
|
||||
.setPriority(Notification.PRIORITY_MIN)
|
||||
.setShowWhen(false);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
foregroundNotification.setChannelId("FOREGROUND");
|
||||
}
|
||||
|
||||
startForeground(-1, foregroundNotification.build());
|
||||
}
|
||||
|
||||
private void updateForegroundNotification() {
|
||||
foregroundNotification.setSubText(Utils.FILES_SCANNED + " files scanned");
|
||||
notificationManager.notify(-1, foregroundNotification.build());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package us.spotco.malwarescanner;
|
||||
|
||||
import android.os.FileObserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Copyright (C) 2012 Bartek Przybylski
|
||||
* Copyright (C) 2015 ownCloud Inc.
|
||||
* Copyright (C) 2016 Daniel Gultsch
|
||||
* Taken from siacs/Conversations and tweaked a bit
|
||||
*/
|
||||
abstract class RecursiveFileObserver {
|
||||
|
||||
private final String path;
|
||||
private final HashSet<SingleFileObserver> mObservers = new HashSet<>();
|
||||
|
||||
public RecursiveFileObserver(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public final synchronized void startWatching() {
|
||||
Stack<String> stack = new Stack<>();
|
||||
stack.push(path);
|
||||
|
||||
while (!stack.empty()) {
|
||||
String parent = stack.pop();
|
||||
mObservers.add(new SingleFileObserver(parent));
|
||||
final File path = new File(parent);
|
||||
final File[] files = path.listFiles();
|
||||
if (files == null) {
|
||||
continue;
|
||||
}
|
||||
for (File file : files) {
|
||||
if (file.isDirectory() && !file.getName().equals(".") && !file.getName().equals("..")) {
|
||||
final String currentPath = file.getAbsolutePath();
|
||||
if (depth(file) <= 8 && !stack.contains(currentPath) && !observing(currentPath)) {
|
||||
stack.push(currentPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (FileObserver observer : mObservers) {
|
||||
observer.startWatching();
|
||||
}
|
||||
}
|
||||
|
||||
private static int depth(File file) {
|
||||
int depth = 0;
|
||||
while ((file = file.getParentFile()) != null) {
|
||||
depth++;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
private boolean observing(String path) {
|
||||
for (SingleFileObserver observer : mObservers) {
|
||||
if (path.equals(observer.path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final synchronized void stopWatching() {
|
||||
for (FileObserver observer : mObservers) {
|
||||
observer.stopWatching();
|
||||
}
|
||||
mObservers.clear();
|
||||
}
|
||||
|
||||
abstract public void onEvent(int event, String path);
|
||||
|
||||
private class SingleFileObserver extends FileObserver {
|
||||
|
||||
private final String path;
|
||||
|
||||
public SingleFileObserver(String path) {
|
||||
super(path);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onEvent(int event, String filename) {
|
||||
RecursiveFileObserver.this.onEvent(event, path + '/' + filename);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package us.spotco.malwarescanner;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
*/
|
||||
public class ScannerFragment extends Fragment {
|
||||
|
||||
public ScannerFragment() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_scanner, container, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package us.spotco.malwarescanner;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
*/
|
||||
public class SettingsFragment extends Fragment {
|
||||
|
||||
public SettingsFragment() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_settings, container, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
Hypatia: An realtime malware scanner for Android
|
||||
Copyright (c) 2017-2018 Divested Computing, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package us.spotco.malwarescanner;
|
||||
|
||||
class SignatureDatabase {
|
||||
|
||||
private String url = null;
|
||||
private String name = null;
|
||||
|
||||
public SignatureDatabase(String url) {
|
||||
this.url = url;
|
||||
this.name = url.replaceAll(Database.baseURL, "");
|
||||
}
|
||||
|
||||
public final String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public final String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
Hypatia: An realtime malware scanner for Android
|
||||
Copyright (c) 2017-2018 Divested Computing, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package us.spotco.malwarescanner;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Socket;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
class Utils {
|
||||
|
||||
public final static int MAX_SCAN_SIZE = (1000 * 1000) * 80; //80MB
|
||||
public final static int MAX_SCAN_SIZE_REALTIME = MAX_SCAN_SIZE / 2; //40MB
|
||||
|
||||
public final static int MAX_HASH_LENGTH = 12;
|
||||
public final static boolean TRIM_VARIANT_NUMBER = false;
|
||||
|
||||
public static int FILES_SCANNED = 0;
|
||||
private static ThreadPoolExecutor threadPoolExecutor = null;
|
||||
|
||||
public static ThreadPoolExecutor getThreadPoolExecutor() {
|
||||
if (threadPoolExecutor == null) {
|
||||
threadPoolExecutor = (ThreadPoolExecutor) Executors.newScheduledThreadPool(getMaxThreads());
|
||||
}
|
||||
return threadPoolExecutor;
|
||||
}
|
||||
|
||||
public static int getMaxThreads() {
|
||||
int maxTheads = Runtime.getRuntime().availableProcessors();
|
||||
if (maxTheads >= 2) {
|
||||
maxTheads /= 2;
|
||||
}
|
||||
return maxTheads;
|
||||
}
|
||||
|
||||
public static HashSet<File> getFilesRecursive(File root) {
|
||||
HashSet<File> filesAll = new HashSet<>();
|
||||
|
||||
File[] files = root.listFiles();
|
||||
if (files != null && files.length > 0) {
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
HashSet<File> filesTmp = getFilesRecursive(f);
|
||||
if (filesTmp != null) {
|
||||
filesAll.addAll(filesTmp);
|
||||
}
|
||||
} else {
|
||||
if (f.length() <= MAX_SCAN_SIZE && f.canRead()) {//Exclude files larger than limit for performance
|
||||
filesAll.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filesAll;
|
||||
}
|
||||
|
||||
//Credit: https://stackoverflow.com/a/5921190
|
||||
public static boolean isServiceRunning(Class<?> serviceClass, Context context) {
|
||||
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
if (serviceClass.getName().equals(service.service.getClassName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void considerStartService(Context context) {
|
||||
if (!Utils.isServiceRunning(MalwareScannerService.class, context)) {
|
||||
SharedPreferences prefs = context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
|
||||
boolean autostart = prefs.getBoolean("autostart", false);
|
||||
|
||||
if (autostart) {
|
||||
Intent realtimeScanner = new Intent(context, MalwareScannerService.class);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(realtimeScanner);
|
||||
} else {
|
||||
context.startService(realtimeScanner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Credit: https://stackoverflow.com/a/6758962
|
||||
public static boolean isPackageInstalled(Context context, String packageID) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
try {
|
||||
pm.getPackageInfo(packageID, PackageManager.GET_META_DATA);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isOrbotInstalled(Context context) {
|
||||
return isPackageInstalled(context, "org.torproject.android");
|
||||
}
|
||||
|
||||
//Credit: OrbotHelper/NetCipher
|
||||
public static void requestStartOrbot(Context context) {
|
||||
Intent intent = new Intent("org.torproject.android.intent.action.START");
|
||||
intent.setPackage("org.torproject.android");
|
||||
intent.putExtra("org.torproject.android.intent.extra.PACKAGE_NAME", context.getPackageName());
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
//Credit: https://www.geekality.net/2013/04/30/java-simple-check-to-see-if-a-server-is-listening-on-a-port/
|
||||
public static boolean isPortListening(String host, int port) {
|
||||
Socket s = null;
|
||||
try {
|
||||
s = new Socket(host, port);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (s != null) {
|
||||
try {
|
||||
s.close();
|
||||
} catch (Exception e1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean waitUntilOrbotIsAvailable() {
|
||||
int tries = 0;
|
||||
boolean listening;
|
||||
while (!(listening = isPortListening("127.0.0.1", 9050)) && tries <= 60) {
|
||||
tries++;
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
return listening;
|
||||
}
|
||||
|
||||
}
|
9
app/src/main/res/drawable/ic_block_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_block_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFf44336"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_looks_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_looks_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFC107"
|
||||
android:pathData="M12,10c-3.86,0 -7,3.14 -7,7h2c0,-2.76 2.24,-5 5,-5s5,2.24 5,5h2c0,-3.86 -3.14,-7 -7,-7zM12,6C5.93,6 1,10.93 1,17h2c0,-4.96 4.04,-9 9,-9s9,4.04 9,9h2c0,-6.07 -4.93,-11 -11,-11z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_settings_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_settings_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF673AB7"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_storage_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_storage_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF2196F3"
|
||||
android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
|
||||
</vector>
|
|
@ -1,33 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="us.spotco.malwarescanner.MainActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/main_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<android.support.design.widget.BottomNavigationView
|
||||
android:id="@+id/navigation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:menu="@menu/navigation" />
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_main" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
app:srcCompat="@android:drawable/ic_media_play" />
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtLogOutput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text=""
|
||||
android:scrollbars="vertical"
|
||||
android:maxLines="500"
|
||||
android:gravity="bottom" />
|
||||
</LinearLayout>
|
12
app/src/main/res/layout/fragment_database.xml
Normal file
12
app/src/main/res/layout/fragment_database.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="us.spotco.malwarescanner.DatabaseFragment">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/title_databases" />
|
||||
|
||||
</FrameLayout>
|
12
app/src/main/res/layout/fragment_log.xml
Normal file
12
app/src/main/res/layout/fragment_log.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="us.spotco.malwarescanner.LogFragment">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/title_log" />
|
||||
|
||||
</FrameLayout>
|
12
app/src/main/res/layout/fragment_scanner.xml
Normal file
12
app/src/main/res/layout/fragment_scanner.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="us.spotco.malwarescanner.ScannerFragment">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/title_scanner" />
|
||||
|
||||
</FrameLayout>
|
12
app/src/main/res/layout/fragment_settings.xml
Normal file
12
app/src/main/res/layout/fragment_settings.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="us.spotco.malwarescanner.SettingsFragment">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/title_settings" />
|
||||
|
||||
</FrameLayout>
|
|
@ -1,40 +0,0 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="us.spotco.malwarescanner.MainActivity">
|
||||
|
||||
<item
|
||||
android:id="@+id/toggleOnionRouting"
|
||||
android:title="@string/lblOnionRoutingToggle"
|
||||
android:checkable="true"/>
|
||||
<item
|
||||
android:id="@+id/mnuUpdateDatabase"
|
||||
android:title="@string/lblUpdateDatabase" />
|
||||
<item
|
||||
android:id="@+id/mnuSelectDatabases"
|
||||
android:title="@string/lblSelectDatabases" />
|
||||
<item
|
||||
android:id="@+id/toggleRealtime"
|
||||
android:title="@string/lblRealtimeScannerToggle"
|
||||
android:checkable="true" />
|
||||
<item
|
||||
android:id="@+id/mnuScanSystem"
|
||||
android:title="@string/lblScanSystem"
|
||||
android:checkable="true"
|
||||
android:checked="false" />
|
||||
<item
|
||||
android:id="@+id/mnuScanApps"
|
||||
android:title="@string/lblScanApps"
|
||||
android:checkable="true"
|
||||
android:checked="true" />
|
||||
<item
|
||||
android:id="@+id/mnuScanInternal"
|
||||
android:title="@string/lblScanInternal"
|
||||
android:checkable="true"
|
||||
android:checked="true" />
|
||||
<item
|
||||
android:id="@+id/mnuScanExternal"
|
||||
android:title="@string/lblScanExternal"
|
||||
android:checkable="true"
|
||||
android:checked="false" />
|
||||
|
||||
</menu>
|
24
app/src/main/res/menu/navigation.xml
Normal file
24
app/src/main/res/menu/navigation.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/navigation_scanner"
|
||||
android:icon="@drawable/ic_looks_24dp"
|
||||
android:title="@string/title_scanner" />
|
||||
|
||||
<item
|
||||
android:id="@+id/navigation_log"
|
||||
android:icon="@drawable/ic_block_24dp"
|
||||
android:title="@string/title_log" />
|
||||
|
||||
<item
|
||||
android:id="@+id/navigation_databases"
|
||||
android:icon="@drawable/ic_storage_24dp"
|
||||
android:title="@string/title_databases" />
|
||||
|
||||
<item
|
||||
android:id="@+id/navigation_settings"
|
||||
android:icon="@drawable/ic_settings_24dp"
|
||||
android:title="@string/title_settings" />
|
||||
|
||||
</menu>
|
|
@ -1,3 +1,6 @@
|
|||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
<resources>
|
||||
<string name="app_name">Hypatia</string>
|
||||
<string name="lblOnionRoutingToggle">Download over Tor</string>
|
||||
<string name="lblOnionRoutingNotInstalled">Orbot is not installed!</string>
|
||||
<string name="lblOnionRoutingNotRunning">Orbot is not running!</string>
|
||||
<string name="lblUpdateDatabase">Update databases</string>
|
||||
<string name="lblSelectDatabases">Select databases</string>
|
||||
<string name="lblSelectDatabasesTitle">Select databases to enable</string>
|
||||
<string name="lblScanSystem">Scan /system</string>
|
||||
<string name="lblScanApps">Scan App APKs</string>
|
||||
<string name="lblScanInternal">Scan Internal Storage</string>
|
||||
<string name="lblScanExternal">Scan External Storage</string>
|
||||
<string name="lblNotificationMalwareDetectionTitle">Malware Detection</string>
|
||||
<string name="lblNotificationMalwareDetectionDescription">Used to alert when malware is detected</string>
|
||||
<string name="lblNotificationRealtimeTitle">Realtime Scanner</string>
|
||||
<string name="lblNotificationRealtimeDescription">Used to show files scanned counter and maintain the background service</string>
|
||||
<string name="lblNotificationRealtimeText">Malware will be detected in realtime</string>
|
||||
<string name="lblNotificationRealtimeDetection">Malware Detected:</string>
|
||||
<string name="lblRealtimeScannerToggle">Realtime Scanner</string>
|
||||
<string name="title_activity_main">Hypatia</string>
|
||||
<string name="title_scanner">Scanner</string>
|
||||
<string name="title_log">History</string>
|
||||
<string name="title_databases">Databases</string>
|
||||
<string name="title_settings">Settings</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
|
||||
</resources>
|
Loading…
Add table
Reference in a new issue