From 42edfff727f32a400e8a9e446e942f24e955aea9 Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sat, 5 Apr 2025 00:19:05 +0800 Subject: [PATCH 1/2] Rename .java to .kt --- .../services/ftp/{FtpTileService.java => FtpTileService.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/{FtpTileService.java => FtpTileService.kt} (100%) diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.kt similarity index 100% rename from app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.java rename to app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.kt From c2b8a7b11d44432197a1e234b8ab490bd45c14ec Mon Sep 17 00:00:00 2001 From: Raymond Lai Date: Sat, 5 Apr 2025 00:19:05 +0800 Subject: [PATCH 2/2] FTP server minor improvements - Replace EventBus with kotlinx.coroutines - FtpServerFragment update code to recommended - FtpService explicitly acquires wakelock and enforces START_STICKY to ensure it's still running in the background even in the doze mode. Fixes #4125 - Upgrade ACRA to 2.13 for fixing https://github.com/square/leakcanary/issues/2568 --- app/build.gradle | 2 - app/src/main/AndroidManifest.xml | 3 +- .../asynchronous/services/ftp/FtpEventBus.kt | 28 ++++ .../asynchronous/services/ftp/FtpService.kt | 111 +++++++------ .../services/ftp/FtpTileService.kt | 147 ++++++++++-------- .../ui/fragments/FtpServerFragment.kt | 108 ++++++++----- gradle/libs.versions.toml | 10 +- 7 files changed, 245 insertions(+), 164 deletions(-) create mode 100644 app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpEventBus.kt diff --git a/app/build.gradle b/app/build.gradle index a693eb8cef..1f941b2ea9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,8 +196,6 @@ dependencies { implementation libs.apache.ftpserver.ftplet.api implementation libs.apache.ftpserver.core - implementation libs.eventbus - implementation libs.libsu.core implementation libs.libsu.io diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e304db0c01..6ab89ebe97 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -286,7 +286,8 @@ android:exported="true" android:icon="@drawable/ic_ftp_dark" android:label="@string/ftp" - android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> + android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" + tools:targetApi="24"> diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpEventBus.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpEventBus.kt new file mode 100644 index 0000000000..be4d11d568 --- /dev/null +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpEventBus.kt @@ -0,0 +1,28 @@ +package com.amaze.filemanager.asynchronous.services.ftp + +import com.amaze.filemanager.ui.fragments.FtpServerFragment +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow + +/** + * Replacement event bus to handle [FtpService] events using Kotlin's Flow. + * + * Original idea: https://mirchev.medium.com/its-21st-century-stop-using-eventbus-3ff5d9c6a00f + * + * @see [FtpService] + * @see [FtpTileService] + * @see [FtpServerFragment] + */ +object FtpEventBus { + private val _events = MutableSharedFlow(replay = 0) + val events = _events.asSharedFlow() + + /** + * Emit the event signal to the event bus as [MutableSharedFlow]. + * + * @param event The event to be emitted. + */ + suspend fun emit(event: FtpService.FtpReceiverActions) { + _events.emit(event) + } +} 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 d946d64d1f..a5a630e278 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 @@ -40,6 +40,7 @@ import android.os.PowerManager import android.os.SystemClock import android.provider.DocumentsContract import androidx.core.app.ServiceCompat +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.amaze.filemanager.BuildConfig import com.amaze.filemanager.R @@ -52,6 +53,10 @@ import com.amaze.filemanager.ui.notifications.FtpNotification import com.amaze.filemanager.ui.notifications.NotificationConstants import com.amaze.filemanager.utils.ObtainableServiceBinder import com.amaze.filemanager.utils.PasswordUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import org.apache.ftpserver.ConnectionConfigFactory import org.apache.ftpserver.FtpServer import org.apache.ftpserver.FtpServerFactory @@ -61,13 +66,13 @@ import org.apache.ftpserver.ssl.ClientAuth import org.apache.ftpserver.ssl.impl.DefaultSslConfiguration import org.apache.ftpserver.usermanager.impl.BaseUser import org.apache.ftpserver.usermanager.impl.WritePermission -import org.greenrobot.eventbus.EventBus import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.IOException import java.security.GeneralSecurityException import java.security.KeyStore import java.util.LinkedList +import java.util.concurrent.TimeUnit import javax.net.ssl.KeyManagerFactory import javax.net.ssl.TrustManagerFactory import kotlin.concurrent.thread @@ -79,6 +84,7 @@ import kotlin.concurrent.thread * Edited by zent-co on 30-07-2019 Edited by bowiechen on 2019-10-19. */ class FtpService : Service(), Runnable { + private val serviceScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) private val binder: IBinder = ObtainableServiceBinder(this) // Service will broadcast via event bus when server start/stop @@ -95,6 +101,12 @@ class FtpService : Service(), Runnable { private var isStartedByTile = false private lateinit var wakeLock: PowerManager.WakeLock + private fun publishEvent(event: FtpReceiverActions) { + serviceScope.launch { + FtpEventBus.emit(event) + } + } + override fun onStartCommand( intent: Intent?, flags: Int, @@ -126,7 +138,7 @@ class FtpService : Service(), Runnable { } else { startForeground(NotificationConstants.FTP_ID, notification) } - return START_NOT_STICKY + return START_STICKY } override fun onCreate() { @@ -143,6 +155,8 @@ class FtpService : Service(), Runnable { @Suppress("LongMethod") override fun run() { + // Acquire the WakeLock for 1 hour, per recommended. + wakeLock.acquire(TimeUnit.HOURS.toMillis(1L)) val preferences = PreferenceManager.getDefaultSharedPreferences(this) FtpServerFactory().run { val connectionConfigFactory = ConnectionConfigFactory() @@ -175,7 +189,7 @@ class FtpService : Service(), Runnable { }.onFailure { log.warn("failed to decrypt password in ftp service", it) AppConfig.toast(applicationContext, R.string.error) - preferences.edit().putString(KEY_PREFERENCE_PASSWORD, "").apply() + preferences.edit { putString(KEY_PREFERENCE_PASSWORD, "") } isPasswordProtected = false } } @@ -224,9 +238,9 @@ class FtpService : Service(), Runnable { ) fac.isImplicitSsl = true } catch (e: GeneralSecurityException) { - preferences.edit().putBoolean(KEY_PREFERENCE_SECURE, false).apply() + preferences.edit { putBoolean(KEY_PREFERENCE_SECURE, false) } } catch (e: IOException) { - preferences.edit().putBoolean(KEY_PREFERENCE_SECURE, false).apply() + preferences.edit { putBoolean(KEY_PREFERENCE_SECURE, false) } } } fac.port = getPort(preferences) @@ -237,23 +251,22 @@ class FtpService : Service(), Runnable { server = createServer().apply { start() - EventBus.getDefault() - .post( - if (isStartedByTile) { - FtpReceiverActions.STARTED_FROM_TILE - } else { - FtpReceiverActions.STARTED - }, - ) + publishEvent( + if (isStartedByTile) { + FtpReceiverActions.STARTED_FROM_TILE + } else { + FtpReceiverActions.STARTED + }, + ) } }.onFailure { - EventBus.getDefault().post(FtpReceiverActions.FAILED_TO_START) + wakeLock.release() + publishEvent(FtpReceiverActions.FAILED_TO_START) } } } override fun onDestroy() { - wakeLock.release() serverThread?.let { serverThread -> serverThread.interrupt() // wait 10 sec for server thread to finish @@ -263,9 +276,13 @@ class FtpService : Service(), Runnable { Companion.serverThread = null } server?.stop().also { - EventBus.getDefault().post(FtpReceiverActions.STOPPED) + publishEvent(FtpReceiverActions.STOPPED) } } + + if (wakeLock.isHeld) { + wakeLock.release() + } } // Restart the service if the app is closed from the recent list @@ -312,39 +329,6 @@ class FtpService : Service(), Runnable { const val TAG_STARTED_BY_TILE = "started_by_tile" // attribute of action_started, used by notification - private lateinit var _enabledCipherSuites: Array - - init { - _enabledCipherSuites = - LinkedList().apply { - if (SDK_INT >= Q) { - add("TLS_AES_128_GCM_SHA256") - add("TLS_AES_256_GCM_SHA384") - add("TLS_CHACHA20_POLY1305_SHA256") - } - if (SDK_INT >= N) { - add("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") - add("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256") - } - if (SDK_INT >= LOLLIPOP) { - add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") - add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") - add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") - add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") - add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") - add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") - add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") - add("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") - add("TLS_RSA_WITH_AES_128_GCM_SHA256") - add("TLS_RSA_WITH_AES_256_GCM_SHA384") - } - if (SDK_INT < LOLLIPOP) { - add("TLS_RSA_WITH_AES_128_CBC_SHA") - add("TLS_RSA_WITH_AES_256_CBC_SHA") - } - }.toTypedArray() - } - /** * Return a list of available ciphers for ftpserver. * @@ -355,7 +339,34 @@ class FtpService : Service(), Runnable { * @see [javax.net.ssl.SSLEngine] */ @JvmStatic - val enabledCipherSuites = _enabledCipherSuites + val enabledCipherSuites: Array = + LinkedList().apply { + if (SDK_INT >= Q) { + add("TLS_AES_128_GCM_SHA256") + add("TLS_AES_256_GCM_SHA384") + add("TLS_CHACHA20_POLY1305_SHA256") + } + if (SDK_INT >= N) { + add("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256") + add("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256") + } + if (SDK_INT >= LOLLIPOP) { + add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA") + add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") + add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA") + add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384") + add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA") + add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") + add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA") + add("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384") + add("TLS_RSA_WITH_AES_128_GCM_SHA256") + add("TLS_RSA_WITH_AES_256_GCM_SHA384") + } + if (SDK_INT < LOLLIPOP) { + add("TLS_RSA_WITH_AES_128_CBC_SHA") + add("TLS_RSA_WITH_AES_256_CBC_SHA") + } + }.toTypedArray() private var serverThread: Thread? = null private var server: FtpServer? = null diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.kt index d0de75487b..1ae5638f08 100644 --- a/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.kt +++ b/app/src/main/java/com/amaze/filemanager/asynchronous/services/ftp/FtpTileService.kt @@ -17,77 +17,98 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +package com.amaze.filemanager.asynchronous.services.ftp -package com.amaze.filemanager.asynchronous.services.ftp; +import android.content.Intent +import android.graphics.drawable.Icon +import android.os.Build +import android.service.quicksettings.Tile +import android.service.quicksettings.TileService +import android.widget.Toast +import androidx.annotation.RequiresApi +import com.amaze.filemanager.R +import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.isRunning +import com.amaze.filemanager.utils.NetworkUtil.isConnectedToLocalNetwork +import com.amaze.filemanager.utils.NetworkUtil.isConnectedToWifi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; - -import com.amaze.filemanager.R; -import com.amaze.filemanager.utils.NetworkUtil; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.graphics.drawable.Icon; -import android.os.Build; -import android.service.quicksettings.Tile; -import android.service.quicksettings.TileService; -import android.widget.Toast; - -/** Created by vishal on 1/1/17. */ -@TargetApi(Build.VERSION_CODES.N) -public class FtpTileService extends TileService { +/** + * [FtpService] tile service to start and stop the FTP server. + * + * Created by vishal on 1/1/17. */ +@RequiresApi(Build.VERSION_CODES.N) +class FtpTileService : TileService() { + private val serviceScope = CoroutineScope(Dispatchers.Main + Job()) - @Subscribe - public void onFtpReceiverActions(FtpService.FtpReceiverActions signal) { - updateTileState(); - } + override fun onCreate() { + super.onCreate() + serviceScope.launch { + FtpEventBus.events.collect { _ -> + onFtpReceiverActions() + } + } + } - @Override - public void onStartListening() { - super.onStartListening(); - EventBus.getDefault().register(this); - updateTileState(); - } + override fun onDestroy() { + super.onDestroy() + serviceScope.cancel() + } - @Override - public void onStopListening() { - super.onStopListening(); - EventBus.getDefault().unregister(this); - } + override fun onStartListening() { + super.onStartListening() + updateTileState() + } - @Override - public void onClick() { - unlockAndRun( - () -> { - if (FtpService.isRunning()) { - getApplicationContext() - .sendBroadcast( - new Intent(FtpService.ACTION_STOP_FTPSERVER).setPackage(getPackageName())); - } else { - if (NetworkUtil.isConnectedToWifi(getApplicationContext()) - || NetworkUtil.isConnectedToLocalNetwork(getApplicationContext())) { - Intent i = new Intent(FtpService.ACTION_START_FTPSERVER).setPackage(getPackageName()); - i.putExtra(FtpService.TAG_STARTED_BY_TILE, true); - getApplicationContext().sendBroadcast(i); + override fun onClick() { + unlockAndRun { + if (isRunning()) { + applicationContext + .sendBroadcast( + Intent(FtpService.ACTION_STOP_FTPSERVER).setPackage(packageName), + ) } else { - Toast.makeText( - getApplicationContext(), getString(R.string.ftp_no_wifi), Toast.LENGTH_LONG) - .show(); + if (isConnectedToWifi(applicationContext) || + isConnectedToLocalNetwork(applicationContext) + ) { + val i = Intent(FtpService.ACTION_START_FTPSERVER).setPackage(packageName) + i.putExtra(FtpService.TAG_STARTED_BY_TILE, true) + applicationContext.sendBroadcast(i) + } else { + Toast.makeText( + applicationContext, + getString(R.string.ftp_no_wifi), + Toast.LENGTH_LONG, + ).show() + } } - } - }); - } + } + } + + private fun updateTileState() { + val tile = qsTile + if (isRunning()) { + tile.state = Tile.STATE_ACTIVE + tile.icon = + Icon.createWithResource( + this, + R.drawable.ic_ftp_dark, + ) + } else { + tile.state = Tile.STATE_INACTIVE + tile.icon = + Icon.createWithResource( + this, + R.drawable.ic_ftp_light, + ) + } + tile.updateTile() + } - private void updateTileState() { - Tile tile = getQsTile(); - if (FtpService.isRunning()) { - tile.setState(Tile.STATE_ACTIVE); - tile.setIcon(Icon.createWithResource(this, R.drawable.ic_ftp_dark)); - } else { - tile.setState(Tile.STATE_INACTIVE); - tile.setIcon(Icon.createWithResource(this, R.drawable.ic_ftp_light)); + private fun onFtpReceiverActions() { + updateTileState() } - tile.updateTile(); - } } 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 329bef09ae..1b6c3dc4dc 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 @@ -27,7 +27,6 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager -import android.graphics.drawable.ColorDrawable import android.net.ConnectivityManager import android.net.Uri import android.os.Build.VERSION.SDK_INT @@ -57,15 +56,23 @@ import androidx.appcompat.widget.AppCompatImageButton import androidx.appcompat.widget.AppCompatTextView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat +import androidx.core.content.edit +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.drawable.toDrawable +import androidx.core.net.toUri import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceManager import com.afollestad.materialdialogs.DialogAction import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.folderselector.FolderChooserDialog import com.amaze.filemanager.R import com.amaze.filemanager.application.AppConfig +import com.amaze.filemanager.asynchronous.services.ftp.FtpEventBus import com.amaze.filemanager.asynchronous.services.ftp.FtpService import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.KEY_PREFERENCE_PATH import com.amaze.filemanager.asynchronous.services.ftp.FtpService.Companion.KEY_PREFERENCE_ROOT_FILESYSTEM @@ -84,11 +91,10 @@ import com.amaze.filemanager.utils.NetworkUtil.isConnectedToWifi import com.amaze.filemanager.utils.OneCharacterCharSequence import com.amaze.filemanager.utils.PasswordUtil import com.amaze.filemanager.utils.Utils +import com.amaze.filemanager.utils.registerReceiverCompat import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar -import org.greenrobot.eventbus.EventBus -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode +import kotlinx.coroutines.launch import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.IOException @@ -163,6 +169,14 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { ftpBtn.setOnClickListener { ftpBtnOnClick() } + + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + FtpEventBus.events.collect { event -> + onFtpReceiveActions(event) + } + } + } return binding.root } @@ -388,13 +402,12 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { } /** - * Handles messages sent from [EventBus]. + * Handles messages sent from [FtpEventBus]. * * @param signal as [FtpReceiverActions] */ - @Subscribe(threadMode = ThreadMode.MAIN_ORDERED) @Suppress("StringLiteralDuplication") - fun onFtpReceiveActions(signal: FtpReceiverActions) { + private fun onFtpReceiveActions(signal: FtpReceiverActions) { updateSpans() when (signal) { FtpReceiverActions.STARTED, FtpReceiverActions.STARTED_FROM_TILE -> { @@ -449,7 +462,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { mainActivity.prefs .getString(KEY_PREFERENCE_PATH, defaultPathFromPreferences)!! if (shouldUseSafFileSystem()) { - Uri.parse(directoryUri).run { + directoryUri.toUri().run { if (requireContext().checkUriPermission( this, Process.myPid(), @@ -543,19 +556,17 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { super.onResume() val wifiFilter = IntentFilter() wifiFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) - requireContext().registerReceiver( + requireContext().registerReceiverCompat( mWifiReceiver, wifiFilter, ContextCompat.RECEIVER_NOT_EXPORTED, ) - EventBus.getDefault().register(this) updateStatus() } override fun onPause() { super.onPause() requireContext().unregisterReceiver(mWifiReceiver) - EventBus.getDefault().unregister(this) dismissSnackbar() } @@ -589,7 +600,11 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { username.text = "${resources.getString(R.string.username)}: $usernameFromPreferences" password.text = "${resources.getString(R.string.password)}: $passwordBulleted" ftpPasswordVisibleButton.setImageDrawable( - resources.getDrawable(R.drawable.ic_eye_grey600_24dp), + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_eye_grey600_24dp, + mainActivity.theme, + ), ) ftpPasswordVisibleButton.visibility = if (passwordDecrypted?.isEmpty() == true) { @@ -602,13 +617,21 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { // password was not visible, let's make it visible password.text = resources.getString(R.string.password) + ": " + passwordDecrypted ftpPasswordVisibleButton.setImageDrawable( - resources.getDrawable(R.drawable.ic_eye_off_grey600_24dp), + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_eye_off_grey600_24dp, + mainActivity.theme, + ), ) } else { // password was visible, let's hide it password.text = resources.getString(R.string.password) + ": " + passwordBulleted ftpPasswordVisibleButton.setImageDrawable( - resources.getDrawable(R.drawable.ic_eye_grey600_24dp), + ResourcesCompat.getDrawable( + resources, + R.drawable.ic_eye_grey600_24dp, + mainActivity.theme, + ), ) } } @@ -757,10 +780,10 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { else -> { } } - val skin_color = mainActivity.currentColorPreference.primaryFirstTab + val skinColor = mainActivity.currentColorPreference.primaryFirstTab val skinTwoColor = mainActivity.currentColorPreference.primarySecondTab mainActivity.updateViews( - ColorDrawable(if (MainActivity.currentTab == 1) skinTwoColor else skin_color), + if (MainActivity.currentTab == 1) skinTwoColor.toDrawable() else skinColor.toDrawable(), ) ftpBtn.setOnKeyListener( @@ -831,7 +854,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { }.onFailure { log.warn("failed to decrypt ftp server password", it) Toast.makeText(requireContext(), R.string.error, Toast.LENGTH_SHORT).show() - mainActivity.prefs.edit().putString(FtpService.KEY_PREFERENCE_PASSWORD, "").apply() + mainActivity.prefs.edit { putString(FtpService.KEY_PREFERENCE_PASSWORD, "") } }.getOrNull() private val defaultPathFromPreferences: String @@ -857,7 +880,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { } private fun changeFTPServerPort(port: Int) { - mainActivity.prefs.edit().putInt(FtpService.PORT_PREFERENCE_KEY, port).apply() + mainActivity.prefs.edit { putInt(FtpService.PORT_PREFERENCE_KEY, port) } // first update spans which will point to an updated status updateSpans() @@ -871,35 +894,36 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { * file:/// or content:// as prefix */ fun changeFTPServerPath(path: String) { - val preferences = PreferenceManager.getDefaultSharedPreferences(mainActivity).edit() - if (FileUtils.isRunningAboveStorage(path)) { - preferences.putBoolean(KEY_PREFERENCE_ROOT_FILESYSTEM, true) + PreferenceManager.getDefaultSharedPreferences(mainActivity).edit { + if (FileUtils.isRunningAboveStorage(path)) { + putBoolean(KEY_PREFERENCE_ROOT_FILESYSTEM, true) + } + putString(KEY_PREFERENCE_PATH, path) } - preferences.putString(KEY_PREFERENCE_PATH, path) - preferences.apply() updateStatus() } private fun setFTPUsername(username: String) { mainActivity .prefs - .edit() - .putString(FtpService.KEY_PREFERENCE_USERNAME, username) - .apply() + .edit { + putString(FtpService.KEY_PREFERENCE_USERNAME, username) + } updateStatus() } + @Suppress("LabeledExpression") private fun setFTPPassword(password: String) { try { context?.run { mainActivity .prefs - .edit() - .putString( - FtpService.KEY_PREFERENCE_PASSWORD, - PasswordUtil.encryptPassword(this, password), - ) - .apply() + .edit { + putString( + FtpService.KEY_PREFERENCE_PASSWORD, + PasswordUtil.encryptPassword(this@run, password), + ) + } } } catch (e: GeneralSecurityException) { log.warn("failed to set ftp password", e) @@ -920,7 +944,7 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { .prefs .getInt(FtpService.KEY_PREFERENCE_TIMEOUT, FtpService.DEFAULT_TIMEOUT) private set(seconds) { - mainActivity.prefs.edit().putInt(FtpService.KEY_PREFERENCE_TIMEOUT, seconds).apply() + mainActivity.prefs.edit { putInt(FtpService.KEY_PREFERENCE_TIMEOUT, seconds) } } private var securePreference: Boolean @@ -931,9 +955,9 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { private set(isSecureEnabled) { mainActivity .prefs - .edit() - .putBoolean(FtpService.KEY_PREFERENCE_SECURE, isSecureEnabled) - .apply() + .edit { + putBoolean(FtpService.KEY_PREFERENCE_SECURE, isSecureEnabled) + } } private var readonlyPreference: Boolean @@ -941,9 +965,9 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { private set(isReadonly) { mainActivity .prefs - .edit() - .putBoolean(FtpService.KEY_PREFERENCE_READONLY, isReadonly) - .apply() + .edit { + putBoolean(FtpService.KEY_PREFERENCE_READONLY, isReadonly) + } } private var legacyFileSystemPreference: Boolean @@ -951,9 +975,9 @@ class FtpServerFragment : Fragment(R.layout.fragment_ftp) { private set(useSafFileSystem) { mainActivity .prefs - .edit() - .putBoolean(FtpService.KEY_PREFERENCE_SAF_FILESYSTEM, useSafFileSystem) - .apply() + .edit { + putBoolean(FtpService.KEY_PREFERENCE_SAF_FILESYSTEM, useSafFileSystem) + } } private fun promptUserToRestartServer() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5845050c77..fa5c2745ea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,6 @@ robolectric = "4.9" glide = "4.16.0" sshj = "0.39.0" jcifs = "2.1.9" -eventbus = "3.3.1" fabSpeedDial = "3.3.0" room = "2.5.2" bouncyCastle = "1.77" @@ -35,8 +34,8 @@ androidXTestExt = "1.2.1" androidXArchCoreTest = "2.2.0" androidXMultidex = "2.0.1" autoService = "1.0-rc4" -kotlinxCoroutineTest = "1.7.3" -kotlinStdlibJdk8 = "1.9.20" +kotlinxCoroutines = "1.7.3" +kotlinStdlibJdk8 = "1.9.25" uiAutomator = "2.3.0" junit = "4.13.2" slf4j = "2.0.13" @@ -67,7 +66,7 @@ rxAndroid = "2.1.1" rxJava = "2.2.9" okHttp = "4.12.0" acraCore = "5.12.0" -leakcanaryAndroid = "2.7" +leakcanaryAndroid = "2.13" mpAndroidChart = "v3.0.2" concurrentTrees = "2.6.1" aboutLibraries = "6.1.1" @@ -115,14 +114,13 @@ google-play-billing = { module = "com.android.billingclient:billing", version.re cloudrail-si-android = { module = "com.cloudrail:cloudrail-si-android", version.ref = "cloudrailSiAndroid" } -eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" } concurrent-trees = { module = "com.github.npgall:concurrent-trees", version.ref = "concurrentTrees" } gson = { module = "com.google.code.gson:gson", version.ref = "gson" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlinStdlibJdk8" } -kotlin-coroutine-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutineTest" } +kotlin-coroutine-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } libsu-io = { module = "com.github.topjohnwu.libsu:io", version.ref = "libsu" }