1+ package com.getcode.network.integrity
2+
3+ import android.annotation.SuppressLint
4+ import android.content.Context
5+ import android.os.Build
6+ import android.provider.Settings
7+ import android.util.Base64
8+ import com.codeinc.gen.common.v1.Model
9+ import com.getcode.api.BuildConfig
10+ import com.getcode.api.BuildConfig.GOOGLE_CLOUD_PROJECT_NUMBER
11+ import com.google.android.gms.tasks.Task
12+ import com.google.android.play.core.integrity.IntegrityManager
13+ import com.google.android.play.core.integrity.IntegrityManagerFactory
14+ import com.google.android.play.core.integrity.IntegrityTokenRequest
15+ import com.google.android.play.core.integrity.IntegrityTokenResponse
16+ import io.reactivex.rxjava3.core.BackpressureStrategy
17+ import io.reactivex.rxjava3.core.Flowable
18+ import io.reactivex.rxjava3.core.Single
19+ import kotlinx.coroutines.suspendCancellableCoroutine
20+ import timber.log.Timber
21+ import kotlin.coroutines.resume
22+ import kotlin.coroutines.resumeWithException
23+
24+ data class DeviceTokenResult (
25+ val token : String?
26+ )
27+
28+ object DeviceCheck {
29+
30+ private lateinit var integrityManager: IntegrityManager
31+
32+ private lateinit var deviceId: String
33+
34+ @SuppressLint(" HardwareIds" )
35+ fun register (context : Context ) {
36+ integrityManager = IntegrityManagerFactory .create(context)
37+ deviceId = Settings .Secure .getString(context.contentResolver, Settings .Secure .ANDROID_ID )
38+ }
39+
40+ private fun handleAppCheckError (error : Throwable ): Boolean {
41+ error.printStackTrace()
42+ return true
43+ }
44+
45+ @Deprecated(" Replace with Result variant" )
46+ fun integrityResponseSingle (): Single <DeviceTokenResult > {
47+ return Single .create { emitter ->
48+ tokenResponse(
49+ onToken = { emitter.onSuccess(DeviceTokenResult (it)) },
50+ onError = { emitter.onError(it) }
51+ )
52+ }
53+ }
54+
55+ @Deprecated(" Replace with Result variant" )
56+ fun integrityResponseFlowable (
57+ backpressureStrategy : BackpressureStrategy = BackpressureStrategy .BUFFER
58+ ): Flowable <DeviceTokenResult > {
59+ return Flowable .create({ emitter ->
60+ tokenResponse(
61+ onToken = { emitter.onNext(DeviceTokenResult (it)) },
62+ onError = { emitter.onError(it) }
63+ )
64+ }, backpressureStrategy)
65+ }
66+
67+ suspend fun integrityResponse (): DeviceTokenResult = suspendCancellableCoroutine { cont ->
68+ tokenResponse(
69+ onToken = { cont.resume(DeviceTokenResult (it)) },
70+ onError = { cont.resumeWithException(it) }
71+ )
72+ }
73+
74+ private fun tokenResponse (onToken : (String? ) -> Unit , onError : (Exception ) -> Unit ) {
75+ val rawNonce = deviceId
76+ val nonce = Base64 .encodeToString(rawNonce.toByteArray(), Base64 .NO_WRAP )
77+ // Request the integrity token by providing a nonce.
78+ val integrityTokenResponse: Task <IntegrityTokenResponse > =
79+ integrityManager.requestIntegrityToken(
80+ IntegrityTokenRequest .builder()
81+ .setCloudProjectNumber(GOOGLE_CLOUD_PROJECT_NUMBER .toLong())
82+ .setNonce(nonce)
83+ .build()
84+ )
85+
86+ integrityTokenResponse.addOnSuccessListener {
87+ Timber .d(" integrity token=${it.token()} " )
88+ onToken(it.token())
89+ }.addOnFailureListener { error ->
90+ if (! handleAppCheckError(error)) {
91+ onError(error)
92+ return @addOnFailureListener
93+ }
94+
95+ onToken(null )
96+ }
97+ }
98+ }
99+
100+ fun String.toDeviceToken () = Model .DeviceToken .newBuilder().setValue(this ).build()
0 commit comments