Introduce flavor for F-Droid

This commit is contained in:
Julian Raufelder 2021-01-12 15:23:31 +01:00
parent 9d43a2eefd
commit 41149b9607
No known key found for this signature in database
GPG Key ID: 17EE71F6634E381D
27 changed files with 504 additions and 35 deletions

View File

@ -25,7 +25,7 @@ Cryptomator for Android is currently available in the following distribution ch
```
git submodule init && git submodule update // (not necessary if cloned using --recurse-submodules)
./gradlew assembleLicenseDebug
./gradlew assembleApkstoreDebug
```
Before connecting to Onedrive or Dropbox you have to provide valid API keys using environment variables: `ONEDRIVE_API_KEY` or `DROPBOX_API_KEY`.

View File

@ -38,9 +38,27 @@ android {
dimension "version"
}
license {
apkstore {
dimension "version"
}
fdroid {
dimension "version"
}
}
sourceSets {
playstore {
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
}
apkstore {
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
}
fdroid {
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
}
}
}
@ -70,17 +88,29 @@ dependencies {
implementation dependencies.dagger
// cloud
implementation dependencies.dropbox
implementation dependencies.googlePlayServicesAuth
implementation(dependencies.googleApiServicesDrive) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
implementation(dependencies.googleApiClientAndroid) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
implementation dependencies.msgraph
playstoreImplementation dependencies.googlePlayServicesAuth
apkstoreImplementation dependencies.googlePlayServicesAuth
playstoreImplementation(dependencies.googleApiServicesDrive) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
apkstoreImplementation(dependencies.googleApiServicesDrive) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
playstoreImplementation(dependencies.googleApiClientAndroid) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
apkstoreImplementation(dependencies.googleApiClientAndroid) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
// rest
implementation dependencies.rxJava
implementation dependencies.rxAndroid

View File

@ -0,0 +1,42 @@
package org.cryptomator.data.cloud;
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory;
import org.cryptomator.data.cloud.onedrive.OnedriveCloudContentRepositoryFactory;
import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory;
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import javax.inject.Inject;
import javax.inject.Singleton;
import static java.util.Arrays.asList;
@Singleton
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
private final Iterable<CloudContentRepositoryFactory> factories;
@Inject
public CloudContentRepositoryFactories(DropboxCloudContentRepositoryFactory dropboxFactory, //
OnedriveCloudContentRepositoryFactory oneDriveFactory, //
CryptoCloudContentRepositoryFactory cryptoFactory, //
LocalStorageContentRepositoryFactory localStorageFactory, //
WebDavCloudContentRepositoryFactory webDavFactory) {
factories = asList(dropboxFactory, //
oneDriveFactory, //
cryptoFactory, //
localStorageFactory, //
webDavFactory);
}
@NotNull
@Override
public Iterator<CloudContentRepositoryFactory> iterator() {
return factories.iterator();
}
}

View File

@ -13,7 +13,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.cryptomator.data.util.TransferredBytesAwareGoogleContentInputStream;
import org.cryptomator.data.util.TransferredBytesAwareOutputStream;
import org.cryptomator.domain.CloudNode;
import org.cryptomator.domain.GoogleDriveCloud;

View File

@ -1,7 +1,9 @@
package org.cryptomator.data.util;
package org.cryptomator.data.cloud.googledrive;
import com.google.api.client.http.AbstractInputStreamContent;
import org.cryptomator.data.util.TransferredBytesAwareInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;

View File

@ -64,9 +64,27 @@ android {
dimension "version"
}
license {
apkstore {
dimension "version"
}
fdroid {
dimension "version"
}
}
sourceSets {
playstore {
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
}
apkstore {
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
}
fdroid {
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
}
}
packagingOptions {
@ -106,11 +124,21 @@ dependencies {
// cloud
implementation dependencies.dropbox
implementation dependencies.msgraph
implementation(dependencies.googleApiServicesDrive) {
playstoreImplementation(dependencies.googleApiServicesDrive) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
implementation(dependencies.googleApiClientAndroid) {
apkstoreImplementation(dependencies.googleApiServicesDrive) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
playstoreImplementation(dependencies.googleApiClientAndroid) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}
apkstoreImplementation(dependencies.googleApiClientAndroid) {
exclude module: 'guava-jdk5'
exclude module: 'httpclient'
}

View File

@ -0,0 +1,338 @@
package org.cryptomator.presentation.presenter
import android.Manifest
import android.accounts.AccountManager
import com.dropbox.core.android.Auth
import org.cryptomator.data.cloud.onedrive.OnedriveClientFactory
import org.cryptomator.data.cloud.onedrive.graph.ClientException
import org.cryptomator.data.cloud.onedrive.graph.ICallback
import org.cryptomator.data.util.X509CertificateHelper
import org.cryptomator.domain.*
import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.exception.FatalBackendException
import org.cryptomator.domain.exception.NetworkConnectionException
import org.cryptomator.domain.exception.authentication.*
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
import org.cryptomator.generator.Callback
import org.cryptomator.presentation.BuildConfig
import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.exception.PermissionNotGrantedException
import org.cryptomator.presentation.intent.AuthenticateCloudIntent
import org.cryptomator.presentation.model.*
import org.cryptomator.presentation.model.mappers.CloudModelMapper
import org.cryptomator.presentation.ui.activity.view.AuthenticateCloudView
import org.cryptomator.presentation.workflow.*
import org.cryptomator.util.ExceptionUtil
import org.cryptomator.util.crypto.CredentialCryptor
import timber.log.Timber
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.inject.Inject
@PerView
class AuthenticateCloudPresenter @Inject constructor( //
exceptionHandlers: ExceptionHandlers, //
private val cloudModelMapper: CloudModelMapper, //
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
private val getUsernameUseCase: GetUsernameUseCase, //
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
private val createNewVaultWorkflow: CreateNewVaultWorkflow) : Presenter<AuthenticateCloudView>(exceptionHandlers) {
private val strategies = arrayOf( //
DropboxAuthStrategy(), //
OnedriveAuthStrategy(), //
WebDAVAuthStrategy(), //
LocalStorageAuthStrategy() //
)
override fun workflows(): Iterable<Workflow<*>> {
return listOf(createNewVaultWorkflow, addExistingVaultWorkflow)
}
override fun resumed() {
val cloud = view?.intent()?.cloud()
val error = view?.intent()?.error()
handleNetworkConnectionExceptionIfRequired(error)
view?.intent()?.let { cloud?.let { cloud -> authStrategyFor(cloud).resumed(it) } }
}
private fun handleNetworkConnectionExceptionIfRequired(error: AuthenticationException?) {
if (error != null && ExceptionUtil.contains(error, NetworkConnectionException::class.java)) {
view?.showMessage(R.string.error_no_network_connection)
finish()
}
}
private fun authStrategyFor(cloud: CloudModel): AuthStrategy {
strategies.forEach { strategy ->
if (strategy.supports(cloud)) {
return strategy
}
}
return FailingAuthStrategy()
}
private fun getUsernameAndSuceedAuthentication(cloud: Cloud) {
getUsernameUseCase.withCloud(cloud).run(object : DefaultResultHandler<String>() {
override fun onSuccess(username: String) {
succeedAuthenticationWith(updateUsernameOf(cloud, username))
}
override fun onError(e: Throwable) {
super.onError(e)
finish()
}
})
}
private fun updateUsernameOf(cloud: Cloud, username: String): Cloud {
when (cloud.type()) {
CloudType.DROPBOX -> return DropboxCloud.aCopyOf(cloud as DropboxCloud).withUsername(username).build()
CloudType.ONEDRIVE -> return OnedriveCloud.aCopyOf(cloud as OnedriveCloud).withUsername(username).build()
}
throw IllegalStateException("Cloud " + cloud.type() + " is not supported")
}
private fun succeedAuthenticationWith(cloud: Cloud) {
addOrChangeCloudConnectionUseCase //
.withCloud(cloud) //
.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
finishWithResult(cloudModelMapper.toModel(cloud))
}
override fun onError(e: Throwable) {
super.onError(e)
finish()
}
})
}
private fun failAuthentication(cloudName: Int) {
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName)))
finish()
}
private fun failAuthentication(error: PermissionNotGrantedException) {
finishWithResult(error)
}
private inner class DropboxAuthStrategy : AuthStrategy {
private var authenticationStarted = false
override fun supports(cloud: CloudModel): Boolean {
return cloud.cloudType() == CloudTypeModel.DROPBOX
}
override fun resumed(intent: AuthenticateCloudIntent) {
if (authenticationStarted) {
handleAuthenticationResult(intent.cloud())
} else {
startAuthentication()
}
}
private fun startAuthentication() {
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
authenticationStarted = true
Auth.startOAuth2Authentication(context(), BuildConfig.DROPBOX_API_KEY)
view?.skipTransition()
}
private fun handleAuthenticationResult(cloudModel: CloudModel) {
val authToken = Auth.getOAuth2Token()
if (authToken == null) {
failAuthentication(cloudModel.name())
} else {
getUsernameAndSuceedAuthentication( //
DropboxCloud.aCopyOf(cloudModel.toCloud() as DropboxCloud) //
.withAccessToken(encrypt(authToken)) //
.build())
}
}
}
@Callback(dispatchResultOkOnly = false)
fun onUserRecoveryFinished(result: ActivityResult, cloud: CloudModel) {
if (result.isResultOk) {
succeedAuthenticationWith(cloud.toCloud())
} else {
failAuthentication(cloud.name())
}
}
@Callback(dispatchResultOkOnly = false)
fun onGoogleDriveAuthenticated(result: ActivityResult, cloud: CloudModel) {
if (result.isResultOk) {
val accountName = result.intent()?.extras?.getString(AccountManager.KEY_ACCOUNT_NAME)
succeedAuthenticationWith(GoogleDriveCloud.aCopyOf(cloud.toCloud() as GoogleDriveCloud) //
.withUsername(accountName) //
.withAccessToken(accountName) //
.build())
} else {
failAuthentication(cloud.name())
}
}
private inner class OnedriveAuthStrategy : AuthStrategy {
private var authenticationStarted = false
override fun supports(cloud: CloudModel): Boolean {
return cloud.cloudType() == CloudTypeModel.ONEDRIVE
}
override fun resumed(intent: AuthenticateCloudIntent) {
if (!authenticationStarted) {
startAuthentication(intent.cloud())
}
}
private fun startAuthentication(cloud: CloudModel) {
authenticationStarted = true
val authenticationAdapter = OnedriveClientFactory.instance(context(), (cloud.toCloud() as OnedriveCloud).accessToken()).authenticationAdapter
authenticationAdapter.login(activity(), object : ICallback<String?> {
override fun success(accessToken: String?) {
if (accessToken == null) {
Timber.tag("AuthicateCloudPrester").e("Onedrive access token is empty")
failAuthentication(cloud.name())
} else {
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
handleAuthenticationResult(cloud, accessToken)
}
}
override fun failure(ex: ClientException) {
Timber.tag("AuthicateCloudPrester").e(ex)
failAuthentication(cloud.name())
}
})
}
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String) {
getUsernameAndSuceedAuthentication( //
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
.withAccessToken(accessToken) //
.build())
}
}
private inner class WebDAVAuthStrategy : AuthStrategy {
override fun supports(cloud: CloudModel): Boolean {
return cloud.cloudType() == CloudTypeModel.WEBDAV
}
override fun resumed(intent: AuthenticateCloudIntent) {
handleWebDavAuthenticationExceptionIfRequired(intent.cloud() as WebDavCloudModel, intent.error())
}
private fun handleWebDavAuthenticationExceptionIfRequired(cloud: WebDavCloudModel, e: AuthenticationException) {
Timber.tag("AuthicateCloudPrester").e(e)
when {
ExceptionUtil.contains(e, WrongCredentialsException::class.java) -> {
failAuthentication(cloud.name())
}
ExceptionUtil.contains(e, WebDavCertificateUntrustedAuthenticationException::class.java) -> {
handleCertificateUntrustedExceptionIfRequired(cloud, e)
}
ExceptionUtil.contains(e, WebDavServerNotFoundException::class.java) -> {
view?.showMessage(R.string.error_server_not_found)
finish()
}
ExceptionUtil.contains(e, WebDavNotSupportedException::class.java) -> {
view?.showMessage(R.string.screen_cloud_error_webdav_not_supported)
finish()
}
}
}
private fun handleCertificateUntrustedExceptionIfRequired(cloud: WebDavCloudModel, e: AuthenticationException) {
val untrustedException = ExceptionUtil.extract(e, WebDavCertificateUntrustedAuthenticationException::class.java)
try {
val certificate = X509CertificateHelper.convertFromPem(untrustedException.get().certificate)
view?.showUntrustedCertificateDialog(cloud.toCloud() as WebDavCloud, certificate)
} catch (ex: CertificateException) {
Timber.tag("AuthicateCloudPrester").e(ex)
throw FatalBackendException(ex)
}
}
}
fun onAcceptWebDavCertificateClicked(cloud: WebDavCloud?, certificate: X509Certificate?) {
try {
val webDavCloudWithAcceptedCert = WebDavCloud.aCopyOf(cloud) //
.withCertificate(X509CertificateHelper.convertToPem(certificate)) //
.build()
finishWithResultAndExtra(cloudModelMapper.toModel(webDavCloudWithAcceptedCert), //
WEBDAV_ACCEPTED_UNTRUSTED_CERTIFICATE, //
true)
} catch (e: CertificateEncodingException) {
Timber.tag("AuthicateCloudPrester").e(e)
throw FatalBackendException(e)
}
}
fun onAcceptWebDavCertificateDenied() {
finish()
}
private inner class LocalStorageAuthStrategy : AuthStrategy {
private var authenticationStarted = false
override fun supports(cloud: CloudModel): Boolean {
return cloud.cloudType() == CloudTypeModel.LOCAL
}
override fun resumed(intent: AuthenticateCloudIntent) {
if (!authenticationStarted) {
startAuthentication(intent.cloud())
}
}
private fun startAuthentication(cloud: CloudModel) {
authenticationStarted = true
requestPermissions(PermissionsResultCallbacks.onLocalStorageAuthenticated(cloud), //
R.string.permission_snackbar_auth_local_vault, //
Manifest.permission.READ_EXTERNAL_STORAGE, //
Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
@Callback
fun onLocalStorageAuthenticated(result: PermissionsResult, cloud: CloudModel) {
if (result.granted()) {
succeedAuthenticationWith(cloud.toCloud())
} else {
failAuthentication(PermissionNotGrantedException(R.string.permission_snackbar_auth_local_vault))
}
}
private fun encrypt(password: String): String {
return CredentialCryptor //
.getInstance(context()) //
.encrypt(password)
}
private inner class FailingAuthStrategy : AuthStrategy {
override fun supports(cloud: CloudModel): Boolean {
return false
}
override fun resumed(intent: AuthenticateCloudIntent) {
view?.showError(R.string.error_authentication_failed)
finish()
}
}
private interface AuthStrategy {
fun supports(cloud: CloudModel): Boolean
fun resumed(intent: AuthenticateCloudIntent)
}
companion object {
const val WEBDAV_ACCEPTED_UNTRUSTED_CERTIFICATE = "acceptedUntrustedCertificate"
}
init {
unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, getUsernameUseCase)
}
}

View File

@ -42,8 +42,14 @@ class CryptomatorApp : MultiDexApplication(), HasComponent<ApplicationComponent>
override fun onCreate() {
super.onCreate()
setupLogging()
val flavor = if (BuildConfig.FLAVOR == "license") "License Edition" else "Google Play Edition"
val flavor = when (BuildConfig.FLAVOR) {
"apkstore" -> {
"APK Store Edition"
}
"fdroid" -> {
"F-Droid Edition"
} else -> "Google Play Edition"
}
Timber.tag("App").i("Cryptomator v%s (%d) \"%s\" started on android %s / API%d using a %s", //
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, flavor, //
Build.VERSION.RELEASE, Build.VERSION.SDK_INT, //

View File

@ -5,6 +5,7 @@ import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.exception.FatalBackendException
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
import org.cryptomator.generator.Callback
import org.cryptomator.presentation.BuildConfig
import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.Intents
@ -32,6 +33,11 @@ class ChooseCloudServicePresenter @Inject constructor( //
override fun resumed() {
val cloudTypeModels: MutableList<CloudTypeModel> = ArrayList(listOf(*CloudTypeModel.values()))
cloudTypeModels.remove(CloudTypeModel.CRYPTO)
if(BuildConfig.FLAVOR == "fdroid") {
cloudTypeModels.remove(CloudTypeModel.GOOGLE_DRIVE)
}
view?.render(cloudTypeModels)
}

View File

@ -9,6 +9,7 @@ import org.cryptomator.domain.usecases.cloud.GetAllCloudsUseCase
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
import org.cryptomator.domain.usecases.cloud.LogoutCloudUseCase
import org.cryptomator.generator.Callback
import org.cryptomator.presentation.BuildConfig
import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.Intents
@ -114,7 +115,10 @@ class CloudSettingsPresenter @Inject constructor( //
private inner class CloudsSubscriber : DefaultResultHandler<List<Cloud>>() {
override fun onSuccess(clouds: List<Cloud>) {
val cloudModel = cloudModelMapper.toModels(clouds).filter { isSingleLoginCloud(it) }.toMutableList() //
val cloudModel = cloudModelMapper.toModels(clouds) //
.filter { isSingleLoginCloud(it) } //
.filter { cloud -> !(BuildConfig.FLAVOR == "fdroid" && cloud.cloudType() == CloudTypeModel.GOOGLE_DRIVE)} //
.toMutableList() //
.also {
it.add(aWebdavCloud())
it.add(aLocalCloud())

View File

@ -66,9 +66,13 @@ class SettingsPresenter @Inject internal constructor(
}
private fun errorReportEmailBody(): String {
var variant = "PlayStore"
if (BuildConfig.FLAVOR == "license") {
variant = "ApkStore"
val variant = when (BuildConfig.FLAVOR) {
"apkstore" -> {
"APK Store"
}
"fdroid" -> {
"F-Droid"
} else -> "Google Play"
}
return StringBuilder().append("## ").append(context().getString(R.string.error_report_subject)).append("\n\n") //
.append("### ").append(context().getString(R.string.error_report_section_summary)).append('\n') //

View File

@ -105,12 +105,12 @@ class VaultListPresenter @Inject constructor( //
}
private fun checkLicense() {
if (BuildConfig.FLAVOR == "license") {
if (BuildConfig.FLAVOR == "apkstore" || BuildConfig.FLAVOR == "fdroid") {
licenseCheckUseCase //
.withLicense("") //
.run(object : NoOpResultHandler<LicenseCheck>() {
override fun onSuccess(licenseCheck: LicenseCheck) {
if (sharedPreferencesHandler.doUpdate()) {
if (BuildConfig.FLAVOR == "apkstore" && sharedPreferencesHandler.doUpdate()) {
checkForAppUpdates()
}
}

View File

@ -21,7 +21,7 @@ class CloudSettingsActivity : BaseActivity(), CloudSettingsView {
setSupportActionBar(toolbar)
}
override fun createFragment(): Fragment? = CloudSettingsFragment()
override fun createFragment(): Fragment = CloudSettingsFragment()
override fun render(cloudModels: List<CloudModel>) {
cloudSettingsFragment().showClouds(cloudModels)

View File

@ -148,18 +148,28 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
private fun setupLicense() {
if (BuildConfig.FLAVOR == "license") {
findPreference(SharedPreferencesHandler.MAIL)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail())
setupUpdateCheck()
} else {
preferenceScreen.removePreference(findPreference(LICENSE_ITEM_KEY))
val versionCategory = findPreference("versionCategory") as PreferenceCategory?
versionCategory?.removePreference(findPreference(UPDATE_CHECK_ITEM_KEY))
versionCategory?.removePreference(findPreference(UPDATE_INTERVAL_ITEM_KEY))
when (BuildConfig.FLAVOR) {
"apkstore" -> {
findPreference(SharedPreferencesHandler.MAIL)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail())
setupUpdateCheck()
}
"fdroid" -> {
findPreference(SharedPreferencesHandler.MAIL)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail())
removeUpdateCheck()
}
else -> {
preferenceScreen.removePreference(findPreference(LICENSE_ITEM_KEY))
removeUpdateCheck()
}
}
}
private fun removeUpdateCheck() {
val versionCategory = findPreference("versionCategory") as PreferenceCategory?
versionCategory?.removePreference(findPreference(UPDATE_CHECK_ITEM_KEY))
versionCategory?.removePreference(findPreference(UPDATE_INTERVAL_ITEM_KEY))
}
fun setupUpdateCheck() {
val preference = findPreference(UPDATE_CHECK_ITEM_KEY)
@ -195,7 +205,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
findPreference(SharedPreferencesHandler.PHOTO_UPLOAD)?.onPreferenceChangeListener = useAutoPhotoUploadChangedListener
findPreference(SharedPreferencesHandler.USE_LRU_CACHE)?.onPreferenceChangeListener = useLruChangedListener
findPreference(SharedPreferencesHandler.LRU_CACHE_SIZE)?.onPreferenceChangeListener = useLruChangedListener
if (BuildConfig.FLAVOR == "license") {
if (BuildConfig.FLAVOR == "apkstore") {
findPreference(UPDATE_CHECK_ITEM_KEY)?.onPreferenceClickListener = updateCheckClickListener
}
}