diff --git a/.github/workflows/publish-test-results.yaml b/.github/workflows/publish-test-results.yaml new file mode 100644 index 0000000000..8a66a35c6e --- /dev/null +++ b/.github/workflows/publish-test-results.yaml @@ -0,0 +1,31 @@ +name: Publish Test Results + +on: + workflow_run: + workflows: ["Android Build CI", "Android Feature CI"] + types: + - completed +permissions: {} + +jobs: + publish_test_results: + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure' + + permissions: + checks: write + pull-requests: write + actions: read + steps: + - name: Download and Extract Artifacts + uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d + with: + run_id: ${{ github.event.workflow_run.id }} + path: artifacts + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2.16.1 + with: + commit: ${{ github.event.workflow_run.head_sha }} + event_file: artifacts/Event File/event.json + event_name: ${{ github.event.workflow_run.event }} + files: "artifacts/**/*.xml" diff --git a/app/build.gradle b/app/build.gradle index 3c380ce487..fcb6402579 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -145,6 +145,8 @@ dependencies { implementation libs.androidX.constraintLayout implementation libs.androidX.multidex //Multiple dex files implementation libs.androidX.biometric + implementation libs.androidX.lifecycle + implementation libs.androidX.lifecycle.process implementation libs.room.runtime implementation libs.room.rxjava2 diff --git a/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt b/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt index 5083db921f..5d2128f3dd 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt +++ b/app/src/androidTest/java/com/amaze/filemanager/asynchronous/services/ftp/FtpServiceEspressoTest.kt @@ -146,7 +146,6 @@ class FtpServiceEspressoTest { .putString( FtpService.KEY_PREFERENCE_PASSWORD, PasswordUtil.encryptPassword( - ApplicationProvider.getApplicationContext(), "passw0rD", ), ) diff --git a/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java b/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java index 7b7728b0e7..053bd80030 100644 --- a/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java +++ b/app/src/androidTest/java/com/amaze/filemanager/filesystem/files/CryptUtilTest.java @@ -22,29 +22,19 @@ import static org.junit.Assert.assertEquals; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import com.amaze.filemanager.BuildConfig; import com.amaze.filemanager.utils.PasswordUtil; -import android.content.Context; import android.util.Base64; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; @RunWith(AndroidJUnit4.class) public class CryptUtilTest { - private Context context; - - @Before - public void setUp() { - context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - } - @Test public void testIvValueIsCorrect() { assertEquals("LxbHiJhhUXcj", BuildConfig.CRYPTO_IV); @@ -53,8 +43,7 @@ public void testIvValueIsCorrect() { @Test public void testEncryptDecrypt() throws Exception { String password = "hackme"; - String encrypted = PasswordUtil.INSTANCE.encryptPassword(context, password, Base64.URL_SAFE); - assertEquals( - password, PasswordUtil.INSTANCE.decryptPassword(context, encrypted, Base64.URL_SAFE)); + String encrypted = PasswordUtil.INSTANCE.encryptPassword(password, Base64.URL_SAFE); + assertEquals(password, PasswordUtil.INSTANCE.decryptPassword(encrypted, Base64.URL_SAFE)); } } diff --git a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java index 93908a81c3..4a26e8905d 100644 --- a/app/src/main/java/com/amaze/filemanager/application/AppConfig.java +++ b/app/src/main/java/com/amaze/filemanager/application/AppConfig.java @@ -41,6 +41,7 @@ import com.amaze.filemanager.fileoperations.exceptions.ShellNotRunningException; import com.amaze.filemanager.fileoperations.filesystem.OpenMode; import com.amaze.filemanager.filesystem.HybridFile; +import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool; import com.amaze.filemanager.filesystem.ssh.CustomSshJConfig; import com.amaze.filemanager.ui.fragments.preferencefragments.PreferencesConstants; import com.amaze.filemanager.ui.provider.UtilitiesProvider; @@ -108,6 +109,8 @@ public void onCreate() { utilsHandler = new UtilsHandler(this, utilitiesDatabase); runInBackground(Config::registerSmbURLHandler); + // Force Kotlin object NetCopyClientConnectionPool to initialize + NetCopyClientConnectionPool.INSTANCE.initialize(); // disabling file exposure method check for api n+ StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt index de2cd9f893..72da9e8521 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/AbstractGetHostInfoTask.kt @@ -43,7 +43,7 @@ abstract class AbstractGetHostInfoTask>( AppConfig.getInstance().run { progressDialog = ProgressDialog.show( - this.mainActivityContext, + requireNotNull(this.mainActivityContext), "", this.resources.getString(R.string.processing), ) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt index 64983a3da5..73366b1559 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTask.kt @@ -27,7 +27,6 @@ import com.amaze.filemanager.asynchronous.asynctasks.Task import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTP_URI_PREFIX import org.apache.commons.net.ftp.FTPClient import org.json.JSONObject -import java.net.ConnectException import java.net.SocketException import java.net.SocketTimeoutException @@ -62,8 +61,7 @@ class FtpAuthenticationTask( @MainThread override fun onError(error: Throwable) { - if (error is SocketException || error is SocketTimeoutException || error is ConnectException - ) { + if (error is SocketException || error is SocketTimeoutException) { AppConfig.toast( AppConfig.getInstance(), AppConfig.getInstance() @@ -79,7 +77,5 @@ class FtpAuthenticationTask( } @MainThread - override fun onFinish(value: FTPClient) { - android.util.Log.d("TEST", value.toString()) - } + override fun onFinish(value: FTPClient) = Unit } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt index ae1cfe8393..937652212d 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpAuthenticationTaskCallable.kt @@ -21,7 +21,6 @@ package com.amaze.filemanager.asynchronous.asynctasks.ftp.auth import androidx.annotation.WorkerThread -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.FTPClientImpl import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.CONNECT_TIMEOUT @@ -55,7 +54,7 @@ open class FtpAuthenticationTaskCallable( ftpClient.login( decode(username, UTF_8.name()), decode( - PasswordUtil.decryptPassword(AppConfig.getInstance(), password), + PasswordUtil.decryptPassword(password), UTF_8.name(), ), ) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt index aa5e9b0d90..9c39cf3261 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ftp/auth/FtpsAuthenticationTaskCallable.kt @@ -20,7 +20,6 @@ package com.amaze.filemanager.asynchronous.asynctasks.ftp.auth -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.FTPClientImpl import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.ARG_TLS import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.TLS_EXPLICIT @@ -57,7 +56,7 @@ class FtpsAuthenticationTaskCallable( } else { ftpClient.login( username, - PasswordUtil.decryptPassword(AppConfig.getInstance(), password), + PasswordUtil.decryptPassword(password), ) } return if (loginSuccess) { diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt index a67c2fdb7c..07eb7eddd9 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskCallable.kt @@ -20,7 +20,6 @@ package com.amaze.filemanager.asynchronous.asynctasks.ssh -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool import com.amaze.filemanager.filesystem.ssh.CustomSshJConfig import com.amaze.filemanager.utils.PasswordUtil @@ -74,13 +73,7 @@ class SshAuthenticationTaskCallable( } else { sshClient.authPassword( decode(username, UTF_8.name()), - decode( - PasswordUtil.decryptPassword( - AppConfig.getInstance(), - password!!, - ), - UTF_8.name(), - ), + decode(PasswordUtil.decryptPassword(password!!), UTF_8.name()), ) sshClient } diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt index a5a630e278..b63116922c 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpService.kt @@ -182,7 +182,6 @@ class FtpService : Service(), Runnable { runCatching { password = PasswordUtil.decryptPassword( - applicationContext, preferences.getString(KEY_PREFERENCE_PASSWORD, "")!!, ) isPasswordProtected = true diff --git a/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt b/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt index cf6943d08f..b21623e0a3 100644 --- a/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt +++ b/app/src/main/java/com/amaze/filemanager/database/UtilitiesDatabase.kt @@ -29,7 +29,6 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.database.daos.BookmarkEntryDao import com.amaze.filemanager.database.daos.GridEntryDao import com.amaze.filemanager.database.daos.HiddenEntryDao @@ -441,13 +440,11 @@ abstract class UtilitiesDatabase : RoomDatabase() { try { val oldPassword = decryptPassword( - AppConfig.getInstance(), password, Base64.DEFAULT, ) val newPassword = encryptPassword( - AppConfig.getInstance(), oldPassword, Base64.URL_SAFE, ) diff --git a/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt b/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt index 9d9a1975c6..a73dc9becc 100644 --- a/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt +++ b/app/src/main/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverter.kt @@ -22,7 +22,6 @@ package com.amaze.filemanager.database.typeconverters import android.util.Log import androidx.room.TypeConverter -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.database.models.StringWrapper import com.amaze.filemanager.utils.PasswordUtil import com.amaze.filemanager.utils.PasswordUtil.decryptPassword @@ -47,9 +46,7 @@ object EncryptedStringTypeConverter { @TypeConverter fun toPassword(encryptedStringEntryInDb: String): StringWrapper { return runCatching { - StringWrapper( - decryptPassword(AppConfig.getInstance(), encryptedStringEntryInDb), - ) + StringWrapper(decryptPassword(encryptedStringEntryInDb)) }.onFailure { Log.e(TAG, "Error decrypting password", it) }.getOrElse { @@ -64,10 +61,7 @@ object EncryptedStringTypeConverter { @TypeConverter fun fromPassword(unencryptedPasswordString: StringWrapper): String? { return runCatching { - encryptPassword( - AppConfig.getInstance(), - unencryptedPasswordString.value, - ) + encryptPassword(unencryptedPasswordString.value) }.onFailure { Log.e(TAG, "Error encrypting password", it) }.getOrElse { diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.kt index a389000915..17d8d02aed 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/files/EncryptDecryptUtils.kt @@ -206,7 +206,6 @@ object EncryptDecryptUtils { decryptIntent, utilsProvider.appTheme, decryptPassword( - c, preferences.getString( PreferencesConstants.PREFERENCE_CRYPT_MASTER_PASSWORD, PreferencesConstants.PREFERENCE_CRYPT_MASTER_PASSWORD_DEFAULT, diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt index 91d43d52e4..86ce770147 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt @@ -21,6 +21,10 @@ package com.amaze.filemanager.filesystem.ftp import android.annotation.SuppressLint +import android.util.LruCache +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.asynchronous.asynctasks.ftp.auth.FtpAuthenticationTask import com.amaze.filemanager.asynchronous.asynctasks.ssh.PemToKeyPairObservable @@ -44,11 +48,9 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import java.security.KeyPair import java.util.concurrent.Callable -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicReference -object NetCopyClientConnectionPool { +object NetCopyClientConnectionPool : DefaultLifecycleObserver { const val FTP_DEFAULT_PORT = 21 const val FTPS_DEFAULT_PORT = 990 const val SSH_DEFAULT_PORT = 22 @@ -57,7 +59,20 @@ object NetCopyClientConnectionPool { const val SSH_URI_PREFIX = "ssh://" const val CONNECT_TIMEOUT = 30000 - private var connections: MutableMap> = ConcurrentHashMap() + private var connections: LruCache> = + object : LruCache>(32) { + override fun entryRemoved( + evicted: Boolean, + key: String, + oldValue: NetCopyClient<*>, + newValue: NetCopyClient<*>?, + ) { + super.entryRemoved(evicted, key, oldValue, newValue) + if (evicted) { + oldValue.expire() + } + } + } @JvmStatic private val LOG: Logger = LoggerFactory.getLogger(NetCopyClientConnectionPool::class.java) @@ -68,6 +83,44 @@ object NetCopyClientConnectionPool { @JvmField var ftpClientFactory: FTPClientFactory = DefaultFTPClientFactory() + init { + // Register this object as a lifecycle observer + ProcessLifecycleOwner.get().lifecycle.addObserver(this) + } + + // Called when app is destroyed + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + shutdown() + } + + private fun closeAllConnections() { + Single.create { emitter -> + if (connections.size() > 0) { + connections.snapshot().values.forEach { + it.expire() + } + } + connections.evictAll() + emitter.onSuccess(Unit) + }.subscribeOn(Schedulers.io()) + .subscribe() + } + + /** + * A no-op method to force eager initialization. + * + * @see [AppConfig.onCreate] + */ + fun initialize() = Unit + + /** + * Lifecycle method called when the app is going to be destroyed. + */ + fun shutdown() { + closeAllConnections() + } + /** * Obtain a [NetCopyClient] connection from the underlying connection pool. * @@ -75,33 +128,37 @@ object NetCopyClientConnectionPool { * put it into the connection pool. * * @param url SSH connection URL, in the form of ` - * ssh://:@:` or ` - * ssh://@:` + * ssh://:@:[/path]` or ` + * ssh://@:[/path]` * @return [NetCopyClient] connection, already opened and authenticated * @throws IOException IOExceptions that occur during connection setup */ fun getConnection(url: String): NetCopyClient? { - var client = connections[url] - if (client == null) { - client = createNetCopyClient.invoke(url) - if (client != null) { - connections[extractBaseUriFrom(url)] = client - } - } else { - if (!validate(client)) { - LOG.debug("Connection no longer usable. Reconnecting...") - expire(client) - connections.remove(url) + // Extract base URI first to ensure consistent cache key + val baseUri = extractBaseUriFrom(url) + synchronized(connections) { + var client = connections[baseUri] + if (client == null) { client = createNetCopyClient.invoke(url) if (client != null) { - connections[extractBaseUriFrom(url)] = client + connections.put(baseUri, client) + } + } else { + if (!validate(client)) { + LOG.debug("Connection no longer usable. Reconnecting...") + expire(client) + connections.remove(baseUri) + client = createNetCopyClient.invoke(url) + if (client != null) { + connections.put(baseUri, client) + } } } - } - return if (client != null) { - client as NetCopyClient? - } else { - null + return if (client != null) { + client as NetCopyClient? + } else { + null + } } } @@ -146,30 +203,34 @@ object NetCopyClientConnectionPool { password, explicitTls, ) - var client = connections[url] - if (client == null) { - client = - createNetCopyClientInternal( - protocol, - host, - port, - hostFingerprint, - username, - password, - keyPair, - explicitTls, - ) - if (client != null) connections[url] = client - } else { - if (!validate(client)) { - LOG.debug("Connection no longer usable. Reconnecting...") - expire(client) - connections.remove(url) - client = createNetCopyClient(url) - if (client != null) connections[url] = client + // Extract base URI to ensure consistent cache key with getConnection(String) + val baseUri = extractBaseUriFrom(url) + synchronized(connections) { + var client = connections[baseUri] + if (client == null) { + client = + createNetCopyClientInternal( + protocol, + host, + port, + hostFingerprint, + username, + password, + keyPair, + explicitTls, + ) + if (client != null) connections.put(baseUri, client) + } else { + if (!validate(client)) { + LOG.debug("Connection no longer usable. Reconnecting...") + client.expire() + connections.remove(baseUri) + client = createNetCopyClient(url) + if (client != null) connections.put(baseUri, client) + } } + return client } - return client } private val createNetCopyClient: (String) -> NetCopyClient<*>? = { url -> @@ -189,21 +250,22 @@ object NetCopyClientConnectionPool { String?, KeyPair?, Boolean, - ) -> NetCopyClient<*>? = { protocol, host, port, hostFingerprint, username, password, keyPair, explicitTls -> - if (protocol == SSH_URI_PREFIX) { - createSshClient(host, port, hostFingerprint!!, username, password, keyPair) - } else { - createFtpClient( - protocol, - host, - port, - hostFingerprint?.let { JSONObject(it) }, - username, - password, - explicitTls, - ) + ) -> NetCopyClient<*>? = + { protocol, host, port, hostFingerprint, username, password, keyPair, explicitTls -> + if (protocol == SSH_URI_PREFIX) { + createSshClient(host, port, hostFingerprint!!, username, password, keyPair) + } else { + createFtpClient( + protocol, + host, + port, + hostFingerprint?.let { JSONObject(it) }, + username, + password, + explicitTls, + ) + } } - } /** * Remove specified connection from connection pool. Disconnects from server before removing. @@ -220,29 +282,13 @@ object NetCopyClientConnectionPool { url: String, callback: () -> Unit, ) { - Maybe.fromCallable(AsyncRemoveConnection(url)) + val baseUri = extractBaseUriFrom(url) + Maybe.fromCallable(AsyncRemoveConnection(baseUri)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { callback.invoke() } } - /** - * Kill any connection that is still in place. Used by MainActivity. - * - * @see MainActivity.onDestroy - * @see MainActivity.exit - */ - fun shutdown() { - AppConfig.getInstance().runInBackground { - if (connections.isNotEmpty()) { - connections.values.forEach { - it.expire() - } - connections.clear() - } - } - } - private fun validate(client: NetCopyClient<*>): Boolean { return Single.fromCallable { client.isConnectionValid() @@ -312,7 +358,7 @@ object NetCopyClientConnectionPool { ) } - @Suppress("LongParameterList") + @Suppress("LongParameterList", "TooGenericExceptionCaught") private fun createSshClientInternal( host: String, port: Int, @@ -330,21 +376,20 @@ object NetCopyClientConnectionPool { password = password, privateKey = keyPair, ) - val latch = CountDownLatch(1) - var retval: SSHClient? = null - Maybe.fromCallable(task.getTask()) - .subscribeOn(Schedulers.io()) - .subscribe({ - retval = it - latch.countDown() - }, { - latch.countDown() - task.onError(it) - }) - latch.await() - return retval?.let { - SSHClientImpl(it) - } + + return runCatching { + Single.create { emitter -> + try { + val retval = task.getTask().call() + emitter.onSuccess(retval) + } catch (e: Exception) { + emitter.onError(e) + } + }.map { sshClient -> SSHClientImpl(sshClient) } + .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) + .blockingGet() + }.getOrNull() } private fun createFtpClient(url: String): NetCopyClient? { @@ -368,7 +413,7 @@ object NetCopyClientConnectionPool { } } - @Suppress("LongParameterList") + @Suppress("LongParameterList", "TooGenericExceptionCaught") private fun createFtpClient( protocol: String, host: String, @@ -388,31 +433,30 @@ object NetCopyClientConnectionPool { password, explicitTls, ) - val latch = CountDownLatch(1) - var result: FTPClient? = null - Single.fromCallable(task.getTask()) - .subscribeOn(Schedulers.io()) - .subscribe({ - result = it - latch.countDown() - }, { - latch.countDown() - task.onError(it) - }) - latch.await() - return result?.let { ftpClient -> - FTPClientImpl(ftpClient) - } + + return kotlin.runCatching { + Single.create { emitter -> + try { + val retval = task.getTask().call() + emitter.onSuccess(retval) + } catch (e: Exception) { + emitter.onError(e) + } + }.map { ftpClient -> FTPClientImpl(ftpClient) } + .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) + .blockingGet() + }.getOrNull() } class AsyncRemoveConnection internal constructor( - private val url: String, + private val baseUri: String, ) : Callable { override fun call() { - extractBaseUriFrom(url).run { - if (connections.containsKey(this)) { - connections[this]?.expire() - connections.remove(this) + synchronized(connections) { + connections[baseUri]?.apply { + this.expire() + connections.remove(baseUri) } } } @@ -460,7 +504,8 @@ object NetCopyClientConnectionPool { FTPSClient( "TLS", !uri.contains(QUESTION_MARK) || - !uri.substringAfter(QUESTION_MARK).contains("$ARG_TLS=$TLS_EXPLICIT"), + !uri.substringAfter(QUESTION_MARK) + .contains("$ARG_TLS=$TLS_EXPLICIT"), ) } else { FTPClient() diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt index 557efb985a..28721aeaa4 100644 --- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt +++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtils.kt @@ -22,7 +22,6 @@ package com.amaze.filemanager.filesystem.ftp import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.fileoperations.filesystem.DOESNT_EXIST import com.amaze.filemanager.fileoperations.filesystem.FolderState import com.amaze.filemanager.fileoperations.filesystem.WRITABLE_ON_REMOTE @@ -43,7 +42,6 @@ import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo.Companion.SLAS import com.amaze.filemanager.filesystem.smb.CifsContexts.SMB_URI_PREFIX import com.amaze.filemanager.filesystem.ssh.SFtpClientTemplate import com.amaze.filemanager.utils.smb.SmbUtil -import com.amaze.filemanager.utils.urlEncoded import io.reactivex.Maybe import io.reactivex.Scheduler import io.reactivex.schedulers.Schedulers @@ -123,10 +121,7 @@ object NetCopyClientUtils { fun encryptFtpPathAsNecessary(fullUri: String): String { val uriWithoutProtocol: String = fullUri.substringAfter("://") return if (uriWithoutProtocol.substringBefore(AT).indexOf(COLON) > 0) { - SmbUtil.getSmbEncryptedPath( - AppConfig.getInstance(), - fullUri, - ) + SmbUtil.getSmbEncryptedPath(fullUri) } else { fullUri } @@ -143,10 +138,7 @@ object NetCopyClientUtils { return runCatching { val uriWithoutProtocol: String = fullUri.substringAfter("://") if (uriWithoutProtocol.lastIndexOf(COLON) > 0) { - SmbUtil.getSmbDecryptedPath( - AppConfig.getInstance(), - fullUri, - ) + SmbUtil.getSmbDecryptedPath(fullUri) } else { fullUri } @@ -183,7 +175,11 @@ object NetCopyClientUtils { } if (!it.arguments.isNullOrEmpty()) { append(QUESTION_MARK) - .append(it.arguments?.entries?.joinToString(AND.toString())) + .append( + it.arguments?.entries + ?.sortedBy { entry -> entry.key } + ?.joinToString(AND.toString()) { entry -> "${entry.key}=${entry.value}" }, + ) } } } @@ -246,14 +242,10 @@ object NetCopyClientUtils { if (pathSuffix == null) pathSuffix = SLASH.toString() if (explicitTls) pathSuffix = "$pathSuffix?$ARG_TLS=$TLS_EXPLICIT" val thisPassword = - if (password == "" || password == null) { + if (password.isNullOrEmpty()) { "" } else { - ":${if (edit) { - password - } else { - password.urlEncoded() - }}" + ":$password" } return if (username == "") { "$prefix$hostname:$port$pathSuffix" diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java index 56e4d8e472..5e9ad7d904 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java +++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java @@ -427,7 +427,7 @@ public void onCreate(final Bundle savedInstanceState) { ExtensionsKt.updateAUAlias( this, - !PackageUtils.Companion.appInstalledOrNot( + !PackageUtils.appInstalledOrNot( AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager()) && !getBoolean( PreferencesConstants.PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS)); diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt index 91e0aefeab..02bb5f6dfc 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/AlertDialog.kt @@ -52,7 +52,7 @@ object AlertDialog { .theme( activity .appTheme - .getMaterialDialogTheme(), + .materialDialogTheme, ) .title(title) .positiveText(positiveButtonText) diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt index f3e9480377..420665dc77 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt @@ -21,9 +21,7 @@ package com.amaze.filemanager.ui.dialogs import android.app.Activity -import android.app.AlertDialog import android.app.Dialog -import android.content.Context import android.content.DialogInterface import android.content.Intent import android.net.Uri @@ -84,7 +82,6 @@ import org.json.JSONObject import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.BufferedReader -import java.lang.ref.WeakReference import java.security.KeyPair import java.security.PublicKey import java.util.concurrent.Callable @@ -125,7 +122,6 @@ class SftpConnectDialog : DialogFragment() { } } - lateinit var ctx: WeakReference private var selectedPem: Uri? = null private var selectedParsedKeyPair: KeyPair? = null private var selectedParsedKeyPairName: String? = null @@ -135,14 +131,13 @@ class SftpConnectDialog : DialogFragment() { @Suppress("ComplexMethod") override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - ctx = WeakReference(activity) binding = SftpDialogBinding.inflate(layoutInflater) val utilsProvider: UtilitiesProvider = AppConfig.getInstance().utilsProvider val edit = requireArguments().getBoolean(ARG_EDIT, false) initForm(edit) - val accentColor = (activity as ThemedActivity).accent + val accentColor = (requireActivity() as ThemedActivity).accent // Use system provided action to get Uri to PEM (mostly via DocumentsUI). binding.selectPemBTN.setOnClickListener { @@ -157,7 +152,7 @@ class SftpConnectDialog : DialogFragment() { // Define action for buttons val dialogBuilder = - MaterialDialog.Builder(ctx.get()!!) + MaterialDialog.Builder(requireActivity()) .title(R.string.scp_connection) .autoDismiss(false) .customView(binding.root, true) @@ -454,7 +449,7 @@ class SftpConnectDialog : DialogFragment() { hostKeyFingerprint, hostInfo, -> - AlertDialog.Builder(ctx.get()) + androidx.appcompat.app.AlertDialog.Builder(requireActivity()) .setTitle(R.string.ssh_host_key_verification_prompt_title) .setMessage( getString( @@ -547,7 +542,7 @@ class SftpConnectDialog : DialogFragment() { edit, ) } else { - AlertDialog.Builder(ctx.get()) + androidx.appcompat.app.AlertDialog.Builder(requireActivity()) .setTitle( R.string.ssh_connect_failed_host_key_changed_title, ).setMessage( @@ -714,7 +709,7 @@ class SftpConnectDialog : DialogFragment() { hostKeyFingerprint, username, if (false == password?.isBlank()) { - PasswordUtil.encryptPassword(requireContext(), password)?.replace("\n", "") + PasswordUtil.encryptPassword(password)?.replace("\n", "") } else { password }, @@ -803,8 +798,8 @@ class SftpConnectDialog : DialogFragment() { private fun getProtocolPrefixFromDropdownSelection(): String { return when (binding.protocolDropDown.selectedItem.toString()) { - requireContext().getString(R.string.protocol_ftp) -> FTP_URI_PREFIX - requireContext().getString(R.string.protocol_ftps) -> FTPS_URI_PREFIX + getString(R.string.protocol_ftp) -> FTP_URI_PREFIX + getString(R.string.protocol_ftps) -> FTPS_URI_PREFIX else -> SSH_URI_PREFIX } } @@ -853,7 +848,7 @@ class SftpConnectDialog : DialogFragment() { if (true == binding.passwordET.text?.isEmpty()) { if (edit) { requireArguments().getString(ARG_PASSWORD, null)?.run { - PasswordUtil.decryptPassword(AppConfig.getInstance(), this) + PasswordUtil.decryptPassword(this) } } else { requireArguments().getString(ARG_PASSWORD, null) diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java index a1f4dcad60..7dc21f6240 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialog.java @@ -271,7 +271,7 @@ public void afterTextChanged(@NonNull Editable s) { try { passp = PasswordUtil.INSTANCE.decryptPassword( - context, inf.substring(inf.indexOf(COLON) + 1), URL_SAFE); + inf.substring(inf.indexOf(COLON) + 1), URL_SAFE); passp = decode(passp, Charsets.UTF_8.name()); } catch (GeneralSecurityException | IOException e) { LOG.warn("Error decrypting password", e); @@ -366,8 +366,7 @@ public void afterTextChanged(@NonNull Editable s) { try { s = new String[] { - conName.getText().toString(), - SmbUtil.getSmbEncryptedPath(getActivity(), smbFile.getPath()) + conName.getText().toString(), SmbUtil.getSmbEncryptedPath(smbFile.getPath()) }; } catch (Exception e) { LOG.warn("failed to load smb dialog info", e); diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/share/ShareTask.java b/app/src/main/java/com/amaze/filemanager/ui/dialogs/share/ShareTask.java index 8f7c44102e..b7645a9048 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/share/ShareTask.java +++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/share/ShareTask.java @@ -87,7 +87,7 @@ protected Void doInBackground(String... strings) { } } if (!bluetooth_present - && PackageUtils.Companion.appInstalledOrNot("com.android.bluetooth", packageManager)) { + && PackageUtils.appInstalledOrNot("com.android.bluetooth", packageManager)) { Intent intent = new Intent(); intent.setComponent( new ComponentName( diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt index 1b6c3dc4dc..5bcbd00727 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/FtpServerFragment.kt @@ -849,7 +849,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { if (encryptedPassword == "") { "" } else { - PasswordUtil.decryptPassword(requireContext(), encryptedPassword) + PasswordUtil.decryptPassword(encryptedPassword) } }.onFailure { log.warn("failed to decrypt ftp server password", it) @@ -921,7 +921,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { .edit { putString( FtpService.KEY_PREFERENCE_PASSWORD, - PasswordUtil.encryptPassword(this@run, password), + PasswordUtil.encryptPassword(password), ) } } diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/SecurityPrefsFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/SecurityPrefsFragment.kt index 74381bfcf8..a9c820a5e3 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/SecurityPrefsFragment.kt +++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/SecurityPrefsFragment.kt @@ -108,7 +108,7 @@ class SecurityPrefsFragment : BasePrefsFragment() { ) { // password is set, try to decrypt - PasswordUtil.decryptPassword(activity, preferencePassword) + PasswordUtil.decryptPassword(preferencePassword) } else { // no password set in preferences, just leave the field empty "" @@ -141,10 +141,7 @@ class SecurityPrefsFragment : BasePrefsFragment() { val editor = activity.prefs.edit() editor.putString( PreferencesConstants.PREFERENCE_CRYPT_MASTER_PASSWORD, - PasswordUtil.encryptPassword( - activity, - dialog.inputEditText!!.text.toString(), - ), + PasswordUtil.encryptPassword(dialog.inputEditText!!.text.toString()), ) editor.apply() } else { diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java index 358bfeb869..ee286ede2e 100644 --- a/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java +++ b/app/src/main/java/com/amaze/filemanager/ui/views/drawer/Drawer.java @@ -549,7 +549,7 @@ public void refreshDrawer() { new MenuMetadata( () -> { boolean isAUInstalled = - PackageUtils.Companion.appInstalledOrNot( + PackageUtils.appInstalledOrNot( AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager()); if (isAUInstalled) { try { @@ -573,7 +573,7 @@ public void refreshDrawer() { new MenuMetadata( () -> { boolean isAUInstalled = - PackageUtils.Companion.appInstalledOrNot( + PackageUtils.appInstalledOrNot( AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager()); if (isAUInstalled) { try { diff --git a/app/src/main/java/com/amaze/filemanager/utils/ImmutableEntry.java b/app/src/main/java/com/amaze/filemanager/utils/ImmutableEntry.java deleted file mode 100644 index 8d51ff7ffa..0000000000 --- a/app/src/main/java/com/amaze/filemanager/utils/ImmutableEntry.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2014-2024 Arpit Khurana , Vishal Nehra , - * Emmanuel Messulam, Raymond Lai and Contributors. - * - * This file is part of Amaze File Manager. - * - * Amaze File Manager is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.amaze.filemanager.utils; - -import java.util.Map; - -import androidx.annotation.Nullable; - -/** - * From: - * https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableEntry.java - * Author: Guava - */ -public class ImmutableEntry implements Map.Entry { - private final K key; - private final V value; - - public ImmutableEntry(@Nullable K key, @Nullable V value) { - this.key = key; - this.value = value; - } - - @Nullable - @Override - public final K getKey() { - return key; - } - - @Nullable - @Override - public final V getValue() { - return value; - } - - @Override - public final V setValue(V value) { - throw new UnsupportedOperationException(); - } -} diff --git a/app/src/main/java/com/amaze/filemanager/utils/PackageUtils.kt b/app/src/main/java/com/amaze/filemanager/utils/PackageUtils.kt index 3bba3c4ce8..71823f4ca4 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/PackageUtils.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/PackageUtils.kt @@ -22,21 +22,20 @@ package com.amaze.filemanager.utils import android.content.pm.PackageManager -class PackageUtils { - companion object { - /** - * Checks whether a package name is installed or not - */ - fun appInstalledOrNot( - uri: String, - pm: PackageManager, - ): Boolean { - return try { - pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES) - true - } catch (e: PackageManager.NameNotFoundException) { - false - } +object PackageUtils { + /** + * Checks whether a package name is installed or not + */ + @JvmStatic + fun appInstalledOrNot( + uri: String, + pm: PackageManager, + ): Boolean { + return try { + pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES) + true + } catch (e: PackageManager.NameNotFoundException) { + false } } } diff --git a/app/src/main/java/com/amaze/filemanager/utils/PasswordUtil.kt b/app/src/main/java/com/amaze/filemanager/utils/PasswordUtil.kt index 0dc123973f..3352d7d7d0 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/PasswordUtil.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/PasswordUtil.kt @@ -20,7 +20,6 @@ package com.amaze.filemanager.utils -import android.content.Context import android.os.Build import android.util.Base64 import androidx.annotation.RequiresApi @@ -77,7 +76,6 @@ object PasswordUtil { IOException::class, ) private fun rsaEncryptPassword( - context: Context, password: String, base64Options: Int, ): String? { @@ -93,7 +91,6 @@ object PasswordUtil { IOException::class, ) private fun rsaDecryptPassword( - context: Context, cipherText: String, base64Options: Int, ): String { @@ -107,14 +104,13 @@ object PasswordUtil { /** Method handles encryption of plain text on various APIs */ @Throws(GeneralSecurityException::class, IOException::class) fun encryptPassword( - context: Context, plainText: String, base64Options: Int = Base64.URL_SAFE, ): String? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { aesEncryptPassword(plainText, base64Options) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - rsaEncryptPassword(context, plainText, base64Options) + rsaEncryptPassword(plainText, base64Options) } else { plainText } @@ -123,14 +119,13 @@ object PasswordUtil { /** Method handles decryption of cipher text on various APIs */ @Throws(GeneralSecurityException::class, IOException::class) fun decryptPassword( - context: Context, cipherText: String, base64Options: Int = Base64.URL_SAFE, ): String { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { aesDecryptPassword(cipherText, base64Options) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - rsaDecryptPassword(context, cipherText, base64Options) + rsaDecryptPassword(cipherText, base64Options) } else { cipherText } diff --git a/app/src/main/java/com/amaze/filemanager/utils/smb/SmbUtil.kt b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbUtil.kt index b7aa0ff9d3..574593fb1d 100644 --- a/app/src/main/java/com/amaze/filemanager/utils/smb/SmbUtil.kt +++ b/app/src/main/java/com/amaze/filemanager/utils/smb/SmbUtil.kt @@ -20,10 +20,9 @@ package com.amaze.filemanager.utils.smb -import android.content.Context import android.net.Uri import android.text.TextUtils -import com.amaze.filemanager.application.AppConfig +import androidx.core.net.toUri import com.amaze.filemanager.fileoperations.filesystem.DOESNT_EXIST import com.amaze.filemanager.fileoperations.filesystem.WRITABLE_ON_REMOTE import com.amaze.filemanager.filesystem.ftp.NetCopyConnectionInfo @@ -55,23 +54,17 @@ object SmbUtil { /** Parse path to decrypt smb password */ @JvmStatic - fun getSmbDecryptedPath( - context: Context, - path: String, - ): String { + fun getSmbDecryptedPath(path: String): String { return buildPath(path, withPassword = { - PasswordUtil.decryptPassword(context, it.urlDecoded()) + PasswordUtil.decryptPassword(it.urlDecoded()) }) } /** Parse path to encrypt smb password */ @JvmStatic - fun getSmbEncryptedPath( - context: Context, - path: String, - ): String { + fun getSmbEncryptedPath(path: String): String { return buildPath(path, withPassword = { - PasswordUtil.encryptPassword(context, it) + PasswordUtil.encryptPassword(it) }) } @@ -114,7 +107,7 @@ object SmbUtil { @JvmStatic @Throws(MalformedURLException::class) fun create(path: String): SmbFile { - val uri = Uri.parse(getSmbDecryptedPath(AppConfig.getInstance(), path)) + val uri = getSmbDecryptedPath(path).toUri() val disableIpcSigningCheck = uri.getQueryParameter( PARAM_DISABLE_IPC_SIGNING_CHECK, diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt index a176c0fdd6..dde004ace2 100644 --- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt +++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt @@ -24,11 +24,13 @@ import android.content.Context import android.os.Build import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Build.VERSION_CODES.P +import android.util.LruCache import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.amaze.filemanager.R -import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.filesystem.ftp.NetCopyClient import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool +import com.amaze.filemanager.filesystem.ftp.SSHClientImpl import com.amaze.filemanager.filesystem.ssh.test.TestKeyProvider import com.amaze.filemanager.shadows.ShadowMultiDex import com.amaze.filemanager.test.ShadowPasswordUtil @@ -102,7 +104,7 @@ class SshAuthenticationTaskTest { hostname = "127.0.0.1", port = 22222, username = "user", - password = PasswordUtil.encryptPassword(AppConfig.getInstance(), "password"), + password = PasswordUtil.encryptPassword("password"), ) val latch = CountDownLatch(1) var e: Throwable? = null @@ -298,7 +300,7 @@ class SshAuthenticationTaskTest { hostname = "127.0.0.1", port = 22222, username = "user", - password = PasswordUtil.encryptPassword(AppConfig.getInstance(), "password"), + password = PasswordUtil.encryptPassword("password"), ) val latch = CountDownLatch(1) var e: Throwable? = null @@ -341,9 +343,12 @@ class SshAuthenticationTaskTest { this.isAccessible = true this.set( NetCopyClientConnectionPool, - mutableMapOf( - Pair("ssh://user:password@127.0.0.1:22222", sshClient), - ), + LruCache>(1).also { + it.put( + "ssh://user:password@127.0.0.1:22222", + SSHClientImpl(sshClient), + ) + }, ) } diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt index a75e56dc37..bc030de08b 100644 --- a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt +++ b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt @@ -106,13 +106,11 @@ class UtilitiesDatabaseMigrationTest { helper.createDatabase(TEST_DB, 5) val password1 = PasswordUtil.encryptPassword( - InstrumentationRegistry.getInstrumentation().targetContext, "passw0rd", Base64.DEFAULT, ) val password2 = PasswordUtil.encryptPassword( - InstrumentationRegistry.getInstrumentation().targetContext, "\\password/%&*()", Base64.DEFAULT, ) @@ -150,10 +148,7 @@ class UtilitiesDatabaseMigrationTest { smbEntries.find { it.name == "test" }?.run { assertEquals( "smb://user:passw0rd@127.0.0.1/user", - SmbUtil.getSmbDecryptedPath( - InstrumentationRegistry.getInstrumentation().targetContext, - this.path, - ), + SmbUtil.getSmbDecryptedPath(this.path), ) } // smbEntries.find { it.name == "test anonymous" }?.run { diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt index bcfc17521b..720c36728b 100644 --- a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt +++ b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt @@ -94,10 +94,7 @@ class UtilsHandlerTest { OperationData( UtilsHandler.Operation.SMB, "SMB Connection 1", - SmbUtil.getSmbEncryptedPath( - AppConfig.getInstance(), - path, - ), + SmbUtil.getSmbEncryptedPath(path), ) AppConfig.getInstance().run { utilsHandler.run { @@ -105,10 +102,7 @@ class UtilsHandlerTest { val verify = smbList assertEquals(1, verify.size) assertEquals( - SmbUtil.getSmbEncryptedPath( - AppConfig.getInstance(), - path, - ), + SmbUtil.getSmbEncryptedPath(path), verify[0][1], ) } @@ -130,10 +124,7 @@ class UtilsHandlerTest { OperationData( UtilsHandler.Operation.SMB, "SMB Connection 1", - SmbUtil.getSmbEncryptedPath( - AppConfig.getInstance(), - path, - ), + SmbUtil.getSmbEncryptedPath(path), ) AppConfig.getInstance().run { utilsHandler.run { diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt index 35d51316ac..698226f59b 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt @@ -24,7 +24,6 @@ import android.os.Build import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Build.VERSION_CODES.P import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTP_URI_PREFIX import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.getConnection import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.shutdown @@ -39,6 +38,7 @@ import org.apache.commons.net.ftp.FTPClient import org.junit.After import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith @@ -88,11 +88,7 @@ class NetCopyClientConnectionPoolFtpTest { host = HOST, port = PORT, username = "testuser", - password = - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "testpassword", - ), + password = PasswordUtil.encryptPassword("testpassword"), ), ) assertNull( @@ -101,11 +97,7 @@ class NetCopyClientConnectionPoolFtpTest { host = HOST, port = PORT, username = "invaliduser", - password = - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "invalidpassword", - ), + password = PasswordUtil.encryptPassword("invalidpassword"), ), ) verify(mock, times(2)).connect(HOST, PORT) @@ -204,6 +196,38 @@ class NetCopyClientConnectionPoolFtpTest { doRunTest(validUsername, validPassword) } + /** + * Test that FTP connections with same base URI but different paths reuse + * the same pooled connection object + */ + @Test + fun testGetConnectionWithSameBaseUriDifferentPaths() { + val validUsername = "testuser" + val validPassword = "testpassword" + val encodedUsername = encode(validUsername, UTF_8.name()) + val encodedPassword = encode(validPassword, UTF_8.name()) + val encryptedPassword = + PasswordUtil.encryptPassword(encodedPassword)?.replace("\n", "") + val mock = createFTPClient(validUsername, validPassword) + TestUtils.saveFtpConnectionSettings(validUsername, validPassword) + + val uri1 = "ftp://$encodedUsername:$encryptedPassword@127.0.0.1:22222" + val uri2 = "ftp://$encodedUsername:$encryptedPassword@127.0.0.1:22222/home/testuser" + val uri3 = "ftp://$encodedUsername:$encryptedPassword@127.0.0.1:22222/var/www/html" + + val client1 = getConnection(uri1) + val client2 = getConnection(uri2) + val client3 = getConnection(uri3) + + assertNotNull(client1) + assertNotNull(client2) + assertNotNull(client3) + assertTrue(client1 === client2) + assertTrue(client1 === client3) + // Should only connect once since connection is reused + verify(mock, times(1)).connect(HOST, PORT) + } + private fun doRunTest( validUsername: String, validPassword: String, @@ -211,10 +235,7 @@ class NetCopyClientConnectionPoolFtpTest { val encodedUsername = encode(validUsername, UTF_8.name()) val encodedPassword = encode(validPassword, UTF_8.name()) val encryptedPassword = - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - encodedPassword, - )?.replace("\n", "") + PasswordUtil.encryptPassword(encodedPassword)?.replace("\n", "") val mock = createFTPClient(validUsername, validPassword) TestUtils.saveFtpConnectionSettings(validUsername, validPassword) assertNotNull( @@ -240,6 +261,8 @@ class NetCopyClientConnectionPoolFtpTest { val mock = Mockito.mock(FTPClient::class.java) doNothing().`when`(mock).connect(HOST, PORT) doNothing().`when`(mock).disconnect() + `when`(mock.isConnected).thenReturn(true) + `when`(mock.isAvailable).thenReturn(true) `when`(mock.login(validUsername, validPassword)).thenReturn(true) `when`( mock.login( diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolTest.kt index 00823c7b8c..3bda99475c 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolTest.kt @@ -1,15 +1,89 @@ package com.amaze.filemanager.filesystem.ftp +import android.os.Build +import android.os.Build.VERSION_CODES.LOLLIPOP +import android.os.Build.VERSION_CODES.P +import android.util.LruCache +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.database.UtilsHandler +import com.amaze.filemanager.shadows.ShadowMultiDex +import com.amaze.filemanager.test.ShadowPasswordUtil +import com.amaze.filemanager.utils.PasswordUtil +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import io.reactivex.android.plugins.RxAndroidPlugins +import io.reactivex.plugins.RxJavaPlugins +import io.reactivex.schedulers.Schedulers +import net.schmizz.sshj.SSHClient +import net.schmizz.sshj.userauth.password.PasswordFinder import org.apache.commons.net.ftp.FTPSClient import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config /** * Unit tests for [NetCopyClientConnectionPool] */ +@RunWith(AndroidJUnit4::class) +@Config( + sdk = [LOLLIPOP, P, Build.VERSION_CODES.R], + shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class], +) @Suppress("StringLiteralDuplication") class NetCopyClientConnectionPoolTest { + /** + * Setup before tests. + */ + @Before + fun setUp() { + RxJavaPlugins.reset() + RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } + RxAndroidPlugins.reset() + RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() } + } + + /** + * Test that NetCopyClientConnectionPool closes connections on lifecycle onDestroy. + */ + @Test + fun `NetCopyClientConnectionPool should close connections on lifecycle onDestroy`() { + // Mock LifecycleOwner + val mockLifecycleOwner = mockk(relaxed = true) + val mockLifecycle = mockk(relaxed = true) + + // Mock connections + val mockConnection = mockk>(relaxed = true) + val mockLruCache = mockk>>(relaxed = true) + + // Replace the connections cache with a mock + val connectionsField = NetCopyClientConnectionPool::class.java.getDeclaredField("connections") + connectionsField.isAccessible = true + connectionsField.set(NetCopyClientConnectionPool, mockLruCache) + + // Simulate lifecycle events + every { mockLifecycleOwner.lifecycle } returns mockLifecycle + every { mockLifecycle.addObserver(any()) } answers { + val observer = it.invocation.args[0] as DefaultLifecycleObserver + observer.onDestroy(mockLifecycleOwner) + } + + // Trigger onDestroy + ProcessLifecycleOwner.get().lifecycle.addObserver(NetCopyClientConnectionPool) + mockLifecycle.addObserver(NetCopyClientConnectionPool) + verify { mockLruCache.evictAll() } + } + /** * Test DefaultFTPClientFactory default behaviour with FTPClient */ @@ -61,4 +135,78 @@ class NetCopyClientConnectionPoolTest { isImplicit.isAccessible = true assertFalse(isImplicit.get(result) as Boolean) } + + /** + * Test getConnection should return same instance for same base URIs with different + * path suffixes. + */ + @Test + fun `getConnection should return same instance for same base URIs with different paths`() { + // Given + val prefix = "ssh://root:${PasswordUtil.encryptPassword("root")}@127.0.0.1" + val uri1 = "$prefix/root" + val uri2 = prefix + val uri3 = "$prefix/usr/src/linux" + val hostKey = "TEST_HOST_KEY" + + // Mock AppConfig singleton + val mockAppConfig = mockk() + mockkStatic(AppConfig::class) + every { AppConfig.getInstance() } returns mockAppConfig + + // Mock dependencies + val mockSshClient = mockk() + val utilsHandler = mockk() + + every { AppConfig.getInstance().utilsHandler } returns utilsHandler + every { utilsHandler.getRemoteHostKey(any()) } returns hostKey + every { utilsHandler.getSshAuthPrivateKey(any()) } returns null + + // Mock SSHClient behavior + justRun { mockSshClient.addHostKeyVerifier(any()) } + justRun { mockSshClient.connect(any(), any()) } + justRun { mockSshClient.authPassword(any(), any()) } + justRun { mockSshClient.authPassword(any(), any()) } + justRun { mockSshClient.authPassword(any(), any()) } + justRun { mockSshClient.connectTimeout = any() } + every { mockSshClient.isConnected } returns true + every { mockSshClient.isAuthenticated } returns true + + // Mock SSHClientFactory + val mockFactory = mockk() + every { mockFactory.create(any()) } returns mockSshClient + NetCopyClientConnectionPool.sshClientFactory = mockFactory + + // When + val client1 = NetCopyClientConnectionPool.getConnection(uri1) + val client2 = NetCopyClientConnectionPool.getConnection(uri2) + val client3 = NetCopyClientConnectionPool.getConnection(uri3) + + // Then + assertNotNull(client1) + assertNotNull(client2) + assertNotNull(client3) + assertTrue(client1 === client2) + assertTrue(client1 === client3) + } + + /** + * Test getConnection should return same instance for URIs with query parameters + * in different order (canonicalization) + */ + @Test + fun `getConnection should return same instance for URIs with reordered query params`() { + // Create URIs with same query params but different order + val uri1 = "ftps://user:pass@127.0.0.1:2121?tls=explicit&timeout=30" + val uri2 = "ftps://user:pass@127.0.0.1:2121?timeout=30&tls=explicit" + + // Extract base URIs - should be identical after canonicalization + val baseUri1 = NetCopyClientUtils.extractBaseUriFrom(uri1) + val baseUri2 = NetCopyClientUtils.extractBaseUriFrom(uri2) + + assertTrue( + "URIs with same query params in different order should produce same base URI", + baseUri1 == baseUri2, + ) + } } diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtilTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtilTest.kt index 5ae3563d59..b6c85195e0 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtilTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientUtilTest.kt @@ -153,11 +153,11 @@ class NetCopyClientUtilTest { NetCopyClientUtils.extractBaseUriFrom("ftps://127.0.0.1:21221/pub/Incoming/shared/test.txt?tls=explicit"), ) assertEquals( - "ssh://root@127.0.0.1:22222?foo=bar&timeout=3000&2fa=true", + "ssh://root@127.0.0.1:22222?2fa=true&foo=bar&timeout=3000", NetCopyClientUtils.extractBaseUriFrom("ssh://root@127.0.0.1:22222?foo=bar&timeout=3000&2fa=true"), ) assertEquals( - "ssh://root@127.0.0.1:22222?foo=bar&timeout=3000&2fa=true", + "ssh://root@127.0.0.1:22222?2fa=true&foo=bar&timeout=3000", NetCopyClientUtils.extractBaseUriFrom("ssh://root@127.0.0.1:22222/mnt/data?foo=bar&timeout=3000&2fa=true"), ) } diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.kt index a7cce7c3f1..01dd6b1f15 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.kt @@ -25,7 +25,6 @@ import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Build.VERSION_CODES.P import android.os.Environment import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.getConnection import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.shutdown @@ -69,7 +68,7 @@ import kotlin.text.Charsets.UTF_8 ) abstract class AbstractSftpServerTest { protected var encryptedPassword: String? = - PasswordUtil.encryptPassword(AppConfig.getInstance(), PASSWORD)?.replace("\n", "") + PasswordUtil.encryptPassword(PASSWORD)?.replace("\n", "") protected var serverPort = 0 private lateinit var server: SshServer @@ -101,7 +100,7 @@ abstract class AbstractSftpServerTest { */ @After @Throws(IOException::class) - fun tearDown() { + open fun tearDown() { shutdown() if (server.isOpen) { server.stop(true) @@ -116,7 +115,7 @@ abstract class AbstractSftpServerTest { serverPort, hostFingerprint, USERNAME, - PasswordUtil.encryptPassword(AppConfig.getInstance(), PASSWORD)?.replace("\n", ""), + PasswordUtil.encryptPassword(PASSWORD)?.replace("\n", ""), null, ) } @@ -135,7 +134,8 @@ abstract class AbstractSftpServerTest { server.subsystemFactories = listOf>(SftpSubsystemFactory()) server.passwordAuthenticator = PasswordAuthenticator { username: String, password: String, _: ServerSession? -> - username == USERNAME && password == PASSWORD + validUsers.containsKey(username) && + validUsers[username] == password } return try { server.port = startPort @@ -156,6 +156,13 @@ abstract class AbstractSftpServerTest { @JvmStatic protected val PASSWORD = "testpassword" + private val validUsers = + mapOf( + USERNAME to PASSWORD, + "testuser2" to "testpassword2", + "testuser3" to "testpassword3", + ) + protected lateinit var hostKeyProvider: TestKeyProvider /** diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/FilesOnSshdTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/FilesOnSshdTest.kt index 076e7149ee..1f07c661da 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/FilesOnSshdTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/FilesOnSshdTest.kt @@ -25,9 +25,7 @@ import androidx.test.core.app.ApplicationProvider import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.fileoperations.filesystem.OpenMode import com.amaze.filemanager.filesystem.HybridFile -import com.amaze.filemanager.filesystem.HybridFileParcelable import com.amaze.filemanager.test.randomBytes -import com.amaze.filemanager.utils.OnFileFound import org.awaitility.Awaitility.await import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers @@ -125,13 +123,10 @@ class FilesOnSshdTest : AbstractSftpServerTest() { file.forEachChildrenFile( ApplicationProvider.getApplicationContext(), false, - object : OnFileFound { - override fun onFileFound(fileFound: HybridFileParcelable) { - assertTrue("${fileFound.path} not seen as directory", fileFound.isDirectory) - result.add(fileFound.name) - } - }, - ) + ) { fileFound -> + assertTrue("${fileFound.path} not seen as directory", fileFound.isDirectory) + result.add(fileFound.name) + } await().until { result.size == 8 } assertThat>( result, @@ -150,12 +145,7 @@ class FilesOnSshdTest : AbstractSftpServerTest() { file.forEachChildrenFile( ApplicationProvider.getApplicationContext(), false, - object : OnFileFound { - override fun onFileFound(fileFound: HybridFileParcelable) { - result.add(fileFound.name) - } - }, - ) + ) { fileFound -> result.add(fileFound.name) } await().atMost(90, TimeUnit.SECONDS).until { result.size == 2 } assertThat>( result, @@ -170,12 +160,7 @@ class FilesOnSshdTest : AbstractSftpServerTest() { file.forEachChildrenFile( ApplicationProvider.getApplicationContext(), false, - object : OnFileFound { - override fun onFileFound(fileFound: HybridFileParcelable) { - result.add(fileFound.name) - } - }, - ) + ) { fileFound -> result.add(fileFound.name) } await().until { result.size == 1 } assertThat>( result, @@ -190,12 +175,7 @@ class FilesOnSshdTest : AbstractSftpServerTest() { file.forEachChildrenFile( ApplicationProvider.getApplicationContext(), false, - object : OnFileFound { - override fun onFileFound(fileFound: HybridFileParcelable) { - result.add(fileFound.name) - } - }, - ) + ) { fileFound -> result.add(fileFound.name) } await().until { result.size == 1 } assertThat>( result, @@ -234,24 +214,21 @@ class FilesOnSshdTest : AbstractSftpServerTest() { file.forEachChildrenFile( ApplicationProvider.getApplicationContext(), false, - object : OnFileFound { - override fun onFileFound(fileFound: HybridFileParcelable) { - if (!fileFound.name.endsWith(".txt")) { - assertTrue( - fileFound.path + " not seen as directory", - fileFound.isDirectory, - ) - dirs.add(fileFound.name) - } else { - assertFalse( - fileFound.path + " not seen as file", - fileFound.isDirectory, - ) - files.add(fileFound.name) - } - } - }, - ) + ) { fileFound -> + if (!fileFound.name.endsWith(".txt")) { + assertTrue( + fileFound.path + " not seen as directory", + fileFound.isDirectory, + ) + dirs.add(fileFound.name) + } else { + assertFalse( + fileFound.path + " not seen as file", + fileFound.isDirectory, + ) + files.add(fileFound.name) + } + } await().until { dirs.size == 8 } assertThat>( dirs, @@ -311,13 +288,10 @@ class FilesOnSshdTest : AbstractSftpServerTest() { file.forEachChildrenFile( ApplicationProvider.getApplicationContext(), false, - object : OnFileFound { - override fun onFileFound(fileFound: HybridFileParcelable) { - assertFalse("${fileFound.path} not seen as directory", fileFound.isDirectory) - result.add(fileFound.name) - } - }, - ) + ) { fileFound -> + assertFalse("${fileFound.path} not seen as directory", fileFound.isDirectory) + result.add(fileFound.name) + } await().until { result.size == 4 } assertThat>( result, @@ -329,13 +303,10 @@ class FilesOnSshdTest : AbstractSftpServerTest() { file.forEachChildrenFile( ApplicationProvider.getApplicationContext(), false, - object : OnFileFound { - override fun onFileFound(fileFound: HybridFileParcelable) { - assertTrue("${fileFound.path} not seen as directory", fileFound.isDirectory) - result2.add(fileFound.name) - } - }, - ) + ) { fileFound -> + assertTrue("${fileFound.path} not seen as directory", fileFound.isDirectory) + result2.add(fileFound.name) + } await().until { result2.size == 8 } assertThat>( result2, diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolReusabilityTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolReusabilityTest.kt new file mode 100644 index 0000000000..f0fc0b25da --- /dev/null +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolReusabilityTest.kt @@ -0,0 +1,95 @@ +package com.amaze.filemanager.filesystem.ssh + +import android.os.Environment +import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool +import com.amaze.filemanager.filesystem.ftp.SSHClientImpl +import org.junit.After +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.io.File + +/** + * Test class for verifying the reusability of connections in NetCopyClientConnectionPool. + */ +@Suppress("StringLiteralDuplication") +class NetCopyClientConnectionPoolReusabilityTest : AbstractSftpServerTest() { + @Before + override fun setUp() { + super.setUp() + for (s in arrayOf("sysroot", "srv", "var", "tmp", "bin", "lib", "usr", "sysroot+v2")) { + File(Environment.getExternalStorageDirectory(), s).mkdir() + } + } + + @After + override fun tearDown() { + super.tearDown() + // Clean up the directories created for the test + for (s in arrayOf("sysroot", "srv", "var", "tmp", "bin", "lib", "usr", "sysroot+v2")) { + File(Environment.getExternalStorageDirectory(), s).deleteRecursively() + } + } + + /** + * Test case to verify that connections are reused correctly in the NetCopyClientConnectionPool. + */ + @Test + fun `test connection reusability`() { + assertNotNull( + NetCopyClientConnectionPool.getConnection( + "ssh://$USERNAME:$encryptedPassword@$HOST:$serverPort", + ), + ) + // Test that connections are reused correctly + assertTrue( + "Connections should be reused", + NetCopyClientConnectionPool.getConnection( + "ssh://$USERNAME:$encryptedPassword@$HOST:$serverPort", + ) + === + NetCopyClientConnectionPool.getConnection( + "ssh://$USERNAME:$encryptedPassword@$HOST:$serverPort/sysroot", + ), + ) + + // Use reflection to access the private 'connections' field + val connectionsField = NetCopyClientConnectionPool::class.java.getDeclaredField("connections") + connectionsField.isAccessible = true + val lruCache = connectionsField.get(NetCopyClientConnectionPool) as android.util.LruCache<*, *> + assertTrue("LruCache size should be 1, but was ${lruCache.size()}", lruCache.size() == 1) + } + + /** + * Test case to verify that connections are reused correctly in the NetCopyClientConnectionPool + * after clearing the previous connections. + */ + @Test + fun `test connection reusability from scratch`() { + NetCopyClientConnectionPool.shutdown() + + assertNotNull( + NetCopyClientConnectionPool.getConnection( + "ssh://$USERNAME:$encryptedPassword@$HOST:$serverPort", + ), + ) + // Test that connections are reused correctly + assertTrue( + "Connections should be reused", + NetCopyClientConnectionPool.getConnection( + "ssh://$USERNAME:$encryptedPassword@$HOST:$serverPort", + ) + === + NetCopyClientConnectionPool.getConnection( + "ssh://$USERNAME:$encryptedPassword@$HOST:$serverPort/sysroot", + ), + ) + + // Use reflection to access the private 'connections' field + val connectionsField = NetCopyClientConnectionPool::class.java.getDeclaredField("connections") + connectionsField.isAccessible = true + val lruCache = connectionsField.get(NetCopyClientConnectionPool) as android.util.LruCache<*, *> + assertTrue("LruCache size should be 1, but was ${lruCache.size()}", lruCache.size() == 1) + } +} diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt index 27b8e5c406..76967355ea 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt @@ -24,7 +24,6 @@ import android.os.Build import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Build.VERSION_CODES.P import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSHClientFactory import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX @@ -101,10 +100,7 @@ class NetCopyClientConnectionPoolSshTest { PORT, SecurityUtils.getFingerprint(hostKeyPair.public), "testuser", - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "testpassword", - ), + PasswordUtil.encryptPassword("testpassword"), null, ), ) @@ -115,10 +111,7 @@ class NetCopyClientConnectionPoolSshTest { PORT, SecurityUtils.getFingerprint(hostKeyPair.public), "invaliduser", - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "invalidpassword", - ), + PasswordUtil.encryptPassword("invalidpassword"), null, ), ) @@ -321,10 +314,7 @@ class NetCopyClientConnectionPoolSshTest { val encodedUsername = encode(validUsername, UTF_8.name()) val encodedPassword = encode(validPassword, UTF_8.name()) val encryptedPassword = - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - encodedPassword, - )?.replace("\n", "") + PasswordUtil.encryptPassword(encodedPassword)?.replace("\n", "") val mock = createSshServer(validUsername, validPassword) TestUtils.saveSshConnectionSettings( hostKeyPair, diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/MockSshConnectionPools.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/MockSshConnectionPools.kt index 5861b5d595..b2e6004614 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/MockSshConnectionPools.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/MockSshConnectionPools.kt @@ -20,6 +20,7 @@ package com.amaze.filemanager.filesystem.ssh.test +import android.util.LruCache import com.amaze.filemanager.filesystem.ftp.NetCopyClient import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool import com.amaze.filemanager.filesystem.ftp.SSHClientImpl @@ -105,12 +106,12 @@ object MockSshConnectionPools { this.isAccessible = true this.set( NetCopyClientConnectionPool, - mutableMapOf( - Pair>( + LruCache>(1).also { + it.put( "ssh://user:password@127.0.0.1:22222", SSHClientImpl(sshClient), - ), - ), + ) + }, ) } diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/TestUtils.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/TestUtils.kt index 06ecc57b55..a538307029 100644 --- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/TestUtils.kt +++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/test/TestUtils.kt @@ -30,6 +30,7 @@ import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_ import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPoolFtpTest import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils.encryptFtpPathAsNecessary import com.amaze.filemanager.filesystem.ssh.NetCopyClientConnectionPoolSshTest +import com.amaze.filemanager.utils.urlEncoded import net.schmizz.sshj.common.SecurityUtils import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.json.JSONObject @@ -73,7 +74,7 @@ object TestUtils { ) if (validUsername != "" && validPassword != "") { fullUri.append(validUsername) - fullUri.append(':').append(validPassword).append("@") + fullUri.append(':').append(validPassword.urlEncoded()).append("@") } fullUri.append("${NetCopyClientConnectionPoolFtpTest.HOST}:$port") @@ -114,7 +115,9 @@ object TestUtils { val fullUri: StringBuilder = StringBuilder() .append(SSH_URI_PREFIX).append(validUsername) - if (validPassword != null) fullUri.append(':').append(validPassword) + if (validPassword != null) { + fullUri.append(':').append(validPassword) + } fullUri.append( "@${NetCopyClientConnectionPoolSshTest.HOST}:$port", ) diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt index fb971d4caf..ef5a505236 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.kt @@ -22,7 +22,6 @@ package com.amaze.filemanager.ui.activities import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario -import androidx.test.core.app.ApplicationProvider import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.utils.smb.SmbUtil.getSmbDecryptedPath import com.amaze.filemanager.utils.smb.SmbUtil.getSmbEncryptedPath @@ -51,11 +50,7 @@ class MainActivityTest : AbstractMainActivityTestBase() { scenario.moveToState(Lifecycle.State.STARTED) scenario.onActivity { activity: MainActivity -> val path = "smb://root:toor@192.168.1.1" - val encryptedPath = - getSmbEncryptedPath( - ApplicationProvider.getApplicationContext(), - path, - ) + val encryptedPath = getSmbEncryptedPath(path) val oldName = "SMB connection" val newName = "root@192.168.1.1" try { @@ -90,7 +85,7 @@ class MainActivityTest : AbstractMainActivityTestBase() { val verify: List> = AppConfig.getInstance().utilsHandler.smbList assertEquals(1, verify.size.toLong()) val entry = verify[0] - assertEquals(path, getSmbDecryptedPath(AppConfig.getInstance(), entry[1])) + assertEquals(path, getSmbDecryptedPath(entry[1])) } finally { scenario.moveToState(Lifecycle.State.DESTROYED) scenario.close() diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogArgumentPopulationTest.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogArgumentPopulationTest.kt index a2a1e42705..f5571dde5e 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogArgumentPopulationTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogArgumentPopulationTest.kt @@ -1,7 +1,6 @@ package com.amaze.filemanager.ui.dialogs import android.os.Bundle -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.ARG_TLS import com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.FTP_URI_PREFIX import com.amaze.filemanager.ui.dialogs.SftpConnectDialog.Companion.ARG_ADDRESS @@ -38,13 +37,7 @@ class SftpConnectDialogArgumentPopulationTest : AbstractSftpConnectDialogUiTests it.putString(ARG_ADDRESS, "127.0.0.1") it.putInt(ARG_PORT, 2121) it.putString(ARG_USERNAME, "root") - it.putString( - ARG_PASSWORD, - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "abcdefgh", - ), - ) + it.putString(ARG_PASSWORD, PasswordUtil.encryptPassword("abcdefgh")) it.putBoolean(ARG_HAS_PASSWORD, true) it.putBoolean(ARG_EDIT, true) }, @@ -77,13 +70,7 @@ class SftpConnectDialogArgumentPopulationTest : AbstractSftpConnectDialogUiTests it.putInt(ARG_PORT, 2121) it.putString(ARG_USERNAME, "root") it.putString(ARG_DEFAULT_PATH, "/root/Private") - it.putString( - ARG_PASSWORD, - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "abcdefgh", - ), - ) + it.putString(ARG_PASSWORD, PasswordUtil.encryptPassword("abcdefgh")) it.putBoolean(ARG_HAS_PASSWORD, true) it.putBoolean(ARG_EDIT, true) }, @@ -145,13 +132,7 @@ class SftpConnectDialogArgumentPopulationTest : AbstractSftpConnectDialogUiTests it.putString(ARG_ADDRESS, "127.0.0.1") it.putInt(ARG_PORT, 2121) it.putString(ARG_USERNAME, "root") - it.putString( - ARG_PASSWORD, - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "abcdefgh", - ), - ) + it.putString(ARG_PASSWORD, PasswordUtil.encryptPassword("abcdefgh")) it.putBoolean(ARG_HAS_PASSWORD, true) it.putString(ARG_TLS, "explicit") it.putBoolean(ARG_EDIT, true) @@ -184,13 +165,7 @@ class SftpConnectDialogArgumentPopulationTest : AbstractSftpConnectDialogUiTests it.putString(ARG_ADDRESS, "127.0.0.1") it.putInt(ARG_PORT, 2121) it.putString(ARG_USERNAME, "root") - it.putString( - ARG_PASSWORD, - PasswordUtil.encryptPassword( - AppConfig.getInstance(), - "abcdefgh", - ), - ) + it.putString(ARG_PASSWORD, PasswordUtil.encryptPassword("abcdefgh")) it.putString(ARG_DEFAULT_PATH, "/root/Documents") it.putBoolean(ARG_HAS_PASSWORD, true) it.putString(ARG_TLS, "explicit") diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogFtpTest.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogFtpTest.kt index 7fcd0855d7..fadbed3446 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogFtpTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogFtpTest.kt @@ -2,7 +2,6 @@ package com.amaze.filemanager.ui.dialogs import android.os.Bundle import android.util.Base64 -import androidx.test.core.app.ApplicationProvider import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.ARG_TLS import com.amaze.filemanager.filesystem.ftp.FTPClientImpl.Companion.TLS_EXPLICIT import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils @@ -144,7 +143,6 @@ class SftpConnectDialogFtpTest : AbstractSftpConnectDialogTests() { assertEquals( verify.getString(key), PasswordUtil.decryptPassword( - ApplicationProvider.getApplicationContext(), args.getString(key)!!, Base64.URL_SAFE, ), diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogSshTest.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogSshTest.kt index 639cf3613d..a404b915d3 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogSshTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialogSshTest.kt @@ -22,8 +22,6 @@ package com.amaze.filemanager.ui.dialogs import android.os.Bundle import android.util.Base64 -import androidx.test.core.app.ApplicationProvider -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.database.UtilsHandler import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils import com.amaze.filemanager.ui.activities.MainActivity @@ -78,7 +76,7 @@ class SftpConnectDialogSshTest : AbstractSftpConnectDialogTests() { verify.putBoolean("edit", true) verify.putString( "password", - PasswordUtil.encryptPassword(AppConfig.getInstance(), "12345678", Base64.URL_SAFE) + PasswordUtil.encryptPassword("12345678", Base64.URL_SAFE) ?.replace("\n", ""), ) testOpenSftpConnectDialog(uri, verify) @@ -103,7 +101,7 @@ class SftpConnectDialogSshTest : AbstractSftpConnectDialogTests() { verify.putString("defaultPath", "/data/incoming") verify.putString( "password", - PasswordUtil.encryptPassword(AppConfig.getInstance(), "12345678", Base64.URL_SAFE) + PasswordUtil.encryptPassword("12345678", Base64.URL_SAFE) ?.replace("\n", ""), ) testOpenSftpConnectDialog(uri, verify) @@ -133,7 +131,7 @@ class SftpConnectDialogSshTest : AbstractSftpConnectDialogTests() { ) verify.putString( "password", - PasswordUtil.encryptPassword(AppConfig.getInstance(), "12345678", Base64.URL_SAFE) + PasswordUtil.encryptPassword("12345678", Base64.URL_SAFE) ?.replace("\n", ""), ) testOpenSftpConnectDialog(uri, verify) @@ -168,7 +166,6 @@ class SftpConnectDialogSshTest : AbstractSftpConnectDialogTests() { assertEquals( verify.getString(key), PasswordUtil.decryptPassword( - ApplicationProvider.getApplicationContext(), mocked.arguments!!.getString(key)!!, Base64.URL_SAFE, ), diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt index 72305b064a..a5b474634f 100644 --- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt +++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/SmbConnectDialogTest.kt @@ -25,7 +25,6 @@ import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario import com.afollestad.materialdialogs.DialogAction import com.afollestad.materialdialogs.MaterialDialog -import com.amaze.filemanager.application.AppConfig import com.amaze.filemanager.ui.activities.AbstractMainActivityTestBase import com.amaze.filemanager.ui.activities.MainActivity import com.amaze.filemanager.ui.dialogs.SmbConnectDialog.ARG_EDIT @@ -61,10 +60,7 @@ class SmbConnectDialogTest : AbstractMainActivityTestBase() { }, withDialog = { dialog, materialDialog -> val encryptedPath = - SmbUtil.getSmbEncryptedPath( - AppConfig.getInstance(), - "smb://user:password@127.0.0.1/", - ) + SmbUtil.getSmbEncryptedPath("smb://user:password@127.0.0.1/") dialog.binding.run { this.connectionET.setText("SMB Connection Test") this.usernameET.setText("user") diff --git a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt index ccc65a0110..db7f76468c 100644 --- a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt +++ b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt @@ -23,7 +23,6 @@ package com.amaze.filemanager.utils import android.os.Build import android.os.Build.VERSION_CODES.LOLLIPOP import android.os.Build.VERSION_CODES.P -import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.amaze.filemanager.fileoperations.filesystem.DOESNT_EXIST import com.amaze.filemanager.fileoperations.filesystem.WRITABLE_ON_REMOTE @@ -56,11 +55,11 @@ class SmbUtilTest { @Test fun testEncryptDecryptSmb() { val path = "smb://root:toor@127.0.0.1" - val encrypted = getSmbEncryptedPath(ApplicationProvider.getApplicationContext(), path) + val encrypted = getSmbEncryptedPath(path) assertNotEquals(path, encrypted) assertTrue(encrypted.startsWith("smb://root:")) assertTrue(encrypted.endsWith("@127.0.0.1")) - val decrypted = getSmbDecryptedPath(ApplicationProvider.getApplicationContext(), encrypted) + val decrypted = getSmbDecryptedPath(encrypted) assertEquals(path, decrypted) } @@ -70,11 +69,11 @@ class SmbUtilTest { @Test fun testEncryptDecryptFtps() { val path = "ftps://root:toor@127.0.0.1" - val encrypted = getSmbEncryptedPath(ApplicationProvider.getApplicationContext(), path) + val encrypted = getSmbEncryptedPath(path) assertNotEquals(path, encrypted) assertTrue(encrypted.startsWith("ftps://root:")) assertTrue(encrypted.endsWith("@127.0.0.1")) - val decrypted = getSmbDecryptedPath(ApplicationProvider.getApplicationContext(), encrypted) + val decrypted = getSmbDecryptedPath(encrypted) assertEquals(path, decrypted) } @@ -84,11 +83,11 @@ class SmbUtilTest { @Test fun testEncryptDecryptFtpsWithExtraParams() { val path = "ftps://root:toor@127.0.0.1?tls=explicit" - val encrypted = getSmbEncryptedPath(ApplicationProvider.getApplicationContext(), path) + val encrypted = getSmbEncryptedPath(path) assertNotEquals(path, encrypted) assertTrue(encrypted.startsWith("ftps://root:")) assertTrue(encrypted.endsWith("@127.0.0.1?tls=explicit")) - val decrypted = getSmbDecryptedPath(ApplicationProvider.getApplicationContext(), encrypted) + val decrypted = getSmbDecryptedPath(encrypted) assertEquals(path, decrypted) } @@ -98,10 +97,7 @@ class SmbUtilTest { @Test fun testEncryptWithoutCredentials() { val path = "smb://127.0.0.1" - assertEquals( - path, - getSmbEncryptedPath(ApplicationProvider.getApplicationContext(), path), - ) + assertEquals(path, getSmbEncryptedPath(path)) } /** @@ -110,10 +106,7 @@ class SmbUtilTest { @Test fun testEncryptWithoutPassword() { val path = "smb://toor@127.0.0.1" - assertEquals( - path, - getSmbEncryptedPath(ApplicationProvider.getApplicationContext(), path), - ) + assertEquals(path, getSmbEncryptedPath(path)) } /** diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e4f84059f..ed10a989a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,6 +34,7 @@ androidXTestRunner = "1.6.2" androidXTestExt = "1.2.1" androidXArchCoreTest = "2.2.0" androidXMultidex = "2.0.1" +androidXLifecycle = "2.8.7" autoService = "1.1.1" kotlinxCoroutines = "1.7.3" kotlinStdlibJdk8 = "1.9.20" @@ -95,6 +96,8 @@ androidX-palette = { module = "androidx.palette:palette-ktx", version.ref = "and androidX-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidXPref" } androidX-vectordrawable-animated = { module = "androidx.vectordrawable:vectordrawable-animated", version.ref = "vectordrawableAnimated" } androidX-biometric = { module = "androidx.biometric:biometric", version.ref = "androidXBiometric" } +androidX-lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidXLifecycle" } +androidX-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidXLifecycle" } androidX-test-core = { module = "androidx.test:core", version.ref = "androidXTest" } androidX-test-runner = { module = "androidx.test:runner", version.ref = "androidXTestRunner" } diff --git a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtil.kt b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtil.kt index 8e38681a01..d8c14fafd4 100644 --- a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtil.kt +++ b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtil.kt @@ -20,7 +20,6 @@ package com.amaze.filemanager.test -import android.content.Context import android.util.Base64 import com.amaze.filemanager.utils.PasswordUtil import org.robolectric.annotation.Implementation @@ -46,7 +45,6 @@ class ShadowPasswordUtil { @Implementation @Throws(GeneralSecurityException::class, IOException::class) fun encryptPassword( - context: Context?, plainText: String, base64Options: Int = Base64.URL_SAFE, ): String { @@ -57,7 +55,6 @@ class ShadowPasswordUtil { @Implementation @Throws(GeneralSecurityException::class, IOException::class) fun decryptPassword( - context: Context?, cipherText: String, base64Options: Int = Base64.URL_SAFE, ): String { diff --git a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java index 47abfe7621..4127cae06c 100644 --- a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java +++ b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java @@ -75,13 +75,8 @@ public void tearDown() { @Test public void testEncryptDecrypt() throws GeneralSecurityException, IOException { String text = "test"; - String encrypted = - PasswordUtil.INSTANCE.encryptPassword( - ApplicationProvider.getApplicationContext(), text, Base64.DEFAULT); - assertEquals( - text, - PasswordUtil.INSTANCE.decryptPassword( - ApplicationProvider.getApplicationContext(), encrypted, Base64.DEFAULT)); + String encrypted = PasswordUtil.INSTANCE.encryptPassword(text, Base64.DEFAULT); + assertEquals(text, PasswordUtil.INSTANCE.decryptPassword(encrypted, Base64.DEFAULT)); } @Test