diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a53068d..2f64485f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,6 @@ [versions] sentinel = "1.3.3-alpha01" gradle = "8.3.2" -desugar = "2.0.4" kotlin = "1.9.22" coroutines = "1.8.0" json = "1.5.1" @@ -46,8 +45,6 @@ tooltimber = { module = "com.infinum.sentinel:tool-timber", version.ref = "senti tools-gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" } -desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar" } - kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-core = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } diff --git a/sample/build.gradle b/sample/build.gradle index 0e8dd123..452b2a59 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -54,10 +54,6 @@ android { } } - compileOptions { - coreLibraryDesugaringEnabled true - } - sourceSets { main.java.srcDirs += "src/main/kotlin" release.java.srcDirs += "src/release/kotlin" @@ -65,7 +61,6 @@ android { } dependencies { - coreLibraryDesugaring libs.desugar implementation libs.kotlin.core implementation libs.androidx.appcompat implementation libs.androidx.preference diff --git a/sample/src/main/kotlin/com/infinum/sentinel/sample/JavaMainActivity.java b/sample/src/main/kotlin/com/infinum/sentinel/sample/JavaMainActivity.java index 26961fa6..a2338fce 100644 --- a/sample/src/main/kotlin/com/infinum/sentinel/sample/JavaMainActivity.java +++ b/sample/src/main/kotlin/com/infinum/sentinel/sample/JavaMainActivity.java @@ -1,5 +1,6 @@ package com.infinum.sentinel.sample; +import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; @@ -34,7 +35,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { tools.add(new GooglePlayTool()); tools.add(new ThimbleTool()); tools.add(new TimberTool()); - tools.add(new CertificateTool()); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ + tools.add(new CertificateTool()); + } Sentinel.watch(tools); viewBinding.showSentinel.setOnClickListener(v -> Sentinel.show()); diff --git a/sample/src/main/kotlin/com/infinum/sentinel/sample/SampleApplication.kt b/sample/src/main/kotlin/com/infinum/sentinel/sample/SampleApplication.kt index 304c19b1..1ccedca4 100644 --- a/sample/src/main/kotlin/com/infinum/sentinel/sample/SampleApplication.kt +++ b/sample/src/main/kotlin/com/infinum/sentinel/sample/SampleApplication.kt @@ -1,6 +1,7 @@ package com.infinum.sentinel.sample import android.app.Application +import android.os.Build import com.infinum.sentinel.Sentinel import com.infinum.sentinel.ui.tools.AppGalleryTool import com.infinum.sentinel.ui.tools.CertificateTool @@ -19,19 +20,23 @@ class SampleApplication : Application() { override fun onCreate() { super.onCreate() - Sentinel.watch( - setOf( - ChuckerTool(), - CollarTool(), - DbInspectorTool(), - LeakCanaryTool(), - AppGalleryTool(appId = "102016595"), - GooglePlayTool(), - ThimbleTool(), - TimberTool(allowedTags = listOf("Main")), - CertificateTool(userCertificates = loadDebugCertificates()) - ) - ) + Sentinel.watch(getWatchedTools()) + } + + private fun getWatchedTools(): Set { + val tools = mutableSetOf() + tools.add(ChuckerTool()) + tools.add(CollarTool()) + tools.add(DbInspectorTool()) + tools.add(LeakCanaryTool()) + tools.add(AppGalleryTool(appId = "102016595")) + tools.add(GooglePlayTool()) + tools.add(ThimbleTool()) + tools.add(TimberTool(allowedTags = listOf("Main"))) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + tools.add(CertificateTool(userCertificates = loadDebugCertificates())) + } + return tools } private fun loadDebugCertificates(): List { diff --git a/sentinel/build.gradle b/sentinel/build.gradle index d1fca6ec..e1b405fb 100644 --- a/sentinel/build.gradle +++ b/sentinel/build.gradle @@ -53,7 +53,6 @@ android { disable "RtlEnabled", "VectorPath" } compileOptions { - coreLibraryDesugaringEnabled true } kotlinOptions { freeCompilerArgs += [ @@ -89,7 +88,6 @@ dokkaJavadoc { } dependencies { - coreLibraryDesugaring libs.desugar implementation libs.kotlin.core implementation libs.kotlin.json implementation libs.coroutines diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/local/CertificateMonitorEntity.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/local/CertificateMonitorEntity.kt index e00eb123..786f78ac 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/local/CertificateMonitorEntity.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/local/CertificateMonitorEntity.kt @@ -3,7 +3,7 @@ package com.infinum.sentinel.data.models.local import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import java.time.temporal.ChronoUnit +import com.infinum.sentinel.utils.ChronoUnit @Entity(tableName = "certificate_monitor") internal data class CertificateMonitorEntity( diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/raw/CertificateData.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/raw/CertificateData.kt index f102ea97..7e2a279c 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/raw/CertificateData.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/data/models/raw/CertificateData.kt @@ -1,5 +1,7 @@ package com.infinum.sentinel.data.models.raw +import android.os.Build +import androidx.annotation.RequiresApi import com.infinum.sentinel.data.models.raw.certificates.FingerprintData import com.infinum.sentinel.data.models.raw.certificates.PublicKeyData import com.infinum.sentinel.data.models.raw.certificates.SignatureData @@ -9,6 +11,7 @@ import java.time.ZoneId import java.time.temporal.ChronoUnit import java.util.Date +@RequiresApi(Build.VERSION_CODES.O) internal data class CertificateData( val publicKey: PublicKeyData, val serialNumber: String, diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/data/sources/raw/collectors/CertificateCollector.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/data/sources/raw/collectors/CertificateCollector.kt index 13e8e8f2..e0ed8918 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/data/sources/raw/collectors/CertificateCollector.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/data/sources/raw/collectors/CertificateCollector.kt @@ -1,5 +1,6 @@ package com.infinum.sentinel.data.sources.raw.collectors +import android.os.Build import com.infinum.sentinel.data.models.raw.CertificateData import com.infinum.sentinel.data.models.raw.certificates.CertificateType import com.infinum.sentinel.data.models.raw.certificates.FingerprintData @@ -39,34 +40,37 @@ internal class CertificateCollector( ) private fun asCertificateData(certificates: List): List = - certificates.map { - CertificateData( - publicKey = PublicKeyData( - algorithm = it.publicKey.algorithm, - size = when (it.publicKey.algorithm) { - "RSA" -> (it.publicKey as RSAPublicKey).modulus.bitLength() - "DSA" -> (it.publicKey as DSAPublicKey).params.p.bitLength() // Or P or Q or G? - "EC" -> (it.publicKey as ECPublicKey).params.order.bitLength() // Or curve or cofactor? - else -> it.publicKey.encoded.size * DEFAULT_PUBLIC_KEY_SIZE_MULTIPLIER // wild guess - } - ), - serialNumber = it.serialNumber.toString(SERIAL_NUMBER_RADIX), - version = it.version, - signature = SignatureData( - algorithmName = it.sigAlgName, - algorithmOID = it.sigAlgOID - ), - issuerData = it.issuerDN.name.asASN(), - subjectData = it.subjectDN.name.asASN(), - startDate = it.notBefore, - endDate = it.notAfter, - fingerprint = FingerprintData( - md5 = fingerprint(it, "MD5")?.lowercase(), - sha1 = fingerprint(it, "SHA1")?.lowercase(), - sha256 = fingerprint(it, "SHA-256")?.lowercase() + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) emptyList() + else { + certificates.map { + CertificateData( + publicKey = PublicKeyData( + algorithm = it.publicKey.algorithm, + size = when (it.publicKey.algorithm) { + "RSA" -> (it.publicKey as RSAPublicKey).modulus.bitLength() + "DSA" -> (it.publicKey as DSAPublicKey).params.p.bitLength() // Or P or Q or G? + "EC" -> (it.publicKey as ECPublicKey).params.order.bitLength() // Or curve or cofactor? + else -> it.publicKey.encoded.size * DEFAULT_PUBLIC_KEY_SIZE_MULTIPLIER // wild guess + } + ), + serialNumber = it.serialNumber.toString(SERIAL_NUMBER_RADIX), + version = it.version, + signature = SignatureData( + algorithmName = it.sigAlgName, + algorithmOID = it.sigAlgOID + ), + issuerData = it.issuerDN.name.asASN(), + subjectData = it.subjectDN.name.asASN(), + startDate = it.notBefore, + endDate = it.notAfter, + fingerprint = FingerprintData( + md5 = fingerprint(it, "MD5")?.lowercase(), + sha1 = fingerprint(it, "SHA1")?.lowercase(), + sha256 = fingerprint(it, "SHA-256")?.lowercase() + ) ) - ) - }.toList().sortedBy { it.title?.lowercase() } + }.toList().sortedBy { it.title?.lowercase() } + } @Throws( NoSuchAlgorithmException::class, diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/di/component/DomainComponent.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/di/component/DomainComponent.kt index e3fb4e66..2301caeb 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/di/component/DomainComponent.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/di/component/DomainComponent.kt @@ -3,6 +3,7 @@ package com.infinum.sentinel.di.component import android.app.Application import android.content.Context import android.content.Intent +import android.os.Build import android.text.format.Formatter import com.google.android.material.snackbar.Snackbar import com.infinum.sentinel.R @@ -255,6 +256,7 @@ internal abstract class DomainComponent( } private fun initializeCertificateMonitor() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return scope.launch { withContext(Dispatchers.IO) { val monitorEntity = certificateMonitor.load(CertificateMonitorParameters()).first() @@ -339,22 +341,27 @@ internal abstract class DomainComponent( context.enableShakeTrigger()?.let { entity.enabled = it } + TriggerType.FOREGROUND -> context.enableForegroundTrigger()?.let { entity.enabled = it } + TriggerType.PROXIMITY -> context.enableProximityTrigger()?.let { entity.enabled = it } + TriggerType.USB_CONNECTED -> context.enableUsbConnectedTrigger()?.let { entity.enabled = it } + TriggerType.AIRPLANE_MODE_ON -> context.enableAirplaneModeOnTrigger()?.let { entity.enabled = it } + else -> null }?.let { triggers.save( diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/extensions/ChronoUnit.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/extensions/ChronoUnit.kt new file mode 100644 index 00000000..1bd225bc --- /dev/null +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/extensions/ChronoUnit.kt @@ -0,0 +1,27 @@ +package com.infinum.sentinel.extensions + +import android.os.Build +import androidx.annotation.RequiresApi +import java.time.temporal.ChronoUnit + +@RequiresApi(Build.VERSION_CODES.O) +@Suppress("CyclomaticComplexMethod") +internal fun ChronoUnit.toSentinelChronoUnit(): com.infinum.sentinel.utils.ChronoUnit = + when (this) { + ChronoUnit.NANOS -> com.infinum.sentinel.utils.ChronoUnit.NANOS + ChronoUnit.MICROS -> com.infinum.sentinel.utils.ChronoUnit.MICROS + ChronoUnit.MILLIS -> com.infinum.sentinel.utils.ChronoUnit.MILLIS + ChronoUnit.SECONDS -> com.infinum.sentinel.utils.ChronoUnit.SECONDS + ChronoUnit.MINUTES -> com.infinum.sentinel.utils.ChronoUnit.MINUTES + ChronoUnit.HOURS -> com.infinum.sentinel.utils.ChronoUnit.HOURS + ChronoUnit.HALF_DAYS -> com.infinum.sentinel.utils.ChronoUnit.HALF_DAYS + ChronoUnit.DAYS -> com.infinum.sentinel.utils.ChronoUnit.DAYS + ChronoUnit.WEEKS -> com.infinum.sentinel.utils.ChronoUnit.WEEKS + ChronoUnit.MONTHS -> com.infinum.sentinel.utils.ChronoUnit.MONTHS + ChronoUnit.YEARS -> com.infinum.sentinel.utils.ChronoUnit.YEARS + ChronoUnit.DECADES -> com.infinum.sentinel.utils.ChronoUnit.DECADES + ChronoUnit.CENTURIES -> com.infinum.sentinel.utils.ChronoUnit.CENTURIES + ChronoUnit.MILLENNIA -> com.infinum.sentinel.utils.ChronoUnit.MILLENNIA + ChronoUnit.ERAS -> com.infinum.sentinel.utils.ChronoUnit.ERAS + ChronoUnit.FOREVER -> com.infinum.sentinel.utils.ChronoUnit.FOREVER + } diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificateViewHolder.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificateViewHolder.kt index e932b33c..c3786f1a 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificateViewHolder.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificateViewHolder.kt @@ -1,13 +1,17 @@ package com.infinum.sentinel.ui.certificates +import android.os.Build +import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.infinum.sentinel.R import com.infinum.sentinel.data.models.local.CertificateMonitorEntity import com.infinum.sentinel.data.models.raw.CertificateData import com.infinum.sentinel.databinding.SentinelItemCertificateBinding +import com.infinum.sentinel.utils.toJavaChronoUnit import java.time.temporal.ChronoUnit +@RequiresApi(Build.VERSION_CODES.O) internal class CertificateViewHolder( private val binding: SentinelItemCertificateBinding ) : RecyclerView.ViewHolder(binding.root) { @@ -24,7 +28,7 @@ internal class CertificateViewHolder( if ( certificate.isValidIn( settings?.expireInAmount ?: 0, - settings?.expireInUnit ?: ChronoUnit.DAYS + settings?.expireInUnit?.toJavaChronoUnit() ?: ChronoUnit.DAYS ) ) { R.color.sentinel_primary diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesActivity.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesActivity.kt index 301e3a5c..1ecbd502 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesActivity.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesActivity.kt @@ -1,10 +1,13 @@ package com.infinum.sentinel.ui.certificates +import android.os.Build import android.os.Bundle +import androidx.annotation.RequiresApi import androidx.annotation.RestrictTo import com.infinum.sentinel.ui.shared.base.BaseChildActivity @RestrictTo(RestrictTo.Scope.LIBRARY) +@RequiresApi(Build.VERSION_CODES.O) internal class CertificatesActivity : BaseChildActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesAdapter.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesAdapter.kt index 13c601c3..b77f4c1a 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesAdapter.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesAdapter.kt @@ -1,12 +1,15 @@ package com.infinum.sentinel.ui.certificates +import android.os.Build import android.view.LayoutInflater import android.view.ViewGroup +import androidx.annotation.RequiresApi import androidx.recyclerview.widget.ListAdapter import com.infinum.sentinel.data.models.local.CertificateMonitorEntity import com.infinum.sentinel.data.models.raw.CertificateData import com.infinum.sentinel.databinding.SentinelItemCertificateBinding +@RequiresApi(Build.VERSION_CODES.O) internal class CertificatesAdapter( private val onListChanged: () -> Unit, private val onClick: (CertificateData) -> Unit diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesFragment.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesFragment.kt index 01e408b5..8a2080f9 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesFragment.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/CertificatesFragment.kt @@ -1,8 +1,10 @@ package com.infinum.sentinel.ui.certificates import android.content.Intent +import android.os.Build import android.os.Bundle import android.view.View +import androidx.annotation.RequiresApi import androidx.annotation.RestrictTo import androidx.core.view.isGone import androidx.core.view.isVisible @@ -18,6 +20,7 @@ import com.infinum.sentinel.ui.shared.delegates.viewBinding import com.infinum.sentinel.ui.shared.edgefactories.bounce.BounceEdgeEffectFactory @RestrictTo(RestrictTo.Scope.LIBRARY) +@RequiresApi(Build.VERSION_CODES.O) internal class CertificatesFragment : BaseChildFragment(R.layout.sentinel_fragment_certificates) { diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsActivity.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsActivity.kt index 7eb8cb63..3b2b9ec3 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsActivity.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsActivity.kt @@ -1,10 +1,13 @@ package com.infinum.sentinel.ui.certificates.details +import android.os.Build import android.os.Bundle +import androidx.annotation.RequiresApi import androidx.annotation.RestrictTo import com.infinum.sentinel.ui.shared.base.BaseChildActivity @RestrictTo(RestrictTo.Scope.LIBRARY) +@RequiresApi(Build.VERSION_CODES.O) internal class CertificateDetailsActivity : BaseChildActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsFragment.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsFragment.kt index 7fdb0169..94a5dcbd 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsFragment.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/details/CertificateDetailsFragment.kt @@ -1,7 +1,9 @@ package com.infinum.sentinel.ui.certificates.details +import android.os.Build import android.os.Bundle import android.view.View +import androidx.annotation.RequiresApi import androidx.annotation.RestrictTo import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -10,9 +12,11 @@ import com.infinum.sentinel.databinding.SentinelFragmentCertificateDetailsBindin import com.infinum.sentinel.extensions.viewModels import com.infinum.sentinel.ui.shared.base.BaseChildFragment import com.infinum.sentinel.ui.shared.delegates.viewBinding +import com.infinum.sentinel.utils.toJavaChronoUnit import java.text.SimpleDateFormat @RestrictTo(RestrictTo.Scope.LIBRARY) +@RequiresApi(Build.VERSION_CODES.O) internal class CertificateDetailsFragment : BaseChildFragment(R.layout.sentinel_fragment_certificate_details) { @@ -56,7 +60,10 @@ internal class CertificateDetailsFragment : issuedView.text = SimpleDateFormat.getDateInstance().format(state.value.startDate) expiresView.text = SimpleDateFormat.getDateInstance().format(state.value.endDate) if (state.value.isValidNow) { - if (state.value.isValidIn(state.settings.expireInAmount, state.settings.expireInUnit)) { + if (state.value.isValidIn( + state.settings.expireInAmount, state.settings.expireInUnit.toJavaChronoUnit() + ) + ) { expiredView.isVisible = false expiredView.setBackgroundColor( ContextCompat.getColor( diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificateCheckWorker.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificateCheckWorker.kt index df2a8264..f3feecdc 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificateCheckWorker.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificateCheckWorker.kt @@ -1,6 +1,7 @@ package com.infinum.sentinel.ui.certificates.observer import android.content.Context +import android.os.Build import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.infinum.sentinel.data.models.raw.certificates.CertificateType @@ -25,6 +26,9 @@ internal class CertificateCheckWorker( } override suspend fun doWork(): Result { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return Result.success() + } val notifyInvalidNow = inputData.getBoolean(NOTIFY_INVALID_NOW, false) val notifyToExpire = inputData.getBoolean(NOTIFY_TO_EXPIRE, false) val expireInAmount = inputData.getInt(EXPIRE_IN_AMOUNT, 0) diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificatesObserver.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificatesObserver.kt index 614fe22f..da93a318 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificatesObserver.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/CertificatesObserver.kt @@ -1,6 +1,7 @@ package com.infinum.sentinel.ui.certificates.observer import android.content.Context +import android.os.Build import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner @@ -10,7 +11,8 @@ import com.infinum.sentinel.data.models.raw.certificates.CertificateType import com.infinum.sentinel.domain.Factories import com.infinum.sentinel.extensions.applicationName import com.infinum.sentinel.ui.shared.notification.NotificationFactory -import java.time.temporal.ChronoUnit +import com.infinum.sentinel.utils.ChronoUnit +import com.infinum.sentinel.utils.toJavaChronoUnit import kotlin.coroutines.resume import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -34,6 +36,7 @@ internal class CertificatesObserver( private var notifyInvalidNow = false private var notifyToExpire = false private var expireInAmount = 0 + private var expireInUnit = ChronoUnit.DAYS init { @@ -55,7 +58,9 @@ internal class CertificatesObserver( this.notifyInvalidNow = entity.notifyInvalidNow this.notifyToExpire = entity.notifyToExpire this.expireInAmount = entity.expireInAmount - this.expireInUnit = entity.expireInUnit + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + this.expireInUnit = entity.expireInUnit + } } fun deactivate() { @@ -68,29 +73,7 @@ internal class CertificatesObserver( if (notifyInvalidNow || notifyToExpire) { currentJob = scope.launch(Dispatchers.Main) { val result = withContext(Dispatchers.IO) { - if (active) { - suspendCancellableCoroutine { - val userCertificates = collectors.certificates() - .invoke()[CertificateType.USER] - .orEmpty() - - val invalidCertificatesCount = userCertificates - .filterNot { certificate -> certificate.isValidNow } - .count() - val toExpireCertificatesCount = userCertificates - .filterNot { certificate -> certificate.isValidIn(expireInAmount, expireInUnit) } - .count() - - it.resume( - CertificateCount( - invalidCertificatesCount, - toExpireCertificatesCount - ) - ) - } - } else { - CertificateCount(0, 0) - } + certificateCount() } if (result.invalid > 0 && notifyInvalidNow) { notificationFactory.showExpiredCertificate(context.applicationName, result.invalid) @@ -103,6 +86,38 @@ internal class CertificatesObserver( // dont run } + private suspend fun certificateCount() = if (active) { + suspendCancellableCoroutine { + val userCertificates = collectors.certificates() + .invoke()[CertificateType.USER] + .orEmpty() + + var invalidCertificatesCount = 0 + var toExpireCertificatesCount = 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + invalidCertificatesCount = userCertificates + .filterNot { certificate -> certificate.isValidNow } + .count() + toExpireCertificatesCount = userCertificates + .filterNot { certificate -> + certificate.isValidIn( + expireInAmount, expireInUnit.toJavaChronoUnit() + ) + } + .count() + } + + it.resume( + CertificateCount( + invalidCertificatesCount, + toExpireCertificatesCount + ) + ) + } + } else { + CertificateCount(0, 0) + } + private fun stop() { currentJob?.cancel() currentJob = null diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/SentinelWorkManager.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/SentinelWorkManager.kt index d0823898..944d7b33 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/SentinelWorkManager.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/certificates/observer/SentinelWorkManager.kt @@ -2,6 +2,7 @@ package com.infinum.sentinel.ui.certificates.observer import android.content.Context import android.os.Build +import androidx.annotation.RequiresApi import androidx.lifecycle.asFlow import androidx.work.Configuration import androidx.work.Constraints @@ -45,6 +46,7 @@ internal class SentinelWorkManager( ) } + @RequiresApi(Build.VERSION_CODES.O) fun startCertificatesCheck(entity: CertificateMonitorEntity) = WorkManager.getInstance(context) .enqueueUniquePeriodicWork( diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsFragment.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsFragment.kt index acbc0925..d0936ac2 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsFragment.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsFragment.kt @@ -23,7 +23,7 @@ import com.infinum.sentinel.databinding.SentinelFragmentSettingsBinding import com.infinum.sentinel.extensions.viewModels import com.infinum.sentinel.ui.shared.base.BaseChildFragment import com.infinum.sentinel.ui.shared.delegates.viewBinding -import java.time.temporal.ChronoUnit +import com.infinum.sentinel.utils.ChronoUnit import kotlin.math.roundToInt @RestrictTo(RestrictTo.Scope.LIBRARY) @@ -91,6 +91,7 @@ internal class SettingsFragment : BaseChildFragment(R.la ) ) } + decreaseLimitButton.setOnClickListener { limitSlider.value = (limitSlider.value - limitSlider.stepSize) .coerceAtLeast(limitSlider.valueFrom) @@ -212,59 +213,10 @@ internal class SettingsFragment : BaseChildFragment(R.la } is SettingsEvent.CertificateMonitorChanged -> { - binding.runOnStartSwitch.setOnCheckedChangeListener(null) - binding.runOnStartSwitch.isChecked = event.value.runOnStart - binding.runOnStartSwitch.setOnCheckedChangeListener { _, isChecked -> - viewModel.updateCertificatesMonitor(event.value.copy(runOnStart = isChecked)) - } - binding.runInBackgroundSwitch.setOnCheckedChangeListener(null) - binding.runInBackgroundSwitch.isChecked = event.value.runInBackground - binding.runInBackgroundSwitch.setOnCheckedChangeListener { _, isChecked -> - viewModel.updateCertificatesMonitor(event.value.copy(runInBackground = isChecked)) - } - binding.checkInvalidNowSwitch.setOnCheckedChangeListener(null) - binding.checkInvalidNowSwitch.isChecked = event.value.notifyInvalidNow - binding.checkInvalidNowSwitch.setOnCheckedChangeListener { _, isChecked -> - viewModel.updateCertificatesMonitor(event.value.copy(notifyInvalidNow = isChecked)) - } - binding.checkToExpireSwitch.setOnCheckedChangeListener(null) - binding.checkToExpireSwitch.isChecked = event.value.notifyToExpire - binding.checkToExpireSwitch.setOnCheckedChangeListener { _, isChecked -> - viewModel.updateCertificatesMonitor(event.value.copy(notifyToExpire = isChecked)) - } - binding.toExpireAmountSlider.clearOnChangeListeners() - binding.toExpireAmountSlider.value = event.value.expireInAmount.toFloat() - binding.toExpireAmountSlider.addOnChangeListener { _, value, _ -> - binding.toExpireValueView.text = String.format( - FORMAT_CERTIFICATE_TO_EXPIRE, - value.roundToInt(), - event.value.expireInUnit.name.lowercase() - ) - viewModel.updateCertificatesMonitor(event.value.copy(expireInAmount = value.roundToInt())) - } - binding.toExpireValueView.text = String.format( - FORMAT_CERTIFICATE_TO_EXPIRE, - event.value.expireInAmount, - event.value.expireInUnit.name.lowercase() - ) - when (event.value.expireInUnit) { - ChronoUnit.DAYS -> binding.daysButton.isChecked = true - ChronoUnit.WEEKS -> binding.weeksButton.isChecked = true - ChronoUnit.MONTHS -> binding.monthsButton.isChecked = true - ChronoUnit.YEARS -> binding.yearsButton.isChecked = true - else -> binding.daysButton.isChecked = true - } - binding.daysButton.setOnClickListener { - viewModel.updateCertificatesMonitor(event.value.copy(expireInUnit = ChronoUnit.DAYS)) - } - binding.weeksButton.setOnClickListener { - viewModel.updateCertificatesMonitor(event.value.copy(expireInUnit = ChronoUnit.WEEKS)) - } - binding.monthsButton.setOnClickListener { - viewModel.updateCertificatesMonitor(event.value.copy(expireInUnit = ChronoUnit.MONTHS)) - } - binding.yearsButton.setOnClickListener { - viewModel.updateCertificatesMonitor(event.value.copy(expireInUnit = ChronoUnit.YEARS)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + setCertificateUi(event) + } else { + hideCertificateUi() } } @@ -273,6 +225,92 @@ internal class SettingsFragment : BaseChildFragment(R.la } } + private fun hideCertificateUi() { + binding.apply { + runOnStartSwitch.visibility = View.GONE + runInBackgroundSwitch.visibility = View.GONE + checkInvalidNowSwitch.visibility = View.GONE + checkToExpireSwitch.visibility = View.GONE + toExpireAmountSlider.visibility = View.GONE + toExpireValueView.visibility = View.GONE + daysButton.visibility = View.GONE + weeksButton.visibility = View.GONE + monthsButton.visibility = View.GONE + yearsButton.visibility = View.GONE + certificatesTitleView.visibility = View.GONE + toExpireLayout.visibility = View.GONE + toExpireSliderLayout.visibility = View.GONE + } + } + + @Suppress("LongMethod") + private fun setCertificateUi(event: SettingsEvent.CertificateMonitorChanged) { + binding.apply { + runOnStartSwitch.setOnCheckedChangeListener(null) + runOnStartSwitch.isChecked = event.value.runOnStart + runOnStartSwitch.setOnCheckedChangeListener { _, isChecked -> + viewModel.updateCertificatesMonitor(event.value.copy(runOnStart = isChecked)) + } + runInBackgroundSwitch.setOnCheckedChangeListener(null) + runInBackgroundSwitch.isChecked = event.value.runInBackground + runInBackgroundSwitch.setOnCheckedChangeListener { _, isChecked -> + viewModel.updateCertificatesMonitor(event.value.copy(runInBackground = isChecked)) + } + checkInvalidNowSwitch.setOnCheckedChangeListener(null) + checkInvalidNowSwitch.isChecked = event.value.notifyInvalidNow + checkInvalidNowSwitch.setOnCheckedChangeListener { _, isChecked -> + viewModel.updateCertificatesMonitor(event.value.copy(notifyInvalidNow = isChecked)) + } + checkToExpireSwitch.setOnCheckedChangeListener(null) + checkToExpireSwitch.isChecked = event.value.notifyToExpire + checkToExpireSwitch.setOnCheckedChangeListener { _, isChecked -> + viewModel.updateCertificatesMonitor(event.value.copy(notifyToExpire = isChecked)) + } + toExpireAmountSlider.clearOnChangeListeners() + toExpireAmountSlider.value = event.value.expireInAmount.toFloat() + toExpireAmountSlider.addOnChangeListener { _, value, _ -> + toExpireValueView.text = String.format( + FORMAT_CERTIFICATE_TO_EXPIRE, + value.roundToInt(), + event.value.expireInUnit.name.lowercase() + ) + viewModel.updateCertificatesMonitor(event.value.copy(expireInAmount = value.roundToInt())) + } + toExpireValueView.text = String.format( + FORMAT_CERTIFICATE_TO_EXPIRE, + event.value.expireInAmount, + event.value.expireInUnit.name.lowercase() + ) + when (event.value.expireInUnit) { + ChronoUnit.DAYS -> daysButton.isChecked = true + ChronoUnit.WEEKS -> weeksButton.isChecked = true + ChronoUnit.MONTHS -> monthsButton.isChecked = true + ChronoUnit.YEARS -> yearsButton.isChecked = true + else -> daysButton.isChecked = true + } + daysButton.setOnClickListener { + viewModel.updateCertificatesMonitor( + event.value.copy(expireInUnit = ChronoUnit.DAYS) + ) + } + weeksButton.setOnClickListener { + viewModel.updateCertificatesMonitor( + event.value.copy(expireInUnit = ChronoUnit.WEEKS) + ) + } + monthsButton.setOnClickListener { + viewModel.updateCertificatesMonitor( + event.value.copy(expireInUnit = ChronoUnit.MONTHS) + ) + } + yearsButton.setOnClickListener { + viewModel.updateCertificatesMonitor( + event.value.copy(expireInUnit = ChronoUnit.YEARS) + ) + } + } + } + private fun handleNotificationsPermission() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { return diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsViewModel.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsViewModel.kt index e46eb1b8..c37b4e78 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsViewModel.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/settings/SettingsViewModel.kt @@ -1,5 +1,6 @@ package com.infinum.sentinel.ui.settings +import android.os.Build import com.infinum.sentinel.data.models.local.BundleMonitorEntity import com.infinum.sentinel.data.models.local.CertificateMonitorEntity import com.infinum.sentinel.data.models.local.CrashMonitorEntity @@ -133,6 +134,9 @@ internal class SettingsViewModel( entity.notifyAnrs || entity.notifyExceptions fun updateCertificatesMonitor(entity: CertificateMonitorEntity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return + } launch { io { certificateMonitor.save( diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/shared/notification/NotificationIntentFactory.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/shared/notification/NotificationIntentFactory.kt index e91305e0..7e9c8a5a 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/shared/notification/NotificationIntentFactory.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/shared/notification/NotificationIntentFactory.kt @@ -2,9 +2,11 @@ package com.infinum.sentinel.ui.shared.notification import android.content.Context import android.content.Intent +import android.os.Build import com.infinum.sentinel.ui.certificates.CertificatesActivity import com.infinum.sentinel.ui.crash.CrashesActivity import com.infinum.sentinel.ui.crash.details.CrashDetailsActivity +import com.infinum.sentinel.ui.settings.SettingsActivity import com.infinum.sentinel.ui.shared.Constants import me.tatarka.inject.annotations.Inject @@ -27,9 +29,13 @@ internal class NotificationIntentFactory( override fun certificate(applicationName: String): Array = arrayOf( - Intent(context, CertificatesActivity::class.java) - .apply { - putExtra(Constants.Keys.APPLICATION_NAME, applicationName) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Intent(context, CertificatesActivity::class.java) + .apply { + putExtra(Constants.Keys.APPLICATION_NAME, applicationName) + } + } else { + Intent(context, SettingsActivity::class.java) + } ) } diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/tools/CertificateTool.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/tools/CertificateTool.kt index 53f54baf..1180b54a 100644 --- a/sentinel/src/main/kotlin/com/infinum/sentinel/ui/tools/CertificateTool.kt +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/ui/tools/CertificateTool.kt @@ -1,6 +1,7 @@ package com.infinum.sentinel.ui.tools import android.content.Intent +import android.os.Build import android.view.View import com.infinum.sentinel.R import com.infinum.sentinel.Sentinel @@ -13,15 +14,17 @@ import java.security.cert.X509Certificate public data class CertificateTool @JvmOverloads constructor( public val userCertificates: List = listOf(), private val listener: View.OnClickListener = View.OnClickListener { - it.context.startActivity( - Intent( - it.context, - CertificatesActivity::class.java - ).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - } - ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + it.context.startActivity( + Intent( + it.context, + CertificatesActivity::class.java + ).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + } + ) + } } ) : Sentinel.Tool { diff --git a/sentinel/src/main/kotlin/com/infinum/sentinel/utils/ChronoUnit.kt b/sentinel/src/main/kotlin/com/infinum/sentinel/utils/ChronoUnit.kt new file mode 100644 index 00000000..9f774717 --- /dev/null +++ b/sentinel/src/main/kotlin/com/infinum/sentinel/utils/ChronoUnit.kt @@ -0,0 +1,49 @@ +package com.infinum.sentinel.utils + +import android.os.Build +import androidx.annotation.RequiresApi + +/** + * Sentinel's implementation of Java 8 Time API ChronoUnit to avoid dependency on Java 8 in Database + * @see [https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html] + */ +internal enum class ChronoUnit { + NANOS, + MICROS, + MILLIS, + SECONDS, + MINUTES, + HOURS, + HALF_DAYS, + DAYS, + WEEKS, + MONTHS, + YEARS, + DECADES, + CENTURIES, + MILLENNIA, + ERAS, + FOREVER +} + +@RequiresApi(Build.VERSION_CODES.O) +@Suppress("CyclomaticComplexMethod") +internal fun ChronoUnit.toJavaChronoUnit(): java.time.temporal.ChronoUnit = + when (this) { + ChronoUnit.NANOS -> java.time.temporal.ChronoUnit.NANOS + ChronoUnit.MICROS -> java.time.temporal.ChronoUnit.MICROS + ChronoUnit.MILLIS -> java.time.temporal.ChronoUnit.MILLIS + ChronoUnit.SECONDS -> java.time.temporal.ChronoUnit.SECONDS + ChronoUnit.MINUTES -> java.time.temporal.ChronoUnit.MINUTES + ChronoUnit.HOURS -> java.time.temporal.ChronoUnit.HOURS + ChronoUnit.HALF_DAYS -> java.time.temporal.ChronoUnit.HALF_DAYS + ChronoUnit.DAYS -> java.time.temporal.ChronoUnit.DAYS + ChronoUnit.WEEKS -> java.time.temporal.ChronoUnit.WEEKS + ChronoUnit.MONTHS -> java.time.temporal.ChronoUnit.MONTHS + ChronoUnit.YEARS -> java.time.temporal.ChronoUnit.YEARS + ChronoUnit.DECADES -> java.time.temporal.ChronoUnit.DECADES + ChronoUnit.CENTURIES -> java.time.temporal.ChronoUnit.CENTURIES + ChronoUnit.MILLENNIA -> java.time.temporal.ChronoUnit.MILLENNIA + ChronoUnit.ERAS -> java.time.temporal.ChronoUnit.ERAS + ChronoUnit.FOREVER -> java.time.temporal.ChronoUnit.FOREVER + } diff --git a/sentinel/src/main/res/layout/sentinel_fragment_settings.xml b/sentinel/src/main/res/layout/sentinel_fragment_settings.xml index 88da897e..aeb7192e 100644 --- a/sentinel/src/main/res/layout/sentinel_fragment_settings.xml +++ b/sentinel/src/main/res/layout/sentinel_fragment_settings.xml @@ -364,6 +364,7 @@