From 187dc1ab283923cf946cc9230e7846da8f390f39 Mon Sep 17 00:00:00 2001 From: Sergey Makarov Date: Thu, 5 Oct 2023 20:33:18 +0300 Subject: [PATCH 1/6] Remove nested safe calls with timeouts, since it makes the code less predictable, as "structured concurrency" is not supported by safe call for now. --- fingerprint/build.gradle.kts | 10 +- .../src/androidTest/AndroidManifest.xml | 3 + .../InstrumentedTests.kt | 32 ++++- .../utils/CallbackUtils.kt | 2 +- .../android/fingerprint/Fingerprinter.kt | 3 +- .../fingerprint/FingerprinterFactory.kt | 36 ++--- .../android/fingerprint/FingerprinterImpl.kt | 12 +- .../device_id_providers/AndroidIdProvider.kt | 4 +- .../device_id_providers/GsfIdProvider.kt | 4 +- .../device_id_providers/MediaDrmIdProvider.kt | 4 +- .../info_providers/BatteryInfoProvider.kt | 6 +- .../info_providers/CameraInfoProvider.kt | 4 +- .../info_providers/CodecInfoProvider.kt | 4 +- .../info_providers/CpuInfoProvider.kt | 10 +- .../DevicePersonalizationInfoProvider.kt | 12 +- .../DeviceSecurityInfoProvider.kt | 8 +- .../FingerprintSensorInfoProvider.kt | 4 +- .../info_providers/GpuInfoProvider.kt | 4 +- .../InputDevicesInfoProvider.kt | 4 +- .../info_providers/MemInfoProvider.kt | 8 +- .../info_providers/OsBuildInfoProvider.kt | 14 +- .../PackageManagerInfoProvider.kt | 6 +- .../info_providers/SensorsInfoProvider.kt | 4 +- .../info_providers/SettingsInfoProvider.kt | 8 +- .../tools/threading/AnotherThread.kt | 4 +- .../fingerprint/tools/threading/safe/Safe.kt | 40 ++++-- .../android/fingerprint/SafeTests.kt | 125 ++++++++++++++---- 27 files changed, 245 insertions(+), 130 deletions(-) create mode 100644 fingerprint/src/androidTest/AndroidManifest.xml rename fingerprint/src/androidTest/java/com/fingerprintjs/android/{playground => fingerprint}/InstrumentedTests.kt (86%) rename fingerprint/src/androidTest/java/com/fingerprintjs/android/{playground => fingerprint}/utils/CallbackUtils.kt (93%) diff --git a/fingerprint/build.gradle.kts b/fingerprint/build.gradle.kts index 1b02384a..f14ad1e7 100644 --- a/fingerprint/build.gradle.kts +++ b/fingerprint/build.gradle.kts @@ -84,6 +84,12 @@ android { } } +androidComponents { + onVariants { + it.androidTest?.packaging?.resources?.excludes?.add("META-INF/*") + } +} + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java) { if (!this.name.contains("Test")) { kotlinOptions.freeCompilerArgs += "-Xexplicit-api=warning" @@ -94,7 +100,9 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib:${Constants.kotlinVersion}") implementation("androidx.appcompat:appcompat:1.6.1") testImplementation("junit:junit:4.13.2") - testImplementation("io.mockk:mockk:1.12.7") + testImplementation("io.mockk:mockk:1.12.8") + androidTestImplementation("io.mockk:mockk:1.12.8") + androidTestImplementation ("io.mockk:mockk-android:1.12.8") androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5") androidTestImplementation("androidx.test:runner:1.5.2") } diff --git a/fingerprint/src/androidTest/AndroidManifest.xml b/fingerprint/src/androidTest/AndroidManifest.xml new file mode 100644 index 00000000..83377617 --- /dev/null +++ b/fingerprint/src/androidTest/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt similarity index 86% rename from fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt rename to fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt index 29e5c0fd..6f118d9a 100644 --- a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt +++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt @@ -1,17 +1,18 @@ -package com.fingerprintjs.android.playground +package com.fingerprintjs.android.fingerprint import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import com.fingerprintjs.android.fingerprint.Configuration -import com.fingerprintjs.android.fingerprint.Fingerprinter -import com.fingerprintjs.android.fingerprint.FingerprinterFactory import com.fingerprintjs.android.fingerprint.signal_providers.StabilityLevel import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getDeviceStateSignals import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getHardwareSignals import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getInstalledAppsSignals import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getOsBuildSignals -import com.fingerprintjs.android.playground.utils.callbackToSync +import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe +import com.fingerprintjs.android.fingerprint.utils.callbackToSync +import io.mockk.every +import io.mockk.mockkObject +import junit.framework.TestCase import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith @@ -182,4 +183,25 @@ class InstrumentedTests { } } } + + @Test + fun nestedSafeCallNeverHappens() { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) + return // object mocks are not supported + + var logCalled = false + mockkObject(Safe) + every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true } + + Fingerprinter.Version.values().forEach { version -> + val fingerprinter = FingerprinterFactory.create(context) + val deviceId = callbackToSync { fingerprinter.getDeviceId(version = version) { emit(it) } } + StabilityLevel.values().forEach { stabilityLevel -> + val fingerprint = callbackToSync { fingerprinter.getFingerprint(version, stabilityLevel) { emit(it) } } + } + val fingerprintingSignalsProvider = fingerprinter.getFingerprintingSignalsProvider()!! + } + + TestCase.assertEquals(false, logCalled) + } } diff --git a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/utils/CallbackUtils.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/CallbackUtils.kt similarity index 93% rename from fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/utils/CallbackUtils.kt rename to fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/CallbackUtils.kt index b33f41f8..98540ef2 100644 --- a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/utils/CallbackUtils.kt +++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/CallbackUtils.kt @@ -1,4 +1,4 @@ -package com.fingerprintjs.android.playground.utils +package com.fingerprintjs.android.fingerprint.utils import java.util.concurrent.CountDownLatch diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt index 3b80e580..b3611e6d 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/Fingerprinter.kt @@ -19,7 +19,6 @@ import com.fingerprintjs.android.fingerprint.tools.hashers.MurMur3x64x128Hasher import com.fingerprintjs.android.fingerprint.tools.logs.Logger import com.fingerprintjs.android.fingerprint.tools.logs.ePleaseReport import com.fingerprintjs.android.fingerprint.tools.threading.runOnAnotherThread -import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe @@ -32,7 +31,7 @@ public class Fingerprinter internal constructor( // does not ruin an entire operation. // another option could be to not use timeout at all, since we have a lot of timeouts // deep inside. - safe(timeoutMs = Safe.timeoutLong) { implFactory() } + safe { implFactory() } } /** diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt index 779f8efa..4f31d66e 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterFactory.kt @@ -27,7 +27,7 @@ import com.fingerprintjs.android.fingerprint.signal_providers.installed_apps.Ins import com.fingerprintjs.android.fingerprint.signal_providers.os_build.OsBuildSignalGroupProvider import com.fingerprintjs.android.fingerprint.tools.hashers.Hasher import com.fingerprintjs.android.fingerprint.tools.hashers.MurMur3x64x128Hasher -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout /** @@ -191,9 +191,9 @@ public object FingerprinterFactory { private fun createMemoryInfoProvider(context: Context): MemInfoProvider { return MemInfoProviderImpl( - activityManager = safe { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull(), - internalStorageStats = safe { StatFs(Environment.getRootDirectory()!!.absolutePath!!) }.getOrNull(), - externalStorageStats = safe { + activityManager = safeWithTimeout { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull(), + internalStorageStats = safeWithTimeout { StatFs(Environment.getRootDirectory()!!.absolutePath!!) }.getOrNull(), + externalStorageStats = safeWithTimeout { context.getExternalFilesDir(null) ?.takeIf { it.canRead() } ?.let { StatFs(it.absolutePath!!) }!! @@ -204,51 +204,51 @@ public object FingerprinterFactory { private fun createOsBuildInfoProvider() = OsBuildInfoProviderImpl() private fun createGsfIdProvider(context: Context) = GsfIdProvider( - safe { context.contentResolver!! }.getOrDefault(null) + safeWithTimeout { context.contentResolver!! }.getOrDefault(null) ) private fun createMediaDrmProvider() = MediaDrmIdProvider() private fun createAndroidIdProvider(context: Context) = AndroidIdProvider( - safe { context.contentResolver!! }.getOrDefault(null) + safeWithTimeout { context.contentResolver!! }.getOrDefault(null) ) private fun createSensorDataSource(context: Context) = SensorDataSourceImpl( - safe { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }.getOrNull() + safeWithTimeout { context.getSystemService(Context.SENSOR_SERVICE) as SensorManager }.getOrNull() ) private fun createInputDevicesDataSource(context: Context) = InputDevicesDataSourceImpl( - safe { context.getSystemService(Context.INPUT_SERVICE) as InputManager }.getOrNull() + safeWithTimeout { context.getSystemService(Context.INPUT_SERVICE) as InputManager }.getOrNull() ) private fun createPackageManagerDataSource(context: Context) = PackageManagerDataSourceImpl( - safe { context.packageManager!! }.getOrNull() + safeWithTimeout { context.packageManager!! }.getOrNull() ) private fun createSettingsDataSource(context: Context) = SettingsDataSourceImpl( - safe { context.contentResolver!! }.getOrNull() + safeWithTimeout { context.contentResolver!! }.getOrNull() ) private fun createDevicePersonalizationDataSource(context: Context) = DevicePersonalizationInfoProviderImpl( - ringtoneManager = safe { RingtoneManager(context) }.getOrNull(), - assetManager = safe { context.assets!! }.getOrNull(), - configuration = safe { context.resources!!.configuration!! }.getOrNull(), + ringtoneManager = safeWithTimeout { RingtoneManager(context) }.getOrNull(), + assetManager = safeWithTimeout { context.assets!! }.getOrNull(), + configuration = safeWithTimeout { context.resources!!.configuration!! }.getOrNull(), ) private fun createFingerprintSensorStatusProvider(context: Context) = FingerprintSensorInfoProviderImpl( - safe { FingerprintManagerCompat.from(context)!! }.getOrNull() + safeWithTimeout { FingerprintManagerCompat.from(context)!! }.getOrNull() ) private fun createDeviceSecurityProvider(context: Context) = DeviceSecurityInfoProviderImpl( - safe { context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager }.getOrNull(), - safe { context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager }.getOrNull(), + safeWithTimeout { context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager }.getOrNull(), + safeWithTimeout { context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager }.getOrNull(), ) private fun createCodecInfoProvider() = CodecInfoProviderImpl( - safe { MediaCodecList(MediaCodecList.ALL_CODECS) }.getOrNull() + safeWithTimeout { MediaCodecList(MediaCodecList.ALL_CODECS) }.getOrNull() ) private fun createBatteryInfoDataSource(context: Context) = BatteryInfoProviderImpl(context) @@ -256,7 +256,7 @@ public object FingerprinterFactory { private fun createCameraInfoProvider(): CameraInfoProvider = CameraInfoProviderImpl() private fun createGpuInfoProvider(context: Context) = - GpuInfoProviderImpl(safe { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull()) + GpuInfoProviderImpl(safeWithTimeout { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }.getOrNull()) //endregion diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt index 9834c045..9f71b332 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/FingerprinterImpl.kt @@ -14,7 +14,6 @@ import com.fingerprintjs.android.fingerprint.signal_providers.os_build.OsBuildSi import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions import com.fingerprintjs.android.fingerprint.tools.hashers.Hasher -import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe @@ -25,6 +24,7 @@ internal class FingerprinterImpl internal constructor( ) { @Volatile private var deviceIdResult: DeviceIdResult? = null + @Volatile private var fingerprintResult: FingerprintResult? = null @@ -33,7 +33,7 @@ internal class FingerprinterImpl internal constructor( fun getDeviceId(): Result { require(legacyArgs != null) - return safe(timeoutMs = Safe.timeoutLong) { + return safe { deviceIdResult?.let { return@safe it } val deviceIdResult = DeviceIdResult( legacyArgs.deviceIdProvider.fingerprint(), @@ -48,7 +48,7 @@ internal class FingerprinterImpl internal constructor( @WorkerThread fun getDeviceId(version: Fingerprinter.Version): Result { - return safe(timeoutMs = Safe.timeoutLong) { + return safe { DeviceIdResult( deviceId = deviceIdSignalsProvider.getSignalMatching(version).getIdString(), gsfId = deviceIdSignalsProvider.gsfIdSignal.getIdString(), @@ -65,7 +65,7 @@ internal class FingerprinterImpl internal constructor( ): Result { require(legacyArgs != null) - return safe(timeoutMs = Safe.timeoutLong) { + return safe { fingerprintResult?.let { return@safe it } val fingerprintSb = StringBuilder() @@ -101,7 +101,7 @@ internal class FingerprinterImpl internal constructor( hasher: Hasher, ): Result { return if (version < Fingerprinter.Version.fingerprintingFlattenedSignalsFirstVersion) { - safe(timeoutMs = Safe.timeoutLong) { + safe { val joinedHashes = with(FingerprintingLegacySchemeSupportExtensions) { listOf( hasher.hash(fpSignalsProvider.getHardwareSignals(version, stabilityLevel)), @@ -126,7 +126,7 @@ internal class FingerprinterImpl internal constructor( fingerprintingSignals: List>, hasher: Hasher, ): Result { - return safe(timeoutMs = Safe.timeoutLong) { hasher.hash(fingerprintingSignals) } + return safe { hasher.hash(fingerprintingSignals) } } internal fun getFingerprintingSignalsProvider(): FingerprintingSignalsProvider { diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt index fefda16e..a1fabfea 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/AndroidIdProvider.kt @@ -5,7 +5,7 @@ import android.annotation.SuppressLint import android.content.ContentResolver import android.provider.Settings import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -14,7 +14,7 @@ public class AndroidIdProvider( ) { @SuppressLint("HardwareIds") public fun getAndroidId(): String { - return safe { + return safeWithTimeout { Settings.Secure.getString( contentResolver!!, Settings.Secure.ANDROID_ID diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt index 76c43204..7f0e63d7 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/GsfIdProvider.kt @@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.device_id_providers import android.content.ContentResolver import android.net.Uri import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -13,7 +13,7 @@ public class GsfIdProvider( ) { public fun getGsfAndroidId(): String? { - return safe { + return safeWithTimeout { getGsfId() }.getOrDefault("") } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt index 35266ba8..64495dce 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt @@ -4,14 +4,14 @@ package com.fingerprintjs.android.fingerprint.device_id_providers import android.media.MediaDrm import android.os.Build import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import java.security.MessageDigest import java.util.UUID @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) public class MediaDrmIdProvider { - public fun getMediaDrmId(): String? = safe { + public fun getMediaDrmId(): String? = safeWithTimeout { mediaDrmId() }.getOrDefault(null) diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt index c9317d78..1f7e1137 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/BatteryInfoProvider.kt @@ -7,7 +7,7 @@ import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -20,7 +20,7 @@ internal class BatteryInfoProviderImpl( private val applicationContext: Context ) : BatteryInfoProvider { override fun batteryHealth(): String { - return safe { + return safeWithTimeout { val intent = applicationContext .registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))!! @@ -36,7 +36,7 @@ internal class BatteryInfoProviderImpl( @SuppressLint("PrivateApi") override fun batteryTotalCapacity(): String { - return safe { + return safeWithTimeout { val mPowerProfile = Class.forName(POWER_PROFILE_CLASS_NAME) .getConstructor(Context::class.java) .newInstance(applicationContext) diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt index a6b0e049..406a1fe0 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CameraInfoProvider.kt @@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.hardware.Camera import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import java.util.LinkedList @@ -22,7 +22,7 @@ public interface CameraInfoProvider { internal class CameraInfoProviderImpl( ) : CameraInfoProvider { override fun getCameraInfo(): List { - return safe { + return safeWithTimeout { extractInfo() }.getOrDefault(emptyList()) } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt index b326806c..2ee40f53 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CodecInfoProvider.kt @@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.media.MediaCodecList import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout public data class MediaCodecInfo( @@ -20,7 +20,7 @@ internal class CodecInfoProviderImpl( private val codecList: MediaCodecList?, ) : CodecInfoProvider { override fun codecsList(): List { - return safe { + return safeWithTimeout { extractCodecInfo() }.getOrDefault(emptyList()) } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt index 01931ef3..abde100f 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/CpuInfoProvider.kt @@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.os.Build import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages import com.fingerprintjs.android.fingerprint.tools.parsers.parseCpuInfo -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import java.io.File import java.util.Scanner @@ -33,26 +33,26 @@ public interface CpuInfoProvider { internal class CpuInfoProviderImpl : CpuInfoProvider { override fun cpuInfo(): Map { - return safe { + return safeWithTimeout { getCpuInfo() }.getOrDefault(emptyMap()) } override fun cpuInfoV2(): CpuInfo { - return safe { + return safeWithTimeout { getCpuInfoV2() }.getOrDefault(CpuInfo.EMPTY) } @Suppress("DEPRECATION") override fun abiType(): String { - return safe { + return safeWithTimeout { Build.SUPPORTED_ABIS[0]!! }.getOrDefault("") } override fun coresCount(): Int { - return safe { + return safeWithTimeout { Runtime.getRuntime()!!.availableProcessors() }.getOrDefault(0) } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt index 41f9d716..6da485c9 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DevicePersonalizationInfoProvider.kt @@ -5,7 +5,7 @@ import android.content.res.AssetManager import android.content.res.Configuration import android.media.RingtoneManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import java.util.Locale import java.util.TimeZone @@ -26,11 +26,11 @@ internal class DevicePersonalizationInfoProviderImpl( ) : DevicePersonalizationInfoProvider { override fun ringtoneSource(): String { - return safe{ ringtoneManager!!.getRingtoneUri(0)!!.toString()!! }.getOrDefault("") + return safeWithTimeout { ringtoneManager!!.getRingtoneUri(0)!!.toString()!! }.getOrDefault("") } override fun availableLocales(): Array { - return safe { + return safeWithTimeout { assetManager!!.locales!! .map { locale: String? -> locale.toString() }.toTypedArray() }.getOrDefault(emptyArray()) @@ -38,14 +38,14 @@ internal class DevicePersonalizationInfoProviderImpl( @Suppress("DEPRECATION") override fun regionCountry(): String { - return safe{ configuration!!.locale!!.country!! }.getOrDefault("") + return safeWithTimeout{ configuration!!.locale!!.country!! }.getOrDefault("") } override fun defaultLanguage(): String { - return safe { Locale.getDefault()!!.language!! } .getOrDefault("") + return safeWithTimeout { Locale.getDefault()!!.language!! } .getOrDefault("") } override fun timezone(): String { - return safe { TimeZone.getDefault()!!.displayName!! }.getOrDefault("") + return safeWithTimeout { TimeZone.getDefault()!!.displayName!! }.getOrDefault("") } } \ No newline at end of file diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt index 85139c01..52c3638b 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/DeviceSecurityInfoProvider.kt @@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.app.KeyguardManager import android.app.admin.DevicePolicyManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import java.security.Security @@ -20,20 +20,20 @@ internal class DeviceSecurityInfoProviderImpl( private val keyguardManager: KeyguardManager?, ) : DeviceSecurityInfoProvider { override fun encryptionStatus(): String { - return safe { + return safeWithTimeout { stringDescriptionForEncryptionStatus(devicePolicyManager!!.storageEncryptionStatus) }.getOrDefault("") } override fun securityProvidersData(): List> { - return safe { + return safeWithTimeout { Security.getProviders().map { Pair(it!!.name!!, it.info ?: "") } }.getOrDefault(emptyList()) } - override fun isPinSecurityEnabled() = safe { + override fun isPinSecurityEnabled() = safeWithTimeout { keyguardManager!!.isKeyguardSecure }.getOrDefault(false) } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt index fb673201..3e12e490 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/FingerprintSensorInfoProvider.kt @@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.os.Build import androidx.core.hardware.fingerprint.FingerprintManagerCompat import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -16,7 +16,7 @@ internal class FingerprintSensorInfoProviderImpl( private val fingerprintManager: FingerprintManagerCompat?, ) : FingerprintSensorInfoProvider { override fun getStatus(): FingerprintSensorStatus { - return safe { + return safeWithTimeout { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { FingerprintSensorStatus.NOT_SUPPORTED } else if (!fingerprintManager!!.isHardwareDetected) { diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt index c0205b63..fb93aa2c 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/GpuInfoProvider.kt @@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.app.ActivityManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -15,7 +15,7 @@ internal class GpuInfoProviderImpl( private val activityManager: ActivityManager?, ) : GpuInfoProvider { override fun glesVersion(): String { - return safe { + return safeWithTimeout { activityManager!!.deviceConfigurationInfo!!.glEsVersion!! }.getOrDefault("") } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt index 3aa9640c..3e15370b 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/InputDevicesInfoProvider.kt @@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.hardware.input.InputManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -20,7 +20,7 @@ internal class InputDevicesDataSourceImpl( private val inputDeviceManager: InputManager?, ) : InputDeviceDataSource { override fun getInputDeviceData(): List { - return safe { + return safeWithTimeout { inputDeviceManager!!.inputDeviceIds!!.map { val inputDevice = inputDeviceManager.getInputDevice(it)!! val vendorId = inputDevice.vendorId.toString() diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt index a023e0ab..1754f35d 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/MemInfoProvider.kt @@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.app.ActivityManager import android.os.StatFs import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -20,7 +20,7 @@ internal class MemInfoProviderImpl( private val externalStorageStats: StatFs?, ) : MemInfoProvider { override fun totalRAM(): Long { - return safe { + return safeWithTimeout { val memoryInfo = ActivityManager.MemoryInfo() activityManager!!.getMemoryInfo(memoryInfo) memoryInfo.totalMem @@ -28,10 +28,10 @@ internal class MemInfoProviderImpl( } override fun totalInternalStorageSpace(): Long { - return safe { internalStorageStats!!.totalBytes }.getOrDefault(0) + return safeWithTimeout { internalStorageStats!!.totalBytes }.getOrDefault(0) } override fun totalExternalStorageSpace(): Long { - return safe { externalStorageStats!!.totalBytes }.getOrDefault(0) + return safeWithTimeout { externalStorageStats!!.totalBytes }.getOrDefault(0) } } \ No newline at end of file diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt index ff3d462e..d6a7dd79 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/OsBuildInfoProvider.kt @@ -3,7 +3,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.os.Build import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -19,26 +19,26 @@ public interface OsBuildInfoProvider { internal class OsBuildInfoProviderImpl : OsBuildInfoProvider { override fun modelName(): String { - return safe { Build.MODEL!! }.getOrDefault("") + return safeWithTimeout { Build.MODEL!! }.getOrDefault("") } override fun manufacturerName(): String { - return safe { Build.MANUFACTURER!! }.getOrDefault("") + return safeWithTimeout { Build.MANUFACTURER!! }.getOrDefault("") } override fun androidVersion(): String { - return safe { Build.VERSION.RELEASE!! }.getOrDefault("") + return safeWithTimeout { Build.VERSION.RELEASE!! }.getOrDefault("") } override fun sdkVersion(): String { - return safe { Build.VERSION.SDK_INT.toString() }.getOrDefault("") + return safeWithTimeout { Build.VERSION.SDK_INT.toString() }.getOrDefault("") } override fun kernelVersion(): String { - return safe { System.getProperty("os.version")!! }.getOrDefault("") + return safeWithTimeout { System.getProperty("os.version")!! }.getOrDefault("") } override fun fingerprint(): String { - return safe { Build.FINGERPRINT!! }.getOrDefault("") + return safeWithTimeout { Build.FINGERPRINT!! }.getOrDefault("") } } \ No newline at end of file diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt index 51046a85..ebbb142c 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt @@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.annotation.SuppressLint import android.content.pm.PackageManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -25,7 +25,7 @@ internal class PackageManagerDataSourceImpl( private val packageManager: PackageManager?, ) : PackageManagerDataSource { @SuppressLint("QueryPermissionsNeeded") - override fun getApplicationsList() = safe { + override fun getApplicationsList() = safeWithTimeout { packageManager!! .getInstalledApplications(PackageManager.GET_META_DATA) .map { @@ -37,7 +37,7 @@ internal class PackageManagerDataSourceImpl( @SuppressLint("QueryPermissionsNeeded") - override fun getSystemApplicationsList() = safe { + override fun getSystemApplicationsList() = safeWithTimeout { packageManager!! .getInstalledApplications(PackageManager.GET_META_DATA) .filter { diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt index b7d62a02..c7cfde2f 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SensorsInfoProvider.kt @@ -4,7 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.hardware.Sensor import android.hardware.SensorManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout public class SensorData( @@ -21,7 +21,7 @@ internal class SensorDataSourceImpl( private val sensorManager: SensorManager?, ) : SensorDataSource { override fun sensors(): List { - return safe { + return safeWithTimeout { sensorManager!!.getSensorList(Sensor.TYPE_ALL)!!.map { SensorData( it!!.name!!, diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt index ee33d215..85bb675a 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/SettingsInfoProvider.kt @@ -5,7 +5,7 @@ import android.content.ContentResolver import android.os.Build import android.provider.Settings import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) @@ -158,19 +158,19 @@ internal class SettingsDataSourceImpl( //endregion private fun extractGlobalSettingsParam(key: String): String { - return safe { + return safeWithTimeout { Settings.Global.getString(contentResolver!!, key)!! }.getOrDefault("") } private fun extractSecureSettingsParam(key: String): String { - return safe { + return safeWithTimeout { Settings.Secure.getString(contentResolver!!, key)!! }.getOrDefault("") } private fun extractSystemSettingsParam(key: String): String { - return safe { + return safeWithTimeout { Settings.System.getString(contentResolver!!, key)!! }.getOrDefault("") } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt index 78d51a8f..9c5a233e 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/AnotherThread.kt @@ -1,5 +1,7 @@ package com.fingerprintjs.android.fingerprint.tools.threading +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe + /** * Returns immediately. The error relates to posting the job to another thread, not the error * that could have occurred on that thread. @@ -7,6 +9,6 @@ package com.fingerprintjs.android.fingerprint.tools.threading */ internal fun runOnAnotherThread( block: () -> Unit, -): Result = runCatching { +): Result = safe { sharedExecutor.submit { block() } } diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt index 40cfeca0..e9cfe61c 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt @@ -1,42 +1,54 @@ package com.fingerprintjs.android.fingerprint.tools.threading.safe +import androidx.annotation.VisibleForTesting +import com.fingerprintjs.android.fingerprint.tools.logs.Logger +import com.fingerprintjs.android.fingerprint.tools.logs.ePleaseReport import com.fingerprintjs.android.fingerprint.tools.threading.sharedExecutor import java.util.concurrent.Callable import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicReference +import kotlin.concurrent.getOrSet internal object Safe { const val timeoutShort = 1_000L - const val timeoutLong = 3_000L - const val timeoutNop = 0L + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal val runningInsideSafeWithTimeout = ThreadLocal() + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun logIllegalSafeWithTimeoutUsage() { + Logger.ePleaseReport(IllegalStateException()) + } } +internal inline fun safe(block: () -> T) = + runCatching(block) + /** * Runs the [block], catching all exceptions and handling unexpected execution locks (configured with [timeoutMs]). */ -internal fun safe( +internal fun safeWithTimeout( timeoutMs: Long = Safe.timeoutShort, block: () -> T, -): Result { - return when (timeoutMs) { - Safe.timeoutNop -> runCatching { block() } - else -> safeWithTimeout(timeoutMs, block) - } -} - -private fun safeWithTimeout( - timeoutMs: Long, - block: () -> T, ): Result { // we can't make a local variable volatile, hence using atomic reference here val executionThread = AtomicReference(null) + if (Safe.runningInsideSafeWithTimeout.getOrSet { false }) { + Safe.logIllegalSafeWithTimeoutUsage() + } + val future = runCatching { sharedExecutor.submit( Callable { + Safe.runningInsideSafeWithTimeout.set(true) executionThread.set(Thread.currentThread()) - block() + try { + block() + } finally { + Safe.runningInsideSafeWithTimeout.remove() + } } )!! }.getOrElse { return Result.failure(it) } diff --git a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt b/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt index 26dc89bf..95654cde 100644 --- a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt +++ b/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt @@ -3,13 +3,19 @@ package com.fingerprintjs.android.fingerprint import com.fingerprintjs.android.fingerprint.tools.threading.createSharedExecutor import com.fingerprintjs.android.fingerprint.tools.threading.runOnAnotherThread import com.fingerprintjs.android.fingerprint.tools.threading.safe.ExecutionTimeoutException -import com.fingerprintjs.android.fingerprint.tools.threading.safe.safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe +import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import com.fingerprintjs.android.fingerprint.tools.threading.sharedExecutor +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkObject import junit.framework.TestCase import org.junit.After import org.junit.Test +import java.util.concurrent.Callable import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutionException +import kotlin.concurrent.getOrSet class SafeTests { @@ -19,37 +25,31 @@ class SafeTests { } @Test - fun safeValueReturned() { - val v = safe { 0 } + fun safeWithTimeoutValueReturned() { + val v = safeWithTimeout { 0 } TestCase.assertEquals(v.getOrNull(), 0) } @Test - fun safeNestedValueReturned() { - val v = safe { safe { 0 } } - TestCase.assertEquals(v.getOrNull()!!.getOrNull(), 0) - } - - @Test - fun safeErrorRetrievable() { + fun safeWithTimeoutErrorRetrievable() { val errorId = "Hello" - val v = safe { throw Exception(errorId) } + val v = safeWithTimeout { throw Exception(errorId) } val err = v.exceptionOrNull() as ExecutionException val errCause = err.cause!! TestCase.assertTrue(errCause is Exception && errCause.message == errorId) } @Test - fun safeExecutionNeverStuck() { + fun safeWithTimeoutExecutionNeverStuck() { val elapsedTime = elapsedTimeMs { - safe(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) } + safeWithTimeout(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) } } TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon) } @Test - fun safeExecutionStuckThreadStackTraceReturned() { - val res = safe(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) } + fun safeWithTimeoutExecutionStuckThreadStackTraceReturned() { + val res = safeWithTimeout(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) } val err = res.exceptionOrNull()!! TestCase.assertTrue( err is ExecutionTimeoutException @@ -59,20 +59,20 @@ class SafeTests { } @Test - fun safeFromMultipleThreadsIsNotBlocked() { + fun safeWithTimeoutFromMultipleThreadsIsNotBlocked() { val countDownLatch = CountDownLatch(2) val elapsedTime = elapsedTimeMs { - runOnAnotherThread { safe { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } } - runOnAnotherThread { safe { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } } + runOnAnotherThread { safeWithTimeout { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } } + runOnAnotherThread { safeWithTimeout { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } } countDownLatch.await() } TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon) } @Test - fun safeThreadsAreReused() { + fun safeWithTimeoutThreadsAreReused() { for (i in 0 until 4) { - safe { } + safeWithTimeout { } TestCase.assertEquals(1, sharedExecutor.poolSize) Thread.sleep(TimeConstants.epsilon) } @@ -80,19 +80,19 @@ class SafeTests { // this is a sad fact but we will leave it as it is @Test - fun safeThreadCountGrowsIfThreadsCantInterrupt() { + fun safeWithTimeoutThreadCountGrowsIfThreadsCantInterrupt() { for (i in 1 until 5) { - safe(timeoutMs = TimeConstants.epsilon) { neverReturn() } + safeWithTimeout(timeoutMs = TimeConstants.epsilon) { neverReturn() } TestCase.assertEquals(i, sharedExecutor.poolSize) Thread.sleep(TimeConstants.epsilon) } } @Test - fun safeOuterTimeoutDominatesOverInner() { + fun safeWithTimeoutOuterTimeoutDominatesOverInner() { val elapsedTime = elapsedTimeMs { - safe(timeoutMs = TimeConstants.t1) { - safe(timeoutMs = TimeConstants.t2) { + safeWithTimeout(timeoutMs = TimeConstants.t1) { + safeWithTimeout(timeoutMs = TimeConstants.t2) { Thread.sleep(TimeConstants.t3) } } @@ -100,14 +100,21 @@ class SafeTests { TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon) } + /** + * This test illustrates the behaviour when using one safe call inside the another. + * Such usage is prohibited, but we'd rather know the what-ifs. + */ @Test - fun safeNestedSafeInterrupted() { + fun safeWithTimeoutNestedSafeInterruptedBehaviour() { val errLvl1: Throwable? var errLvl2: Throwable? = null var errLvl3: Throwable? = null val countDownLatch = CountDownLatch(2) - errLvl1 = safe(timeoutMs = TimeConstants.t1) { - errLvl2 = safe(timeoutMs = TimeConstants.t2) { + mockkObject(Safe) + every { Safe.logIllegalSafeWithTimeoutUsage() } answers {} + + errLvl1 = safeWithTimeout(timeoutMs = TimeConstants.t1) { + errLvl2 = safeWithTimeout(timeoutMs = TimeConstants.t2) { try { Thread.sleep(TimeConstants.t3) } catch (t: Throwable) { @@ -118,10 +125,72 @@ class SafeTests { countDownLatch.countDown() }.exceptionOrNull() countDownLatch.await() + + unmockkObject(Safe) TestCase.assertTrue(errLvl1 is ExecutionTimeoutException) TestCase.assertTrue(errLvl2 is InterruptedException) TestCase.assertTrue(errLvl3 is InterruptedException) } + + + /** + * Same motivation for the test as for the above. + */ + @Test + fun safeWithTimeoutNestedValueReturned() { + mockkObject(Safe) + every { Safe.logIllegalSafeWithTimeoutUsage() } answers { } + + val v = safeWithTimeout { safeWithTimeout { 0 } } + + unmockkObject(Safe) + TestCase.assertEquals(v.getOrNull()!!.getOrNull(), 0) + } + + @Test + fun safeContextFlagUnsetWhenSafeBlockReturns() = + safeWithTimeoutContextFlagUnset(whenBlockThrows = false) + + @Test + fun safeContextFlagUnsetWhenSafeBlockThrows() = + safeWithTimeoutContextFlagUnset(whenBlockThrows = true) + + private fun safeWithTimeoutContextFlagUnset(whenBlockThrows: Boolean) { + var executionThread1: Long? = null + var executionThread2: Long? = null + + safeWithTimeout { + executionThread1 = Thread.currentThread().id + if (whenBlockThrows) + throw Exception() + } + + val countDownLatch = CountDownLatch(1) + var insideSafe: Boolean? = null + sharedExecutor.submit(Callable { + executionThread2 = Thread.currentThread().id + insideSafe = Safe.runningInsideSafeWithTimeout.getOrSet { false } + countDownLatch.countDown() + }) + countDownLatch.await() + + unmockkObject(Safe) + TestCase.assertEquals(executionThread1, executionThread2) + TestCase.assertEquals(false, insideSafe) + } + + @Test + fun safeWithTimeoutNestedUsageReported() { + var logCalled = false + mockkObject(Safe) + every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true } + + safeWithTimeout { safeWithTimeout {} } + + unmockkObject(Safe) + TestCase.assertEquals(true, logCalled) + } + } private object TimeConstants { From cff1d7f1f0a98d736e42520305354650ff4ff652 Mon Sep 17 00:00:00 2001 From: Sergey Makarov Date: Fri, 6 Oct 2023 13:25:18 +0300 Subject: [PATCH 2/6] Make safeWithTimeoutContextFlagUnset test more explicit & fix it on CI (previously on CI executionThread1 != executionThread2) --- .../fingerprint/tools/threading/safe/Safe.kt | 23 +++++++++--- .../android/fingerprint/SafeTests.kt | 35 +++++++++++-------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt index e9cfe61c..36250a20 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt @@ -8,13 +8,26 @@ import java.util.concurrent.Callable import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicReference -import kotlin.concurrent.getOrSet internal object Safe { const val timeoutShort = 1_000L + private val runningInsideSafeWithTimeout = ThreadLocal() + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun markInsideSafeWithTimeout() { + runningInsideSafeWithTimeout.set(true) + } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal val runningInsideSafeWithTimeout = ThreadLocal() + internal fun clearInsideSafeWithTimeout() { + runningInsideSafeWithTimeout.remove() + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun getInsideSafeWithTimeout(): Boolean { + return runningInsideSafeWithTimeout.get() ?: false + } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun logIllegalSafeWithTimeoutUsage() { @@ -35,19 +48,19 @@ internal fun safeWithTimeout( // we can't make a local variable volatile, hence using atomic reference here val executionThread = AtomicReference(null) - if (Safe.runningInsideSafeWithTimeout.getOrSet { false }) { + if (Safe.getInsideSafeWithTimeout()) { Safe.logIllegalSafeWithTimeoutUsage() } val future = runCatching { sharedExecutor.submit( Callable { - Safe.runningInsideSafeWithTimeout.set(true) + Safe.markInsideSafeWithTimeout() executionThread.set(Thread.currentThread()) try { block() } finally { - Safe.runningInsideSafeWithTimeout.remove() + Safe.clearInsideSafeWithTimeout() } } )!! diff --git a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt b/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt index 95654cde..0d1b5641 100644 --- a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt +++ b/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt @@ -9,13 +9,13 @@ import com.fingerprintjs.android.fingerprint.tools.threading.sharedExecutor import io.mockk.every import io.mockk.mockkObject import io.mockk.unmockkObject +import io.mockk.verify +import io.mockk.verifyOrder import junit.framework.TestCase import org.junit.After import org.junit.Test -import java.util.concurrent.Callable import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutionException -import kotlin.concurrent.getOrSet class SafeTests { @@ -156,27 +156,32 @@ class SafeTests { safeWithTimeoutContextFlagUnset(whenBlockThrows = true) private fun safeWithTimeoutContextFlagUnset(whenBlockThrows: Boolean) { - var executionThread1: Long? = null - var executionThread2: Long? = null + mockkObject(Safe) + var clearThreadId: Long? = null + every { Safe.clearInsideSafeWithTimeout() } answers { + callOriginal().also { clearThreadId = Thread.currentThread().id } + } + var markThreadId: Long? = null + every { Safe.markInsideSafeWithTimeout() } answers { + callOriginal().also { markThreadId = Thread.currentThread().id } + } safeWithTimeout { - executionThread1 = Thread.currentThread().id if (whenBlockThrows) throw Exception() } - val countDownLatch = CountDownLatch(1) - var insideSafe: Boolean? = null - sharedExecutor.submit(Callable { - executionThread2 = Thread.currentThread().id - insideSafe = Safe.runningInsideSafeWithTimeout.getOrSet { false } - countDownLatch.countDown() - }) - countDownLatch.await() + verify(exactly = 1) { + Safe.markInsideSafeWithTimeout() + Safe.clearInsideSafeWithTimeout() + } + verifyOrder { + Safe.markInsideSafeWithTimeout() + Safe.clearInsideSafeWithTimeout() + } + TestCase.assertEquals(markThreadId, clearThreadId) unmockkObject(Safe) - TestCase.assertEquals(executionThread1, executionThread2) - TestCase.assertEquals(false, insideSafe) } @Test From bb46a59fd0504fb9757dee6aec16dc968e108b4b Mon Sep 17 00:00:00 2001 From: Sergey Makarov Date: Fri, 6 Oct 2023 15:03:52 +0300 Subject: [PATCH 3/6] Make SafeTests instrumented --- .../{InstrumentedTests.kt => ApiTests.kt} | 27 +------------- .../android/fingerprint/SafeTests.kt | 35 +++++++++++++++++++ .../android/fingerprint/utils/MockkUtils.kt | 4 +++ 3 files changed, 40 insertions(+), 26 deletions(-) rename fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/{InstrumentedTests.kt => ApiTests.kt} (87%) rename fingerprint/src/{test => androidTest}/java/com/fingerprintjs/android/fingerprint/SafeTests.kt (82%) create mode 100644 fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/MockkUtils.kt diff --git a/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/ApiTests.kt similarity index 87% rename from fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt rename to fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/ApiTests.kt index 6f118d9a..bc23c7c1 100644 --- a/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/InstrumentedTests.kt +++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/ApiTests.kt @@ -8,17 +8,13 @@ import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSup import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getHardwareSignals import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getInstalledAppsSignals import com.fingerprintjs.android.fingerprint.tools.FingerprintingLegacySchemeSupportExtensions.getOsBuildSignals -import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe import com.fingerprintjs.android.fingerprint.utils.callbackToSync -import io.mockk.every -import io.mockk.mockkObject -import junit.framework.TestCase import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class InstrumentedTests { +class ApiTests { private val context: Context get() = InstrumentationRegistry.getInstrumentation().targetContext @@ -183,25 +179,4 @@ class InstrumentedTests { } } } - - @Test - fun nestedSafeCallNeverHappens() { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) - return // object mocks are not supported - - var logCalled = false - mockkObject(Safe) - every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true } - - Fingerprinter.Version.values().forEach { version -> - val fingerprinter = FingerprinterFactory.create(context) - val deviceId = callbackToSync { fingerprinter.getDeviceId(version = version) { emit(it) } } - StabilityLevel.values().forEach { stabilityLevel -> - val fingerprint = callbackToSync { fingerprinter.getFingerprint(version, stabilityLevel) { emit(it) } } - } - val fingerprintingSignalsProvider = fingerprinter.getFingerprintingSignalsProvider()!! - } - - TestCase.assertEquals(false, logCalled) - } } diff --git a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/SafeTests.kt similarity index 82% rename from fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt rename to fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/SafeTests.kt index 0d1b5641..c05a0725 100644 --- a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt +++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/SafeTests.kt @@ -1,11 +1,17 @@ package com.fingerprintjs.android.fingerprint +import android.content.Context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.fingerprintjs.android.fingerprint.signal_providers.StabilityLevel import com.fingerprintjs.android.fingerprint.tools.threading.createSharedExecutor import com.fingerprintjs.android.fingerprint.tools.threading.runOnAnotherThread import com.fingerprintjs.android.fingerprint.tools.threading.safe.ExecutionTimeoutException import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import com.fingerprintjs.android.fingerprint.tools.threading.sharedExecutor +import com.fingerprintjs.android.fingerprint.utils.callbackToSync +import com.fingerprintjs.android.fingerprint.utils.mockkObjectSupported import io.mockk.every import io.mockk.mockkObject import io.mockk.unmockkObject @@ -14,11 +20,16 @@ import io.mockk.verifyOrder import junit.framework.TestCase import org.junit.After import org.junit.Test +import org.junit.runner.RunWith import java.util.concurrent.CountDownLatch import java.util.concurrent.ExecutionException +@RunWith(AndroidJUnit4::class) class SafeTests { + private val context: Context + get() = InstrumentationRegistry.getInstrumentation().targetContext + @After fun recreateExecutor() { sharedExecutor = createSharedExecutor() @@ -106,6 +117,7 @@ class SafeTests { */ @Test fun safeWithTimeoutNestedSafeInterruptedBehaviour() { + if (!mockkObjectSupported()) return val errLvl1: Throwable? var errLvl2: Throwable? = null var errLvl3: Throwable? = null @@ -138,6 +150,7 @@ class SafeTests { */ @Test fun safeWithTimeoutNestedValueReturned() { + if (!mockkObjectSupported()) return mockkObject(Safe) every { Safe.logIllegalSafeWithTimeoutUsage() } answers { } @@ -156,6 +169,7 @@ class SafeTests { safeWithTimeoutContextFlagUnset(whenBlockThrows = true) private fun safeWithTimeoutContextFlagUnset(whenBlockThrows: Boolean) { + if (!mockkObjectSupported()) return mockkObject(Safe) var clearThreadId: Long? = null every { Safe.clearInsideSafeWithTimeout() } answers { @@ -186,6 +200,7 @@ class SafeTests { @Test fun safeWithTimeoutNestedUsageReported() { + if (!mockkObjectSupported()) return var logCalled = false mockkObject(Safe) every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true } @@ -196,6 +211,26 @@ class SafeTests { TestCase.assertEquals(true, logCalled) } + + @Test + fun nestedSafeCallNeverHappens() { + if (!mockkObjectSupported()) return + + var logCalled = false + mockkObject(Safe) + every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true } + + Fingerprinter.Version.values().forEach { version -> + val fingerprinter = FingerprinterFactory.create(context) + val deviceId = callbackToSync { fingerprinter.getDeviceId(version = version) { emit(it) } } + StabilityLevel.values().forEach { stabilityLevel -> + val fingerprint = callbackToSync { fingerprinter.getFingerprint(version, stabilityLevel) { emit(it) } } + } + val fingerprintingSignalsProvider = fingerprinter.getFingerprintingSignalsProvider()!! + } + + TestCase.assertEquals(false, logCalled) + } } private object TimeConstants { diff --git a/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/MockkUtils.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/MockkUtils.kt new file mode 100644 index 00000000..652de1c3 --- /dev/null +++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/utils/MockkUtils.kt @@ -0,0 +1,4 @@ +package com.fingerprintjs.android.fingerprint.utils + +internal fun mockkObjectSupported(): Boolean = + (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) From 9fba647fcebceed24a76b0ac7328e94c457d782b Mon Sep 17 00:00:00 2001 From: Sergey Makarov Date: Fri, 6 Oct 2023 16:46:41 +0300 Subject: [PATCH 4/6] CI fix: increase timeout for mediaDrmId retrieval --- .github/workflows/blank.yml | 2 +- .github/workflows/instumented_tests.yml | 2 +- fingerprint/build.gradle.kts | 6 ++++++ .../fingerprint/device_id_providers/MediaDrmIdProvider.kt | 8 ++++++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml index 9b239206..a78caa19 100644 --- a/.github/workflows/blank.yml +++ b/.github/workflows/blank.yml @@ -36,7 +36,7 @@ jobs: run: ./gradlew fingerprint:lint - name: Test - run: ./gradlew fingerprint:test + run: ./gradlew fingerprint:test -PCItest="true" - name: Build library run: ./gradlew fingerprint:assembleRelease diff --git a/.github/workflows/instumented_tests.yml b/.github/workflows/instumented_tests.yml index 045aface..a139ba74 100644 --- a/.github/workflows/instumented_tests.yml +++ b/.github/workflows/instumented_tests.yml @@ -49,7 +49,7 @@ jobs: touch emulator.log chmod 777 emulator.log adb logcat >> emulator.log & - ./gradlew :fingerprint:connectedCheck + ./gradlew :fingerprint:connectedCheck -PCItest="true" - name: Save report if tests failed if: always() && (steps.instrumented_tests.outcome == 'failure') diff --git a/fingerprint/build.gradle.kts b/fingerprint/build.gradle.kts index f14ad1e7..55e5e726 100644 --- a/fingerprint/build.gradle.kts +++ b/fingerprint/build.gradle.kts @@ -45,6 +45,8 @@ android { minSdk = 21 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") + + buildConfigField("boolean", "CI_TEST", (project.properties.get("CItest") as? String) ?: "false") } lint { @@ -82,6 +84,10 @@ android { kotlinOptions { jvmTarget = "1.8" } + + buildFeatures { + buildConfig = true + } } androidComponents { diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt index 64495dce..02244a40 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt @@ -3,6 +3,7 @@ package com.fingerprintjs.android.fingerprint.device_id_providers import android.media.MediaDrm import android.os.Build +import com.fingerprintjs.android.fingerprint.BuildConfig import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import java.security.MessageDigest @@ -11,7 +12,7 @@ import java.util.UUID @Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API) public class MediaDrmIdProvider { - public fun getMediaDrmId(): String? = safeWithTimeout { + public fun getMediaDrmId(): String? = safeWithTimeout(timeoutMs = MEDIA_DRM_ID_TIMEOUT_MS) { mediaDrmId() }.getOrDefault(null) @@ -44,4 +45,7 @@ private fun ByteArray.toHexString(): String { } private const val WIDEWINE_UUID_MOST_SIG_BITS = -0x121074568629b532L -private const val WIDEWINE_UUID_LEAST_SIG_BITS = -0x5c37d8232ae2de13L \ No newline at end of file +private const val WIDEWINE_UUID_LEAST_SIG_BITS = -0x5c37d8232ae2de13L +// on CI, the timeout of 1s is not enough for emulator with api 32 and google apis. +// therefore, let's make it much higher for CI tests and a bit higher for all builds (just in case) +private val MEDIA_DRM_ID_TIMEOUT_MS = if (BuildConfig.CI_TEST) 600_000L else 3000L From 900efb0936df4fe28b3fb9a141a4c5cda3c2b257 Mon Sep 17 00:00:00 2001 From: Sergey Makarov Date: Fri, 6 Oct 2023 19:14:41 +0300 Subject: [PATCH 5/6] Dummy results: replace zeros with empty string --- .../android/fingerprint/tools/DummyResults.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/DummyResults.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/DummyResults.kt index 1f3192f8..d3015b00 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/DummyResults.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/DummyResults.kt @@ -5,8 +5,8 @@ import com.fingerprintjs.android.fingerprint.FingerprintResult import com.fingerprintjs.android.fingerprint.signal_providers.SignalGroupProvider internal object DummyResults { - private const val ALL_ZEROS = "0000000000000000" - const val fingerprint = ALL_ZEROS + private const val dummyString = "" + const val fingerprint = dummyString val fingerprintResult = object : FingerprintResult { override val fingerprint: String get() = this@DummyResults.fingerprint @@ -16,9 +16,9 @@ internal object DummyResults { } } val deviceIdResult = DeviceIdResult( - deviceId = ALL_ZEROS, - gsfId = ALL_ZEROS, - androidId = ALL_ZEROS, - mediaDrmId = ALL_ZEROS, + deviceId = dummyString, + gsfId = dummyString, + androidId = dummyString, + mediaDrmId = dummyString, ) } From b3e9bde0d4dc42bff03389173b4bd56759c97c96 Mon Sep 17 00:00:00 2001 From: Sergey Makarov Date: Fri, 6 Oct 2023 20:19:24 +0300 Subject: [PATCH 6/6] Long timeout for applications list. Just as a precaution --- .../fingerprint/device_id_providers/MediaDrmIdProvider.kt | 3 ++- .../fingerprint/info_providers/PackageManagerInfoProvider.kt | 5 +++-- .../android/fingerprint/tools/threading/safe/Safe.kt | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt index 02244a40..103e34c3 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/device_id_providers/MediaDrmIdProvider.kt @@ -5,6 +5,7 @@ import android.media.MediaDrm import android.os.Build import com.fingerprintjs.android.fingerprint.BuildConfig import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages +import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout import java.security.MessageDigest import java.util.UUID @@ -48,4 +49,4 @@ private const val WIDEWINE_UUID_MOST_SIG_BITS = -0x121074568629b532L private const val WIDEWINE_UUID_LEAST_SIG_BITS = -0x5c37d8232ae2de13L // on CI, the timeout of 1s is not enough for emulator with api 32 and google apis. // therefore, let's make it much higher for CI tests and a bit higher for all builds (just in case) -private val MEDIA_DRM_ID_TIMEOUT_MS = if (BuildConfig.CI_TEST) 600_000L else 3000L +private val MEDIA_DRM_ID_TIMEOUT_MS = if (BuildConfig.CI_TEST) 600_000L else Safe.timeoutLong diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt index ebbb142c..b451c55e 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/info_providers/PackageManagerInfoProvider.kt @@ -4,6 +4,7 @@ package com.fingerprintjs.android.fingerprint.info_providers import android.annotation.SuppressLint import android.content.pm.PackageManager import com.fingerprintjs.android.fingerprint.tools.DeprecationMessages +import com.fingerprintjs.android.fingerprint.tools.threading.safe.Safe import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout @@ -25,7 +26,7 @@ internal class PackageManagerDataSourceImpl( private val packageManager: PackageManager?, ) : PackageManagerDataSource { @SuppressLint("QueryPermissionsNeeded") - override fun getApplicationsList() = safeWithTimeout { + override fun getApplicationsList() = safeWithTimeout(timeoutMs = Safe.timeoutLong) { packageManager!! .getInstalledApplications(PackageManager.GET_META_DATA) .map { @@ -37,7 +38,7 @@ internal class PackageManagerDataSourceImpl( @SuppressLint("QueryPermissionsNeeded") - override fun getSystemApplicationsList() = safeWithTimeout { + override fun getSystemApplicationsList() = safeWithTimeout(timeoutMs = Safe.timeoutLong) { packageManager!! .getInstalledApplications(PackageManager.GET_META_DATA) .filter { diff --git a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt index 36250a20..348026d6 100644 --- a/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt +++ b/fingerprint/src/main/java/com/fingerprintjs/android/fingerprint/tools/threading/safe/Safe.kt @@ -11,6 +11,7 @@ import java.util.concurrent.atomic.AtomicReference internal object Safe { const val timeoutShort = 1_000L + const val timeoutLong = 3_000L private val runningInsideSafeWithTimeout = ThreadLocal()