Start of overhaul, scaffolding for new interface

This commit is contained in:
Tad 2018-10-21 19:24:56 -04:00
parent 716b58bd16
commit 2b8de41221
30 changed files with 338 additions and 1234 deletions

46
.idea/assetWizardSettings.xml generated Normal file
View 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>

Binary file not shown.

View file

@ -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'
}

View file

@ -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>

View file

@ -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");
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}
}

View 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);
}
}

View file

@ -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);
}
}
};
}

View file

@ -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();
}
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}
}

View 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 ScannerFragment extends Fragment {
public ScannerFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_scanner, container, false);
}
}

View 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 SettingsFragment extends Fragment {
public SettingsFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_settings, container, false);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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>

View 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>

View 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>

View 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>

View file

@ -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>

View file

@ -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>

View 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>

View 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>

View 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>

View 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>

View file

@ -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>

View 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>

View file

@ -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>

View file

@ -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>