From de7cb84b4c3de274dcf069080308268a6986999c Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Sat, 3 Aug 2019 12:54:51 +0430 Subject: [PATCH 1/9] upgrade libraries --- app/build.gradle | 21 ++++++++++------ .../com/snatik/storage/app/FilesAdapter.java | 2 +- .../storage/app/GenericFileProvider.java | 2 +- .../java/com/snatik/storage/app/Helper.java | 2 +- .../com/snatik/storage/app/MainActivity.java | 25 ++++++++++--------- .../snatik/storage/app/ViewTextActivity.java | 8 +++--- .../storage/app/dialogs/AddItemsDialog.java | 2 +- .../storage/app/dialogs/UpdateItemDialog.java | 2 +- app/src/main/res/layout/activity_main.xml | 12 ++++----- .../res/layout/activity_view_text_file.xml | 6 ++--- app/src/main/res/layout/content_main.xml | 6 ++--- build.gradle | 6 ++++- gradle.properties | 4 ++- gradle/wrapper/gradle-wrapper.properties | 2 +- storage/build.gradle | 22 ++++++++++------ 15 files changed, 72 insertions(+), 50 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d317cb3..b08ac30 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,15 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 25 - buildToolsVersion '25.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.1' defaultConfig { applicationId "com.snatik.storage.sample" minSdkVersion 14 - targetSdkVersion 25 + targetSdkVersion 29 versionCode 1 versionName "1.0" } @@ -20,8 +22,13 @@ android { } dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - compile project(':storage') - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.android.support:design:25.3.1' + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation project(':storage') + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android.material:material:1.0.0' + implementation "androidx.core:core-ktx:1.0.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } diff --git a/app/src/main/java/com/snatik/storage/app/FilesAdapter.java b/app/src/main/java/com/snatik/storage/app/FilesAdapter.java index 3fee6ea..9facd2b 100644 --- a/app/src/main/java/com/snatik/storage/app/FilesAdapter.java +++ b/app/src/main/java/com/snatik/storage/app/FilesAdapter.java @@ -1,7 +1,7 @@ package com.snatik.storage.app; import android.content.Context; -import android.support.v7.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/com/snatik/storage/app/GenericFileProvider.java b/app/src/main/java/com/snatik/storage/app/GenericFileProvider.java index cb0f615..af888f5 100644 --- a/app/src/main/java/com/snatik/storage/app/GenericFileProvider.java +++ b/app/src/main/java/com/snatik/storage/app/GenericFileProvider.java @@ -1,6 +1,6 @@ package com.snatik.storage.app; -import android.support.v4.content.FileProvider; +import androidx.core.content.FileProvider; /** * Created by sromku on July, 2017. diff --git a/app/src/main/java/com/snatik/storage/app/Helper.java b/app/src/main/java/com/snatik/storage/app/Helper.java index c3dc7ea..a13f568 100644 --- a/app/src/main/java/com/snatik/storage/app/Helper.java +++ b/app/src/main/java/com/snatik/storage/app/Helper.java @@ -1,6 +1,6 @@ package com.snatik.storage.app; -import android.support.design.widget.Snackbar; +import com.google.android.material.snackbar.Snackbar; import android.view.View; /** diff --git a/app/src/main/java/com/snatik/storage/app/MainActivity.java b/app/src/main/java/com/snatik/storage/app/MainActivity.java index 08c1e57..3536b40 100644 --- a/app/src/main/java/com/snatik/storage/app/MainActivity.java +++ b/app/src/main/java/com/snatik/storage/app/MainActivity.java @@ -6,14 +6,15 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v4.content.FileProvider; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.Toolbar; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; @@ -69,13 +70,13 @@ protected void onCreate(Bundle savedInstanceState) { mStorage = new Storage(getApplicationContext()); setContentView(R.layout.activity_main); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - mRecyclerView = (RecyclerView) findViewById(R.id.recycler); - mPathView = (TextView) findViewById(R.id.path); + mRecyclerView = findViewById(R.id.recycler); + mPathView = findViewById(R.id.path); mMovingLayout = findViewById(R.id.moving_layout); - mMovingText = (TextView) mMovingLayout.findViewById(R.id.moving_file_name); + mMovingText = mMovingLayout.findViewById(R.id.moving_file_name); mMovingLayout.findViewById(R.id.accept_move).setOnClickListener(new View.OnClickListener() { @Override diff --git a/app/src/main/java/com/snatik/storage/app/ViewTextActivity.java b/app/src/main/java/com/snatik/storage/app/ViewTextActivity.java index 90321ee..a8583d8 100644 --- a/app/src/main/java/com/snatik/storage/app/ViewTextActivity.java +++ b/app/src/main/java/com/snatik/storage/app/ViewTextActivity.java @@ -1,10 +1,10 @@ package com.snatik.storage.app; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.graphics.drawable.DrawerArrowDrawable; -import android.support.v7.widget.Toolbar; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.graphics.drawable.DrawerArrowDrawable; +import androidx.appcompat.widget.Toolbar; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java index 36accc9..77b7e27 100644 --- a/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java +++ b/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java @@ -5,7 +5,7 @@ import android.app.DialogFragment; import android.content.DialogInterface; import android.os.Bundle; -import android.support.design.widget.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java index 0cee461..dffc702 100644 --- a/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java +++ b/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java @@ -5,7 +5,7 @@ import android.app.DialogFragment; import android.content.DialogInterface; import android.os.Bundle; -import android.support.design.widget.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ddc2568..31691aa 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,28 +1,28 @@ - - - - + - - + diff --git a/app/src/main/res/layout/activity_view_text_file.xml b/app/src/main/res/layout/activity_view_text_file.xml index 4c90e7f..9ff799d 100644 --- a/app/src/main/res/layout/activity_view_text_file.xml +++ b/app/src/main/res/layout/activity_view_text_file.xml @@ -5,19 +5,19 @@ android:layout_height="match_parent" android:orientation="vertical"> - - - + - - @@ -87,4 +87,4 @@ - + diff --git a/build.gradle b/build.gradle index d0aa704..d4bdfb6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.41' repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' + classpath 'com.android.tools.build:gradle:3.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -14,6 +17,7 @@ buildscript { allprojects { repositories { + google() jcenter() } } diff --git a/gradle.properties b/gradle.properties index 1d3591c..915f0e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,4 +15,6 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true \ No newline at end of file +# org.gradle.parallel=true +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index da47e3b..faa4f76 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/storage/build.gradle b/storage/build.gradle index 576f64b..1b90858 100644 --- a/storage/build.gradle +++ b/storage/build.gradle @@ -1,12 +1,14 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 25 - buildToolsVersion '25.0.3' + compileSdkVersion 29 + buildToolsVersion '29.0.1' defaultConfig { minSdkVersion 14 - targetSdkVersion 25 + targetSdkVersion 29 versionCode 1 versionName "1.0" } @@ -47,11 +49,12 @@ apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1. buildscript { repositories { + google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.2' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' + classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' } } @@ -59,6 +62,11 @@ buildscript { tasks.withType(Javadoc).all { enabled = false } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation 'junit:junit:4.12' + implementation "androidx.core:core-ktx:1.0.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } \ No newline at end of file From f364c6b61103a9a6f8c49f7b30a93ce8cc697b8c Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Sun, 4 Aug 2019 11:50:06 +0430 Subject: [PATCH 2/9] change codes to kotlin --- .../snatik/storage/EncryptConfiguration.java | 229 --------- .../snatik/storage/EncryptConfiguration.kt | 223 +++++++++ .../storage/{Storable.java => Storable.kt} | 6 +- .../main/java/com/snatik/storage/Storage.java | 438 ------------------ .../main/java/com/snatik/storage/Storage.kt | 404 ++++++++++++++++ .../snatik/storage/helpers/ImmutablePair.java | 41 -- .../snatik/storage/helpers/ImmutablePair.kt | 42 ++ .../com/snatik/storage/helpers/OrderType.java | 46 -- .../com/snatik/storage/helpers/OrderType.kt | 32 ++ .../com/snatik/storage/helpers/SizeUnit.java | 35 -- .../com/snatik/storage/helpers/SizeUnit.kt | 28 ++ .../storage/security/CipherAlgorithmType.java | 57 --- .../storage/security/CipherAlgorithmType.kt | 49 ++ .../storage/security/CipherModeType.java | 33 -- .../snatik/storage/security/CipherModeType.kt | 24 + .../storage/security/CipherPaddingType.java | 27 -- .../storage/security/CipherPaddingType.kt | 18 + .../security/CipherTransformationType.java | 53 --- .../security/CipherTransformationType.kt | 52 +++ .../snatik/storage/security/SecurityUtil.java | 107 ----- .../snatik/storage/security/SecurityUtil.kt | 113 +++++ 21 files changed, 988 insertions(+), 1069 deletions(-) delete mode 100644 storage/src/main/java/com/snatik/storage/EncryptConfiguration.java create mode 100644 storage/src/main/java/com/snatik/storage/EncryptConfiguration.kt rename storage/src/main/java/com/snatik/storage/{Storable.java => Storable.kt} (51%) delete mode 100644 storage/src/main/java/com/snatik/storage/Storage.java create mode 100644 storage/src/main/java/com/snatik/storage/Storage.kt delete mode 100644 storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.java create mode 100644 storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt delete mode 100644 storage/src/main/java/com/snatik/storage/helpers/OrderType.java create mode 100644 storage/src/main/java/com/snatik/storage/helpers/OrderType.kt delete mode 100644 storage/src/main/java/com/snatik/storage/helpers/SizeUnit.java create mode 100644 storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt delete mode 100644 storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.java create mode 100644 storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt delete mode 100644 storage/src/main/java/com/snatik/storage/security/CipherModeType.java create mode 100644 storage/src/main/java/com/snatik/storage/security/CipherModeType.kt delete mode 100644 storage/src/main/java/com/snatik/storage/security/CipherPaddingType.java create mode 100644 storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt delete mode 100644 storage/src/main/java/com/snatik/storage/security/CipherTransformationType.java create mode 100644 storage/src/main/java/com/snatik/storage/security/CipherTransformationType.kt delete mode 100644 storage/src/main/java/com/snatik/storage/security/SecurityUtil.java create mode 100644 storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt diff --git a/storage/src/main/java/com/snatik/storage/EncryptConfiguration.java b/storage/src/main/java/com/snatik/storage/EncryptConfiguration.java deleted file mode 100644 index 0cdc61d..0000000 --- a/storage/src/main/java/com/snatik/storage/EncryptConfiguration.java +++ /dev/null @@ -1,229 +0,0 @@ -package com.snatik.storage; - -import android.os.Build; -import android.util.Log; - -import java.io.FileInputStream; -import java.io.UnsupportedEncodingException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; - -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; - -/** - * Each of the specific storage types (like External storage tool) need its own - * configurations. This configuration class is build and used in the storage - * classes.
- *
- *

- * Examples:
- *
- *

- * Default unsecured configuration:
- *

- *

- * {@code
- *  SimpleStorageConfiguration configuration = new SimpleStorageConfiguration.Builder()
- *  	.build()
- * }
- * 
- *

- * Secured configuration: - *

- *

- * {
- * 	@code
- * 	final int CHUNK_SIZE = 16 * 1024;
- * 	final String IVX = "1234567890123456";
- * 	final String SECRET_KEY = "secret1234567890";
- *
- * 	SimpleStorageConfiguration configuration = new SimpleStorageConfiguration.Builder().setChuckSize(CHUNK_SIZE)
- * 	.setEncryptContent(IVX, SECRET_KEY).build();
- * }
- * 
- * - * @author Roman Kushnarenko - sromku (sromku@gmail.com) - */ -public class EncryptConfiguration { - - private final static String TAG = "EncryptConfiguration"; - - /** - * The best chunk size: http://stackoverflow.com/a/237495/334522 - */ - private int mChunkSize; - private boolean mIsEncrypted; - private byte[] mIvParameter; - private byte[] mSecretKey; - - private EncryptConfiguration(Builder builder) { - mChunkSize = builder._chunkSize; - mIsEncrypted = builder._isEncrypted; - mIvParameter = builder._ivParameter; - mSecretKey = builder._secretKey; - } - - /** - * Get chunk size. The chuck size is used while reading the file by chunks - * {@link FileInputStream#read(byte[], int, int)}. - * - * @return The chunk size - */ - public int getChuckSize() { - return mChunkSize; - } - - /** - * Encrypt the file content.
- * - * @see Block - * cipher mode of operation - */ - public boolean isEncrypted() { - return mIsEncrypted; - } - - /** - * Get secret key - * - * @return - */ - public byte[] getSecretKey() { - return mSecretKey; - } - - /** - * Get iv parameter - * - * @return - */ - public byte[] getIvParameter() { - return mIvParameter; - } - - /** - * Configuration Builder class.
- * Following Builder design pattern. - * - * @author sromku - */ - public static class Builder { - private int _chunkSize = 8192; - private boolean _isEncrypted = false; - private byte[] _ivParameter = null; - private byte[] _secretKey = null; - - private static final String UTF_8 = "UTF-8"; - - public Builder() { - } - - /** - * Build the configuration for storage. - * - * @return - */ - public EncryptConfiguration build() { - return new EncryptConfiguration(this); - } - - /** - * Set chunk size. The chuck size is used while reading the file by - * chunks {@link FileInputStream#read(byte[], int, int)}. The preferable - * value is 1024xN bits. While N is power of 2 (like 1,2,4,8,16,...)
- *
- *

- * The default: 8 * 1024 = 8192 bits - * - * @param chunkSize The chunk size in bits - * @return The {@link Builder} - */ - public Builder setChuckSize(int chunkSize) { - _chunkSize = chunkSize; - return this; - } - - /** - * Encrypt and descrypt the file content while writing and reading - * to/from disc.
- * - * @param ivx This is not have to be secret. It used just for better - * randomizing the cipher. You have to use the same IV - * parameter within the same encrypted and written files. - * Means, if you want to have the same content after - * descryption then the same IV must be used.
- *
- *

- * Important: The length must be 16 long
- *

- * About this parameter from wiki: - * https://en.wikipedia.org - * /wiki/Block_cipher_modes_of_operation - * #Initialization_vector_.28IV.29
- *
- * @param secretKey Set the secret key for encryption of file content.
- *
- *

- * Important: The length must be 16 long
- *

- * Uses SHA-256 to generate a hash from your key and trim - * the result to 128 bit (16 bytes)
- *
- * @see Block - * cipher mode of operation - */ - public Builder setEncryptContent(String ivx, String secretKey, byte[] salt) { - _isEncrypted = true; - - // Set IV parameter - try { - _ivParameter = ivx.getBytes(UTF_8); - } catch (UnsupportedEncodingException e) { - Log.e(TAG, "UnsupportedEncodingException", e); - } - - // Set secret key - try { - /* - * We generate random salt and then use 1000 iterations to - * initialize secret key factory which in-turn generates key. - */ - int iterationCount = 1000; // recommended by PKCS#5 - int keyLength = 128; - - KeySpec keySpec = new PBEKeySpec(secretKey.toCharArray(), salt, iterationCount, keyLength); - SecretKeyFactory keyFactory = null; - if (Build.VERSION.SDK_INT >= 19) { - // see: - // http://android-developers.blogspot.co.il/2013/12/changes-to-secretkeyfactory-api-in.html - // Use compatibility key factory -- only uses lower 8-bits - // of passphrase chars - keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit"); - } else { - // Traditional key factory. Will use lower 8-bits of - // passphrase chars on - // older Android versions (API level 18 and lower) and all - // available bits - // on KitKat and newer (API level 19 and higher). - keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); - } - byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded(); - - _secretKey = keyBytes; - - } catch (InvalidKeySpecException e) { - Log.e(TAG, "InvalidKeySpecException", e); - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "NoSuchAlgorithmException", e); - } - - return this; - } - - } - -} diff --git a/storage/src/main/java/com/snatik/storage/EncryptConfiguration.kt b/storage/src/main/java/com/snatik/storage/EncryptConfiguration.kt new file mode 100644 index 0000000..00e01e8 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/EncryptConfiguration.kt @@ -0,0 +1,223 @@ +package com.snatik.storage + +import android.os.Build +import android.util.Log + +import java.io.FileInputStream +import java.io.UnsupportedEncodingException +import java.security.NoSuchAlgorithmException +import java.security.spec.InvalidKeySpecException +import java.security.spec.KeySpec + +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.PBEKeySpec + +/** + * Each of the specific storage types (like External storage tool) need its own + * configurations. This configuration class is build and used in the storage + * classes.

+ *

+ * + * + * **Examples:**

+ *

+ * + * + * Default unsecured configuration:

+ * + * + *

+ * `SimpleStorageConfiguration configuration = new SimpleStorageConfiguration.Builder()
+ * .build()
+` *
+
* + * + * + * Secured configuration: + * + * + *
+ * {
+ * @code
+ * final int CHUNK_SIZE = 16 * 1024;
+ * final String IVX = "1234567890123456";
+ * final String SECRET_KEY = "secret1234567890";
+ *
+ * SimpleStorageConfiguration configuration = new SimpleStorageConfiguration.Builder().setChuckSize(CHUNK_SIZE)
+ * .setEncryptContent(IVX, SECRET_KEY).build();
+ * }
+
* + * + * @author Roman Kushnarenko - sromku (sromku@gmail.com) + */ +class EncryptConfiguration private constructor(builder: Builder) { + + /** + * The best chunk size: *http://stackoverflow.com/a/237495/334522* + */ + /** + * Get chunk size. The chuck size is used while reading the file by chunks + * [FileInputStream.read]. + * + * @return The chunk size + */ + val chuckSize: Int + /** + * Encrypt the file content.

+ * + * @see [Block + * cipher mode of operation](https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation) + */ + val isEncrypted: Boolean + /** + * Get iv parameter + * + * @return + */ + val ivParameter: ByteArray? + /** + * Get secret key + * + * @return + */ + val secretKey: ByteArray? + + init { + chuckSize = builder._chunkSize + isEncrypted = builder._isEncrypted + ivParameter = builder._ivParameter + secretKey = builder._secretKey + } + + /** + * Configuration Builder class.

+ * Following Builder design pattern. + * + * @author sromku + */ + class Builder { + var _chunkSize = 8192 + var _isEncrypted = false + var _ivParameter: ByteArray? = null + var _secretKey: ByteArray? = null + + /** + * Build the configuration for storage. + * + * @return + */ + fun build(): EncryptConfiguration { + return EncryptConfiguration(this) + } + + /** + * Set chunk size. The chuck size is used while reading the file by + * chunks [FileInputStream.read]. The preferable + * value is 1024xN bits. While N is power of 2 (like 1,2,4,8,16,...)

+ *

+ * + * + * The default: **8 * 1024** = 8192 bits + * + * @param chunkSize The chunk size in bits + * @return The [Builder] + */ + fun setChuckSize(chunkSize: Int): Builder { + _chunkSize = chunkSize + return this + } + + /** + * Encrypt and descrypt the file content while writing and reading + * to/from disc.

+ * + * @param ivx This is not have to be secret. It used just for better + * randomizing the cipher. You have to use the same IV + * parameter within the same encrypted and written files. + * Means, if you want to have the same content after + * descryption then the same IV must be used.

+ *

+ * + * + * **Important: The length must be 16 long**

+ * + * + * *About this parameter from wiki: + * https://en.wikipedia.org + * /wiki/Block_cipher_modes_of_operation + * #Initialization_vector_.28IV.29*

+ *

+ * @param secretKey Set the secret key for encryption of file content.

+ *

+ * + * + * **Important: The length must be 16 long**

+ * + * + * *Uses SHA-256 to generate a hash from your key and trim + * the result to 128 bit (16 bytes)*

+ *

+ * @see [Block + * cipher mode of operation](https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation) + */ + fun setEncryptContent(ivx: String, secretKey: String, salt: ByteArray): Builder { + _isEncrypted = true + + // Set IV parameter + try { + _ivParameter = ivx.toByteArray(charset(UTF_8)) + } catch (e: UnsupportedEncodingException) { + Log.e(TAG, "UnsupportedEncodingException", e) + } + + // Set secret key + try { + /* + * We generate random salt and then use 1000 iterations to + * initialize secret key factory which in-turn generates key. + */ + val iterationCount = 1000 // recommended by PKCS#5 + val keyLength = 128 + + val keySpec = PBEKeySpec(secretKey.toCharArray(), salt, iterationCount, keyLength) + var keyFactory: SecretKeyFactory? = null + if (Build.VERSION.SDK_INT >= 19) { + // see: + // http://android-developers.blogspot.co.il/2013/12/changes-to-secretkeyfactory-api-in.html + // Use compatibility key factory -- only uses lower 8-bits + // of passphrase chars + keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit") + } else { + // Traditional key factory. Will use lower 8-bits of + // passphrase chars on + // older Android versions (API level 18 and lower) and all + // available bits + // on KitKat and newer (API level 19 and higher). + keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") + } + val keyBytes = keyFactory!!.generateSecret(keySpec).encoded + + _secretKey = keyBytes + + } catch (e: InvalidKeySpecException) { + Log.e(TAG, "InvalidKeySpecException", e) + } catch (e: NoSuchAlgorithmException) { + Log.e(TAG, "NoSuchAlgorithmException", e) + } + + return this + } + + companion object { + + private val UTF_8 = "UTF-8" + } + + } + + companion object { + + private val TAG = "EncryptConfiguration" + } + +} diff --git a/storage/src/main/java/com/snatik/storage/Storable.java b/storage/src/main/java/com/snatik/storage/Storable.kt similarity index 51% rename from storage/src/main/java/com/snatik/storage/Storable.java rename to storage/src/main/java/com/snatik/storage/Storable.kt index 2c3782b..a19e35c 100644 --- a/storage/src/main/java/com/snatik/storage/Storable.java +++ b/storage/src/main/java/com/snatik/storage/Storable.kt @@ -1,8 +1,8 @@ -package com.snatik.storage; +package com.snatik.storage /** * As it sounds, anything that can stored and represented as byte array. */ -public interface Storable { - byte[] getBytes(); +interface Storable { + val bytes: ByteArray } diff --git a/storage/src/main/java/com/snatik/storage/Storage.java b/storage/src/main/java/com/snatik/storage/Storage.java deleted file mode 100644 index 1cfa91b..0000000 --- a/storage/src/main/java/com/snatik/storage/Storage.java +++ /dev/null @@ -1,438 +0,0 @@ -package com.snatik.storage; - -import android.content.Context; -import android.graphics.Bitmap; -import android.os.Build; -import android.os.Environment; -import android.os.StatFs; -import android.util.Log; - -import com.snatik.storage.helpers.ImmutablePair; -import com.snatik.storage.helpers.SizeUnit; -import com.snatik.storage.security.SecurityUtil; - -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -import javax.crypto.Cipher; - -/** - * Common class for internal and external storage implementations - * - * @author Roman Kushnarenko - sromku (sromku@gmail.com) - */ -public class Storage { - - private static final String TAG = "Storage"; - - private final Context mContext; - private EncryptConfiguration mConfiguration; - - public Storage(Context context) { - mContext = context; - } - - public void setEncryptConfiguration(EncryptConfiguration configuration) { - mConfiguration = configuration; - } - - public String getExternalStorageDirectory() { - return Environment.getExternalStorageDirectory().getAbsolutePath(); - } - - public String getExternalStorageDirectory(String publicDirectory) { - return Environment.getExternalStoragePublicDirectory(publicDirectory).getAbsolutePath(); - } - - public String getInternalRootDirectory() { - return Environment.getRootDirectory().getAbsolutePath(); - } - - public String getInternalFilesDirectory() { - return mContext.getFilesDir().getAbsolutePath(); - } - - public String getInternalCacheDirectory() { - return mContext.getCacheDir().getAbsolutePath(); - } - - public static boolean isExternalWritable() { - String state = Environment.getExternalStorageState(); - if (Environment.MEDIA_MOUNTED.equals(state)) { - return true; - } - return false; - } - - public boolean createDirectory(String path) { - File directory = new File(path); - if (directory.exists()) { - Log.w(TAG, "Directory '" + path + "' already exists"); - return false; - } - return directory.mkdirs(); - } - - public boolean createDirectory(String path, boolean override) { - - // Check if directory exists. If yes, then delete all directory - if (override && isDirectoryExists(path)) { - deleteDirectory(path); - } - - // Create new directory - return createDirectory(path); - } - - public boolean deleteDirectory(String path) { - return deleteDirectoryImpl(path); - } - - public boolean isDirectoryExists(String path) { - return new File(path).exists(); - } - - public boolean createFile(String path, String content) { - return createFile(path, content.getBytes()); - } - - public boolean createFile(String path, Storable storable) { - return createFile(path, storable.getBytes()); - } - - public boolean createFile(String path, byte[] content) { - try { - OutputStream stream = new FileOutputStream(new File(path)); - - // encrypt if needed - if (mConfiguration != null && mConfiguration.isEncrypted()) { - content = encrypt(content, Cipher.ENCRYPT_MODE); - } - - stream.write(content); - stream.flush(); - stream.close(); - } catch (IOException e) { - Log.e(TAG, "Failed create file", e); - return false; - } - return true; - } - - public boolean createFile(String path, Bitmap bitmap) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - byte[] byteArray = stream.toByteArray(); - return createFile(path, byteArray); - } - - public boolean deleteFile(String path) { - File file = new File(path); - return file.delete(); - } - - public boolean isFileExist(String path) { - return new File(path).exists(); - } - - public byte[] readFile(String path) { - final FileInputStream stream; - try { - stream = new FileInputStream(new File(path)); - return readFile(stream); - } catch (FileNotFoundException e) { - Log.e(TAG, "Failed to read file to input stream", e); - return null; - } - } - - public String readTextFile(String path) { - byte[] bytes = readFile(path); - return new String(bytes); - } - - public void appendFile(String path, String content) { - appendFile(path, content.getBytes()); - } - - public void appendFile(String path, byte[] bytes) { - if (!isFileExist(path)) { - Log.w(TAG, "Impossible to append content, because such file doesn't exist"); - return; - } - - try { - FileOutputStream stream = new FileOutputStream(new File(path), true); - stream.write(bytes); - stream.write(System.getProperty("line.separator").getBytes()); - stream.flush(); - stream.close(); - } catch (IOException e) { - Log.e(TAG, "Failed to append content to file", e); - } - } - - public List getNestedFiles(String path) { - File file = new File(path); - List out = new ArrayList(); - getDirectoryFilesImpl(file, out); - return out; - } - - public List getFiles(String dir) { - return getFiles(dir, null); - } - - public List getFiles(String dir, final String matchRegex) { - File file = new File(dir); - File[] files = null; - if (matchRegex != null) { - FilenameFilter filter = new FilenameFilter() { - @Override - public boolean accept(File dir, String fileName) { - return fileName.matches(matchRegex); - } - }; - files = file.listFiles(filter); - } else { - files = file.listFiles(); - } - return files != null ? Arrays.asList(files) : null; - } - - public File getFile(String path) { - return new File(path); - } - - public boolean rename(String fromPath, String toPath) { - File file = getFile(fromPath); - File newFile = new File(toPath); - return file.renameTo(newFile); - } - - public double getSize(File file, SizeUnit unit) { - long length = file.length(); - return (double) length / (double) unit.inBytes(); - } - - public String getReadableSize(File file) { - long length = file.length(); - return SizeUnit.readableSizeUnit(length); - } - - public long getFreeSpace(String dir, SizeUnit sizeUnit) { - StatFs statFs = new StatFs(dir); - long availableBlocks; - long blockSize; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { - availableBlocks = statFs.getAvailableBlocks(); - blockSize = statFs.getBlockSize(); - } else { - availableBlocks = statFs.getAvailableBlocksLong(); - blockSize = statFs.getBlockSizeLong(); - } - long freeBytes = availableBlocks * blockSize; - return freeBytes / sizeUnit.inBytes(); - } - - public long getUsedSpace(String dir, SizeUnit sizeUnit) { - StatFs statFs = new StatFs(dir); - long availableBlocks; - long blockSize; - long totalBlocks; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { - availableBlocks = statFs.getAvailableBlocks(); - blockSize = statFs.getBlockSize(); - totalBlocks = statFs.getBlockCount(); - } else { - availableBlocks = statFs.getAvailableBlocksLong(); - blockSize = statFs.getBlockSizeLong(); - totalBlocks = statFs.getBlockCountLong(); - } - long usedBytes = totalBlocks * blockSize - availableBlocks * blockSize; - return usedBytes / sizeUnit.inBytes(); - } - - public boolean copy(String fromPath, String toPath) { - File file = getFile(fromPath); - if (!file.isFile()) { - return false; - } - - FileInputStream inStream = null; - FileOutputStream outStream = null; - try { - inStream = new FileInputStream(file); - outStream = new FileOutputStream(new File(toPath)); - FileChannel inChannel = inStream.getChannel(); - FileChannel outChannel = outStream.getChannel(); - inChannel.transferTo(0, inChannel.size(), outChannel); - } catch (Exception e) { - Log.e(TAG, "Failed copy", e); - return false; - } finally { - closeSilently(inStream); - closeSilently(outStream); - } - return true; - } - - public boolean move(String fromPath, String toPath) { - if (copy(fromPath, toPath)) { - return getFile(fromPath).delete(); - } - return false; - } - - protected byte[] readFile(final FileInputStream stream) { - class Reader extends Thread { - byte[] array = null; - } - - Reader reader = new Reader() { - public void run() { - LinkedList> chunks = new LinkedList>(); - - // read the file and build chunks - int size = 0; - int globalSize = 0; - do { - try { - int chunkSize = mConfiguration != null ? mConfiguration.getChuckSize() : 8192; - // read chunk - byte[] buffer = new byte[chunkSize]; - size = stream.read(buffer, 0, chunkSize); - if (size > 0) { - globalSize += size; - - // add chunk to list - chunks.add(new ImmutablePair(buffer, size)); - } - } catch (Exception e) { - // very bad - } - } while (size > 0); - - try { - stream.close(); - } catch (Exception e) { - // very bad - } - - array = new byte[globalSize]; - - // append all chunks to one array - int offset = 0; - for (ImmutablePair chunk : chunks) { - // flush chunk to array - System.arraycopy(chunk.element1, 0, array, offset, chunk.element2); - offset += chunk.element2; - } - } - - ; - }; - - reader.start(); - try { - reader.join(); - } catch (InterruptedException e) { - Log.e(TAG, "Failed on reading file from storage while the locking Thread", e); - return null; - } - - if (mConfiguration != null && mConfiguration.isEncrypted()) { - return encrypt(reader.array, Cipher.DECRYPT_MODE); - } else { - return reader.array; - } - } - - /** - * Encrypt or Descrypt the content.
- * - * @param content The content to encrypt or descrypt. - * @param encryptionMode Use: {@link Cipher#ENCRYPT_MODE} or - * {@link Cipher#DECRYPT_MODE} - * @return - */ - private synchronized byte[] encrypt(byte[] content, int encryptionMode) { - final byte[] secretKey = mConfiguration.getSecretKey(); - final byte[] ivx = mConfiguration.getIvParameter(); - return SecurityUtil.encrypt(content, encryptionMode, secretKey, ivx); - } - - /** - * Delete the directory and all sub content. - * - * @param path The absolute directory path. For example: - * mnt/sdcard/NewFolder/. - * @return True if the directory was deleted, otherwise return - * False - */ - private boolean deleteDirectoryImpl(String path) { - File directory = new File(path); - - // If the directory exists then delete - if (directory.exists()) { - File[] files = directory.listFiles(); - if (files == null) { - return true; - } - // Run on all sub files and folders and delete them - for (int i = 0; i < files.length; i++) { - if (files[i].isDirectory()) { - deleteDirectoryImpl(files[i].getAbsolutePath()); - } else { - files[i].delete(); - } - } - } - return directory.delete(); - } - - /** - * Get all files under the directory - * - * @param directory - * @param out - * @return - */ - private void getDirectoryFilesImpl(File directory, List out) { - if (directory.exists()) { - File[] files = directory.listFiles(); - if (files == null) { - return; - } else { - for (int i = 0; i < files.length; i++) { - if (files[i].isDirectory()) { - getDirectoryFilesImpl(files[i], out); - } else { - out.add(files[i]); - } - } - } - } - } - - private void closeSilently(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException e) { - } - } - } -} diff --git a/storage/src/main/java/com/snatik/storage/Storage.kt b/storage/src/main/java/com/snatik/storage/Storage.kt new file mode 100644 index 0000000..0ea2e93 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/Storage.kt @@ -0,0 +1,404 @@ +package com.snatik.storage + +import android.content.Context +import android.graphics.Bitmap +import android.os.Build +import android.os.Environment +import android.os.StatFs +import android.util.Log + +import com.snatik.storage.helpers.ImmutablePair +import com.snatik.storage.helpers.SizeUnit +import com.snatik.storage.security.SecurityUtil + +import java.io.ByteArrayOutputStream +import java.io.Closeable +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.FilenameFilter +import java.io.IOException +import java.io.OutputStream +import java.nio.channels.FileChannel +import java.util.ArrayList +import java.util.Arrays +import java.util.LinkedList + +import javax.crypto.Cipher + +/** + * Common class for internal and external storage implementations + * + * @author Roman Kushnarenko - sromku (sromku@gmail.com) + */ + +class Storage(private val mContext: Context) { + private var mConfiguration: EncryptConfiguration? = null + + val externalStorageDirectory: String + get() = Environment.getExternalStorageDirectory().absolutePath + + val internalRootDirectory: String + get() = Environment.getRootDirectory().absolutePath + + val internalFilesDirectory: String + get() = mContext.filesDir.absolutePath + + val internalCacheDirectory: String + get() = mContext.cacheDir.absolutePath + + fun setEncryptConfiguration(configuration: EncryptConfiguration) { + mConfiguration = configuration + } + + fun getExternalStorageDirectory(publicDirectory: String): String { + return Environment.getExternalStoragePublicDirectory(publicDirectory).absolutePath + } + + fun createDirectory(path: String): Boolean { + val directory = File(path) + if (directory.exists()) { + Log.w(TAG, "Directory '$path' already exists") + return false + } + return directory.mkdirs() + } + + fun createDirectory(path: String, override: Boolean): Boolean { + + // Check if directory exists. If yes, then delete all directory + if (override && isDirectoryExists(path)) { + deleteDirectory(path) + } + + // Create new directory + return createDirectory(path) + } + + fun deleteDirectory(path: String): Boolean { + return deleteDirectoryImpl(path) + } + + fun isDirectoryExists(path: String): Boolean { + return File(path).exists() + } + + fun createFile(path: String, content: String): Boolean { + return createFile(path, content.toByteArray()) + } + + fun createFile(path: String, storable: Storable): Boolean { + return createFile(path, storable.bytes) + } + + fun createFile(path: String, content: ByteArray?): Boolean { + var content = content + try { + val stream = FileOutputStream(File(path)) + + // encrypt if needed + if (mConfiguration != null && mConfiguration!!.isEncrypted) { + content = encrypt(content, Cipher.ENCRYPT_MODE) + } + + stream.write(content!!) + stream.flush() + stream.close() + } catch (e: IOException) { + Log.e(TAG, "Failed create file", e) + return false + } + + return true + } + + fun createFile(path: String, bitmap: Bitmap): Boolean { + val stream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream) + val byteArray = stream.toByteArray() + return createFile(path, byteArray) + } + + fun deleteFile(path: String): Boolean { + val file = File(path) + return file.delete() + } + + fun isFileExist(path: String): Boolean { + return File(path).exists() + } + + fun readFile(path: String): ByteArray? { + val stream: FileInputStream + try { + stream = FileInputStream(File(path)) + return readFile(stream) + } catch (e: FileNotFoundException) { + Log.e(TAG, "Failed to read file to input stream", e) + return null + } + + } + + fun readTextFile(path: String): String { + val bytes = readFile(path) + return String(bytes!!) + } + + fun appendFile(path: String, content: String) { + appendFile(path, content.toByteArray()) + } + + fun appendFile(path: String, bytes: ByteArray) { + if (!isFileExist(path)) { + Log.w(TAG, "Impossible to append content, because such file doesn't exist") + return + } + + try { + val stream = FileOutputStream(File(path), true) + stream.write(bytes) + val separator = System.getProperty("line.separator") + if (separator != null) { + stream.write(separator.toByteArray()) + } + stream.flush() + stream.close() + } catch (e: IOException) { + Log.e(TAG, "Failed to append content to file", e) + } + + } + + fun getNestedFiles(path: String): List { + val file = File(path) + val out = ArrayList() + getDirectoryFilesImpl(file, out) + return out + } + + @JvmOverloads + fun getFiles(dir: String, matchRegex: String? = null): List? { + val file = File(dir) + var files: Array? = null + files = if (matchRegex != null) { + val filter = FilenameFilter { _, fileName -> fileName.matches(matchRegex.toRegex()) } + file.listFiles(filter) + } else { + file.listFiles() + } + return if (files != null) Arrays.asList(*files) else null + } + + fun getFile(path: String): File { + return File(path) + } + + fun rename(fromPath: String, toPath: String): Boolean { + val file = getFile(fromPath) + val newFile = File(toPath) + return file.renameTo(newFile) + } + + fun getSize(file: File, unit: SizeUnit): Double { + val length = file.length() + return length.toDouble() / unit.inBytes().toDouble() + } + + fun getReadableSize(file: File): String { + val length = file.length() + return SizeUnit.readableSizeUnit(length) + } + + fun getFreeSpace(dir: String, sizeUnit: SizeUnit): Long { + val statFs = StatFs(dir) + val availableBlocks: Long + val blockSize: Long + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + availableBlocks = statFs.availableBlocks.toLong() + blockSize = statFs.blockSize.toLong() + } else { + availableBlocks = statFs.availableBlocksLong + blockSize = statFs.blockSizeLong + } + val freeBytes = availableBlocks * blockSize + return freeBytes / sizeUnit.inBytes() + } + + fun getUsedSpace(dir: String, sizeUnit: SizeUnit): Long { + val statFs = StatFs(dir) + val availableBlocks: Long + val blockSize: Long + val totalBlocks: Long + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { + availableBlocks = statFs.availableBlocks.toLong() + blockSize = statFs.blockSize.toLong() + totalBlocks = statFs.blockCount.toLong() + } else { + availableBlocks = statFs.availableBlocksLong + blockSize = statFs.blockSizeLong + totalBlocks = statFs.blockCountLong + } + val usedBytes = totalBlocks * blockSize - availableBlocks * blockSize + return usedBytes / sizeUnit.inBytes() + } + + fun copy(fromPath: String, toPath: String): Boolean { + val file = getFile(fromPath) + if (!file.isFile) { + return false + } + + var inStream: FileInputStream? = null + var outStream: FileOutputStream? = null + try { + inStream = FileInputStream(file) + outStream = FileOutputStream(File(toPath)) + val inChannel = inStream.channel + val outChannel = outStream.channel + inChannel.transferTo(0, inChannel.size(), outChannel) + } catch (e: Exception) { + Log.e(TAG, "Failed copy", e) + return false + } finally { + closeSilently(inStream) + closeSilently(outStream) + } + return true + } + + fun move(fromPath: String, toPath: String): Boolean { + return if (copy(fromPath, toPath)) { + getFile(fromPath).delete() + } else false + } + + protected fun readFile(stream: FileInputStream): ByteArray? { + open class Reader : Thread() { + var array: ByteArray? = null + } + + val reader = object : Reader() { + override fun run() { + val chunks = LinkedList>() + + // read the file and build chunks + var size = 0 + var globalSize = 0 + do { + try { + val chunkSize = if (mConfiguration != null) mConfiguration!!.chuckSize else 8192 + // read chunk + val buffer = ByteArray(chunkSize) + size = stream.read(buffer, 0, chunkSize) + if (size > 0) { + globalSize += size + + // add chunk to list + chunks.add(ImmutablePair(buffer, size)) + } + } catch (e: Exception) { + // very bad + } + + } while (size > 0) + + try { + stream.close() + } catch (e: Exception) { + // very bad + } + + array = ByteArray(globalSize) + + // append all chunks to one array + var offset = 0 + for (chunk in chunks) { + // flush chunk to array + System.arraycopy(chunk.element1, 0, array!!, offset, chunk.element2) + offset += chunk.element2 + } + } + } + + reader.start() + try { + reader.join() + } catch (e: InterruptedException) { + Log.e(TAG, "Failed on reading file from storage while the locking Thread", e) + return null + } + + return if (mConfiguration != null && mConfiguration!!.isEncrypted) { + encrypt(reader.array, Cipher.DECRYPT_MODE) + } else { + reader.array + } + } + + @Synchronized + private fun encrypt(content: ByteArray?, encryptionMode: Int): ByteArray? { + val secretKey = mConfiguration!!.secretKey + val ivx = mConfiguration!!.ivParameter + return SecurityUtil.encrypt(content, encryptionMode, secretKey, ivx) + } + + private fun deleteDirectoryImpl(path: String): Boolean { + val directory = File(path) + + // If the directory exists then delete + if (directory.exists()) { + val files = directory.listFiles() ?: return true +// Run on all sub files and folders and delete them + for (i in files.indices) { + if (files[i].isDirectory) { + deleteDirectoryImpl(files[i].absolutePath) + } else { + files[i].delete() + } + } + } + return directory.delete() + } + + private fun getDirectoryFilesImpl(directory: File, out: MutableList) { + if (directory.exists()) { + val files = directory.listFiles() + if (files == null) { + return + } else { + for (i in files.indices) { + if (files[i].isDirectory) { + getDirectoryFilesImpl(files[i], out) + } else { + out.add(files[i]) + } + } + } + } + } + + private fun closeSilently(closeable: Closeable?) { + if (closeable != null) { + try { + closeable.close() + } catch (e: IOException) { + } + + } + } + + companion object { + + private val TAG = "Storage" + + val isExternalWritable: Boolean + get() { + val state = Environment.getExternalStorageState() + return if (Environment.MEDIA_MOUNTED == state) { + true + } else false + } + } +} diff --git a/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.java b/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.java deleted file mode 100644 index 34b83ba..0000000 --- a/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.snatik.storage.helpers; - -import java.io.Serializable; - -/** - * @param - * @param - */ -public class ImmutablePair implements Serializable { - private static final long serialVersionUID = 40; - - public final T element1; - public final S element2; - - public ImmutablePair() { - element1 = null; - element2 = null; - } - - public ImmutablePair(T element1, S element2) { - this.element1 = element1; - this.element2 = element2; - } - - @Override - public boolean equals(Object object) { - if (object instanceof ImmutablePair == false) { - return false; - } - - Object object1 = ((ImmutablePair) object).element1; - Object object2 = ((ImmutablePair) object).element2; - - return element1.equals(object1) && element2.equals(object2); - } - - @Override - public int hashCode() { - return element1.hashCode() << 16 + element2.hashCode(); - } -} diff --git a/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt b/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt new file mode 100644 index 0000000..e2a8af7 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt @@ -0,0 +1,42 @@ +package com.snatik.storage.helpers + +import java.io.Serializable + +/** + * @param + * @param + */ +class ImmutablePair : Serializable { + + val element1: T? + val element2: S? + + constructor() { + element1 = null + element2 = null + } + + constructor(element1: T, element2: S) { + this.element1 = element1 + this.element2 = element2 + } + + override fun equals(`object`: Any?): Boolean { + if (`object` !is ImmutablePair<*, *>) { + return false + } + + val object1 = `object`.element1 + val object2 = `object`.element2 + + return element1 == object1 && element2 == object2 + } + + override fun hashCode(): Int { + return element1!!.hashCode() shl 16 + element2!!.hashCode() + } + + companion object { + private const val serialVersionUID: Long = 40 + } +} diff --git a/storage/src/main/java/com/snatik/storage/helpers/OrderType.java b/storage/src/main/java/com/snatik/storage/helpers/OrderType.java deleted file mode 100644 index f77dd8c..0000000 --- a/storage/src/main/java/com/snatik/storage/helpers/OrderType.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.snatik.storage.helpers; - -import java.io.File; -import java.util.Comparator; - -public enum OrderType { - - NAME, - /** - * Last modified is the first - */ - DATE, - /** - * Smaller size will be in the first place - */ - SIZE; - - public Comparator getComparator() { - switch (ordinal()) { - case 0: // name - return new Comparator() { - @Override - public int compare(File lhs, File rhs) { - return lhs.getName().compareTo(rhs.getName()); - } - }; - case 1: // date - return new Comparator() { - @Override - public int compare(File lhs, File rhs) { - return (int) (rhs.lastModified() - lhs.lastModified()); - } - }; - case 2: // size - return new Comparator() { - @Override - public int compare(File lhs, File rhs) { - return (int) (lhs.length() - rhs.length()); - } - }; - default: - break; - } - return null; - } -} diff --git a/storage/src/main/java/com/snatik/storage/helpers/OrderType.kt b/storage/src/main/java/com/snatik/storage/helpers/OrderType.kt new file mode 100644 index 0000000..7125aa8 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/helpers/OrderType.kt @@ -0,0 +1,32 @@ +package com.snatik.storage.helpers + +import java.io.File +import java.util.Comparator + +enum class OrderType { + + NAME, + /** + * Last modified is the first + */ + DATE, + /** + * Smaller size will be in the first place + */ + SIZE; + + // name + // date + // size + val comparator: Comparator? + get() { + when (ordinal) { + 0 -> return Comparator { lhs, rhs -> lhs.name.compareTo(rhs.name) } + 1 -> return Comparator { lhs, rhs -> (rhs.lastModified() - lhs.lastModified()).toInt() } + 2 -> return Comparator { lhs, rhs -> (lhs.length() - rhs.length()).toInt() } + else -> { + } + } + return null + } +} diff --git a/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.java b/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.java deleted file mode 100644 index c055402..0000000 --- a/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.snatik.storage.helpers; - -import java.text.DecimalFormat; - -public enum SizeUnit { - B(1), - KB(SizeUnit.BYTES), - MB(SizeUnit.BYTES * SizeUnit.BYTES), - GB(SizeUnit.BYTES * SizeUnit.BYTES * SizeUnit.BYTES), - TB(SizeUnit.BYTES * SizeUnit.BYTES * SizeUnit.BYTES * SizeUnit.BYTES); - - private long inBytes; - private static final int BYTES = 1024; - - private SizeUnit(long bytes) { - this.inBytes = bytes; - } - - public long inBytes() { - return inBytes; - } - - public static String readableSizeUnit(long bytes) { - DecimalFormat df = new DecimalFormat("0.00"); - if (bytes < KB.inBytes()) { - return df.format(bytes / (float) B.inBytes()) + " B"; - } else if (bytes < MB.inBytes()) { - return df.format(bytes / (float) KB.inBytes()) + " KB"; - } else if (bytes < GB.inBytes()) { - return df.format(bytes / (float) MB.inBytes()) + " MB"; - } else { - return df.format(bytes / (float) GB.inBytes()) + " GB"; - } - } -} diff --git a/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt b/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt new file mode 100644 index 0000000..c27a816 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt @@ -0,0 +1,28 @@ +package com.snatik.storage.helpers + +import java.text.DecimalFormat + +const val BYTES = 1024L +enum class SizeUnit private constructor(private val inBytes: Long) { + B(1), + KB(BYTES), + MB(BYTES * BYTES), + GB(BYTES * BYTES * BYTES), + TB(BYTES * BYTES * BYTES * BYTES); + + fun inBytes(): Long { + return inBytes + } + + companion object { + fun readableSizeUnit(bytes: Long): String { + val df = DecimalFormat("0.00") + return when { + bytes < KB.inBytes() -> df.format((bytes / B.inBytes().toFloat()).toDouble()) + " B" + bytes < MB.inBytes() -> df.format((bytes / KB.inBytes().toFloat()).toDouble()) + " KB" + bytes < GB.inBytes() -> df.format((bytes / MB.inBytes().toFloat()).toDouble()) + " MB" + else -> df.format((bytes / GB.inBytes().toFloat()).toDouble()) + " GB" + } + } + } +} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.java b/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.java deleted file mode 100644 index ff6ee39..0000000 --- a/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.snatik.storage.security; - -import javax.crypto.Cipher; -import javax.crypto.CipherOutputStream; - -/** - * The algorithm of the {@link Cipher}. Those algorithms could be used while - * encrypting the files by using {@link CipherOutputStream}.
- *
- *

- * http://developer.android.com/reference/javax/crypto/Cipher.html - * http://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html - * http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher - * - * @author Roman Kushnarenko - sromku (sromku@gmail.com) - */ -public enum CipherAlgorithmType { - /** - * Advanced Encryption Standard as specified by NIST in FIPS 197. Also known - * as the Rijndael algorithm by Joan Daemen and Vincent Rijmen, AES is a - * 128-bit block cipher supporting keys of 128, 192, and 256 bits. - */ - AES("AES"), - - /** - * The Digital Encryption Standard. - */ - DES("DES"), - - /** - * Triple DES Encryption (also known as DES-EDE, 3DES, or Triple-DES). Data - * is encrypted using the DES algorithm three separate times. It is first - * encrypted using the first subkey, then decrypted with the second subkey, - * and encrypted with the third subkey. - */ - DESede("DESede"), - - /** - * The RSA encryption algorithm - */ - RSA("RSA"); - - private String mName; - - private CipherAlgorithmType(String name) { - mName = name; - } - - /** - * Get the algorithm name of the enum value. - * - * @return The algorithm name - */ - public String getAlgorithmName() { - return mName; - } -} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt b/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt new file mode 100644 index 0000000..98e91f0 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt @@ -0,0 +1,49 @@ +package com.snatik.storage.security + +import javax.crypto.Cipher +import javax.crypto.CipherOutputStream + +/** + * The algorithm of the [Cipher]. Those algorithms could be used while + * encrypting the files by using [CipherOutputStream].

+ *

+ * + * + * *http://developer.android.com/reference/javax/crypto/Cipher.html* + * *http://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html* + * *http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher* + * + * @author Roman Kushnarenko - sromku (sromku@gmail.com) + */ +enum class CipherAlgorithmType private constructor( + /** + * Get the algorithm name of the enum value. + * + * @return The algorithm name + */ + val algorithmName: String) { + /** + * Advanced Encryption Standard as specified by NIST in FIPS 197. Also known + * as the Rijndael algorithm by Joan Daemen and Vincent Rijmen, AES is a + * 128-bit block cipher supporting keys of 128, 192, and 256 bits. + */ + AES("AES"), + + /** + * The Digital Encryption Standard. + */ + DES("DES"), + + /** + * Triple DES Encryption (also known as DES-EDE, 3DES, or Triple-DES). Data + * is encrypted using the DES algorithm three separate times. It is first + * encrypted using the first subkey, then decrypted with the second subkey, + * and encrypted with the third subkey. + */ + DESede("DESede"), + + /** + * The RSA encryption algorithm + */ + RSA("RSA") +} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherModeType.java b/storage/src/main/java/com/snatik/storage/security/CipherModeType.java deleted file mode 100644 index 6a8a5ed..0000000 --- a/storage/src/main/java/com/snatik/storage/security/CipherModeType.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.snatik.storage.security; - -/** - * https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation - * - * @author Roman Kushnarenko - sromku (sromku@gmail.com) - */ -public enum CipherModeType { - /** - * Cipher Block Chaining Mode - */ - CBC("CBC"), - - /** - * Electronic Codebook Mode - */ - ECB("ECB"); - - private String mName; - - private CipherModeType(String name) { - mName = name; - } - - /** - * Get the algorithm name of the enum value. - * - * @return The algorithm name - */ - public String getAlgorithmName() { - return mName; - } -} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherModeType.kt b/storage/src/main/java/com/snatik/storage/security/CipherModeType.kt new file mode 100644 index 0000000..6063fb5 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/security/CipherModeType.kt @@ -0,0 +1,24 @@ +package com.snatik.storage.security + +/** + * *https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation* + * + * @author Roman Kushnarenko - sromku (sromku@gmail.com) + */ +enum class CipherModeType private constructor( + /** + * Get the algorithm name of the enum value. + * + * @return The algorithm name + */ + val algorithmName: String) { + /** + * Cipher Block Chaining Mode + */ + CBC("CBC"), + + /** + * Electronic Codebook Mode + */ + ECB("ECB") +} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.java b/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.java deleted file mode 100644 index 24b72b8..0000000 --- a/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.snatik.storage.security; - -/** - * @author Roman Kushnarenko - sromku (sromku@gmail.com) - */ -public enum CipherPaddingType { - NoPadding("NoPadding"), - PKCS5Padding("PKCS5Padding"), - PKCS1Padding("PKCS1Padding"), - OAEPWithSHA_1AndMGF1Padding("OAEPWithSHA-1AndMGF1Padding"), - OAEPWithSHA_256AndMGF1Padding("OAEPWithSHA-256AndMGF1Padding"); - - private String mName; - - private CipherPaddingType(String name) { - mName = name; - } - - /** - * Get the algorithm name of the enum value. - * - * @return The algorithm name - */ - public String getAlgorithmName() { - return mName; - } -} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt b/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt new file mode 100644 index 0000000..6efc717 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt @@ -0,0 +1,18 @@ +package com.snatik.storage.security + +/** + * @author Roman Kushnarenko - sromku (sromku@gmail.com) + */ +enum class CipherPaddingType private constructor( + /** + * Get the algorithm name of the enum value. + * + * @return The algorithm name + */ + val algorithmName: String) { + NoPadding("NoPadding"), + PKCS5Padding("PKCS5Padding"), + PKCS1Padding("PKCS1Padding"), + OAEPWithSHA_1AndMGF1Padding("OAEPWithSHA-1AndMGF1Padding"), + OAEPWithSHA_256AndMGF1Padding("OAEPWithSHA-256AndMGF1Padding") +} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherTransformationType.java b/storage/src/main/java/com/snatik/storage/security/CipherTransformationType.java deleted file mode 100644 index 53ffe8d..0000000 --- a/storage/src/main/java/com/snatik/storage/security/CipherTransformationType.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.snatik.storage.security; - -/** - * Supported types: - * http://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html
- *
- * - * Every implementation of the Java platform is required to support the - * following standard Cipher transformations with the keysizes in parentheses: - *

    - *
  • AES/CBC/NoPadding (128)
  • - *
  • AES/CBC/PKCS5Padding (128)
  • - *
  • AES/ECB/NoPadding (128)
  • - *
  • AES/ECB/PKCS5Padding (128) - *
  • DES/CBC/NoPadding (56)
  • - *
  • DES/CBC/PKCS5Padding (56)
  • - *
  • DES/ECB/NoPadding (56) - *
  • DES/ECB/PKCS5Padding (56)
  • - *
  • DESede/CBC/NoPadding (168)
  • - *
  • DESede/CBC/PKCS5Padding (168)
  • - *
  • DESede/ECB/NoPadding (168)
  • - *
  • DESede/ECB/PKCS5Padding (168)
  • - *
  • RSA/ECB/PKCS1Padding (1024, 2048)
  • - *
  • RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
  • - *
  • RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
  • - *
- * - * @author Roman Kushnarenko - sromku (sromku@gmail.com) - * - */ -public class CipherTransformationType { - private static final String _ = "/"; - - public static final String AES_CBC_NoPadding = CipherAlgorithmType.AES + _ + CipherModeType.CBC + _ + CipherPaddingType.NoPadding; - public static final String AES_CBC_PKCS5Padding = CipherAlgorithmType.AES + _ + CipherModeType.CBC + _ + CipherPaddingType.PKCS5Padding; - public static final String AES_ECB_NoPadding = CipherAlgorithmType.AES + _ + CipherModeType.ECB + _ + CipherPaddingType.NoPadding; - public static final String AES_ECB_PKCS5Padding = CipherAlgorithmType.AES + _ + CipherModeType.ECB + _ + CipherPaddingType.PKCS5Padding; - - public static final String DES_CBC_NoPadding = CipherAlgorithmType.DES + _ + CipherModeType.CBC + _ + CipherPaddingType.NoPadding; - public static final String DES_CBC_PKCS5Padding = CipherAlgorithmType.DES + _ + CipherModeType.CBC + _ + CipherPaddingType.PKCS5Padding; - public static final String DES_ECB_NoPadding = CipherAlgorithmType.DES + _ + CipherModeType.ECB + _ + CipherPaddingType.NoPadding; - public static final String DES_ECB_PKCS5Padding = CipherAlgorithmType.DES + _ + CipherModeType.ECB + _ + CipherPaddingType.PKCS5Padding; - - public static final String DESede_CBC_NoPadding = CipherAlgorithmType.DESede + _ + CipherModeType.CBC + _ + CipherPaddingType.NoPadding; - public static final String DESede_CBC_PKCS5Padding = CipherAlgorithmType.DESede + _ + CipherModeType.CBC + _ + CipherPaddingType.PKCS5Padding; - public static final String DESede_ECB_NoPadding = CipherAlgorithmType.DESede + _ + CipherModeType.ECB + _ + CipherPaddingType.NoPadding; - public static final String DESede_ECB_PKCS5Padding = CipherAlgorithmType.DESede + _ + CipherModeType.ECB + _ + CipherPaddingType.PKCS5Padding; - - public static final String RSA_ECB_PKCS1Padding = CipherAlgorithmType.RSA + _ + CipherModeType.ECB + _ + CipherPaddingType.PKCS1Padding; - public static final String RSA_ECB_OAEPWithSHA_1AndMGF1Padding = CipherAlgorithmType.RSA + _ + CipherModeType.ECB + _ + CipherPaddingType.OAEPWithSHA_1AndMGF1Padding; - public static final String RSA_ECB_OAEPWithSHA_256AndMGF1Padding = CipherAlgorithmType.RSA + _ + CipherModeType.ECB + _ + CipherPaddingType.OAEPWithSHA_256AndMGF1Padding; - -} diff --git a/storage/src/main/java/com/snatik/storage/security/CipherTransformationType.kt b/storage/src/main/java/com/snatik/storage/security/CipherTransformationType.kt new file mode 100644 index 0000000..670d24b --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/security/CipherTransformationType.kt @@ -0,0 +1,52 @@ +package com.snatik.storage.security + +/** + * Supported types: + * http://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html

+ *

+ * + * Every implementation of the Java platform is required to support the + * following standard Cipher transformations with the keysizes in parentheses: + * + * * AES/CBC/NoPadding (128) + * * AES/CBC/PKCS5Padding (128) + * * AES/ECB/NoPadding (128) + * * AES/ECB/PKCS5Padding (128) + * * DES/CBC/NoPadding (56) + * * DES/CBC/PKCS5Padding (56) + * * DES/ECB/NoPadding (56) + * * DES/ECB/PKCS5Padding (56) + * * DESede/CBC/NoPadding (168) + * * DESede/CBC/PKCS5Padding (168) + * * DESede/ECB/NoPadding (168) + * * DESede/ECB/PKCS5Padding (168) + * * RSA/ECB/PKCS1Padding (1024, 2048) + * * RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048) + * * RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048) + * + * + * @author Roman Kushnarenko - sromku (sromku@gmail.com) + */ +object CipherTransformationType { + private val SLASH = "/" + + val AES_CBC_NoPadding = CipherAlgorithmType.AES.toString() + SLASH + CipherModeType.CBC + SLASH + CipherPaddingType.NoPadding + val AES_CBC_PKCS5Padding = CipherAlgorithmType.AES.toString() + SLASH + CipherModeType.CBC + SLASH + CipherPaddingType.PKCS5Padding + val AES_ECB_NoPadding = CipherAlgorithmType.AES.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.NoPadding + val AES_ECB_PKCS5Padding = CipherAlgorithmType.AES.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.PKCS5Padding + + val DES_CBC_NoPadding = CipherAlgorithmType.DES.toString() + SLASH + CipherModeType.CBC + SLASH + CipherPaddingType.NoPadding + val DES_CBC_PKCS5Padding = CipherAlgorithmType.DES.toString() + SLASH + CipherModeType.CBC + SLASH + CipherPaddingType.PKCS5Padding + val DES_ECB_NoPadding = CipherAlgorithmType.DES.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.NoPadding + val DES_ECB_PKCS5Padding = CipherAlgorithmType.DES.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.PKCS5Padding + + val DESede_CBC_NoPadding = CipherAlgorithmType.DESede.toString() + SLASH + CipherModeType.CBC + SLASH + CipherPaddingType.NoPadding + val DESede_CBC_PKCS5Padding = CipherAlgorithmType.DESede.toString() + SLASH + CipherModeType.CBC + SLASH + CipherPaddingType.PKCS5Padding + val DESede_ECB_NoPadding = CipherAlgorithmType.DESede.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.NoPadding + val DESede_ECB_PKCS5Padding = CipherAlgorithmType.DESede.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.PKCS5Padding + + val RSA_ECB_PKCS1Padding = CipherAlgorithmType.RSA.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.PKCS1Padding + val RSA_ECB_OAEPWithSHA_1AndMGF1Padding = CipherAlgorithmType.RSA.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.OAEPWithSHA_1AndMGF1Padding + val RSA_ECB_OAEPWithSHA_256AndMGF1Padding = CipherAlgorithmType.RSA.toString() + SLASH + CipherModeType.ECB + SLASH + CipherPaddingType.OAEPWithSHA_256AndMGF1Padding + +} diff --git a/storage/src/main/java/com/snatik/storage/security/SecurityUtil.java b/storage/src/main/java/com/snatik/storage/security/SecurityUtil.java deleted file mode 100644 index ff3fea2..0000000 --- a/storage/src/main/java/com/snatik/storage/security/SecurityUtil.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.snatik.storage.security; - -import android.util.Log; - -import java.io.UnsupportedEncodingException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * Security utils for encryption, xor and more - * - * @author Roman Kushnarenko - sromku (sromku@gmail.com) - */ -public class SecurityUtil { - - private static final String TAG = "SecurityUtil"; - - /** - * Encrypt or Descrypt the content.
- * - * @param content The content to encrypt or descrypt. - * @param encryptionMode Use: {@link Cipher#ENCRYPT_MODE} or - * {@link Cipher#DECRYPT_MODE} - * @param secretKey Set the secret key for encryption of file content. - * Important: The length must be 16 long. Uses SHA-256 - * to generate a hash from your key and trim the result to 128 - * bit (16 bytes) - * @param ivx This is not have to be secret. It used just for better - * randomizing the cipher. You have to use the same IV parameter - * within the same encrypted and written files. Means, if you - * want to have the same content after descryption then the same - * IV must be used. About this parameter from wiki: - * https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation - * #Initialization_vector_.28IV.29 Important: The length - * must be 16 long - * @return - */ - public static byte[] encrypt(byte[] content, int encryptionMode, final byte[] secretKey, final byte[] ivx) { - if (secretKey.length != 16 || ivx.length != 16) { - Log.w(TAG, "Set the encryption parameters correctly. The must be 16 length long each"); - return null; - } - - try { - SecretKey secretkey = new SecretKeySpec(secretKey, CipherAlgorithmType.AES.getAlgorithmName()); - IvParameterSpec IV = new IvParameterSpec(ivx); - String transformation = CipherTransformationType.AES_CBC_PKCS5Padding; - Cipher decipher = Cipher.getInstance(transformation); - decipher.init(encryptionMode, secretkey, IV); - return decipher.doFinal(content); - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "Failed to encrypt/descrypt - Unknown Algorithm", e); - return null; - } catch (NoSuchPaddingException e) { - Log.e(TAG, "Failed to encrypt/descrypt- Unknown Padding", e); - return null; - } catch (InvalidKeyException e) { - Log.e(TAG, "Failed to encrypt/descrypt - Invalid Key", e); - return null; - } catch (InvalidAlgorithmParameterException e) { - Log.e(TAG, "Failed to encrypt/descrypt - Invalid Algorithm Parameter", e); - return null; - } catch (IllegalBlockSizeException e) { - Log.e(TAG, "Failed to encrypt/descrypt", e); - return null; - } catch (BadPaddingException e) { - Log.e(TAG, "Failed to encrypt/descrypt", e); - return null; - } - } - - /** - * Do xor operation on the string with the key - * - * @param msg The string to xor on - * @param key The key by which the xor will work - * @return The string after xor - */ - public String xor(String msg, String key) { - try { - final String UTF_8 = "UTF-8"; - byte[] msgArray; - - msgArray = msg.getBytes(UTF_8); - - byte[] keyArray = key.getBytes(UTF_8); - - byte[] out = new byte[msgArray.length]; - for (int i = 0; i < msgArray.length; i++) { - out[i] = (byte) (msgArray[i] ^ keyArray[i % keyArray.length]); - } - return new String(out, UTF_8); - } catch (UnsupportedEncodingException e) { - } - return null; - } - -} diff --git a/storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt b/storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt new file mode 100644 index 0000000..1975384 --- /dev/null +++ b/storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt @@ -0,0 +1,113 @@ +package com.snatik.storage.security + +import android.util.Log + +import java.io.UnsupportedEncodingException +import java.security.InvalidAlgorithmParameterException +import java.security.InvalidKeyException +import java.security.NoSuchAlgorithmException + +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException +import javax.crypto.SecretKey +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec +import kotlin.experimental.xor + +/** + * Security utils for encryption, xor and more + * + * @author Roman Kushnarenko - sromku (sromku@gmail.com) + */ +class SecurityUtil { + + /** + * Do xor operation on the string with the key + * + * @param msg The string to xor on + * @param key The key by which the xor will work + * @return The string after xor + */ + fun xor(msg: String, key: String): String? { + try { + val UTF_8 = "UTF-8" + val msgArray: ByteArray + + msgArray = msg.toByteArray(charset(UTF_8)) + + val keyArray = key.toByteArray(charset(UTF_8)) + + val out = ByteArray(msgArray.size) + for (i in msgArray.indices) { + out[i] = (msgArray[i] xor keyArray[i % keyArray.size]).toByte() + } + return String(out, Charsets.UTF_8) + } catch (e: UnsupportedEncodingException) { + } + + return null + } + + companion object { + + private val TAG = "SecurityUtil" + + /** + * Encrypt or Descrypt the content.

+ * + * @param content The content to encrypt or descrypt. + * @param encryptionMode Use: [Cipher.ENCRYPT_MODE] or + * [Cipher.DECRYPT_MODE] + * @param secretKey Set the secret key for encryption of file content. + * **Important: The length must be 16 long**. *Uses SHA-256 + * to generate a hash from your key and trim the result to 128 + * bit (16 bytes)* + * @param ivx This is not have to be secret. It used just for better + * randomizing the cipher. You have to use the same IV parameter + * within the same encrypted and written files. Means, if you + * want to have the same content after descryption then the same + * IV must be used. *About this parameter from wiki: + * https://en.wikipedia.org/wiki/Block_cipher_modes_of_operation + * #Initialization_vector_.28IV.29* **Important: The length + * must be 16 long** + * @return + */ + fun encrypt(content: ByteArray, encryptionMode: Int, secretKey: ByteArray, ivx: ByteArray): ByteArray? { + if (secretKey.size != 16 || ivx.size != 16) { + Log.w(TAG, "Set the encryption parameters correctly. The must be 16 length long each") + return null + } + + try { + val secretkey = SecretKeySpec(secretKey, CipherAlgorithmType.AES.algorithmName) + val IV = IvParameterSpec(ivx) + val transformation = CipherTransformationType.AES_CBC_PKCS5Padding + val decipher = Cipher.getInstance(transformation) + decipher.init(encryptionMode, secretkey, IV) + return decipher.doFinal(content) + } catch (e: NoSuchAlgorithmException) { + Log.e(TAG, "Failed to encrypt/descrypt - Unknown Algorithm", e) + return null + } catch (e: NoSuchPaddingException) { + Log.e(TAG, "Failed to encrypt/descrypt- Unknown Padding", e) + return null + } catch (e: InvalidKeyException) { + Log.e(TAG, "Failed to encrypt/descrypt - Invalid Key", e) + return null + } catch (e: InvalidAlgorithmParameterException) { + Log.e(TAG, "Failed to encrypt/descrypt - Invalid Algorithm Parameter", e) + return null + } catch (e: IllegalBlockSizeException) { + Log.e(TAG, "Failed to encrypt/descrypt", e) + return null + } catch (e: BadPaddingException) { + Log.e(TAG, "Failed to encrypt/descrypt", e) + return null + } + + } + } + +} From 0233c8ee504045329523934933378d9eabff4214 Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Sun, 4 Aug 2019 11:56:57 +0430 Subject: [PATCH 3/9] fix kotlin migration problems --- storage/src/main/java/com/snatik/storage/Storage.kt | 4 ++-- .../src/main/java/com/snatik/storage/security/SecurityUtil.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/storage/src/main/java/com/snatik/storage/Storage.kt b/storage/src/main/java/com/snatik/storage/Storage.kt index 0ea2e93..5966fa2 100644 --- a/storage/src/main/java/com/snatik/storage/Storage.kt +++ b/storage/src/main/java/com/snatik/storage/Storage.kt @@ -316,7 +316,7 @@ class Storage(private val mContext: Context) { var offset = 0 for (chunk in chunks) { // flush chunk to array - System.arraycopy(chunk.element1, 0, array!!, offset, chunk.element2) + System.arraycopy(chunk.element1!!, 0, array!!, offset, chunk.element2!!) offset += chunk.element2 } } @@ -341,7 +341,7 @@ class Storage(private val mContext: Context) { private fun encrypt(content: ByteArray?, encryptionMode: Int): ByteArray? { val secretKey = mConfiguration!!.secretKey val ivx = mConfiguration!!.ivParameter - return SecurityUtil.encrypt(content, encryptionMode, secretKey, ivx) + return SecurityUtil.encrypt(content, encryptionMode, secretKey!!, ivx!!) } private fun deleteDirectoryImpl(path: String): Boolean { diff --git a/storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt b/storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt index 1975384..117f8f3 100644 --- a/storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt +++ b/storage/src/main/java/com/snatik/storage/security/SecurityUtil.kt @@ -74,7 +74,7 @@ class SecurityUtil { * must be 16 long** * @return */ - fun encrypt(content: ByteArray, encryptionMode: Int, secretKey: ByteArray, ivx: ByteArray): ByteArray? { + fun encrypt(content: ByteArray?, encryptionMode: Int, secretKey: ByteArray, ivx: ByteArray): ByteArray? { if (secretKey.size != 16 || ivx.size != 16) { Log.w(TAG, "Set the encryption parameters correctly. The must be 16 length long each") return null From 68c4a8368ee5f9a81d6dfcc3b38524f415377ae7 Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Sun, 4 Aug 2019 12:08:01 +0430 Subject: [PATCH 4/9] change app module to kotlin --- .../com/snatik/storage/app/FilesAdapter.java | 96 ----- .../com/snatik/storage/app/FilesAdapter.kt | 86 +++++ .../storage/app/GenericFileProvider.java | 9 - .../snatik/storage/app/GenericFileProvider.kt | 8 + .../java/com/snatik/storage/app/Helper.java | 34 -- .../java/com/snatik/storage/app/Helper.kt | 35 ++ .../com/snatik/storage/app/MainActivity.java | 362 ------------------ .../com/snatik/storage/app/MainActivity.kt | 305 +++++++++++++++ .../com/snatik/storage/app/MyApplication.java | 13 - .../com/snatik/storage/app/MyApplication.kt | 15 + .../snatik/storage/app/ViewTextActivity.java | 84 ---- .../snatik/storage/app/ViewTextActivity.kt | 85 ++++ .../storage/app/dialogs/AddItemsDialog.java | 88 ----- .../storage/app/dialogs/AddItemsDialog.kt | 73 ++++ .../app/dialogs/ConfirmDeleteDialog.java | 54 --- .../app/dialogs/ConfirmDeleteDialog.kt | 46 +++ .../storage/app/dialogs/NewFolderDialog.java | 100 ----- .../storage/app/dialogs/NewFolderDialog.kt | 78 ++++ .../app/dialogs/NewTextFileDialog.java | 118 ------ .../storage/app/dialogs/NewTextFileDialog.kt | 92 +++++ .../storage/app/dialogs/RenameDialog.java | 119 ------ .../storage/app/dialogs/RenameDialog.kt | 99 +++++ .../storage/app/dialogs/UpdateItemDialog.java | 125 ------ .../storage/app/dialogs/UpdateItemDialog.kt | 105 +++++ 24 files changed, 1027 insertions(+), 1202 deletions(-) delete mode 100644 app/src/main/java/com/snatik/storage/app/FilesAdapter.java create mode 100644 app/src/main/java/com/snatik/storage/app/FilesAdapter.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/GenericFileProvider.java create mode 100644 app/src/main/java/com/snatik/storage/app/GenericFileProvider.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/Helper.java create mode 100644 app/src/main/java/com/snatik/storage/app/Helper.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/MainActivity.java create mode 100644 app/src/main/java/com/snatik/storage/app/MainActivity.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/MyApplication.java create mode 100644 app/src/main/java/com/snatik/storage/app/MyApplication.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/ViewTextActivity.java create mode 100644 app/src/main/java/com/snatik/storage/app/ViewTextActivity.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java create mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.java create mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.java create mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.java create mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.java create mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.kt delete mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java create mode 100644 app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.kt diff --git a/app/src/main/java/com/snatik/storage/app/FilesAdapter.java b/app/src/main/java/com/snatik/storage/app/FilesAdapter.java deleted file mode 100644 index 9facd2b..0000000 --- a/app/src/main/java/com/snatik/storage/app/FilesAdapter.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.snatik.storage.app; - -import android.content.Context; -import androidx.recyclerview.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.snatik.storage.Storage; - -import java.io.File; -import java.util.List; - -/** - * Created by sromku on June, 2017. - */ -public class FilesAdapter extends RecyclerView.Adapter { - - private List mFiles; - private OnFileItemListener mListener; - private Storage mStorage; - - public FilesAdapter(Context context) { - mStorage = new Storage(context); - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_line_view, parent, false); - return new FileViewHolder(view); - } - - @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - final File file = mFiles.get(position); - FileViewHolder fileViewHolder = (FileViewHolder) holder; - fileViewHolder.itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mListener.onClick(file); - } - }); - fileViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - mListener.onLongClick(file); - return true; - } - }); - fileViewHolder.mName.setText(file.getName()); - fileViewHolder.mIcon.setImageResource(file.isDirectory() ? R.drawable.ic_folder_primary_24dp : R.drawable - .ic_file_primary_24dp); - if (file.isDirectory()) { - fileViewHolder.mSize.setVisibility(View.GONE); - } else { - fileViewHolder.mSize.setVisibility(View.VISIBLE); - fileViewHolder.mSize.setText(mStorage.getReadableSize(file)); - } - - } - - @Override - public int getItemCount() { - return mFiles != null ? mFiles.size() : 0; - } - - public void setFiles(List files) { - mFiles = files; - } - - public void setListener(OnFileItemListener listener) { - mListener = listener; - } - - static class FileViewHolder extends RecyclerView.ViewHolder { - - TextView mName; - TextView mSize; - ImageView mIcon; - - FileViewHolder(View v) { - super(v); - mName = (TextView) v.findViewById(R.id.name); - mSize = (TextView) v.findViewById(R.id.size); - mIcon = (ImageView) v.findViewById(R.id.icon); - } - } - - public interface OnFileItemListener { - void onClick(File file); - - void onLongClick(File file); - } -} diff --git a/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt new file mode 100644 index 0000000..13a2d92 --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt @@ -0,0 +1,86 @@ +package com.snatik.storage.app + +import android.content.Context +import androidx.recyclerview.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView + +import com.snatik.storage.Storage + +import java.io.File + +/** + * Created by sromku on June, 2017. + */ +class FilesAdapter(context: Context) : RecyclerView.Adapter() { + + private var mFiles: List? = null + private var mListener: OnFileItemListener? = null + private val mStorage: Storage + + init { + mStorage = Storage(context) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.file_line_view, parent, false) + return FileViewHolder(view) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val file = mFiles!![position] + val fileViewHolder = holder as FileViewHolder + fileViewHolder.itemView.setOnClickListener { mListener!!.onClick(file) } + fileViewHolder.itemView.setOnLongClickListener { + mListener!!.onLongClick(file) + true + } + fileViewHolder.mName.text = file.name + fileViewHolder.mIcon.setImageResource(if (file.isDirectory) + R.drawable.ic_folder_primary_24dp + else + R.drawable + .ic_file_primary_24dp) + if (file.isDirectory) { + fileViewHolder.mSize.visibility = View.GONE + } else { + fileViewHolder.mSize.visibility = View.VISIBLE + fileViewHolder.mSize.text = mStorage.getReadableSize(file) + } + + } + + override fun getItemCount(): Int { + return if (mFiles != null) mFiles!!.size else 0 + } + + fun setFiles(files: List) { + mFiles = files + } + + fun setListener(listener: OnFileItemListener) { + mListener = listener + } + + internal class FileViewHolder(v: View) : RecyclerView.ViewHolder(v) { + + var mName: TextView + var mSize: TextView + var mIcon: ImageView + + init { + mName = v.findViewById(R.id.name) as TextView + mSize = v.findViewById(R.id.size) as TextView + mIcon = v.findViewById(R.id.icon) as ImageView + } + } + + interface OnFileItemListener { + fun onClick(file: File) + + fun onLongClick(file: File) + } +} diff --git a/app/src/main/java/com/snatik/storage/app/GenericFileProvider.java b/app/src/main/java/com/snatik/storage/app/GenericFileProvider.java deleted file mode 100644 index af888f5..0000000 --- a/app/src/main/java/com/snatik/storage/app/GenericFileProvider.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.snatik.storage.app; - -import androidx.core.content.FileProvider; - -/** - * Created by sromku on July, 2017. - */ -public class GenericFileProvider extends FileProvider { -} diff --git a/app/src/main/java/com/snatik/storage/app/GenericFileProvider.kt b/app/src/main/java/com/snatik/storage/app/GenericFileProvider.kt new file mode 100644 index 0000000..d83556d --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/GenericFileProvider.kt @@ -0,0 +1,8 @@ +package com.snatik.storage.app + +import androidx.core.content.FileProvider + +/** + * Created by sromku on July, 2017. + */ +class GenericFileProvider : FileProvider() diff --git a/app/src/main/java/com/snatik/storage/app/Helper.java b/app/src/main/java/com/snatik/storage/app/Helper.java deleted file mode 100644 index a13f568..0000000 --- a/app/src/main/java/com/snatik/storage/app/Helper.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.snatik.storage.app; - -import com.google.android.material.snackbar.Snackbar; -import android.view.View; - -/** - * Created by sromku on June, 2017. - */ -public class Helper { - - public static void showSnackbar(String message, View root) { - Snackbar.make(root, message, Snackbar.LENGTH_SHORT).show(); - } - - public static String fileExt(String url) { - if (url.indexOf("?") > -1) { - url = url.substring(0, url.indexOf("?")); - } - if (url.lastIndexOf(".") == -1) { - return null; - } else { - String ext = url.substring(url.lastIndexOf(".") + 1); - if (ext.indexOf("%") > -1) { - ext = ext.substring(0, ext.indexOf("%")); - } - if (ext.indexOf("/") > -1) { - ext = ext.substring(0, ext.indexOf("/")); - } - return ext.toLowerCase(); - - } - } - -} diff --git a/app/src/main/java/com/snatik/storage/app/Helper.kt b/app/src/main/java/com/snatik/storage/app/Helper.kt new file mode 100644 index 0000000..7f73162 --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/Helper.kt @@ -0,0 +1,35 @@ +package com.snatik.storage.app + +import com.google.android.material.snackbar.Snackbar +import android.view.View + +/** + * Created by sromku on June, 2017. + */ +object Helper { + + fun showSnackbar(message: String, root: View) { + Snackbar.make(root, message, Snackbar.LENGTH_SHORT).show() + } + + fun fileExt(url: String): String? { + var url = url + if (url.indexOf("?") > -1) { + url = url.substring(0, url.indexOf("?")) + } + if (url.lastIndexOf(".") == -1) { + return null + } else { + var ext = url.substring(url.lastIndexOf(".") + 1) + if (ext.indexOf("%") > -1) { + ext = ext.substring(0, ext.indexOf("%")) + } + if (ext.indexOf("/") > -1) { + ext = ext.substring(0, ext.indexOf("/")) + } + return ext.toLowerCase() + + } + } + +} diff --git a/app/src/main/java/com/snatik/storage/app/MainActivity.java b/app/src/main/java/com/snatik/storage/app/MainActivity.java deleted file mode 100644 index 3536b40..0000000 --- a/app/src/main/java/com/snatik/storage/app/MainActivity.java +++ /dev/null @@ -1,362 +0,0 @@ -package com.snatik.storage.app; - -import android.Manifest; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.appcompat.widget.Toolbar; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.webkit.MimeTypeMap; -import android.widget.PopupMenu; -import android.widget.TextView; - -import com.snatik.storage.EncryptConfiguration; -import com.snatik.storage.Storage; -import com.snatik.storage.app.dialogs.AddItemsDialog; -import com.snatik.storage.app.dialogs.ConfirmDeleteDialog; -import com.snatik.storage.app.dialogs.NewFolderDialog; -import com.snatik.storage.app.dialogs.NewTextFileDialog; -import com.snatik.storage.app.dialogs.RenameDialog; -import com.snatik.storage.app.dialogs.UpdateItemDialog; -import com.snatik.storage.helpers.OrderType; -import com.snatik.storage.helpers.SizeUnit; - -import java.io.File; -import java.util.Collections; -import java.util.List; - -import static com.snatik.storage.app.Helper.fileExt; - -public class MainActivity extends AppCompatActivity implements - FilesAdapter.OnFileItemListener, - AddItemsDialog.DialogListener, - UpdateItemDialog.DialogListener, - NewFolderDialog.DialogListener, - NewTextFileDialog.DialogListener, - ConfirmDeleteDialog.ConfirmListener, - RenameDialog.DialogListener { - - private static final int PERMISSION_REQUEST_CODE = 1000; - private RecyclerView mRecyclerView; - private FilesAdapter mFilesAdapter; - private Storage mStorage; - private TextView mPathView; - private TextView mMovingText; - private boolean mCopy; - private View mMovingLayout; - private int mTreeSteps = 0; - private final static String IVX = "abcdefghijklmnop"; - private final static String SECRET_KEY = "secret1234567890"; - private final static byte[] SALT = "0000111100001111".getBytes(); - private String mMovingPath; - private boolean mInternal = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mStorage = new Storage(getApplicationContext()); - - setContentView(R.layout.activity_main); - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - mRecyclerView = findViewById(R.id.recycler); - mPathView = findViewById(R.id.path); - mMovingLayout = findViewById(R.id.moving_layout); - mMovingText = mMovingLayout.findViewById(R.id.moving_file_name); - - mMovingLayout.findViewById(R.id.accept_move).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mMovingLayout.setVisibility(View.GONE); - if (mMovingPath != null) { - - if (!mCopy) { - String toPath = getCurrentPath() + File.separator + mStorage.getFile(mMovingPath).getName(); - if (!mMovingPath.equals(toPath)) { - mStorage.move(mMovingPath, toPath); - Helper.showSnackbar("Moved", mRecyclerView); - showFiles(getCurrentPath()); - } else { - Helper.showSnackbar("The file is already here", mRecyclerView); - } - } else { - String toPath = getCurrentPath() + File.separator + "copy " + mStorage.getFile(mMovingPath) - .getName(); - mStorage.copy(mMovingPath, toPath); - Helper.showSnackbar("Copied", mRecyclerView); - showFiles(getCurrentPath()); - } - mMovingPath = null; - } - } - }); - - mMovingLayout.findViewById(R.id.decline_move).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mMovingLayout.setVisibility(View.GONE); - mMovingPath = null; - } - }); - - RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); - mRecyclerView.setLayoutManager(layoutManager); - mFilesAdapter = new FilesAdapter(getApplicationContext()); - mFilesAdapter.setListener(this); - mRecyclerView.setAdapter(mFilesAdapter); - - findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - AddItemsDialog.newInstance().show(getFragmentManager(), "add_items"); - } - }); - - mPathView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - showPathMenu(); - } - }); - - // load files - showFiles(mStorage.getExternalStorageDirectory()); - - checkPermission(); - } - - private void showPathMenu() { - PopupMenu popupmenu = new PopupMenu(this, mPathView); - MenuInflater inflater = popupmenu.getMenuInflater(); - inflater.inflate(R.menu.path_menu, popupmenu.getMenu()); - - popupmenu.getMenu().findItem(R.id.go_internal).setVisible(!mInternal); - popupmenu.getMenu().findItem(R.id.go_external).setVisible(mInternal); - - popupmenu.show(); - - popupmenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.go_up: - String previousPath = getPreviousPath(); - mTreeSteps = 0; - showFiles(previousPath); - break; - case R.id.go_internal: - showFiles(mStorage.getInternalFilesDirectory()); - mInternal = true; - break; - case R.id.go_external: - showFiles(mStorage.getExternalStorageDirectory()); - mInternal = false; - break; - } - return true; - } - }); - } - - private void showFiles(String path) { - mPathView.setText(path); - List files = mStorage.getFiles(path); - if (files != null) { - Collections.sort(files, OrderType.NAME.getComparator()); - } - mFilesAdapter.setFiles(files); - mFilesAdapter.notifyDataSetChanged(); - } - - - @Override - public void onClick(File file) { - if (file.isDirectory()) { - mTreeSteps++; - String path = file.getAbsolutePath(); - showFiles(path); - } else { - - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExt(file.getAbsolutePath())); - Uri apkURI = FileProvider.getUriForFile( - this, - getApplicationContext() - .getPackageName() + ".provider", file); - intent.setDataAndType(apkURI, mimeType); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivity(intent); - } catch (ActivityNotFoundException e) { - if (mStorage.getSize(file, SizeUnit.KB) > 500) { - Helper.showSnackbar("The file is too big for preview", mRecyclerView); - return; - } - Intent intent = new Intent(this, ViewTextActivity.class); - intent.putExtra(ViewTextActivity.EXTRA_FILE_NAME, file.getName()); - intent.putExtra(ViewTextActivity.EXTRA_FILE_PATH, file.getAbsolutePath()); - startActivity(intent); - } - - } - } - - @Override - public void onLongClick(File file) { - UpdateItemDialog.newInstance(file.getAbsolutePath()).show(getFragmentManager(), "update_item"); - } - - @Override - public void onBackPressed() { - if (mTreeSteps > 0) { - String path = getPreviousPath(); - mTreeSteps--; - showFiles(path); - return; - } - super.onBackPressed(); - } - - private String getCurrentPath() { - return mPathView.getText().toString(); - } - - private String getPreviousPath() { - String path = getCurrentPath(); - int lastIndexOf = path.lastIndexOf(File.separator); - if (lastIndexOf < 0) { - Helper.showSnackbar("Can't go anymore", mRecyclerView); - return getCurrentPath(); - } - return path.substring(0, lastIndexOf); - } - - @Override - public void onOptionClick(int which, String path) { - switch (which) { - case R.id.new_file: - NewTextFileDialog.newInstance().show(getFragmentManager(), "new_file_dialog"); - break; - case R.id.new_folder: - NewFolderDialog.newInstance().show(getFragmentManager(), "new_folder_dialog"); - break; - case R.id.delete: - ConfirmDeleteDialog.newInstance(path).show(getFragmentManager(), "confirm_delete"); - break; - case R.id.rename: - RenameDialog.newInstance(path).show(getFragmentManager(), "rename"); - break; - case R.id.move: - mMovingText.setText(getString(R.string.moving_file, mStorage.getFile(path).getName())); - mMovingPath = path; - mCopy = false; - mMovingLayout.setVisibility(View.VISIBLE); - break; - case R.id.copy: - mMovingText.setText(getString(R.string.copy_file, mStorage.getFile(path).getName())); - mMovingPath = path; - mCopy = true; - mMovingLayout.setVisibility(View.VISIBLE); - break; - } - } - - @Override - public void onNewFolder(String name) { - String currentPath = getCurrentPath(); - String folderPath = currentPath + File.separator + name; - boolean created = mStorage.createDirectory(folderPath); - if (created) { - showFiles(currentPath); - Helper.showSnackbar("New folder created: " + name, mRecyclerView); - } else { - Helper.showSnackbar("Failed create folder: " + name, mRecyclerView); - } - } - - @Override - public void onNewFile(String name, String content, boolean encrypted) { - String currentPath = getCurrentPath(); - String folderPath = currentPath + File.separator + name; - if (encrypted) { - mStorage.setEncryptConfiguration(new EncryptConfiguration.Builder() - .setEncryptContent(IVX, SECRET_KEY, SALT) - .build()); - } - mStorage.createFile(folderPath, content); - showFiles(currentPath); - Helper.showSnackbar("New file created: " + name, mRecyclerView); - } - - @Override - public void onConfirmDelete(String path) { - if (mStorage.getFile(path).isDirectory()) { - mStorage.deleteDirectory(path); - Helper.showSnackbar("Folder was deleted", mRecyclerView); - } else { - mStorage.deleteFile(path); - Helper.showSnackbar("File was deleted", mRecyclerView); - } - showFiles(getCurrentPath()); - } - - @Override - public void onRename(String fromPath, String toPath) { - mStorage.rename(fromPath, toPath); - showFiles(getCurrentPath()); - Helper.showSnackbar("Renamed", mRecyclerView); - } - -// @Override -// public boolean onCreateOptionsMenu(Menu menu) { -// MenuInflater inflater = getMenuInflater(); -// inflater.inflate(R.menu.main_menu, menu); -// return true; -// } -// -// @Override -// public boolean onOptionsItemSelected(MenuItem item) { -// switch (item.getItemId()) { -// case R.id.order: -// break; -// case R.id.filter: -// break; -// } -// -// return super.onOptionsItemSelected(item); -// } - - private void checkPermission() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager - .PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSION_REQUEST_CODE); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] - grantResults) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - showFiles(mStorage.getExternalStorageDirectory()); - } else { - finish(); - } - } -} diff --git a/app/src/main/java/com/snatik/storage/app/MainActivity.kt b/app/src/main/java/com/snatik/storage/app/MainActivity.kt new file mode 100644 index 0000000..bbce758 --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/MainActivity.kt @@ -0,0 +1,305 @@ +package com.snatik.storage.app + +import android.Manifest +import android.content.ActivityNotFoundException +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.View +import android.webkit.MimeTypeMap +import android.widget.PopupMenu +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.snatik.storage.EncryptConfiguration +import com.snatik.storage.Storage +import com.snatik.storage.app.Helper.fileExt +import com.snatik.storage.app.dialogs.* +import com.snatik.storage.helpers.OrderType +import com.snatik.storage.helpers.SizeUnit +import java.io.File +import java.util.* + +class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddItemsDialog.DialogListener, UpdateItemDialog.DialogListener, NewFolderDialog.DialogListener, NewTextFileDialog.DialogListener, ConfirmDeleteDialog.ConfirmListener, RenameDialog.DialogListener { + private var mRecyclerView: RecyclerView? = null + private var mFilesAdapter: FilesAdapter? = null + private var mStorage: Storage? = null + private var mPathView: TextView? = null + private var mMovingText: TextView? = null + private var mCopy: Boolean = false + private var mMovingLayout: View? = null + private var mTreeSteps = 0 + private var mMovingPath: String? = null + private var mInternal = false + + private val currentPath: String + get() = mPathView!!.text.toString() + + private val previousPath: String + get() { + val path = currentPath + val lastIndexOf = path.lastIndexOf(File.separator) + if (lastIndexOf < 0) { + Helper.showSnackbar("Can't go anymore", mRecyclerView!!) + return currentPath + } + return path.substring(0, lastIndexOf) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + mStorage = Storage(applicationContext) + + setContentView(R.layout.activity_main) + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + mRecyclerView = findViewById(R.id.recycler) + mPathView = findViewById(R.id.path) + mMovingLayout = findViewById(R.id.moving_layout) + mMovingText = mMovingLayout!!.findViewById(R.id.moving_file_name) + + mMovingLayout!!.findViewById(R.id.accept_move).setOnClickListener { + mMovingLayout!!.visibility = View.GONE + if (mMovingPath != null) { + + if (!mCopy) { + val toPath = currentPath + File.separator + mStorage!!.getFile(mMovingPath!!).name + if (mMovingPath != toPath) { + mStorage!!.move(mMovingPath!!, toPath) + Helper.showSnackbar("Moved", mRecyclerView!!) + showFiles(currentPath) + } else { + Helper.showSnackbar("The file is already here", mRecyclerView!!) + } + } else { + val toPath = currentPath + File.separator + "copy " + mStorage!!.getFile(mMovingPath!!) + .name + mStorage!!.copy(mMovingPath!!, toPath) + Helper.showSnackbar("Copied", mRecyclerView!!) + showFiles(currentPath) + } + mMovingPath = null + } + } + + mMovingLayout!!.findViewById(R.id.decline_move).setOnClickListener { + mMovingLayout!!.visibility = View.GONE + mMovingPath = null + } + + val layoutManager = LinearLayoutManager(this) + mRecyclerView!!.layoutManager = layoutManager + mFilesAdapter = FilesAdapter(applicationContext) + mFilesAdapter!!.setListener(this) + mRecyclerView!!.adapter = mFilesAdapter + + findViewById(R.id.fab).setOnClickListener { AddItemsDialog.newInstance().show(supportFragmentManager, "add_items") } + + mPathView!!.setOnClickListener { showPathMenu() } + + // load files + showFiles(mStorage!!.externalStorageDirectory) + + checkPermission() + } + + private fun showPathMenu() { + val popupmenu = PopupMenu(this, mPathView) + val inflater = popupmenu.menuInflater + inflater.inflate(R.menu.path_menu, popupmenu.menu) + + popupmenu.menu.findItem(R.id.go_internal).isVisible = !mInternal + popupmenu.menu.findItem(R.id.go_external).isVisible = mInternal + + popupmenu.show() + + popupmenu.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.go_up -> { + val previousPath = previousPath + mTreeSteps = 0 + showFiles(previousPath) + } + R.id.go_internal -> { + showFiles(mStorage!!.internalFilesDirectory) + mInternal = true + } + R.id.go_external -> { + showFiles(mStorage!!.externalStorageDirectory) + mInternal = false + } + } + true + } + } + + private fun showFiles(path: String) { + mPathView!!.text = path + val files = mStorage!!.getFiles(path) + if (files != null) { + Collections.sort(files, OrderType.NAME.comparator) + } + mFilesAdapter!!.setFiles(files!!) + mFilesAdapter!!.notifyDataSetChanged() + } + + + override fun onClick(file: File) { + if (file.isDirectory) { + mTreeSteps++ + val path = file.absolutePath + showFiles(path) + } else { + + try { + val intent = Intent(Intent.ACTION_VIEW) + val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExt(file.absolutePath)) + val apkURI = FileProvider.getUriForFile( + this, + applicationContext + .packageName + ".provider", file) + intent.setDataAndType(apkURI, mimeType) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivity(intent) + } catch (e: ActivityNotFoundException) { + if (mStorage!!.getSize(file, SizeUnit.KB) > 500) { + Helper.showSnackbar("The file is too big for preview", mRecyclerView!!) + return + } + val intent = Intent(this, ViewTextActivity::class.java) + intent.putExtra(ViewTextActivity.EXTRA_FILE_NAME, file.name) + intent.putExtra(ViewTextActivity.EXTRA_FILE_PATH, file.absolutePath) + startActivity(intent) + } + + } + } + + override fun onLongClick(file: File) { + UpdateItemDialog.newInstance(file.absolutePath).show(supportFragmentManager, "update_item") + } + + override fun onBackPressed() { + if (mTreeSteps > 0) { + val path = previousPath + mTreeSteps-- + showFiles(path) + return + } + super.onBackPressed() + } + + override fun onOptionClick(which: Int, path: String?) { + when (which) { + R.id.new_file -> NewTextFileDialog.newInstance().show(supportFragmentManager, "new_file_dialog") + R.id.new_folder -> NewFolderDialog.newInstance().show(supportFragmentManager, "new_folder_dialog") + R.id.delete -> ConfirmDeleteDialog.newInstance(path!!).show(supportFragmentManager, "confirm_delete") + R.id.rename -> RenameDialog.newInstance(path!!).show(supportFragmentManager, "rename") + R.id.move -> { + mMovingText!!.text = getString(R.string.moving_file, mStorage!!.getFile(path!!).name) + mMovingPath = path + mCopy = false + mMovingLayout!!.visibility = View.VISIBLE + } + R.id.copy -> { + mMovingText!!.text = getString(R.string.copy_file, mStorage!!.getFile(path!!).name) + mMovingPath = path + mCopy = true + mMovingLayout!!.visibility = View.VISIBLE + } + } + } + + override fun onNewFolder(name: String) { + val currentPath = currentPath + val folderPath = currentPath + File.separator + name + val created = mStorage!!.createDirectory(folderPath) + if (created) { + showFiles(currentPath) + Helper.showSnackbar("New folder created: $name", mRecyclerView!!) + } else { + Helper.showSnackbar("Failed create folder: $name", mRecyclerView!!) + } + } + + override fun onNewFile(name: String, content: String, encrypted: Boolean) { + val currentPath = currentPath + val folderPath = currentPath + File.separator + name + if (encrypted) { + mStorage!!.setEncryptConfiguration(EncryptConfiguration.Builder() + .setEncryptContent(IVX, SECRET_KEY, SALT) + .build()) + } + mStorage!!.createFile(folderPath, content) + showFiles(currentPath) + Helper.showSnackbar("New file created: $name", mRecyclerView!!) + } + + override fun onConfirmDelete(path: String?) { + if (mStorage!!.getFile(path!!).isDirectory) { + mStorage!!.deleteDirectory(path) + Helper.showSnackbar("Folder was deleted", mRecyclerView!!) + } else { + mStorage!!.deleteFile(path) + Helper.showSnackbar("File was deleted", mRecyclerView!!) + } + showFiles(currentPath) + } + + override fun onRename(fromPath: String, toPath: String) { + mStorage!!.rename(fromPath, toPath) + showFiles(currentPath) + Helper.showSnackbar("Renamed", mRecyclerView!!) + } + + // @Override + // public boolean onCreateOptionsMenu(Menu menu) { + // MenuInflater inflater = getMenuInflater(); + // inflater.inflate(R.menu.main_menu, menu); + // return true; + // } + // + // @Override + // public boolean onOptionsItemSelected(MenuItem item) { + // switch (item.getItemId()) { + // case R.id.order: + // break; + // case R.id.filter: + // break; + // } + // + // return super.onOptionsItemSelected(item); + // } + + private fun checkPermission() { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager + .PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + PERMISSION_REQUEST_CODE) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + showFiles(mStorage!!.externalStorageDirectory) + } else { + finish() + } + } + + companion object { + + private val PERMISSION_REQUEST_CODE = 1000 + private val IVX = "abcdefghijklmnop" + private val SECRET_KEY = "secret1234567890" + private val SALT = "0000111100001111".toByteArray() + } +} diff --git a/app/src/main/java/com/snatik/storage/app/MyApplication.java b/app/src/main/java/com/snatik/storage/app/MyApplication.java deleted file mode 100644 index d2bcfbf..0000000 --- a/app/src/main/java/com/snatik/storage/app/MyApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.snatik.storage.app; - -import android.app.Application; - -public class MyApplication extends Application { - - public static String TAG = "MyApplication"; - - @Override - public void onCreate() { - super.onCreate(); - } -} diff --git a/app/src/main/java/com/snatik/storage/app/MyApplication.kt b/app/src/main/java/com/snatik/storage/app/MyApplication.kt new file mode 100644 index 0000000..157cb2b --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/MyApplication.kt @@ -0,0 +1,15 @@ +package com.snatik.storage.app + +import android.app.Application + +class MyApplication : Application() { + + override fun onCreate() { + super.onCreate() + } + + companion object { + + var TAG = "MyApplication" + } +} diff --git a/app/src/main/java/com/snatik/storage/app/ViewTextActivity.java b/app/src/main/java/com/snatik/storage/app/ViewTextActivity.java deleted file mode 100644 index a8583d8..0000000 --- a/app/src/main/java/com/snatik/storage/app/ViewTextActivity.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.snatik.storage.app; - -import android.os.Bundle; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.graphics.drawable.DrawerArrowDrawable; -import androidx.appcompat.widget.Toolbar; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.widget.TextView; - -import com.snatik.storage.EncryptConfiguration; -import com.snatik.storage.Storage; - -/** - * Created by sromku on June, 2017. - */ -public class ViewTextActivity extends AppCompatActivity { - - public final static String EXTRA_FILE_NAME = "name"; - public final static String EXTRA_FILE_PATH = "path"; - - private final static String IVX = "abcdefghijklmnop"; - private final static String SECRET_KEY = "secret1234567890"; - private final static byte[] SALT = "0000111100001111".getBytes(); - - private TextView mContentView; - private String mPath; - private Storage mStorage; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - String name = getIntent().getStringExtra(EXTRA_FILE_NAME); - mPath = getIntent().getStringExtra(EXTRA_FILE_PATH); - - setContentView(R.layout.activity_view_text_file); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - DrawerArrowDrawable drawerDrawable = new DrawerArrowDrawable(this); - drawerDrawable.setColor(getResources().getColor(android.R.color.white)); - drawerDrawable.setProgress(1f); - toolbar.setNavigationIcon(drawerDrawable); - - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(name); - getSupportActionBar().setHomeButtonEnabled(true); - - mContentView = (TextView) findViewById(R.id.content); - mStorage = new Storage(this); - byte[] bytes = mStorage.readFile(mPath); - mContentView.setText(new String(bytes)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.text_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - return true; - case R.id.decrypt: - mStorage.setEncryptConfiguration(new EncryptConfiguration.Builder() - .setEncryptContent(IVX, SECRET_KEY, SALT) - .build()); - byte[] bytes = mStorage.readFile(mPath); - if (bytes != null) { - mContentView.setText(new String(bytes)); - } else { - Helper.showSnackbar("Failed to decrypt", mContentView); - } - break; - } - - return super.onOptionsItemSelected(item); - } -} diff --git a/app/src/main/java/com/snatik/storage/app/ViewTextActivity.kt b/app/src/main/java/com/snatik/storage/app/ViewTextActivity.kt new file mode 100644 index 0000000..24b03d6 --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/ViewTextActivity.kt @@ -0,0 +1,85 @@ +package com.snatik.storage.app + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.graphics.drawable.DrawerArrowDrawable +import androidx.appcompat.widget.Toolbar +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.widget.TextView + +import com.snatik.storage.EncryptConfiguration +import com.snatik.storage.Storage + +/** + * Created by sromku on June, 2017. + */ +class ViewTextActivity : AppCompatActivity() { + + private var mContentView: TextView? = null + private var mPath: String? = null + private var mStorage: Storage? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val name = intent.getStringExtra(EXTRA_FILE_NAME) + mPath = intent.getStringExtra(EXTRA_FILE_PATH) + + setContentView(R.layout.activity_view_text_file) + val toolbar = findViewById(R.id.toolbar) as Toolbar + val drawerDrawable = DrawerArrowDrawable(this) + drawerDrawable.color = resources.getColor(android.R.color.white) + drawerDrawable.progress = 1f + toolbar.navigationIcon = drawerDrawable + + setSupportActionBar(toolbar) + supportActionBar!!.setDisplayHomeAsUpEnabled(true) + supportActionBar!!.setTitle(name) + supportActionBar!!.setHomeButtonEnabled(true) + + mContentView = findViewById(R.id.content) as TextView + mStorage = Storage(this) + val bytes = mStorage!!.readFile(mPath!!) + mContentView!!.text = String(bytes!!) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + val inflater = menuInflater + inflater.inflate(R.menu.text_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + R.id.decrypt -> { + mStorage!!.setEncryptConfiguration(EncryptConfiguration.Builder() + .setEncryptContent(IVX, SECRET_KEY, SALT) + .build()) + val bytes = mStorage!!.readFile(mPath!!) + if (bytes != null) { + mContentView!!.text = String(bytes) + } else { + Helper.showSnackbar("Failed to decrypt", mContentView!!) + } + } + } + + return super.onOptionsItemSelected(item) + } + + companion object { + + val EXTRA_FILE_NAME = "name" + val EXTRA_FILE_PATH = "path" + + private val IVX = "abcdefghijklmnop" + private val SECRET_KEY = "secret1234567890" + private val SALT = "0000111100001111".toByteArray() + } +} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java deleted file mode 100644 index 77b7e27..0000000 --- a/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.snatik.storage.app.dialogs; - -import android.app.Activity; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.snatik.storage.app.R; - -public class AddItemsDialog extends DialogFragment { - - private DialogListener mListener; - - public static AddItemsDialog newInstance() { - AddItemsDialog fragment = new AddItemsDialog(); - return fragment; - } - - public AddItemsDialog() { - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - - final Dialog dialog = new BottomSheetDialog(getActivity(), getTheme()); - - View view = LayoutInflater.from(getActivity()).inflate(R.layout.add_items_dialog, null); - dialog.setContentView(view); - dialog.setCancelable(true); - - view.findViewById(R.id.new_folder).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.dismiss(); - mListener.onOptionClick(R.id.new_folder, null); - } - }); - - view.findViewById(R.id.new_file).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.dismiss(); - mListener.onOptionClick(R.id.new_file, null); - } - }); - - // control dialog width on different devices - dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialogINterface) { - int width = (int) getResources().getDimension(R.dimen.bottom_sheet_dialog_width); - dialog.getWindow().setLayout( - width == 0 ? ViewGroup.LayoutParams.MATCH_PARENT : width, - ViewGroup.LayoutParams.MATCH_PARENT); - } - }); - - return dialog; - } - - public interface DialogListener { - void onOptionClick(int which, String path); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mListener = (DialogListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement DialogListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; - } - - -} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.kt b/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.kt new file mode 100644 index 0000000..c102858 --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/dialogs/AddItemsDialog.kt @@ -0,0 +1,73 @@ +package com.snatik.storage.app.dialogs + +import android.app.Activity +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.snatik.storage.app.R + +class AddItemsDialog : DialogFragment() { + + private var mListener: DialogListener? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + + val dialog = BottomSheetDialog(activity!!, theme) + + val view = LayoutInflater.from(activity).inflate(R.layout.add_items_dialog, null) + dialog.setContentView(view) + dialog.setCancelable(true) + + view.findViewById(R.id.new_folder).setOnClickListener { + dialog.dismiss() + mListener!!.onOptionClick(R.id.new_folder, null) + } + + view.findViewById(R.id.new_file).setOnClickListener { + dialog.dismiss() + mListener!!.onOptionClick(R.id.new_file, null) + } + + // control dialog width on different devices + dialog.setOnShowListener { + val width = resources.getDimension(R.dimen.bottom_sheet_dialog_width).toInt() + dialog.window!!.setLayout( + if (width == 0) ViewGroup.LayoutParams.MATCH_PARENT else width, + ViewGroup.LayoutParams.MATCH_PARENT) + } + + return dialog + } + + interface DialogListener { + fun onOptionClick(which: Int, path: String?) + } + + override fun onAttach(activity: Activity) { + super.onAttach(activity) + try { + mListener = activity as DialogListener + } catch (e: ClassCastException) { + throw ClassCastException("$activity must implement DialogListener") + } + + } + + override fun onDetach() { + super.onDetach() + mListener = null + } + + companion object { + + fun newInstance(): AddItemsDialog { + return AddItemsDialog() + } + } + + +} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.java deleted file mode 100644 index 44d9b5b..0000000 --- a/app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.snatik.storage.app.dialogs; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; - -import com.snatik.storage.Storage; -import com.snatik.storage.app.R; - -import java.io.File; - -public class ConfirmDeleteDialog extends DialogFragment { - - private final static String PATH = "path"; - private String mPath; - - public static ConfirmDeleteDialog newInstance(String path) { - ConfirmDeleteDialog fragment = new ConfirmDeleteDialog(); - Bundle args = new Bundle(); - args.putString(PATH, path); - fragment.setArguments(args); - return fragment; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - String msg; - final String path = getArguments().getString(PATH); - Storage storage = new Storage(getActivity()); - File file = storage.getFile(path); - if (file.isDirectory()) { - msg = "You are about to delete the folder with all it's content for real."; - } else { - msg = "You are about to delete the file"; - } - builder.setMessage(msg); - builder.setPositiveButton(R.string.label_delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ((ConfirmListener) getActivity()).onConfirmDelete(path); - } - }); - builder.setNegativeButton(R.string.label_cancel, null); - return builder.create(); - } - - public interface ConfirmListener { - void onConfirmDelete(String path); - } -} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.kt b/app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.kt new file mode 100644 index 0000000..d1f627a --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/dialogs/ConfirmDeleteDialog.kt @@ -0,0 +1,46 @@ +package com.snatik.storage.app.dialogs + +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.snatik.storage.Storage +import com.snatik.storage.app.R + +class ConfirmDeleteDialog : DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val builder = AlertDialog.Builder(activity) + + val msg: String + val path = arguments?.getString(PATH) + val storage = Storage(activity!!) + val file = storage.getFile(path!!) + msg = if (file.isDirectory) { + "You are about to delete the folder with all it's content for real." + } else { + "You are about to delete the file" + } + builder.setMessage(msg) + builder.setPositiveButton(R.string.label_delete) { _, _ -> (activity as ConfirmListener).onConfirmDelete(path) } + builder.setNegativeButton(R.string.label_cancel, null) + return builder.create() + } + + interface ConfirmListener { + fun onConfirmDelete(path: String?) + } + + companion object { + + private const val PATH = "path" + + fun newInstance(path: String): ConfirmDeleteDialog { + val fragment = ConfirmDeleteDialog() + val args = Bundle() + args.putString(PATH, path) + fragment.arguments = args + return fragment + } + } +} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.java deleted file mode 100644 index 44aae9c..0000000 --- a/app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.snatik.storage.app.dialogs; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; - -import com.snatik.storage.app.R; - -/** - * Created by sromku on June, 2017. - */ -public class NewFolderDialog extends DialogFragment { - - private NewFolderDialog.DialogListener mListener; - - public static NewFolderDialog newInstance() { - NewFolderDialog fragment = new NewFolderDialog(); - return fragment; - } - - public NewFolderDialog() { - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - final View view = LayoutInflater.from(getActivity()) - .inflate(R.layout.new_folder_dialog, (ViewGroup) getView(), false); - - // if text is empty, disable the dialog positive button - final EditText editText = (EditText) view.findViewById(R.id.name); - editText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void afterTextChanged(Editable editable) { - ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(editable != null && - editable.length() > 0); - } - }); - - builder.setTitle(R.string.new_folder); - builder.setView(view); - builder.setPositiveButton(R.string.label_save, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - mListener.onNewFolder(editText.getText().toString()); - } - }); - - final AlertDialog dialog = builder.create(); - view.post(new Runnable() { - @Override - public void run() { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - } - }); - dialog.setCancelable(false); - return dialog; - } - - public interface DialogListener { - void onNewFolder(String name); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mListener = (DialogListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement DialogListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; - } - -} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.kt b/app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.kt new file mode 100644 index 0000000..60c42af --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/dialogs/NewFolderDialog.kt @@ -0,0 +1,78 @@ +package com.snatik.storage.app.dialogs + +import android.app.Activity +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.DialogFragment +import com.snatik.storage.app.R + +/** + * Created by sromku on June, 2017. + */ +class NewFolderDialog : DialogFragment() { + + private var mListener: NewFolderDialog.DialogListener? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + + val builder = AlertDialog.Builder(activity) + + val view = LayoutInflater.from(activity) + .inflate(R.layout.new_folder_dialog, view as ViewGroup?, false) + + // if text is empty, disable the dialog positive button + val editText = view.findViewById(R.id.name) as EditText + editText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun afterTextChanged(editable: Editable?) { + (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editable != null && editable.length > 0 + } + }) + + builder.setTitle(R.string.new_folder) + builder.setView(view) + builder.setPositiveButton(R.string.label_save) { dialogInterface, i -> mListener!!.onNewFolder(editText.text.toString()) } + + val dialog = builder.create() + view.post { dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false } + dialog.setCancelable(false) + return dialog + } + + interface DialogListener { + fun onNewFolder(name: String) + } + + override fun onAttach(activity: Activity) { + super.onAttach(activity) + try { + mListener = activity as DialogListener + } catch (e: ClassCastException) { + throw ClassCastException("$activity must implement DialogListener") + } + + } + + override fun onDetach() { + super.onDetach() + mListener = null + } + + companion object { + + fun newInstance(): NewFolderDialog { + return NewFolderDialog() + } + } + +} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.java deleted file mode 100644 index fbfa245..0000000 --- a/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.snatik.storage.app.dialogs; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.EditText; - -import com.snatik.storage.app.R; - -/** - * Created by sromku on June, 2017. - */ -public class NewTextFileDialog extends DialogFragment { - - private NewTextFileDialog.DialogListener mListener; - - public static NewTextFileDialog newInstance() { - NewTextFileDialog fragment = new NewTextFileDialog(); - return fragment; - } - - public NewTextFileDialog() { - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - final View view = LayoutInflater.from(getActivity()) - .inflate(R.layout.new_file_dialog, (ViewGroup) getView(), false); - - // if text is empty, disable the dialog positive button - final EditText nameEditText = (EditText) view.findViewById(R.id.name); - final EditText contentEditText = (EditText) view.findViewById(R.id.content); - final CheckBox encryptCheckbox = (CheckBox) view.findViewById(R.id.checkbox); - - nameEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void afterTextChanged(Editable editable) { - ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(editable.length() > 0 && contentEditText.getText().length() > 0); - } - }); - - contentEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void afterTextChanged(Editable editable) { - ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(editable.length() > 0 && nameEditText.getText().length() > 0); - } - }); - - builder.setTitle(R.string.new_file); - builder.setView(view); - builder.setPositiveButton(R.string.label_save, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - mListener.onNewFile(nameEditText.getText().toString(), contentEditText.getText().toString(), encryptCheckbox.isChecked()); - } - }); - - final AlertDialog dialog = builder.create(); - view.post(new Runnable() { - @Override - public void run() { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - } - }); - dialog.setCancelable(false); - return dialog; - } - - public interface DialogListener { - void onNewFile(String name, String content, boolean encrypt); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mListener = (DialogListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement DialogListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; - } - -} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt b/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt new file mode 100644 index 0000000..6923f68 --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt @@ -0,0 +1,92 @@ +package com.snatik.storage.app.dialogs + +import android.app.Activity +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.EditText +import androidx.fragment.app.DialogFragment +import com.snatik.storage.app.R + +/** + * Created by sromku on June, 2017. + */ +class NewTextFileDialog : DialogFragment() { + + private var mListener: DialogListener? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + + val builder = AlertDialog.Builder(activity) + + val view = LayoutInflater.from(activity) + .inflate(R.layout.new_file_dialog, view as ViewGroup?, false) + + // if text is empty, disable the dialog positive button + val nameEditText = view.findViewById(R.id.name) as EditText + val contentEditText = view.findViewById(R.id.content) as EditText + val encryptCheckbox = view.findViewById(R.id.checkbox) as CheckBox + + nameEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun afterTextChanged(editable: Editable) { + (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editable.length > 0 && contentEditText.text.length > 0 + } + }) + + contentEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun afterTextChanged(editable: Editable) { + (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editable.length > 0 && nameEditText.text.length > 0 + } + }) + + builder.setTitle(R.string.new_file) + builder.setView(view) + builder.setPositiveButton(R.string.label_save) { dialogInterface, i -> mListener!!.onNewFile(nameEditText.text.toString(), contentEditText.text.toString(), encryptCheckbox.isChecked) } + + val dialog = builder.create() + view.post { dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false } + dialog.setCancelable(false) + return dialog + } + + interface DialogListener { + fun onNewFile(name: String, content: String, encrypt: Boolean) + } + + override fun onAttach(activity: Activity) { + super.onAttach(activity) + try { + mListener = activity as DialogListener + } catch (e: ClassCastException) { + throw ClassCastException("$activity must implement DialogListener") + } + + } + + override fun onDetach() { + super.onDetach() + mListener = null + } + + companion object { + + fun newInstance(): NewTextFileDialog { + return NewTextFileDialog() + } + } + +} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.java deleted file mode 100644 index 63b6a07..0000000 --- a/app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.snatik.storage.app.dialogs; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; - -import com.snatik.storage.Storage; -import com.snatik.storage.app.R; - -import java.io.File; - -/** - * Created by sromku on June, 2017. - */ -public class RenameDialog extends DialogFragment { - - private final static String PATH = "path"; - private DialogListener mListener; - private Storage mStorage; - - public static RenameDialog newInstance(String path) { - RenameDialog fragment = new RenameDialog(); - Bundle args = new Bundle(); - args.putString(PATH, path); - fragment.setArguments(args); - return fragment; - } - - public RenameDialog() { - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - - mStorage = new Storage(getActivity()); - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - final View view = LayoutInflater.from(getActivity()) - .inflate(R.layout.rename_dialog, (ViewGroup) getView(), false); - - // if text is empty, disable the dialog positive button - final EditText currentNameText = (EditText) view.findViewById(R.id.current_name); - String path = getArguments().getString(PATH); - - final File file = mStorage.getFile(path); - currentNameText.setText(file.getName()); - final String parent = file.getParent(); - - final EditText newNameText = (EditText) view.findViewById(R.id.new_name); - newNameText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - } - - @Override - public void afterTextChanged(Editable editable) { - ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(editable != null && - editable.length() > 0); - } - }); - - builder.setTitle(R.string.rename); - builder.setView(view); - builder.setPositiveButton(R.string.label_save, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - String newName = newNameText.getText().toString(); - String toPath = parent == null ? newName : parent + File.separator + newName; - mListener.onRename(file.getPath(), toPath); - } - }); - - final AlertDialog dialog = builder.create(); - view.post(new Runnable() { - @Override - public void run() { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - } - }); - dialog.setCancelable(false); - return dialog; - } - - public interface DialogListener { - void onRename(String fromPath, String toPath); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mListener = (DialogListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement DialogListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; - } - -} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.kt b/app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.kt new file mode 100644 index 0000000..a81b464 --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/dialogs/RenameDialog.kt @@ -0,0 +1,99 @@ +package com.snatik.storage.app.dialogs + +import android.app.Activity +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.fragment.app.DialogFragment +import com.snatik.storage.Storage +import com.snatik.storage.app.R +import java.io.File + +/** + * Created by sromku on June, 2017. + */ +class RenameDialog : DialogFragment() { + private var mListener: DialogListener? = null + private var mStorage: Storage? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + + mStorage = Storage(activity!!) + + val builder = AlertDialog.Builder(activity) + + val view = LayoutInflater.from(activity) + .inflate(R.layout.rename_dialog, view as ViewGroup?, false) + + // if text is empty, disable the dialog positive button + val currentNameText = view.findViewById(R.id.current_name) as EditText + val path = arguments?.getString(PATH) + + val file = mStorage!!.getFile(path!!) + currentNameText.setText(file.name) + val parent = file.parent + + val newNameText = view.findViewById(R.id.new_name) as EditText + newNameText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} + + override fun afterTextChanged(editable: Editable?) { + (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editable != null && editable.length > 0 + } + }) + + builder.setTitle(R.string.rename) + builder.setView(view) + builder.setPositiveButton(R.string.label_save) { dialogInterface, i -> + val newName = newNameText.text.toString() + val toPath = if (parent == null) newName else parent + File.separator + newName + mListener!!.onRename(file.path, toPath) + } + + val dialog = builder.create() + view.post { dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false } + dialog.setCancelable(false) + return dialog + } + + interface DialogListener { + fun onRename(fromPath: String, toPath: String) + } + + override fun onAttach(activity: Activity) { + super.onAttach(activity) + try { + mListener = activity as DialogListener + } catch (e: ClassCastException) { + throw ClassCastException("$activity must implement DialogListener") + } + + } + + override fun onDetach() { + super.onDetach() + mListener = null + } + + companion object { + + private val PATH = "path" + + fun newInstance(path: String): RenameDialog { + val fragment = RenameDialog() + val args = Bundle() + args.putString(PATH, path) + fragment.arguments = args + return fragment + } + } + +} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java b/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java deleted file mode 100644 index dffc702..0000000 --- a/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.snatik.storage.app.dialogs; - -import android.app.Activity; -import android.app.Dialog; -import android.app.DialogFragment; -import android.content.DialogInterface; -import android.os.Bundle; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.snatik.storage.Storage; -import com.snatik.storage.app.R; - -public class UpdateItemDialog extends DialogFragment { - - private final static String PATH = "path"; - private DialogListener mListener; - - public static UpdateItemDialog newInstance(String path) { - UpdateItemDialog fragment = new UpdateItemDialog(); - Bundle args = new Bundle(); - args.putString(PATH, path); - fragment.setArguments(args); - return fragment; - } - - public UpdateItemDialog() { - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - - final Dialog dialog = new BottomSheetDialog(getActivity(), getTheme()); - final String path = getArguments().getString(PATH); - boolean isDirectory = new Storage(getActivity()).getFile(path).isDirectory(); - View view = LayoutInflater.from(getActivity()).inflate(R.layout.update_item_dialog, null); - dialog.setContentView(view); - dialog.setCancelable(true); - - // title - TextView title = (TextView) view.findViewById(R.id.title); - title.setText(isDirectory ? getString(R.string.folder_options) : getString(R.string.file_options)); - - View rename = view.findViewById(R.id.rename); - View delete = view.findViewById(R.id.delete); - View move = view.findViewById(R.id.move); - View copy = view.findViewById(R.id.copy); - - rename.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.dismiss(); - mListener.onOptionClick(R.id.rename, path); - } - }); - - delete.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.dismiss(); - mListener.onOptionClick(R.id.delete, path); - } - }); - - if (!isDirectory) { - move.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.dismiss(); - mListener.onOptionClick(R.id.move, path); - } - }); - - copy.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.dismiss(); - mListener.onOptionClick(R.id.copy, path); - } - }); - } else { - move.setVisibility(View.GONE); - copy.setVisibility(View.GONE); - } - - // control dialog width on different devices - dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialogINterface) { - int width = (int) getResources().getDimension(R.dimen.bottom_sheet_dialog_width); - dialog.getWindow().setLayout( - width == 0 ? ViewGroup.LayoutParams.MATCH_PARENT : width, - ViewGroup.LayoutParams.MATCH_PARENT); - } - }); - - return dialog; - } - - public interface DialogListener { - void onOptionClick(int which, String path); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mListener = (DialogListener) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() - + " must implement DialogListener"); - } - } - - @Override - public void onDetach() { - super.onDetach(); - mListener = null; - } - - -} diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.kt b/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.kt new file mode 100644 index 0000000..63ce66d --- /dev/null +++ b/app/src/main/java/com/snatik/storage/app/dialogs/UpdateItemDialog.kt @@ -0,0 +1,105 @@ +package com.snatik.storage.app.dialogs + +import android.app.Activity +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.DialogFragment +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.snatik.storage.Storage +import com.snatik.storage.app.R + +class UpdateItemDialog : DialogFragment() { + private var mListener: DialogListener? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + + val dialog = BottomSheetDialog(activity!!, theme) + val path = arguments?.getString(PATH) + val isDirectory = Storage(activity!!).getFile(path!!).isDirectory + val view = LayoutInflater.from(activity).inflate(R.layout.update_item_dialog, null) + dialog.setContentView(view) + dialog.setCancelable(true) + + // title + val title = view.findViewById(R.id.title) as TextView + title.text = if (isDirectory) getString(R.string.folder_options) else getString(R.string.file_options) + + val rename = view.findViewById(R.id.rename) + val delete = view.findViewById(R.id.delete) + val move = view.findViewById(R.id.move) + val copy = view.findViewById(R.id.copy) + + rename.setOnClickListener { + dialog.dismiss() + mListener!!.onOptionClick(R.id.rename, path) + } + + delete.setOnClickListener { + dialog.dismiss() + mListener!!.onOptionClick(R.id.delete, path) + } + + if (!isDirectory) { + move.setOnClickListener { + dialog.dismiss() + mListener!!.onOptionClick(R.id.move, path) + } + + copy.setOnClickListener { + dialog.dismiss() + mListener!!.onOptionClick(R.id.copy, path) + } + } else { + move.visibility = View.GONE + copy.visibility = View.GONE + } + + // control dialog width on different devices + dialog.setOnShowListener { + val width = resources.getDimension(R.dimen.bottom_sheet_dialog_width).toInt() + dialog.window!!.setLayout( + if (width == 0) ViewGroup.LayoutParams.MATCH_PARENT else width, + ViewGroup.LayoutParams.MATCH_PARENT) + } + + return dialog + } + + interface DialogListener { + fun onOptionClick(which: Int, path: String?) + } + + override fun onAttach(activity: Activity) { + super.onAttach(activity) + try { + mListener = activity as DialogListener + } catch (e: ClassCastException) { + throw ClassCastException("$activity must implement DialogListener") + } + + } + + override fun onDetach() { + super.onDetach() + mListener = null + } + + companion object { + + private val PATH = "path" + + fun newInstance(path: String): UpdateItemDialog { + val fragment = UpdateItemDialog() + val args = Bundle() + args.putString(PATH, path) + fragment.arguments = args + return fragment + } + } + + +} From 8ef6b136d8a22f4eaab9f9dd8cab5a1490ddc7fa Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Sun, 4 Aug 2019 12:08:47 +0430 Subject: [PATCH 5/9] remove test --- .../com/snatik/storage/StorageTestCase.java | 258 ------------------ 1 file changed, 258 deletions(-) delete mode 100644 storage/src/androidTest/java/com/snatik/storage/StorageTestCase.java diff --git a/storage/src/androidTest/java/com/snatik/storage/StorageTestCase.java b/storage/src/androidTest/java/com/snatik/storage/StorageTestCase.java deleted file mode 100644 index ba71cc3..0000000 --- a/storage/src/androidTest/java/com/snatik/storage/StorageTestCase.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.snatik.storage; - -import android.test.InstrumentationTestCase; - -public class StorageTestCase extends InstrumentationTestCase { - -// private StorageDel mStorage; -// -// private final static String DIR_NAME = "Storage Test"; -// private final static String FILE_NAME = "test.txt"; -// private final static String FILE_CONTENT = "some file content"; -// -// private final static String FILE_SECURE_NAME = "test_secure.txt"; -// private final static String FILE_SECURE_CONTENT = "something very secret"; -// -// @Override -// protected void setUp() throws Exception { -// Context context = getInstrumentation().getContext(); -// -// // set a storage -// mStorage = null; -// if (SimpleStorageDel.isExternalStorageWritable()) { -// mStorage = SimpleStorageDel.getExternalStorage(Environment.DIRECTORY_PICTURES); -// } else { -// mStorage = SimpleStorageDel.getInternalStorage(context); -// } -// } -// -// @Override -// protected void tearDown() throws Exception { -// -// // delete dir if exists -// mStorage.deleteDirectory(DIR_NAME); -// -// super.tearDown(); -// } -// -// /** -// * Create directory and check that the directory was created -// */ -// public void testCreateDirectory() { -// -// // TEST: create dir -// boolean wasCreated = mStorage.createDirectory(DIR_NAME, true); -// assertEquals(true, wasCreated); -// -// } -// -// /** -// * Create directory and check that the directory was created -// */ -// public void testCreateFile() { -// -// // create dir -// testCreateDirectory(); -// -// // TEST: create file -// boolean wasCreated = mStorage.createFile(DIR_NAME, FILE_NAME, FILE_CONTENT); -// assertEquals(true, wasCreated); -// -// } -// -// /** -// * Create directory and check that the directory was created -// */ -// public void testReadFile() { -// -// // create file with content -// testCreateFile(); -// -// // TEST: read the content and test -// String content = mStorage.readTextFile(DIR_NAME, FILE_NAME); -// assertEquals(FILE_CONTENT, content); -// -// } -// -// /** -// * Create directory and check that the directory was created -// */ -// public void testAppendFile() { -// -// // create file with content -// testCreateFile(); -// -// String newData = "new added data"; -// -// // TEST: append new data and test -// mStorage.appendFile(DIR_NAME, FILE_NAME, newData); -// String content = mStorage.readTextFile(DIR_NAME, FILE_NAME); -// assertTrue(content.contains(newData)); -// } -// -// /** -// * Create file with encrypted data -// */ -// public void testEncryptContent() { -// -// // create dir -// testCreateDirectory(); -// -// // set encryption -// final String IVX = "abcdefghijklmnop"; -// final String SECRET_KEY = "secret1234567890"; -// -// EncryptConfiguration configuration = new EncryptConfiguration.Builder().setEncryptContent(IVX, SECRET_KEY).build(); -// SimpleStorageDel.updateConfiguration(configuration); -// -// // create file -// mStorage.createFile(DIR_NAME, FILE_SECURE_NAME, FILE_SECURE_CONTENT); -// -// // TEST: check the content of the file to be encrypted -// String content = mStorage.readTextFile(DIR_NAME, FILE_SECURE_NAME); -// assertEquals(FILE_SECURE_CONTENT, content); -// -// // TEST: check after reseting the configuration to default -// SimpleStorageDel.resetConfiguration(); -// content = mStorage.readTextFile(DIR_NAME, FILE_SECURE_NAME); -// assertNotSame(FILE_SECURE_CONTENT, content); -// } -// -// public void testRename() { -// -// // create file -// testCreateFile(); -// -// // rename -// File file = mStorage.getFile(DIR_NAME, FILE_NAME); -// mStorage.rename(file, "new_" + FILE_NAME); -// boolean isExist = mStorage.isFileExist(DIR_NAME, "new_" + FILE_NAME); -// assertEquals(true, isExist); -// } -// -// public void testCopy() { -// -// // create file -// testCreateFile(); -// -// // copy file -// File fileSource = mStorage.getFile(DIR_NAME, FILE_NAME); -// mStorage.copy(fileSource, DIR_NAME, FILE_NAME + "C"); -// -// // validate existence -// boolean isExist = mStorage.isFileExist(DIR_NAME, FILE_NAME + "C"); -// assertEquals(true, isExist); -// -// // validate content -// assertEquals(mStorage.readTextFile(DIR_NAME, FILE_NAME), mStorage.readTextFile(DIR_NAME, FILE_NAME + "C")); -// } -// -// public void testMove() { -// -// // create file -// testCreateFile(); -// -// // copy file -// File fileSource = mStorage.getFile(DIR_NAME, FILE_NAME); -// mStorage.move(fileSource, DIR_NAME, FILE_NAME + "C"); -// -// // validate existence destination -// boolean isExist = mStorage.isFileExist(DIR_NAME, FILE_NAME + "C"); -// assertEquals(true, isExist); -// -// // validate existence source (it shouldn't exist) -// isExist = mStorage.isFileExist(DIR_NAME, FILE_NAME); -// assertEquals(false, isExist); -// } -// -// public void testGetFilesByRegex() { -// -// // create dir -// testCreateDirectory(); -// -// // create 5 files -// mStorage.createFile(DIR_NAME, "file1.txt", ""); -// mStorage.createFile(DIR_NAME, "file2.txt", ""); -// mStorage.createFile(DIR_NAME, "file3.log", ""); -// mStorage.createFile(DIR_NAME, "file4.log", ""); -// mStorage.createFile(DIR_NAME, "file5.txt", ""); -// -// // get files that ends with *.txt only. should be 3 of them -// String TXT_PATTERN = "([^\\s]+(\\.(?i)(txt))$)"; -// List filesTexts = mStorage.getFiles(DIR_NAME, TXT_PATTERN); -// assertEquals(3, filesTexts.size()); -// -// // create more log files and check for *.log. should be 4 of them -// String LOG_PATTERN = "([^\\s]+(\\.(?i)(log))$)"; -// mStorage.createFile(DIR_NAME, "file6.log", ""); -// mStorage.createFile(DIR_NAME, "file7.log", ""); -// List filesLogs = mStorage.getFiles(DIR_NAME, LOG_PATTERN); -// assertEquals(4, filesLogs.size()); -// -// // create dir and add files to dir. check again for *.log files. should -// // be 4 of them. -// mStorage.createDirectory(DIR_NAME + File.separator + "New Dir"); -// mStorage.createFile(DIR_NAME + File.separator + "New Dir", "file8.log", ""); -// mStorage.createFile(DIR_NAME + File.separator + "New Dir", "file9.log", ""); -// mStorage.createFile(DIR_NAME + File.separator + "New Dir", "file10.txt", ""); -// List filesLogs2 = mStorage.getFiles(DIR_NAME, LOG_PATTERN); -// assertEquals(4, filesLogs2.size()); -// -// // check inside new dir for *.log files. should be 2 of them -// List filesLogs3 = mStorage.getFiles(DIR_NAME + File.separator + "New Dir", LOG_PATTERN); -// assertEquals(2, filesLogs3.size()); -// } -// -// public void testGetFilesByOrder() { -// -// // create dir -// testCreateDirectory(); -// -// // TEST - Order by SIZE -// mStorage.createFile(DIR_NAME, "file1.txt", "111222333"); -// mStorage.createFile(DIR_NAME, "file2.txt", ""); -// mStorage.createFile(DIR_NAME, "file3.log", "111"); -// List filesSize = mStorage.getFiles(DIR_NAME, OrderType.SIZE); -// assertEquals("file2.txt", filesSize.get(0).getName()); -// assertEquals("file3.log", filesSize.get(1).getName()); -// assertEquals("file1.txt", filesSize.get(2).getName()); -// -// // refresh directory -// mStorage.deleteDirectory(DIR_NAME); -// testCreateDirectory(); -// -// // TEST - Order by NAME -// mStorage.createFile(DIR_NAME, "bbb.txt", "111222333"); -// mStorage.createFile(DIR_NAME, "ccc.txt", ""); -// mStorage.createFile(DIR_NAME, "aaa.log", "111"); -// List filesName = mStorage.getFiles(DIR_NAME, OrderType.NAME); -// assertEquals("aaa.log", filesName.get(0).getName()); -// assertEquals("bbb.txt", filesName.get(1).getName()); -// assertEquals("ccc.txt", filesName.get(2).getName()); -// -// // refresh directory -// mStorage.deleteDirectory(DIR_NAME); -// testCreateDirectory(); -// -// // TEST - Order by DATE -// mStorage.createFile(DIR_NAME, "aaa.txt", "123456789"); -// sleep(1000); -// mStorage.createFile(DIR_NAME, "bbb.txt", "123456789"); -// sleep(1000); -// mStorage.createFile(DIR_NAME, "ccc.log", "123456789"); -// sleep(1000); -// mStorage.appendFile(DIR_NAME, "bbb.txt", "some new content"); -// List files = mStorage.getFiles(DIR_NAME, OrderType.DATE); -// assertEquals("bbb.txt", files.get(0).getName()); -// assertEquals("ccc.log", files.get(1).getName()); -// assertEquals("aaa.txt", files.get(2).getName()); -// } -// -// private void sleep(long millis) { -// try { -// Thread.sleep(millis); -// } catch (InterruptedException e) { -// e.printStackTrace(); -// } -// } -} From 70c37720f62acad66cca842fc22825c97fd58432 Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Sun, 4 Aug 2019 13:19:35 +0430 Subject: [PATCH 6/9] refactor --- .../com/snatik/storage/app/FilesAdapter.kt | 19 ++++------------ .../com/snatik/storage/app/MainActivity.kt | 22 +++++++++---------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt index 13a2d92..6a741db 100644 --- a/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt +++ b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt @@ -19,11 +19,7 @@ class FilesAdapter(context: Context) : RecyclerView.Adapter? = null private var mListener: OnFileItemListener? = null - private val mStorage: Storage - - init { - mStorage = Storage(context) - } + private val mStorage: Storage = Storage(context) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.file_line_view, parent, false) @@ -66,16 +62,9 @@ class FilesAdapter(context: Context) : RecyclerView.Adapter(R.id.name) as TextView - mSize = v.findViewById(R.id.size) as TextView - mIcon = v.findViewById(R.id.icon) as ImageView - } + var mName: TextView = v.findViewById(R.id.name) as TextView + var mSize: TextView = v.findViewById(R.id.size) as TextView + var mIcon: ImageView = v.findViewById(R.id.icon) as ImageView } interface OnFileItemListener { diff --git a/app/src/main/java/com/snatik/storage/app/MainActivity.kt b/app/src/main/java/com/snatik/storage/app/MainActivity.kt index bbce758..5ea6d18 100644 --- a/app/src/main/java/com/snatik/storage/app/MainActivity.kt +++ b/app/src/main/java/com/snatik/storage/app/MainActivity.kt @@ -111,16 +111,16 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt } private fun showPathMenu() { - val popupmenu = PopupMenu(this, mPathView) - val inflater = popupmenu.menuInflater - inflater.inflate(R.menu.path_menu, popupmenu.menu) + val popUpMenu = PopupMenu(this, mPathView) + val inflater = popUpMenu.menuInflater + inflater.inflate(R.menu.path_menu, popUpMenu.menu) - popupmenu.menu.findItem(R.id.go_internal).isVisible = !mInternal - popupmenu.menu.findItem(R.id.go_external).isVisible = mInternal + popUpMenu.menu.findItem(R.id.go_internal).isVisible = !mInternal + popUpMenu.menu.findItem(R.id.go_external).isVisible = mInternal - popupmenu.show() + popUpMenu.show() - popupmenu.setOnMenuItemClickListener { item -> + popUpMenu.setOnMenuItemClickListener { item -> when (item.itemId) { R.id.go_up -> { val previousPath = previousPath @@ -288,7 +288,7 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { showFiles(mStorage!!.externalStorageDirectory) } else { finish() @@ -297,9 +297,9 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt companion object { - private val PERMISSION_REQUEST_CODE = 1000 - private val IVX = "abcdefghijklmnop" - private val SECRET_KEY = "secret1234567890" + private const val PERMISSION_REQUEST_CODE = 1000 + private const val IVX = "abcdefghijklmnop" + private const val SECRET_KEY = "secret1234567890" private val SALT = "0000111100001111".toByteArray() } } From 82cd5162982320392701337bbd2e441aba601508 Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Sun, 4 Aug 2019 13:20:32 +0430 Subject: [PATCH 7/9] add get directories function --- .../main/java/com/snatik/storage/Storage.kt | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/storage/src/main/java/com/snatik/storage/Storage.kt b/storage/src/main/java/com/snatik/storage/Storage.kt index 5966fa2..5a05add 100644 --- a/storage/src/main/java/com/snatik/storage/Storage.kt +++ b/storage/src/main/java/com/snatik/storage/Storage.kt @@ -6,25 +6,11 @@ import android.os.Build import android.os.Environment import android.os.StatFs import android.util.Log - import com.snatik.storage.helpers.ImmutablePair import com.snatik.storage.helpers.SizeUnit import com.snatik.storage.security.SecurityUtil - -import java.io.ByteArrayOutputStream -import java.io.Closeable -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.FilenameFilter -import java.io.IOException -import java.io.OutputStream -import java.nio.channels.FileChannel -import java.util.ArrayList -import java.util.Arrays -import java.util.LinkedList - +import java.io.* +import java.util.* import javax.crypto.Cipher /** @@ -131,12 +117,12 @@ class Storage(private val mContext: Context) { fun readFile(path: String): ByteArray? { val stream: FileInputStream - try { + return try { stream = FileInputStream(File(path)) - return readFile(stream) + readFile(stream) } catch (e: FileNotFoundException) { Log.e(TAG, "Failed to read file to input stream", e) - return null + null } } @@ -181,16 +167,19 @@ class Storage(private val mContext: Context) { @JvmOverloads fun getFiles(dir: String, matchRegex: String? = null): List? { val file = File(dir) - var files: Array? = null - files = if (matchRegex != null) { + val files = if (matchRegex != null) { val filter = FilenameFilter { _, fileName -> fileName.matches(matchRegex.toRegex()) } file.listFiles(filter) } else { file.listFiles() } - return if (files != null) Arrays.asList(*files) else null + return if (files != null) listOf(*files) else null } + @JvmOverloads + fun getDirectories(dir: String, matchRegex: String? = null): List? = + getFiles(dir, matchRegex)?.filter { it.isDirectory } + fun getFile(path: String): File { return File(path) } @@ -391,14 +380,12 @@ class Storage(private val mContext: Context) { companion object { - private val TAG = "Storage" + private const val TAG = "Storage" val isExternalWritable: Boolean get() { val state = Environment.getExternalStorageState() - return if (Environment.MEDIA_MOUNTED == state) { - true - } else false + return Environment.MEDIA_MOUNTED == state } } } From 8033ae2936e9dbf077f1c2b3d300ff9c2861a2ea Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Mon, 5 Aug 2019 08:06:11 +0430 Subject: [PATCH 8/9] change list to grid and improve UX --- .../com/snatik/storage/app/FilesAdapter.kt | 14 ++++---- .../java/com/snatik/storage/app/Helper.kt | 14 ++++---- .../com/snatik/storage/app/MainActivity.kt | 7 ++-- .../storage/app/dialogs/NewTextFileDialog.kt | 2 +- .../res/drawable/ic_file_primary_24dp.xml | 2 +- .../res/drawable/ic_folder_primary_24dp.xml | 2 +- app/src/main/res/layout/file_line_view.xml | 32 ++++++++++--------- .../snatik/storage/helpers/ImmutablePair.kt | 16 ++-------- .../com/snatik/storage/helpers/OrderType.kt | 20 ++++++++++-- .../com/snatik/storage/helpers/SizeUnit.kt | 2 +- .../storage/security/CipherAlgorithmType.kt | 2 +- .../snatik/storage/security/CipherModeType.kt | 2 +- .../storage/security/CipherPaddingType.kt | 2 +- 13 files changed, 63 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt index 6a741db..02b1a09 100644 --- a/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt +++ b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt @@ -35,13 +35,10 @@ class FilesAdapter(context: Context) : RecyclerView.Adapter -1) { - url = url.substring(0, url.indexOf("?")) + var newUrl = url + if (newUrl.indexOf("?") > -1) { + newUrl = newUrl.substring(0, newUrl.indexOf("?")) } - if (url.lastIndexOf(".") == -1) { - return null + return if (newUrl.lastIndexOf(".") == -1) { + null } else { - var ext = url.substring(url.lastIndexOf(".") + 1) + var ext = newUrl.substring(newUrl.lastIndexOf(".") + 1) if (ext.indexOf("%") > -1) { ext = ext.substring(0, ext.indexOf("%")) } if (ext.indexOf("/") > -1) { ext = ext.substring(0, ext.indexOf("/")) } - return ext.toLowerCase() + ext.toLowerCase() } } diff --git a/app/src/main/java/com/snatik/storage/app/MainActivity.kt b/app/src/main/java/com/snatik/storage/app/MainActivity.kt index 5ea6d18..643ac95 100644 --- a/app/src/main/java/com/snatik/storage/app/MainActivity.kt +++ b/app/src/main/java/com/snatik/storage/app/MainActivity.kt @@ -14,6 +14,7 @@ import androidx.appcompat.widget.Toolbar import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.snatik.storage.EncryptConfiguration @@ -94,7 +95,7 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt mMovingPath = null } - val layoutManager = LinearLayoutManager(this) + val layoutManager = GridLayoutManager(this, 3) mRecyclerView!!.layoutManager = layoutManager mFilesAdapter = FilesAdapter(applicationContext) mFilesAdapter!!.setListener(this) @@ -229,10 +230,10 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt } } - override fun onNewFile(name: String, content: String, encrypted: Boolean) { + override fun onNewFile(name: String, content: String, encrypt: Boolean) { val currentPath = currentPath val folderPath = currentPath + File.separator + name - if (encrypted) { + if (encrypt) { mStorage!!.setEncryptConfiguration(EncryptConfiguration.Builder() .setEncryptContent(IVX, SECRET_KEY, SALT) .build()) diff --git a/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt b/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt index 6923f68..c36209c 100644 --- a/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt +++ b/app/src/main/java/com/snatik/storage/app/dialogs/NewTextFileDialog.kt @@ -55,7 +55,7 @@ class NewTextFileDialog : DialogFragment() { builder.setTitle(R.string.new_file) builder.setView(view) - builder.setPositiveButton(R.string.label_save) { dialogInterface, i -> mListener!!.onNewFile(nameEditText.text.toString(), contentEditText.text.toString(), encryptCheckbox.isChecked) } + builder.setPositiveButton(R.string.label_save) { _, _ -> mListener!!.onNewFile(nameEditText.text.toString(), contentEditText.text.toString(), encryptCheckbox.isChecked) } val dialog = builder.create() view.post { dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false } diff --git a/app/src/main/res/drawable/ic_file_primary_24dp.xml b/app/src/main/res/drawable/ic_file_primary_24dp.xml index 0fb747b..a666297 100644 --- a/app/src/main/res/drawable/ic_file_primary_24dp.xml +++ b/app/src/main/res/drawable/ic_file_primary_24dp.xml @@ -4,6 +4,6 @@ android:viewportWidth="24.0" android:viewportHeight="24.0"> diff --git a/app/src/main/res/drawable/ic_folder_primary_24dp.xml b/app/src/main/res/drawable/ic_folder_primary_24dp.xml index 5889397..9e40498 100644 --- a/app/src/main/res/drawable/ic_folder_primary_24dp.xml +++ b/app/src/main/res/drawable/ic_folder_primary_24dp.xml @@ -4,6 +4,6 @@ android:viewportHeight="24.0" android:viewportWidth="24.0"> diff --git a/app/src/main/res/layout/file_line_view.xml b/app/src/main/res/layout/file_line_view.xml index d06cc8d..c54e728 100644 --- a/app/src/main/res/layout/file_line_view.xml +++ b/app/src/main/res/layout/file_line_view.xml @@ -1,41 +1,43 @@ + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/selectableItemBackground" + android:gravity="center" + android:orientation="vertical" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + android:layout_width="72dp" + android:layout_height="72dp" + android:layout_marginTop="8dp" + android:src="@drawable/ic_folder_primary_24dp" /> + android:textSize="16sp" /> + android:gravity="center" + android:textSize="14sp" /> diff --git a/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt b/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt index e2a8af7..2174da2 100644 --- a/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt +++ b/storage/src/main/java/com/snatik/storage/helpers/ImmutablePair.kt @@ -6,20 +6,10 @@ import java.io.Serializable * @param * @param */ -class ImmutablePair : Serializable { +class ImmutablePair(element1: T, element2: S) : Serializable { - val element1: T? - val element2: S? - - constructor() { - element1 = null - element2 = null - } - - constructor(element1: T, element2: S) { - this.element1 = element1 - this.element2 = element2 - } + val element1: T? = element1 + val element2: S? = element2 override fun equals(`object`: Any?): Boolean { if (`object` !is ImmutablePair<*, *>) { diff --git a/storage/src/main/java/com/snatik/storage/helpers/OrderType.kt b/storage/src/main/java/com/snatik/storage/helpers/OrderType.kt index 7125aa8..666351c 100644 --- a/storage/src/main/java/com/snatik/storage/helpers/OrderType.kt +++ b/storage/src/main/java/com/snatik/storage/helpers/OrderType.kt @@ -21,12 +21,26 @@ enum class OrderType { val comparator: Comparator? get() { when (ordinal) { - 0 -> return Comparator { lhs, rhs -> lhs.name.compareTo(rhs.name) } - 1 -> return Comparator { lhs, rhs -> (rhs.lastModified() - lhs.lastModified()).toInt() } - 2 -> return Comparator { lhs, rhs -> (lhs.length() - rhs.length()).toInt() } + 0 -> return comparatorForName() + 1 -> return comparatorForDate() + 2 -> return comparatorForSize() else -> { } } return null } + + private fun comparatorForName(): Comparator { + return Comparator { lhs, rhs -> lhs.name.compareTo(rhs.name) } + } + + private fun comparatorForDate(): Comparator { + return Comparator { lhs, rhs -> (rhs.lastModified() - lhs.lastModified()).toInt() } + } + + private fun comparatorForSize(): Comparator { + return Comparator { lhs, rhs -> (lhs.length() - rhs.length()).toInt() } + } + + } diff --git a/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt b/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt index c27a816..a1a26d2 100644 --- a/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt +++ b/storage/src/main/java/com/snatik/storage/helpers/SizeUnit.kt @@ -3,7 +3,7 @@ package com.snatik.storage.helpers import java.text.DecimalFormat const val BYTES = 1024L -enum class SizeUnit private constructor(private val inBytes: Long) { +enum class SizeUnit(private val inBytes: Long) { B(1), KB(BYTES), MB(BYTES * BYTES), diff --git a/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt b/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt index 98e91f0..7ea3451 100644 --- a/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt +++ b/storage/src/main/java/com/snatik/storage/security/CipherAlgorithmType.kt @@ -15,7 +15,7 @@ import javax.crypto.CipherOutputStream * * @author Roman Kushnarenko - sromku (sromku@gmail.com) */ -enum class CipherAlgorithmType private constructor( +enum class CipherAlgorithmType( /** * Get the algorithm name of the enum value. * diff --git a/storage/src/main/java/com/snatik/storage/security/CipherModeType.kt b/storage/src/main/java/com/snatik/storage/security/CipherModeType.kt index 6063fb5..12ad967 100644 --- a/storage/src/main/java/com/snatik/storage/security/CipherModeType.kt +++ b/storage/src/main/java/com/snatik/storage/security/CipherModeType.kt @@ -5,7 +5,7 @@ package com.snatik.storage.security * * @author Roman Kushnarenko - sromku (sromku@gmail.com) */ -enum class CipherModeType private constructor( +enum class CipherModeType( /** * Get the algorithm name of the enum value. * diff --git a/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt b/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt index 6efc717..a4d953a 100644 --- a/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt +++ b/storage/src/main/java/com/snatik/storage/security/CipherPaddingType.kt @@ -3,7 +3,7 @@ package com.snatik.storage.security /** * @author Roman Kushnarenko - sromku (sromku@gmail.com) */ -enum class CipherPaddingType private constructor( +enum class CipherPaddingType( /** * Get the algorithm name of the enum value. * From 491e563b042ee6c3c4fb6e04ecd008fd199964b1 Mon Sep 17 00:00:00 2001 From: Ali Shariat Date: Mon, 5 Aug 2019 08:52:34 +0430 Subject: [PATCH 9/9] change show path strategy --- .../com/snatik/storage/app/FilesAdapter.kt | 2 +- .../com/snatik/storage/app/MainActivity.kt | 29 ++++++++++++++----- app/src/main/res/layout/activity_main.xml | 13 +++++---- app/src/main/res/layout/content_main.xml | 25 +++++----------- app/src/main/res/values/colors.xml | 6 ++-- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt index 02b1a09..2308690 100644 --- a/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt +++ b/app/src/main/java/com/snatik/storage/app/FilesAdapter.kt @@ -55,7 +55,7 @@ class FilesAdapter(context: Context) : RecyclerView.Adapter) { + fun setFiles(files: List?) { mFiles = files } diff --git a/app/src/main/java/com/snatik/storage/app/MainActivity.kt b/app/src/main/java/com/snatik/storage/app/MainActivity.kt index 643ac95..683e77b 100644 --- a/app/src/main/java/com/snatik/storage/app/MainActivity.kt +++ b/app/src/main/java/com/snatik/storage/app/MainActivity.kt @@ -15,7 +15,6 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.snatik.storage.EncryptConfiguration import com.snatik.storage.Storage @@ -23,6 +22,7 @@ import com.snatik.storage.app.Helper.fileExt import com.snatik.storage.app.dialogs.* import com.snatik.storage.helpers.OrderType import com.snatik.storage.helpers.SizeUnit +import kotlinx.android.synthetic.main.activity_main.* import java.io.File import java.util.* @@ -31,15 +31,14 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt private var mFilesAdapter: FilesAdapter? = null private var mStorage: Storage? = null private var mPathView: TextView? = null + private var currentPath: String = "" private var mMovingText: TextView? = null private var mCopy: Boolean = false private var mMovingLayout: View? = null private var mTreeSteps = 0 private var mMovingPath: String? = null private var mInternal = false - - private val currentPath: String - get() = mPathView!!.text.toString() + lateinit var toolbar: Toolbar private val previousPath: String get() { @@ -58,7 +57,7 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt mStorage = Storage(applicationContext) setContentView(R.layout.activity_main) - val toolbar = findViewById(R.id.toolbar) + toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) mRecyclerView = findViewById(R.id.recycler) @@ -142,15 +141,31 @@ class MainActivity : AppCompatActivity(), FilesAdapter.OnFileItemListener, AddIt } private fun showFiles(path: String) { - mPathView!!.text = path + toolbar.title = File(path).name + currentPath = path + + mPathView!!.text = getPathString(path) val files = mStorage!!.getFiles(path) if (files != null) { Collections.sort(files, OrderType.NAME.comparator) } - mFilesAdapter!!.setFiles(files!!) + mFilesAdapter!!.setFiles(files) mFilesAdapter!!.notifyDataSetChanged() } + private fun getPathString(path: String): CharSequence? { + val file = File(path) + val sb = StringBuilder() + if (file.parentFile != null && file.parentFile!!.parentFile != null) { + sb.append("...") + .append(" > ") + } + sb.append(file.parentFile!!.name) + .append(" > ") + .append(file.name) + return sb.toString() + } + override fun onClick(file: File) { if (file.isDirectory) { diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 31691aa..8d5ba91 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ - + android:theme="@style/AppTheme.AppBarOverlay" + app:elevation="0dp"> + app:popupTheme="@style/AppTheme.PopupOverlay" + app:titleTextColor="#000000" /> - + + android:src="@drawable/ic_add_white_24dp" /> diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 8c629c1..11b34ab 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -1,10 +1,10 @@ - @@ -27,8 +27,7 @@ android:layout_height="wrap_content" android:layout_margin="16dp" android:text="Moving: file.txt" - android:textColor="@android:color/white" - android:textSize="14sp"/> + android:textSize="14sp" /> + android:text="@string/decline" />