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 1b02384a..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,16 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
+
+ buildFeatures {
+ buildConfig = true
+ }
+}
+
+androidComponents {
+ onVariants {
+ it.androidTest?.packaging?.resources?.excludes?.add("META-INF/*")
+ }
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java) {
@@ -94,7 +106,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/ApiTests.kt
similarity index 95%
rename from fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt
rename to fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/ApiTests.kt
index 29e5c0fd..bc23c7c1 100644
--- a/fingerprint/src/androidTest/java/com/fingerprintjs/android/playground/InstrumentedTests.kt
+++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/ApiTests.kt
@@ -1,23 +1,20 @@
-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.utils.callbackToSync
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
diff --git a/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/SafeTests.kt b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/SafeTests.kt
new file mode 100644
index 00000000..c05a0725
--- /dev/null
+++ b/fingerprint/src/androidTest/java/com/fingerprintjs/android/fingerprint/SafeTests.kt
@@ -0,0 +1,253 @@
+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
+import io.mockk.verify
+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()
+ }
+
+ @Test
+ fun safeWithTimeoutValueReturned() {
+ val v = safeWithTimeout { 0 }
+ TestCase.assertEquals(v.getOrNull(), 0)
+ }
+
+ @Test
+ fun safeWithTimeoutErrorRetrievable() {
+ val errorId = "Hello"
+ 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 safeWithTimeoutExecutionNeverStuck() {
+ val elapsedTime = elapsedTimeMs {
+ safeWithTimeout(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) }
+ }
+ TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon)
+ }
+
+ @Test
+ fun safeWithTimeoutExecutionStuckThreadStackTraceReturned() {
+ val res = safeWithTimeout(timeoutMs = TimeConstants.t1) { Thread.sleep(TimeConstants.t4) }
+ val err = res.exceptionOrNull()!!
+ TestCase.assertTrue(
+ err is ExecutionTimeoutException
+ && err.executionThreadStackTrace != null
+ && err.executionThreadStackTrace.any { it.className == "java.lang.Thread" && it.methodName == "sleep" }
+ )
+ }
+
+ @Test
+ fun safeWithTimeoutFromMultipleThreadsIsNotBlocked() {
+ val countDownLatch = CountDownLatch(2)
+ val elapsedTime = elapsedTimeMs {
+ 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 safeWithTimeoutThreadsAreReused() {
+ for (i in 0 until 4) {
+ safeWithTimeout { }
+ TestCase.assertEquals(1, sharedExecutor.poolSize)
+ Thread.sleep(TimeConstants.epsilon)
+ }
+ }
+
+ // this is a sad fact but we will leave it as it is
+ @Test
+ fun safeWithTimeoutThreadCountGrowsIfThreadsCantInterrupt() {
+ for (i in 1 until 5) {
+ safeWithTimeout(timeoutMs = TimeConstants.epsilon) { neverReturn() }
+ TestCase.assertEquals(i, sharedExecutor.poolSize)
+ Thread.sleep(TimeConstants.epsilon)
+ }
+ }
+
+ @Test
+ fun safeWithTimeoutOuterTimeoutDominatesOverInner() {
+ val elapsedTime = elapsedTimeMs {
+ safeWithTimeout(timeoutMs = TimeConstants.t1) {
+ safeWithTimeout(timeoutMs = TimeConstants.t2) {
+ Thread.sleep(TimeConstants.t3)
+ }
+ }
+ }
+ 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 safeWithTimeoutNestedSafeInterruptedBehaviour() {
+ if (!mockkObjectSupported()) return
+ val errLvl1: Throwable?
+ var errLvl2: Throwable? = null
+ var errLvl3: Throwable? = null
+ val countDownLatch = CountDownLatch(2)
+ mockkObject(Safe)
+ every { Safe.logIllegalSafeWithTimeoutUsage() } answers {}
+
+ errLvl1 = safeWithTimeout(timeoutMs = TimeConstants.t1) {
+ errLvl2 = safeWithTimeout(timeoutMs = TimeConstants.t2) {
+ try {
+ Thread.sleep(TimeConstants.t3)
+ } catch (t: Throwable) {
+ errLvl3 = t
+ countDownLatch.countDown()
+ }
+ }.exceptionOrNull()
+ 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() {
+ if (!mockkObjectSupported()) return
+ 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) {
+ if (!mockkObjectSupported()) return
+ 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 {
+ if (whenBlockThrows)
+ throw Exception()
+ }
+
+ verify(exactly = 1) {
+ Safe.markInsideSafeWithTimeout()
+ Safe.clearInsideSafeWithTimeout()
+ }
+ verifyOrder {
+ Safe.markInsideSafeWithTimeout()
+ Safe.clearInsideSafeWithTimeout()
+ }
+
+ TestCase.assertEquals(markThreadId, clearThreadId)
+ unmockkObject(Safe)
+ }
+
+ @Test
+ fun safeWithTimeoutNestedUsageReported() {
+ if (!mockkObjectSupported()) return
+ var logCalled = false
+ mockkObject(Safe)
+ every { Safe.logIllegalSafeWithTimeoutUsage() } answers { logCalled = true }
+
+ safeWithTimeout { safeWithTimeout {} }
+
+ unmockkObject(Safe)
+ 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 {
+ const val epsilon = 200L
+ const val t1 = epsilon * 3
+ const val t2 = t1 * 2
+ const val t3 = t1 * 3
+ const val t4 = t1 * 4
+}
+
+private inline fun elapsedTimeMs(block: () -> Unit): Long {
+ val currentTime = System.currentTimeMillis()
+ block()
+ return System.currentTimeMillis() - currentTime
+}
+
+@Suppress("ControlFlowWithEmptyBody")
+private fun neverReturn() {
+ while (true);
+}
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/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)
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..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
@@ -3,15 +3,17 @@ 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.safe
+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(timeoutMs = MEDIA_DRM_ID_TIMEOUT_MS) {
mediaDrmId()
}.getOrDefault(null)
@@ -44,4 +46,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 Safe.timeoutLong
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..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,7 +4,8 @@ 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.Safe
+import com.fingerprintjs.android.fingerprint.tools.threading.safe.safeWithTimeout
@Deprecated(message = DeprecationMessages.UNREACHABLE_SYMBOL_UNINTENDED_PUBLIC_API)
@@ -25,7 +26,7 @@ internal class PackageManagerDataSourceImpl(
private val packageManager: PackageManager?,
) : PackageManagerDataSource {
@SuppressLint("QueryPermissionsNeeded")
- override fun getApplicationsList() = safe {
+ 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() = safe {
+ 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/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/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,
)
}
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..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
@@ -1,5 +1,8 @@
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
@@ -9,34 +12,57 @@ import java.util.concurrent.atomic.AtomicReference
internal object Safe {
const val timeoutShort = 1_000L
const val timeoutLong = 3_000L
- const val timeoutNop = 0L
+
+ private val runningInsideSafeWithTimeout = ThreadLocal()
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun markInsideSafeWithTimeout() {
+ runningInsideSafeWithTimeout.set(true)
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun clearInsideSafeWithTimeout() {
+ runningInsideSafeWithTimeout.remove()
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal fun getInsideSafeWithTimeout(): Boolean {
+ return runningInsideSafeWithTimeout.get() ?: false
+ }
+
+ @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.getInsideSafeWithTimeout()) {
+ Safe.logIllegalSafeWithTimeoutUsage()
+ }
+
val future = runCatching {
sharedExecutor.submit(
Callable {
+ Safe.markInsideSafeWithTimeout()
executionThread.set(Thread.currentThread())
- block()
+ try {
+ block()
+ } finally {
+ Safe.clearInsideSafeWithTimeout()
+ }
}
)!!
}.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
deleted file mode 100644
index 26dc89bf..00000000
--- a/fingerprint/src/test/java/com/fingerprintjs/android/fingerprint/SafeTests.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-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.sharedExecutor
-import junit.framework.TestCase
-import org.junit.After
-import org.junit.Test
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.ExecutionException
-
-class SafeTests {
-
- @After
- fun recreateExecutor() {
- sharedExecutor = createSharedExecutor()
- }
-
- @Test
- fun safeValueReturned() {
- val v = safe { 0 }
- TestCase.assertEquals(v.getOrNull(), 0)
- }
-
- @Test
- fun safeNestedValueReturned() {
- val v = safe { safe { 0 } }
- TestCase.assertEquals(v.getOrNull()!!.getOrNull(), 0)
- }
-
- @Test
- fun safeErrorRetrievable() {
- val errorId = "Hello"
- val v = safe { throw Exception(errorId) }
- val err = v.exceptionOrNull() as ExecutionException
- val errCause = err.cause!!
- TestCase.assertTrue(errCause is Exception && errCause.message == errorId)
- }
-
- @Test
- fun safeExecutionNeverStuck() {
- val elapsedTime = elapsedTimeMs {
- safe(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) }
- val err = res.exceptionOrNull()!!
- TestCase.assertTrue(
- err is ExecutionTimeoutException
- && err.executionThreadStackTrace != null
- && err.executionThreadStackTrace.any { it.className == "java.lang.Thread" && it.methodName == "sleep" }
- )
- }
-
- @Test
- fun safeFromMultipleThreadsIsNotBlocked() {
- val countDownLatch = CountDownLatch(2)
- val elapsedTime = elapsedTimeMs {
- runOnAnotherThread { safe { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } }
- runOnAnotherThread { safe { Thread.sleep(TimeConstants.t1); countDownLatch.countDown() } }
- countDownLatch.await()
- }
- TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon)
- }
-
- @Test
- fun safeThreadsAreReused() {
- for (i in 0 until 4) {
- safe { }
- TestCase.assertEquals(1, sharedExecutor.poolSize)
- Thread.sleep(TimeConstants.epsilon)
- }
- }
-
- // this is a sad fact but we will leave it as it is
- @Test
- fun safeThreadCountGrowsIfThreadsCantInterrupt() {
- for (i in 1 until 5) {
- safe(timeoutMs = TimeConstants.epsilon) { neverReturn() }
- TestCase.assertEquals(i, sharedExecutor.poolSize)
- Thread.sleep(TimeConstants.epsilon)
- }
- }
-
- @Test
- fun safeOuterTimeoutDominatesOverInner() {
- val elapsedTime = elapsedTimeMs {
- safe(timeoutMs = TimeConstants.t1) {
- safe(timeoutMs = TimeConstants.t2) {
- Thread.sleep(TimeConstants.t3)
- }
- }
- }
- TestCase.assertTrue(elapsedTime - TimeConstants.t1 < TimeConstants.epsilon)
- }
-
- @Test
- fun safeNestedSafeInterrupted() {
- 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) {
- try {
- Thread.sleep(TimeConstants.t3)
- } catch (t: Throwable) {
- errLvl3 = t
- countDownLatch.countDown()
- }
- }.exceptionOrNull()
- countDownLatch.countDown()
- }.exceptionOrNull()
- countDownLatch.await()
- TestCase.assertTrue(errLvl1 is ExecutionTimeoutException)
- TestCase.assertTrue(errLvl2 is InterruptedException)
- TestCase.assertTrue(errLvl3 is InterruptedException)
- }
-}
-
-private object TimeConstants {
- const val epsilon = 200L
- const val t1 = epsilon * 3
- const val t2 = t1 * 2
- const val t3 = t1 * 3
- const val t4 = t1 * 4
-}
-
-private inline fun elapsedTimeMs(block: () -> Unit): Long {
- val currentTime = System.currentTimeMillis()
- block()
- return System.currentTimeMillis() - currentTime
-}
-
-@Suppress("ControlFlowWithEmptyBody")
-private fun neverReturn() {
- while (true);
-}