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); -}