commit 7bc646f2e7891649adfd8346dbc70acfe633bb65 Author: bytedream Date: Thu Apr 28 19:44:17 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..603b140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/.name b/.idea/.name new file mode 100755 index 0000000..8c31413 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +cryptoGX \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100755 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100755 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100755 index 0000000..5cd135a --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100755 index 0000000..2370474 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100755 index 0000000..37a7509 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100755 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d05ef1 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ⚠️ UNFINISHED PROJECT ⚠️ + +> The unfinished port of [cryptoGX](https://github.com/ByteDream/cryptoGX) for android diff --git a/app/.gitignore b/app/.gitignore new file mode 100755 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100755 index 0000000..11d4076 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + repositories { + maven { + url "https://jitpack.io" + } + } + + defaultConfig { + applicationId "org.blueshard.android.cryptogx" + minSdkVersion 18 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation 'com.google.android.gms:play-services-ads:19.2.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'com.android.support:design:29.0.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100755 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/release/cryptoGX_android_1.0.apk b/app/release/cryptoGX_android_1.0.apk new file mode 100755 index 0000000..912b4c3 Binary files /dev/null and b/app/release/cryptoGX_android_1.0.apk differ diff --git a/app/release/output.json b/app/release/output.json new file mode 100755 index 0000000..fb6688e --- /dev/null +++ b/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release","dirName":""},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/androidTest/java/org/blueshard/android/cryptogx/ExampleInstrumentedTest.java b/app/src/androidTest/java/org/blueshard/android/cryptogx/ExampleInstrumentedTest.java new file mode 100755 index 0000000..e744e82 --- /dev/null +++ b/app/src/androidTest/java/org/blueshard/android/cryptogx/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package org.blueshard.android.cryptogx; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("org.blueshard.android.cryptogx", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100755 index 0000000..ad976ac --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/blueshard/android/cryptogx/CollectionPagerAdapter.java b/app/src/main/java/org/blueshard/android/cryptogx/CollectionPagerAdapter.java new file mode 100755 index 0000000..5eb45b5 --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/CollectionPagerAdapter.java @@ -0,0 +1,60 @@ +package org.blueshard.android.cryptogx; + +import android.content.res.Resources; +import android.os.Bundle; +import android.widget.EditText; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; + +public class CollectionPagerAdapter extends FragmentStatePagerAdapter { + + public CollectionPagerAdapter(FragmentManager fragmentManager) { + super(fragmentManager); + } + + @Override + public Fragment getItem(int position) { + position++; + + if (position == 1) { + Fragment fragment = new TextEnDecrypt(); + Bundle args = new Bundle(); + args.putInt(TextEnDecrypt.ARG, position); + return fragment; + } else if (position == 2) { + Fragment fragment = new FileEnDecrypt(); + Bundle args = new Bundle(); + args.putInt(FileEnDecrypt.ARG, position); + return fragment; + } else if (position == 3) { + Fragment fragment = new SecureDeleteFiles(); + Bundle args = new Bundle(); + args.putInt(SecureDeleteFiles.ARG, position); + return fragment; + } else { + return new Fragment(); + } + } + + @Override + public int getCount() { + return 3; + } + + @Nullable + @Override + public CharSequence getPageTitle(int position) { + position++; + if (position == 1) { + return "text en- / decrypt"; + } else if (position == 2) { + return "file en- / decrypt"; + } else if (position == 3) { + return "secure delete files"; + } + return null; + } +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/EnDecrypt.java b/app/src/main/java/org/blueshard/android/cryptogx/EnDecrypt.java new file mode 100755 index 0000000..8a98d4a --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/EnDecrypt.java @@ -0,0 +1,283 @@ +package org.blueshard.android.cryptogx; + +import android.util.Base64; + +import javax.crypto.*; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.security.*; +import java.security.spec.InvalidKeySpecException; + + +public class EnDecrypt { + + public static String UTF_8 = "UTF-8"; + + public static class AES extends Thread { + + public int iterations = 65536; + + private final String secretKeyFactoryAlgorithm = "PBKDF2WithHmacSHA1"; + + private int keySize = 256; + + private final String key; + private final byte[] salt; + private final String algorithm; + + public AES(String key, byte[] salt, String algorithm) { + this.key = key; + this.salt = salt; + this.algorithm = algorithm; + } + + public AES(String key, byte[] salt, String algorithm, int keySize) { + this.key = key; + this.salt = salt; + this.algorithm = algorithm; + this.keySize = keySize; + } + + public AES(String key, byte[] salt, String algorithm, int iterations, int keySize) { + this.key = key; + this.salt = salt; + this.iterations = iterations; + this.algorithm = algorithm; + this.keySize = keySize; + } + + public byte[] createSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKeyFactoryAlgorithm); + PBEKeySpec keySpec = new PBEKeySpec(key.toCharArray(), salt, iterations, keySize); + + return factory.generateSecret(keySpec).getEncoded(); + } + + /** + *

Writes {@param inputStream} to {@param outputStream}

+ * + * @param inputStream from which is written + * @param outputStream to which is written + * @param buffer + * @throws IOException + * + * @since 1.12.0 + */ + private void write(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws IOException { + int numOfBytesRead; + while ((numOfBytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, numOfBytesRead); + } + outputStream.close(); + inputStream.close(); + } + + /** + *

Encrypts the {@param inputStream} to {@param outputStream}

+ * + * @param inputStream that should be encrypted + * @param outputStream to which the encrypted {@param inputFile} should be written to + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * + * @since 1.12.0 + */ + public void encryptFile(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException { + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); + write(cipherInputStream, outputStream, buffer); + } + + /** + *

Encrypts all files in the {@param inputDirectory} to the {@param outputDirectory}

+ * + * @param inputDirectory that should be encrypted + * @param outputDirectory to which the encrypted {@param inputDirectory} files should be written to + * @param fileEnding get added to every file that gets encrypted (if the {@param fileEnding} starts and ends with + * a '@', the {@param fileEnding} will get removed from the file if it exists) + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * + * @since 1.12.0 + */ + public void encryptDirectory(String inputDirectory, String outputDirectory, String fileEnding, byte[] buffer) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException { + boolean remove = false; + + if (fileEnding == null) { + fileEnding = ""; + } else if (fileEnding.startsWith("@") && fileEnding.endsWith("@")) { + fileEnding = fileEnding.substring(1, fileEnding.length() - 1); + remove = true; + } + + final Utils.RecursivelyGetDirFile decryptedDir = new Utils.RecursivelyGetDirFile(new File(inputDirectory)); + for (File dir: decryptedDir.getDirectories()) { + String dirPath = dir.getAbsolutePath(); + new File(dirPath.replace(inputDirectory, outputDirectory + "/")).mkdir(); + } + for (File file: decryptedDir.getFiles()) { + String filePath = file.getAbsolutePath(); + if (remove) { + encryptFile(new FileInputStream(file),new FileOutputStream(new File(filePath + .substring(0, filePath.lastIndexOf(fileEnding)) + .replace(inputDirectory, outputDirectory + "/") + fileEnding)), buffer); + } else { + encryptFile(new FileInputStream(file),new FileOutputStream(new File(filePath + .replace(inputDirectory, outputDirectory + "/") + fileEnding)), buffer); + } + } + } + + /** + *

Decrypts the {@param inputStream} to {@param outputStream}

+ * + * @param inputStream that should be decrypted + * @param outputStream to which the decrypted {@param inputFile} should be written to + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * @throws InvalidAlgorithmParameterException + * + * @since 1.12.0 + */ + public void decryptFile(InputStream inputStream, OutputStream outputStream, byte[] buffer) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException{ + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + + CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher); + write(inputStream, cipherOutputStream, buffer); + } + + /** + *

Decrypts all files in the {@param inputDirectory} to the {@param outputDirectory}

+ * + * @param inputDirectory that should be decrypted + * @param outputDirectory to which the decrypted {@param inputDirectory} files should be written to + * @param fileEnding get added to every file that gets decrypted (if the {@param fileEnding} starts and ends with + * a '@', the {@param fileEnding} will get removed from the file if it exists) + * @param buffer + * @throws InvalidKeySpecException + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + * @throws IOException + * + * @since 1.12.0 + */ + public void decryptDirectory(String inputDirectory, String outputDirectory, String fileEnding, byte[] buffer) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException { + boolean remove = false; + + if (fileEnding == null) { + fileEnding = ""; + } else if (fileEnding.startsWith("@") && fileEnding.endsWith("@")) { + fileEnding = fileEnding.substring(1, fileEnding.length() - 1); + remove = true; + } + + final Utils.RecursivelyGetDirFile decryptedDir = new Utils.RecursivelyGetDirFile(new File(inputDirectory)); + for (File dir: decryptedDir.getDirectories()) { + String dirPath = dir.getAbsolutePath(); + new File(dirPath.replace(inputDirectory, outputDirectory + "/")).mkdir(); + } + for (File file: decryptedDir.getFiles()) { + String filePath = file.getAbsolutePath(); + if (remove) { + decryptFile(new FileInputStream(file),new FileOutputStream(new File(filePath + .substring(0, filePath.lastIndexOf(fileEnding)) + .replace(inputDirectory, outputDirectory + "/") + fileEnding)), buffer); + } else { + decryptFile(new FileInputStream(file),new FileOutputStream(new File(filePath + .replace(inputDirectory, outputDirectory + "/") + fileEnding)), buffer); + } + } + } + + /** + *

Encrypt {@param bytes}

+ * + * @param bytes that should be encrypted + * @return encrypted bytes + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + * + * @since 1.0.0 + */ + public byte[] encrypt(byte[] bytes) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher encryptCipher = Cipher.getInstance(algorithm); + encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey); + return encryptCipher.doFinal(bytes); + } + + /** + *

Encrypt {@param bytes}

+ * + * @param string that should be encrypted + * + * @see EnDecrypt.AES#encrypt(byte[]) + * + * @since 1.0.0 + */ + public String encrypt(String string) throws BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, UnsupportedEncodingException { + return android.util.Base64.encodeToString(encrypt(string.getBytes(UTF_8)), Base64.DEFAULT); + } + + /** + *

Decrypt encrypted {@param bytes}

+ * + * @param bytes that should be decrypted + * @return decrypted bytes + * @throws BadPaddingException + * @throws IllegalBlockSizeException + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws InvalidKeyException + * + * @since 1.12.0 + */ + public byte[] decrypt(byte[] bytes) throws BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + Key secretKey = new SecretKeySpec(createSecretKey(), "AES"); + + Cipher decryptCipher = Cipher.getInstance(algorithm); + decryptCipher.init(Cipher.DECRYPT_MODE, secretKey); + return decryptCipher.doFinal(android.util.Base64.decode(bytes, Base64.DEFAULT)); + } + + /** + *

Decrypt encrypted {@param string}

+ * + * @param string that should be decrypted + * + * @see EnDecrypt.AES#decrypt(byte[]) + * + * @since 1.0.0 + */ + public String decrypt(String string) throws BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, UnsupportedEncodingException { + return new String(decrypt(string.getBytes(UTF_8)), UTF_8); + } + } +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/FileEnDecrypt.java b/app/src/main/java/org/blueshard/android/cryptogx/FileEnDecrypt.java new file mode 100755 index 0000000..9fe3af9 --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/FileEnDecrypt.java @@ -0,0 +1,306 @@ +package org.blueshard.android.cryptogx; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import android.os.Environment; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.Toast; + +import com.google.android.gms.ads.AdSize; +import com.google.android.material.textfield.TextInputEditText; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException;; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +import javax.crypto.NoSuchPaddingException; + +public class FileEnDecrypt extends Fragment { + + public static final String ARG = "fileEnDecrypt"; + + private static Fragment fragment; + private static boolean permission = false; + private static byte[] buffer = new byte[64]; + private static Map idFileMap = Collections.synchronizedMap(new HashMap()); + private static final int fileChooseReturnCode = 23905; + + private static ListView fileEnDecryptFileBox; + private static Spinner fileEnDecryptAlgorithms; + private static TextInputEditText fileEnDecryptKeyEntry; + private static TextInputEditText fileEnDecryptSaltEntry; + + private static ArrayAdapter fileEnDecryptFileBoxAdapter; + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (requestCode == fileChooseReturnCode && resultCode == Activity.RESULT_OK) { + if (data.getClipData() != null) { + for (int i = 0; i < data.getClipData().getItemCount(); i++) { + try { + Uri uri = data.getClipData().getItemAt(i).getUri(); + idFileMap.put(Utils.getFileName(this.getContext(), uri), uri); + fileEnDecryptFileBoxAdapter.add(Utils.getFileName(this.getContext(), uri)); + fileEnDecryptFileBoxAdapter.notifyDataSetChanged(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + } else if (data.getData() != null) { + try { + Uri uri = data.getData(); + idFileMap.put(Utils.getFileName(this.getContext(), uri), uri); + fileEnDecryptFileBoxAdapter.add(Utils.getFileName(this.getContext(), uri)); + fileEnDecryptFileBoxAdapter.notifyDataSetChanged(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + switch (requestCode) { + case 0: + permission = grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults.length > 0; + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.file_en_decrypt, container, false); + + fragment = this; + + fileEnDecryptKeyEntry = view.findViewById(R.id.fileEnDecryptKeyEntry); + fileEnDecryptSaltEntry = view.findViewById(R.id.fileEnDecryptSaltEntry); + fileEnDecryptFileBox = view.findViewById(R.id.fileEnDecryptFileBox); + fileEnDecryptFileBoxAdapter = new ArrayAdapter<>(this.getContext(), android.R.layout.simple_list_item_1); + fileEnDecryptFileBox.setAdapter(fileEnDecryptFileBoxAdapter); + fileEnDecryptAlgorithms = view.findViewById(R.id.fileEnDecryptAlgorithm); + + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int bannerSize = AdSize.BANNER.getHeight(); + + fileEnDecryptFileBox.getLayoutParams().height = Utils.percentOf(displayMetrics.heightPixels - bannerSize, 30); + + int fileEnDecryptAdvancedTextWidth = view.findViewById(R.id.fileEnDecryptAdvancedText).getLayoutParams().width; + View fileEnDecryptSeparator1 = view.findViewById(R.id.fileEnDecryptSeparator1); + View fileEnDecryptSeparator2 = view.findViewById(R.id.fileEnDecryptSeparator2); + fileEnDecryptSeparator1.getLayoutParams().width = (displayMetrics.widthPixels - fileEnDecryptAdvancedTextWidth) / 2 - 10; + fileEnDecryptSeparator2.getLayoutParams().width = (displayMetrics.widthPixels - fileEnDecryptAdvancedTextWidth) / 2 - 10; + + fileEnDecryptAlgorithms = view.findViewById(R.id.fileEnDecryptAlgorithm); + String[] algorithms = Utils.algorithms.keySet().toArray(new String[Utils.algorithms.size()]); + ArrayAdapter arrayAdapter = new ArrayAdapter(view.getContext(), android.R.layout.simple_list_item_1, algorithms); + fileEnDecryptAlgorithms.setAdapter(arrayAdapter); + + return view; + } + + protected static void chooseFiles(View view) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + intent.addCategory(Intent.CATEGORY_OPENABLE); + + fragment.startActivityForResult(Intent.createChooser(intent, "Choose files"), fileChooseReturnCode); + } + + protected static void encrypt(View view) { + permission = Utils.askPermission(fragment, Manifest.permission.WRITE_EXTERNAL_STORAGE, 0); + + if (permission) { + File cryptoGXFileDir = new File(Environment.getExternalStorageDirectory() + "/cryptoGX/"); + if (!cryptoGXFileDir.isDirectory()) { + if (!cryptoGXFileDir.mkdir()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.could_not_create_folder) + " cryptoGX", Toast.LENGTH_LONG).show(); + return; + } + } else { + File cryptoGXEncryptedFilesDir = new File(cryptoGXFileDir + "/encrypted/"); + if (!cryptoGXEncryptedFilesDir.isDirectory()) { + if (!cryptoGXEncryptedFilesDir.mkdir()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.could_not_create_folder) + " cryptoGX/encrypted", Toast.LENGTH_LONG).show(); + return; + } + } + String key = fileEnDecryptKeyEntry.getText().toString(); + byte[] salt; + + if (key.isEmpty()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.empty_key), Toast.LENGTH_SHORT).show(); + return; + } + if (fileEnDecryptSaltEntry.getText().toString().isEmpty()) { + salt = new byte[16]; + } else { + try { + salt = fileEnDecryptSaltEntry.getText().toString().getBytes(Utils.UTF_8); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + salt = new byte[16]; + } + } + + int lenght; + + try { + lenght = Integer.parseInt(fileEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 7)); + } catch (NumberFormatException e) { + try { + lenght = Integer.parseInt(fileEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 8)); + } catch (NumberFormatException ex) { + lenght = Integer.parseInt(fileEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 6)); + } + } + + HashSet success = new HashSet<>(); + + EnDecrypt.AES encrypt = new EnDecrypt.AES(key, + salt, + Utils.algorithms.get(fileEnDecryptAlgorithms.getSelectedItem().toString()), + lenght); + + for (Map.Entry entry : idFileMap.entrySet()) { + String name = entry.getKey(); + Uri file = entry.getValue(); + + try { + encrypt.encryptFile(view.getContext().getContentResolver().openInputStream(file), new FileOutputStream(cryptoGXEncryptedFilesDir + "/" + name + ".cryptoGX"), buffer); + success.add(name); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + } + + for (String name: success) { + idFileMap.remove(name); + fileEnDecryptFileBoxAdapter.remove(name); + } + fileEnDecryptFileBoxAdapter.notifyDataSetChanged(); + } + } + } + + protected static void decrypt(View view) { + permission = Utils.askPermission(fragment, Manifest.permission.WRITE_EXTERNAL_STORAGE, 0); + + if (permission) { + File cryptoGXFileDir = new File(Environment.getExternalStorageDirectory() + "/cryptoGX/"); + if (!cryptoGXFileDir.isDirectory()) { + if (!cryptoGXFileDir.mkdir()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.could_not_create_folder) + " cryptoGX", Toast.LENGTH_LONG).show(); + return; + } + } else { + File cryptoGXDecryptedFilesDir = new File(cryptoGXFileDir + "/decrypted/"); + if (!cryptoGXDecryptedFilesDir.isDirectory()) { + if (!cryptoGXDecryptedFilesDir.mkdir()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.could_not_create_folder) + " cryptoGX/decrypted", Toast.LENGTH_LONG).show(); + return; + } + } + String key = fileEnDecryptKeyEntry.getText().toString(); + byte[] salt; + + if (key.isEmpty()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.empty_key), Toast.LENGTH_SHORT).show(); + return; + } + if (fileEnDecryptSaltEntry.getText().toString().isEmpty()) { + salt = new byte[16]; + } else { + try { + salt = fileEnDecryptSaltEntry.getText().toString().getBytes(Utils.UTF_8); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + salt = new byte[16]; + } + } + + int lenght; + + try { + lenght = Integer.parseInt(fileEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 7)); + } catch (NumberFormatException e) { + try { + lenght = Integer.parseInt(fileEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 8)); + } catch (NumberFormatException ex) { + lenght = Integer.parseInt(fileEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 6)); + } + } + + HashSet success = new HashSet<>(); + + EnDecrypt.AES decrypt = new EnDecrypt.AES(key, + salt, + Utils.algorithms.get(fileEnDecryptAlgorithms.getSelectedItem().toString()), + lenght); + + for (Map.Entry entry : idFileMap.entrySet()) { + String name = entry.getKey(); + Uri file = entry.getValue(); + + try { + decrypt.decryptFile(view.getContext().getContentResolver().openInputStream(file), new FileOutputStream(cryptoGXDecryptedFilesDir + "/" + name + ".cryptoGX"), buffer); + success.add(name); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } + } + + for (String name: success) { + idFileMap.remove(name); + fileEnDecryptFileBoxAdapter.remove(name); + } + fileEnDecryptFileBoxAdapter.notifyDataSetChanged(); + } + } + } + +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/MainActivity.java b/app/src/main/java/org/blueshard/android/cryptogx/MainActivity.java new file mode 100755 index 0000000..d45371a --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/MainActivity.java @@ -0,0 +1,223 @@ +package org.blueshard.android.cryptogx; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.viewpager.widget.ViewPager; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.view.View; +import android.widget.ProgressBar; + +import com.google.android.gms.ads.AdRequest; +import com.google.android.gms.ads.AdSize; +import com.google.android.gms.ads.AdView; +import com.google.android.gms.ads.MobileAds; +import com.google.android.gms.ads.initialization.InitializationStatus; +import com.google.android.gms.ads.initialization.OnInitializationCompleteListener; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class MainActivity extends AppCompatActivity { + + private Set textEnDecryptThreads = Collections.synchronizedSet(new HashSet()); + private Set fileEnDecryptThreads = Collections.synchronizedSet(new HashSet()); + private Set fileDeleteThreads = Collections.synchronizedSet(new HashSet()); + private AppCompatActivity mainView; + + CollectionPagerAdapter fragmentChanger; + ViewPager viewPager; + + @SuppressLint("SourceLockedOrientationActivity") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mainView = this; + + setContentView(R.layout.activity_main); + + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + + MobileAds.initialize(this, new OnInitializationCompleteListener() { + @Override + public void onInitializationComplete(InitializationStatus initializationStatus) { + } + }); + + AdView adView = new AdView(this); + adView.setAdSize(AdSize.BANNER); + adView.setAdUnitId("ca-app-pub-3940256099942544/6300978111"); + + adView = findViewById(R.id.adView); + AdRequest adRequest = new AdRequest.Builder().build(); + adView.loadAd(adRequest); + + fragmentChanger = new CollectionPagerAdapter(getSupportFragmentManager()); + viewPager = (ViewPager) findViewById(R.id.pager); + viewPager.setAdapter(fragmentChanger); + } + + public void encryptTextButton(final View view) { + final ProgressBar textEnDecryptProgressBar = mainView.findViewById(R.id.textEnDecryptProgressBar); + textEnDecryptProgressBar.setVisibility(View.VISIBLE); + Thread thread = new Thread() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + textEnDecryptThreads.add(Thread.currentThread()); + TextEnDecrypt.encrypt(view); + if (textEnDecryptThreads.size() - 1 <= 0) { + runOnUiThread(new Runnable() { + @Override + public void run() { + textEnDecryptProgressBar.setVisibility(View.INVISIBLE); + } + }); + } + textEnDecryptThreads.remove(Thread.currentThread()); + } + }); + } + }; + thread.start(); + } + + public void decryptTextButton(final View view) { + final ProgressBar textEnDecryptProgressBar = mainView.findViewById(R.id.textEnDecryptProgressBar); + textEnDecryptProgressBar.setVisibility(View.VISIBLE); + Thread thread = new Thread() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + textEnDecryptThreads.add(currentThread()); + TextEnDecrypt.decrypt(view); + if (textEnDecryptThreads.size() - 1 <= 0) { + runOnUiThread(new Runnable() { + @Override + public void run() { + textEnDecryptProgressBar.setVisibility(View.INVISIBLE); + } + }); + } + textEnDecryptThreads.remove(currentThread()); + } + }); + } + }; + thread.start(); + } + + //------------------------------------------------------------// + + public void fileEnDecryptChooseFiles(View view) { + FileEnDecrypt.chooseFiles(view); + } + + public void encryptFileButton(final View view) { + final ProgressBar fileEnDecryptProgressBar = mainView.findViewById(R.id.fileEnDecryptProgressBar); + fileEnDecryptProgressBar.setVisibility(View.VISIBLE); + Thread thread = new Thread() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + fileEnDecryptThreads.add(currentThread()); + FileEnDecrypt.encrypt(view); + if (fileEnDecryptThreads.size() - 1 <= 0) { + fileEnDecryptProgressBar.setVisibility(View.INVISIBLE); + } + fileEnDecryptThreads.remove(currentThread()); + } + }); + } + }; + thread.start(); + } + + public void decryptFileButton(final View view) { + final ProgressBar fileEnDecryptProgressBar = mainView.findViewById(R.id.fileEnDecryptProgressBar); + fileEnDecryptProgressBar.setVisibility(View.VISIBLE); + Thread thread = new Thread() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + fileEnDecryptThreads.add(currentThread()); + FileEnDecrypt.decrypt(view); + if (fileEnDecryptThreads.size() - 1 <= 0) { + fileEnDecryptProgressBar.setVisibility(View.INVISIBLE); + } + fileEnDecryptThreads.remove(currentThread()); + } + }); + } + }; + thread.start(); + } + + public void cancelEnDecryptFileButton(View view) { + for (Iterator iterator = fileEnDecryptThreads.iterator(); iterator.hasNext();) { + Thread thread = iterator.next(); + while (thread.isAlive() && !thread.isInterrupted()) { + thread.stop(); + thread.interrupt(); + } + iterator.remove(); + } + mainView.findViewById(R.id.fileEnDecryptProgressBar).setVisibility(View.INVISIBLE); + } + + //------------------------------------------------------------// + + public void fileDeleteChooseFiles(View view) { + SecureDeleteFiles.chooseFiles(view); + } + + public void deleteFileButton(final View view) { + final ProgressBar fileEnDecryptProgressBar = mainView.findViewById(R.id.fileDeleteProgressBar); + fileEnDecryptProgressBar.setVisibility(View.VISIBLE); + Thread thread = new Thread() { + @Override + public void run() { + runOnUiThread(new Runnable() { + @Override + public void run() { + fileDeleteThreads.add(currentThread()); + SecureDeleteFiles.delete(view); + if (fileDeleteThreads.size() - 1 <= 0) { + fileEnDecryptProgressBar.setVisibility(View.INVISIBLE); + } + fileDeleteThreads.remove(currentThread()); + } + }); + } + }; + thread.start(); + } + + public void cancelDeleteFileButton(View view) { + for (Iterator iterator = fileDeleteThreads.iterator(); iterator.hasNext();) { + Thread thread = iterator.next(); + while (thread.isAlive() && !thread.isInterrupted()) { + thread.stop(); + thread.interrupt(); + } + iterator.remove(); + } + mainView.findViewById(R.id.fileDeleteProgressBar).setVisibility(View.INVISIBLE); + } + +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/SecureDelete.java b/app/src/main/java/org/blueshard/android/cryptogx/SecureDelete.java new file mode 100755 index 0000000..3507fb1 --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/SecureDelete.java @@ -0,0 +1,196 @@ +package org.blueshard.android.cryptogx; + +import java.io.*; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; + +public class SecureDelete { + + /** + *

Overwrites the file {@param iterations} times at once with random bytes an delete it

+ * + * @see SecureDelete#deleteFileAllInOne(File, int) + */ + public static boolean deleteFileAllInOne(String filename, int iterations) throws IOException, NoSuchAlgorithmException { + return deleteFileAllInOne(new File(filename), iterations); + } + + /** + *

Overwrites the file {@param iterations} times at once with random bytes and delete it

+ * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileAllInOne(File file, int iterations) throws IOException, NoSuchAlgorithmException { + long fileLength = file.length() + 1 ; + for (int i=0; i 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) fileLength / 1000000000); + for (int len=0; lenOverwrites the file {@param iterations} times at once with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it

+ * + * @see SecureDelete#deleteFileAllInOne(String, int, long, long) + */ + public static boolean deleteFileAllInOne(String filename, int iterations, long minFileSize, long maxFileSize) throws IOException, NoSuchAlgorithmException { + return deleteFileAllInOne(new File(filename), iterations, minFileSize, maxFileSize); + } + + /** + *

Overwrites the file {@param iterations} times at once with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it

+ * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @param minFileSize is the minimal file size for every {@param iterations} + * @param maxFileSize is the maximal file size for every {@param iterations} + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileAllInOne(File file, int iterations, long minFileSize, long maxFileSize) throws IOException, NoSuchAlgorithmException { + for (int i = 0; i < iterations; i++) { + BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(file)); + if (maxFileSize > 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) maxFileSize / 1000000000); + for (int len = 0; len < numOfByteArrays; len++) { + int newMaxFileSize = (int) maxFileSize / numOfByteArrays; + int newMinFileSize = 0; + if (minFileSize != 0) { + newMinFileSize = (int) minFileSize / numOfByteArrays; + } + byte[] randomBytes = new byte[new Random().nextInt(newMaxFileSize - newMinFileSize) + newMinFileSize]; + SecureRandom.getInstance("SHA1PRNG").nextBytes(randomBytes); + bufferedOutputStream.write(randomBytes); + } + } else { + byte[] randomBytes = new byte[new Random().nextInt((int) maxFileSize - (int) minFileSize) + (int) minFileSize]; + SecureRandom.getInstance("SHA1PRNG").nextBytes(randomBytes); + bufferedOutputStream.write(randomBytes); + } + bufferedOutputStream.flush(); + bufferedOutputStream.close(); + } + + return file.delete(); + } + + /** + *

Overwrites the file {@param iterations} times line by line with random bytes and delete it

+ * + * @see SecureDelete#deleteFileLineByLine(File, int) + */ + public static boolean deleteFileLineByLine(String filename, int iterations) throws NoSuchAlgorithmException, IOException { + return deleteFileLineByLine(new File(filename), iterations); + } + + /** + *

Overwrites the file {@param iterations} times line by line with random bytes and delete it

+ * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileLineByLine(File file, int iterations) throws NoSuchAlgorithmException, IOException { + long fileLength = file.length() + 1 ; + for (int i=0; i 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) fileLength / 1000000000); + for (int len=0; lenOverwrites the file {@param iterations} times line by line with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it

+ */ + public static boolean deleteFileLineByLine(String filename, int iterations, long minFileSize, long maxFileSize) throws NoSuchAlgorithmException, IOException { + return deleteFileLineByLine(new File(filename), iterations, minFileSize, maxFileSize); + } + + /** + *

Overwrites the file {@param iterations} times line by line with random bytes (minimal size {@param minFileSize}; maximal size {@param maxFileSize}) and delete it

+ * + * @param file that should be deleted + * @param iterations how many times the file should be overwritten before it gets deleted + * @param minFileSize is the minimal file size for every {@param iterations} + * @param maxFileSize is the maximal file size for every {@param iterations} + * @return if the file could be deleted + * @throws IOException + * @throws NoSuchAlgorithmException + */ + public static boolean deleteFileLineByLine(File file, int iterations, long minFileSize, long maxFileSize) throws NoSuchAlgorithmException, IOException { + for (int i=0; i 1000000000) { + int numOfByteArrays = (int) Math.ceil((double) maxFileSize / 1000000000); + for (int len=0; len idFileMap = Collections.synchronizedMap(new HashMap()); + + private static ArrayAdapter fileDeleteFileBoxAdapter; + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + if (requestCode == fileChooseReturnCode && resultCode == Activity.RESULT_OK) { + if (data.getClipData() != null) { + for (int i = 0; i < data.getClipData().getItemCount(); i++) { + try { + Uri uri = data.getClipData().getItemAt(i).getUri(); + idFileMap.put(Utils.getFileName(this.getContext(), uri), uri); + fileDeleteFileBoxAdapter.add(Utils.getFileName(this.getContext(), uri)); + fileDeleteFileBoxAdapter.notifyDataSetChanged(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + } else if (data.getData() != null) { + try { + Uri uri = data.getData(); + idFileMap.put(Utils.getFileName(this.getContext(), uri), uri); + fileDeleteFileBoxAdapter.add(Utils.getFileName(this.getContext(), uri)); + fileDeleteFileBoxAdapter.notifyDataSetChanged(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + switch (requestCode) { + case 0: + permission = grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults.length > 0; + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.secure_delete_files, container, false); + + fragment = this; + + fileDeleteFileBox = view.findViewById(R.id.fileDeleteFileBox); + fileDeleteFileBoxAdapter = new ArrayAdapter<>(this.getContext(), android.R.layout.simple_list_item_1); + fileDeleteFileBox.setAdapter(fileDeleteFileBoxAdapter); + fileDeleteIterationsEntry = view.findViewById(R.id.fileDeleteIterationsEntry); + + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int bannerSize = AdSize.BANNER.getHeight(); + + ListView fileDeleteFileBox = view.findViewById(R.id.fileDeleteFileBox); + fileDeleteFileBox.getLayoutParams().height = Utils.percentOf(displayMetrics.heightPixels - bannerSize, 44); + + int fileDeleteAdvancedTextWidth = view.findViewById(R.id.fileDeleteAdvancedText).getLayoutParams().width; + View fileDeleteSeparator1 = view.findViewById(R.id.fileDeleteSeparator1); + View fileDeleteSeparator2 = view.findViewById(R.id.fileDeleteSeparator2); + fileDeleteSeparator1.getLayoutParams().width = (displayMetrics.widthPixels - fileDeleteAdvancedTextWidth) / 2 - 10; + fileDeleteSeparator2.getLayoutParams().width = (displayMetrics.widthPixels - fileDeleteAdvancedTextWidth) / 2 - 10; + + return view; + } + + protected static void chooseFiles(View view) { + Intent i = new Intent(fragment.getContext(), FileDirChooser.class); + fragment.startActivity(i); + } + + protected static void delete(View view) { + permission = Utils.askPermission(fragment, Manifest.permission.WRITE_EXTERNAL_STORAGE, 0); + + if (permission) { + HashSet success = new HashSet<>(); + int iterations; + try { + String iterationEntry = fileDeleteIterationsEntry.getText().toString(); + if (iterationEntry.isEmpty()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.empty_iterations), Toast.LENGTH_SHORT).show(); + return; + } + iterations = Integer.parseInt(iterationEntry); + } catch (NumberFormatException e) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.wrong_iterations), Toast.LENGTH_SHORT).show(); + return; + } + + for (Map.Entry entry: idFileMap.entrySet()) { + try { + File file = new File(Utils.getPath(view.getContext(), entry.getValue())); + if (file == null) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.no_file_path) + " " + entry.getKey(), Toast.LENGTH_LONG).show(); + } else { + SecureDelete.deleteFileLineByLine(file, iterations); + while (file.isFile()) { + file.delete(); + } + success.add(entry.getKey()); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + for (String name: success) { + idFileMap.remove(name); + fileDeleteFileBoxAdapter.remove(name); + } + fileDeleteFileBoxAdapter.notifyDataSetChanged(); + } + } + +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/TextEnDecrypt.java b/app/src/main/java/org/blueshard/android/cryptogx/TextEnDecrypt.java new file mode 100755 index 0000000..33975a4 --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/TextEnDecrypt.java @@ -0,0 +1,171 @@ +package org.blueshard.android.cryptogx; + +import android.os.Bundle; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import androidx.fragment.app.Fragment; + +import com.google.android.gms.ads.AdSize; +import com.google.android.material.textfield.TextInputEditText; + +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +public class TextEnDecrypt extends Fragment { + + private static TextInputEditText textEnDecryptKeyEntry; + private static TextInputEditText textEnDecryptSaltEntry; + private static EditText textEnDecryptDecryptedText; + private static EditText textEnDecryptEncryptedText; + private static Spinner textEnDecryptAlgorithms; + + public static final String ARG = "textEnDecrypt"; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.text_en_decrypt, container, false); + + textEnDecryptKeyEntry = view.findViewById(R.id.textEnDecryptKeyEntry); + textEnDecryptSaltEntry = view.findViewById(R.id.textEnDecryptSaltEntry); + textEnDecryptDecryptedText = view.findViewById(R.id.textEnDecryptDecryptedText); + textEnDecryptEncryptedText = view.findViewById(R.id.textEnDecryptEncryptedText); + + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int bannerSize = AdSize.BANNER.getHeight(); + + EditText textEnDecryptDecryptedText = view.findViewById(R.id.textEnDecryptDecryptedText); + textEnDecryptDecryptedText.getLayoutParams().height = Utils.percentOf(displayMetrics.heightPixels - bannerSize, 21); + + EditText textEnDecryptEncryptedText = view.findViewById(R.id.textEnDecryptEncryptedText); + textEnDecryptEncryptedText.getLayoutParams().height = Utils.percentOf(displayMetrics.heightPixels - bannerSize, 21); + + int textEnDecryptAdvancedTextWidth = view.findViewById(R.id.textEnDecryptAdvancedText).getLayoutParams().width; + View textEnDecryptSeparator1 = view.findViewById(R.id.textEnDecryptSeparator1); + View textEnDecryptSeparator2 = view.findViewById(R.id.textEnDecryptSeparator2); + textEnDecryptSeparator1.getLayoutParams().width = (displayMetrics.widthPixels - textEnDecryptAdvancedTextWidth) / 2 - 10; + textEnDecryptSeparator2.getLayoutParams().width = (displayMetrics.widthPixels - textEnDecryptAdvancedTextWidth) / 2 - 10; + + textEnDecryptAlgorithms = view.findViewById(R.id.textEnDecryptAlgorithms); + String[] algorithms = Utils.algorithms.keySet().toArray(new String[Utils.algorithms.size()]); + ArrayAdapter arrayAdapter = new ArrayAdapter(view.getContext(), android.R.layout.simple_spinner_item, algorithms); + textEnDecryptAlgorithms.setAdapter(arrayAdapter); + + return view; + } + + protected static void encrypt(View view) { + if (textEnDecryptKeyEntry.getText().toString().isEmpty()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.empty_key), Toast.LENGTH_SHORT).show(); + return; + } + try { + byte[] salt; + int lenght; + + try { + lenght = Integer.parseInt(textEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 7)); + } catch (NumberFormatException e) { + try { + lenght = Integer.parseInt(textEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 8)); + } catch (NumberFormatException ex) { + lenght = Integer.parseInt(textEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 6)); + } + } + + if (textEnDecryptSaltEntry.getText().toString().isEmpty()) { + salt = new byte[16]; + } else { + salt = textEnDecryptSaltEntry.getText().toString().getBytes(EnDecrypt.UTF_8); + } + EnDecrypt.AES encrypt = new EnDecrypt.AES(textEnDecryptKeyEntry.getText().toString(), + salt, + Utils.algorithms.get(textEnDecryptAlgorithms.getSelectedItem().toString()), + lenght); + String encryptedText = encrypt.encrypt(textEnDecryptDecryptedText.getText().toString()); + textEnDecryptEncryptedText.setText(encryptedText); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (BadPaddingException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + protected static void decrypt(View view) { + if (textEnDecryptKeyEntry.getText().toString().isEmpty()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.empty_key), Toast.LENGTH_SHORT).show(); + return; + } else if (textEnDecryptEncryptedText.getText().toString().isEmpty()) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.empty_encrypted_text), Toast.LENGTH_LONG).show(); + return; + } + try { + byte[] salt; + int lenght; + + try { + lenght = Integer.parseInt(textEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 7)); + } catch (NumberFormatException e) { + try { + lenght = Integer.parseInt(textEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 8)); + } catch (NumberFormatException ex) { + lenght = Integer.parseInt(textEnDecryptAlgorithms.getSelectedItem().toString().substring(4, 6)); + } + } + + if (textEnDecryptSaltEntry.getText().toString().isEmpty()) { + salt = new byte[16]; + } else { + salt = textEnDecryptSaltEntry.getText().toString().getBytes(EnDecrypt.UTF_8); + } + EnDecrypt.AES decrypt = new EnDecrypt.AES(textEnDecryptKeyEntry.getText().toString(), + salt, + Utils.algorithms.get(textEnDecryptAlgorithms.getSelectedItem().toString()), + lenght); + String encryptedText = decrypt.decrypt(textEnDecryptEncryptedText.getText().toString()); + textEnDecryptDecryptedText.setText(encryptedText); + } catch (IllegalArgumentException e) { + Toast.makeText(view.getContext(), view.getResources().getString(R.string.empty_key), Toast.LENGTH_LONG).show(); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (BadPaddingException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + } catch (IllegalBlockSizeException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/Utils.java b/app/src/main/java/org/blueshard/android/cryptogx/Utils.java new file mode 100755 index 0000000..0c2669d --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/Utils.java @@ -0,0 +1,314 @@ +package org.blueshard.android.cryptogx; + +import android.Manifest; +import android.content.ContentUris; +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; +import android.util.Size; +import android.webkit.MimeTypeMap; + +import androidx.fragment.app.Fragment; + +import java.io.File; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.TreeMap; +import java.util.TreeSet; + +public class Utils { + + public static final int normalFile = 0; + public static final int imageFile = 1; + public static final int audioFile = 2; + public static final int videoFile = 3; + + public static String UTF_8 = "UTF-8"; + public static TreeMap algorithms = allAlgorithms(); + + private static TreeMap allAlgorithms() { + TreeMap return_map = new TreeMap<>(); + + int[] aesKeySizes = {128, 192, 256}; + + for (int i: aesKeySizes) { + return_map.put("AES-" + i, "AES"); + } + + return return_map; + } + + protected static boolean askPermission(Fragment fragment, String permission, int requestCode) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + int permissionCheck = fragment.getContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (permissionCheck == PackageManager.PERMISSION_GRANTED) { + return true; + } + fragment.requestPermissions(new String[]{permission}, requestCode); + return false; + } else { + return true; + } + } + + public static String getFileName(Context context, Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + } + } finally { + cursor.close(); + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; + } + + public static String getPath(final Context context, final Uri uri) { + + // check here to KITKAT or new version + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (GetPathUtils.isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + + split[1]; + } + } + // DownloadsProvider + else if (GetPathUtils.isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), + Long.valueOf(id)); + + return GetPathUtils.getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (GetPathUtils.isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + + return GetPathUtils.getDataColumn(context, contentUri, selection, + selectionArgs); + } + } else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (GetPathUtils.isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return GetPathUtils.getDataColumn(context, uri, null, null); + } else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + private static class GetPathUtils { + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context + * The context. + * @param uri + * The Uri to query. + * @param selection + * (Optional) Filter used in the query. + * @param selectionArgs + * (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, + String selection, String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { column }; + + try { + cursor = context.getContentResolver().query(uri, projection, + selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + /** + * @param uri + * The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri + .getAuthority()); + } + + /** + * @param uri + * The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri + .getAuthority()); + } + + /** + * @param uri + * The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri + .getAuthority()); + } + + /** + * @param uri + * The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri + .getAuthority()); + } + } + + public static int getTypeOfFile(String fname) { + if (!fname.contains(".")) { + return normalFile; + } + switch (fname.substring(fname.lastIndexOf(".") + 1)) { + case "bpm": + case "gif": + case "jpg": + case "png": + case "webp": + return imageFile; + case "heic": + case "heif": + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return imageFile; + } else { + return normalFile; + } + + case "m4a": + case "acc": + case "flac": + case "gsm": + case "mid": + case "xmf": + case "mxmf": + case "rtttl": + case "rtx": + case "ota": + case "imy": + case "mp3": + case "wav": + case "ogg": + return audioFile; + + case "3gp": + case "mp4": + case "ts": + case "webm": + case "mkv": + return videoFile; + + + default: + return normalFile; + } + } + + public static int percentOf(int i, float percent) { + return (int) (i * (percent / 100)); + } + + public static class RecursivelyGetDirFile { + + private TreeSet directories = new TreeSet<>(); + private TreeSet files = new TreeSet<>(); + + RecursivelyGetDirFile(File file) { + getAll(file); + } + + private void getAll(File startFile) { + for (File file: startFile.listFiles()) { + if (file.isFile()) { + files.add(file); + } else { + directories.add(file); + getAll(file); + } + } + } + + public TreeSet getDirectories() { + return directories; + } + + public TreeSet getFiles() { + return files; + } + + } + + +} + diff --git a/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirAdapter.java b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirAdapter.java new file mode 100755 index 0000000..f26d18d --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirAdapter.java @@ -0,0 +1,71 @@ +package org.blueshard.android.cryptogx.filedirchooser; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.blueshard.android.cryptogx.R; + + +public class FileDirAdapter extends RecyclerView.Adapter { + + private FileDirData[] dataSet; + private View.OnClickListener onClickListener; + private View.OnLongClickListener onLongClickListener; + + public FileDirAdapter(FileDirData[] dataSet, View.OnClickListener onClickListener, View.OnLongClickListener onLongClickListener) { + this.dataSet = dataSet; + this.onClickListener = onClickListener; + this.onLongClickListener = onLongClickListener; + } + + public class FileDirHolder extends RecyclerView.ViewHolder { + + private final TextView textView; + private final ImageView imageView; + + public FileDirHolder(View view) { + super(view); + this.textView = view.findViewById(R.id.fileDirText); + this.imageView = view.findViewById(R.id.fileDirImage); + } + } + + @NonNull + @Override + public FileDirHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View chooserItem = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_dir_item, parent, false); + + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) chooserItem.getLayoutParams(); + params.height = 200; + chooserItem.setLayoutParams(params); + + chooserItem.setOnClickListener(onClickListener); + chooserItem.setOnLongClickListener(onLongClickListener); + + return new FileDirHolder(chooserItem); + } + + @Override + public void onBindViewHolder(@NonNull FileDirHolder holder, int position) { + FileDirData data = dataSet[position]; + if (data != null) { + holder.textView.setText(data.getFile().getName()); + holder.imageView.setImageDrawable(data.getImage()); + } + } + + @Override + public int getItemCount() { + return dataSet.length; + } + + public FileDirData getData(int index) { + return dataSet[index]; + } +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirChooser.java b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirChooser.java new file mode 100755 index 0000000..7cb52ed --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirChooser.java @@ -0,0 +1,109 @@ +package org.blueshard.android.cryptogx.filedirchooser; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.view.View; + +import org.blueshard.android.cryptogx.R; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.concurrent.atomic.AtomicInteger; + +public class FileDirChooser extends AppCompatActivity { + + private RecyclerView files; + private RecyclerView selectedFileDirs; + private FileDirAdapter filesAdapter; + private SelectedFileDirsAdapter selectedFileDirsAdapter; + private Context context = this; + private FileDirData[] dataSet; + private ArrayList selectedDataSet; + + private boolean multipleMode = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_file_dir_chooser); + + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fileDirFragment, new FileDirFragment()); + transaction.addToBackStack(null); + transaction.commit(); + + final Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + File startDirectory; + if (extras != null) { + String directory = extras.getString("directory"); + if (directory != null && !directory.equals("")) { + startDirectory = new File(directory); + } else { + startDirectory = Environment.getExternalStorageDirectory(); + } + + multipleMode = extras.getBoolean("multipleMode"); + + } else { + startDirectory = Environment.getExternalStorageDirectory(); + } + + dataSet = new FileDirData[startDirectory.listFiles().length]; + + filesInDir(startDirectory); + + filesAdapter = new FileDirAdapter(dataSet, new View.OnClickListener() { + @Override + public void onClick(View view) { + File file = filesAdapter.getData(files.getChildLayoutPosition(view)).getFile(); + if (file.isDirectory()) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fileDirFragment, new FileDirFragment()); + transaction.addToBackStack(null); + transaction.commit(); + } else if (!multipleMode) { + View selectedFileDirs = findViewById(R.id.selectedFileDirs); + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) selectedFileDirs.getLayoutParams(); + params.height = 0; + selectedFileDirs.setLayoutParams(params); + } + } + + }, new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + FileDirData data = filesAdapter.getData(files.getChildLayoutPosition(view)); + if (multipleMode) { + selectedDataSet.add(data); + selectedFileDirsAdapter.notifyDataSetChanged(); + return true; + } else if (data.getFile().isFile()) { + setResult(Activity.RESULT_OK, intent); + finish(); + return true; + } + return false; + } + }); + + files = findViewById(R.id.files); + files.setAdapter(filesAdapter); + files.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + + selectedFileDirs = findViewById(R.id.selectedFileDirs); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirData.java b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirData.java new file mode 100755 index 0000000..eecfeca --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirData.java @@ -0,0 +1,26 @@ +package org.blueshard.android.cryptogx.filedirchooser; + + +import android.graphics.drawable.Drawable; + +import java.io.File; + +public class FileDirData { + + private final File file; + private final Drawable image; + + public FileDirData(File file, Drawable image) { + this.file = file; + this.image = image; + } + + public File getFile() { + return file; + } + + public Drawable getImage() { + return image; + } + +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirFragment.java b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirFragment.java new file mode 100755 index 0000000..e518fd7 --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/FileDirFragment.java @@ -0,0 +1,159 @@ +package org.blueshard.android.cryptogx.filedirchooser; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + + +import org.blueshard.android.cryptogx.R; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.concurrent.atomic.AtomicInteger; + +public class FileDirFragment extends Fragment { + + private Context context; + private File directory; + private RecyclerView files; + private RecyclerView selectedFileDirs; + private FileDirAdapter filesAdapter; + private SelectedFileDirsAdapter selectedFileDirsAdapter; + private FileDirData[] dataSet; + private ArrayList selectedDataSet; + + private boolean multipleMode = false; + + public FileDirFragment(File directory) { + dataSet = new FileDirData[directory.listFiles().length]; + this.directory = directory; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + context = view.getContext(); + + RecyclerView files = (RecyclerView) view.findViewById(R.id.files); + + filesInDir(directory); + + filesAdapter = new FileDirAdapter(dataSet, new View.OnClickListener() { + @Override + public void onClick(View view) { + File file = filesAdapter.getData(files.getChildLayoutPosition(view)).getFile(); + if (file.isDirectory()) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fileDirFragment, new FileDirFragment()); + transaction.addToBackStack(null); + transaction.commit(); + } else if (!multipleMode) { + View selectedFileDirs = findViewById(R.id.selectedFileDirs); + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) selectedFileDirs.getLayoutParams(); + params.height = 0; + selectedFileDirs.setLayoutParams(params); + } + } + + }, new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + FileDirData data = filesAdapter.getData(files.getChildLayoutPosition(view)); + if (multipleMode) { + selectedDataSet.add(data); + selectedFileDirsAdapter.notifyDataSetChanged(); + return true; + } else if (data.getFile().isFile()) { + setResult(Activity.RESULT_OK, intent); + finish(); + return true; + } + return false; + } + }); + + files = view.findViewById(R.id.files); + files.setAdapter(filesAdapter); + files.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + + selectedFileDirs = new FileDirChooser().findViewById(R.id.selectedFileDirs); + } + + private void filesInDir(File directory) { + final File[] files = directory.listFiles(); + + System.out.println(Arrays.toString(files)); + + if (files != null) { + Arrays.sort(files, new Comparator() { + @Override + public int compare(File object1, File object2) { + return object1.getName().compareToIgnoreCase(object2.getName()); + } + }); + + final int cores; + if (files.length == 0) { + return; + } else if (files.length < Runtime.getRuntime().availableProcessors()) { + cores = files.length; + } else { + cores = (int) Math.ceil(files.length / Runtime.getRuntime().availableProcessors()); + } + final AtomicInteger itemsPerThread = new AtomicInteger((int) Math.ceil(files.length / cores)); + for (int core = 0; core < cores; core++) { + final int finalCore = core; + Thread thread = new Thread() { + @Override + public void run() { + File file; + for (int i = itemsPerThread.get() * finalCore; i < i + itemsPerThread.get(); i++) { + try { + file = files[i]; + } catch (ArrayIndexOutOfBoundsException e) { + return; + } + if (file.isDirectory()) { + dataSet[i] = new FileDirData(file, getResources().getDrawable(R.drawable.normal_folder)); + continue; + } else { + try { + /*FileInputStream fis = new FileInputStream(file); + Bitmap imageBitmap = BitmapFactory.decodeStream(fis); + + Float width = new Float(imageBitmap.getWidth()); + Float height = new Float(imageBitmap.getHeight()); + Float ratio = width/height; + imageBitmap = Bitmap.createScaledBitmap(imageBitmap, (int)(64 * ratio), 64, false); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); + byte[] imageData = baos.toByteArray(); + BitmapFactory.decodeByteArray(imageData, 0, imageData.length); + dataSet[i] = new FileDirData(file, new BitmapDrawable(BitmapFactory.decodeByteArray(imageData, 0, imageData.length)));*/ + dataSet[i] = new FileDirData(file, context.getResources().getDrawable(R.drawable.image_file)); + + } catch (Exception e) { + dataSet[i] = new FileDirData(file, getResources().getDrawable(R.drawable.normal_file)); + } + } + } + } + }; + thread.start(); + } + } + } + +} diff --git a/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/SelectedFileDirsAdapter.java b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/SelectedFileDirsAdapter.java new file mode 100755 index 0000000..fbc3019 --- /dev/null +++ b/app/src/main/java/org/blueshard/android/cryptogx/filedirchooser/SelectedFileDirsAdapter.java @@ -0,0 +1,72 @@ +package org.blueshard.android.cryptogx.filedirchooser; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.blueshard.android.cryptogx.R; + +import java.util.ArrayList; + +public class SelectedFileDirsAdapter extends RecyclerView.Adapter{ + + private ArrayList dataSet; + private View.OnClickListener onClickListener; + private View.OnLongClickListener onLongClickListener; + + public SelectedFileDirsAdapter(ArrayList dataSet, View.OnClickListener onClickListener, View.OnLongClickListener onLongClickListener) { + this.dataSet = dataSet; + this.onClickListener = onClickListener; + this.onLongClickListener = onLongClickListener; + } + + public class SelectedFileDirsHolder extends RecyclerView.ViewHolder { + public final TextView textView; + public final ImageView imageView; + + public SelectedFileDirsHolder(View view) { + super(view); + this.textView = view.findViewById(R.id.fileImage); + this.imageView = view.findViewById(R.id.fileName); + } + } + + @NonNull + @Override + public SelectedFileDirsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View selectedItem = LayoutInflater.from(parent.getContext()).inflate(R.layout.selected_file_dirs_item, parent, false); + + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) selectedItem.getLayoutParams(); + params.width = 120; + selectedItem.setLayoutParams(params); + + selectedItem.setOnClickListener(onClickListener); + selectedItem.setOnLongClickListener(onLongClickListener); + + return new SelectedFileDirsHolder(selectedItem); + } + + @Override + public void onBindViewHolder(@NonNull SelectedFileDirsHolder holder, int position) { + FileDirData data = dataSet.get(position); + if (data != null) { + holder.imageView.setImageDrawable(data.getImage()); + holder.textView.setText(data.getFile().getName()); + } + } + + @Override + public int getItemCount() { + return dataSet.size(); + } + + public FileDirData getData(int index) { + return dataSet.get(index); + } + +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100755 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/audio_file.png b/app/src/main/res/drawable/audio_file.png new file mode 100755 index 0000000..d6d475a Binary files /dev/null and b/app/src/main/res/drawable/audio_file.png differ diff --git a/app/src/main/res/drawable/cancel_button_style.xml b/app/src/main/res/drawable/cancel_button_style.xml new file mode 100755 index 0000000..19e9dc6 --- /dev/null +++ b/app/src/main/res/drawable/cancel_button_style.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/choose_files_style.xml b/app/src/main/res/drawable/choose_files_style.xml new file mode 100755 index 0000000..a1197e3 --- /dev/null +++ b/app/src/main/res/drawable/choose_files_style.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/decrypt_border_style.xml b/app/src/main/res/drawable/decrypt_border_style.xml new file mode 100755 index 0000000..d85782d --- /dev/null +++ b/app/src/main/res/drawable/decrypt_border_style.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/encrypt_border_style.xml b/app/src/main/res/drawable/encrypt_border_style.xml new file mode 100755 index 0000000..294f94f --- /dev/null +++ b/app/src/main/res/drawable/encrypt_border_style.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/file_box_style.xml b/app/src/main/res/drawable/file_box_style.xml new file mode 100755 index 0000000..e67639f --- /dev/null +++ b/app/src/main/res/drawable/file_box_style.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100755 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/image_file.png b/app/src/main/res/drawable/image_file.png new file mode 100755 index 0000000..0105051 Binary files /dev/null and b/app/src/main/res/drawable/image_file.png differ diff --git a/app/src/main/res/drawable/normal_file.png b/app/src/main/res/drawable/normal_file.png new file mode 100755 index 0000000..56c1d10 Binary files /dev/null and b/app/src/main/res/drawable/normal_file.png differ diff --git a/app/src/main/res/drawable/normal_folder.png b/app/src/main/res/drawable/normal_folder.png new file mode 100755 index 0000000..df43175 Binary files /dev/null and b/app/src/main/res/drawable/normal_folder.png differ diff --git a/app/src/main/res/drawable/round_button_style.xml b/app/src/main/res/drawable/round_button_style.xml new file mode 100755 index 0000000..8897fb2 --- /dev/null +++ b/app/src/main/res/drawable/round_button_style.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/spinner_drop.png b/app/src/main/res/drawable/spinner_drop.png new file mode 100755 index 0000000..9a889ea Binary files /dev/null and b/app/src/main/res/drawable/spinner_drop.png differ diff --git a/app/src/main/res/drawable/spinner_style.xml b/app/src/main/res/drawable/spinner_style.xml new file mode 100755 index 0000000..016fe1f --- /dev/null +++ b/app/src/main/res/drawable/spinner_style.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/video_file.png b/app/src/main/res/drawable/video_file.png new file mode 100755 index 0000000..1cbc94d Binary files /dev/null and b/app/src/main/res/drawable/video_file.png differ diff --git a/app/src/main/res/layout/activity_file_dir_chooser.xml b/app/src/main/res/layout/activity_file_dir_chooser.xml new file mode 100755 index 0000000..b50c85e --- /dev/null +++ b/app/src/main/res/layout/activity_file_dir_chooser.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100755 index 0000000..a1ac3fe --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/file_dir_chooser.xml b/app/src/main/res/layout/file_dir_chooser.xml new file mode 100755 index 0000000..93a3522 --- /dev/null +++ b/app/src/main/res/layout/file_dir_chooser.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/file_dir_item.xml b/app/src/main/res/layout/file_dir_item.xml new file mode 100755 index 0000000..fb95ced --- /dev/null +++ b/app/src/main/res/layout/file_dir_item.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/file_en_decrypt.xml b/app/src/main/res/layout/file_en_decrypt.xml new file mode 100755 index 0000000..7741899 --- /dev/null +++ b/app/src/main/res/layout/file_en_decrypt.xml @@ -0,0 +1,226 @@ + + + + + + + + + + + + +