From 16d249a764be5ffeb21540892e9f6985c0621855 Mon Sep 17 00:00:00 2001 From: RadiationX Date: Mon, 1 Apr 2024 22:42:49 +0500 Subject: [PATCH 1/5] feat: add ssl compat --- .../main/java/ru/radiationx/anilibria/App.kt | 2 +- data/build.gradle | 2 + .../java/ru/radiationx/data/di/DataModule.kt | 24 ++++- .../data/di/providers/ApiOkHttpProvider.kt | 6 +- .../data/di/providers/MainOkHttpProvider.kt | 6 +- .../data/di/providers/PlayerOkHttpProvider.kt | 4 + .../data/di/providers/SimpleOkHttpProvider.kt | 5 + .../data/sslcompat/CompatSSLSocketFactory.kt | 77 ++++++++++++++++ .../data/sslcompat/CompatTrustManager.kt | 62 +++++++++++++ .../ru/radiationx/data/sslcompat/SslCompat.kt | 86 ++++++++++++++++++ .../radiationx/data/sslcompat/SslCompatExt.kt | 16 ++++ data/src/main/res/raw/gsr4.der | Bin 0 -> 480 bytes data/src/main/res/raw/gtsr1.der | Bin 0 -> 1371 bytes data/src/main/res/raw/gtsr2.der | Bin 0 -> 1371 bytes data/src/main/res/raw/gtsr3.der | Bin 0 -> 525 bytes data/src/main/res/raw/gtsr4.der | Bin 0 -> 525 bytes .../main/res/xml/network_security_config.xml | 5 + 17 files changed, 289 insertions(+), 6 deletions(-) create mode 100644 data/src/main/java/ru/radiationx/data/sslcompat/CompatSSLSocketFactory.kt create mode 100644 data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt create mode 100644 data/src/main/java/ru/radiationx/data/sslcompat/SslCompat.kt create mode 100644 data/src/main/java/ru/radiationx/data/sslcompat/SslCompatExt.kt create mode 100644 data/src/main/res/raw/gsr4.der create mode 100644 data/src/main/res/raw/gtsr1.der create mode 100644 data/src/main/res/raw/gtsr2.der create mode 100644 data/src/main/res/raw/gtsr3.der create mode 100644 data/src/main/res/raw/gtsr4.der diff --git a/app-mobile/src/main/java/ru/radiationx/anilibria/App.kt b/app-mobile/src/main/java/ru/radiationx/anilibria/App.kt index f1452e1be..a7a6d4b99 100644 --- a/app-mobile/src/main/java/ru/radiationx/anilibria/App.kt +++ b/app-mobile/src/main/java/ru/radiationx/anilibria/App.kt @@ -101,7 +101,7 @@ class App : Application() { private fun initDependencies() { Toothpick.setConfiguration(Configuration.forProduction()) - Quill.getRootScope().installModules(AppModule(this), DataModule()) + Quill.getRootScope().installModules(AppModule(this), DataModule(this)) } private fun appVersionCheck() { diff --git a/data/build.gradle b/data/build.gradle index 6696ece66..d62d54bec 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -39,6 +39,8 @@ dependencies { api "com.jakewharton.timber:timber:$timber_version" + api 'org.conscrypt:conscrypt-android:2.5.2' + api "com.squareup.moshi:moshi:$moshi_version" api "com.squareup.moshi:moshi-adapters:$moshi_version" ksp "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" diff --git a/data/src/main/java/ru/radiationx/data/di/DataModule.kt b/data/src/main/java/ru/radiationx/data/di/DataModule.kt index 5229c671e..2b9653cc0 100644 --- a/data/src/main/java/ru/radiationx/data/di/DataModule.kt +++ b/data/src/main/java/ru/radiationx/data/di/DataModule.kt @@ -9,9 +9,11 @@ import com.squareup.moshi.Moshi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import okhttp3.ConnectionSpec import ru.radiationx.data.ApiClient import ru.radiationx.data.DataPreferences import ru.radiationx.data.MainClient +import ru.radiationx.data.R import ru.radiationx.data.SimpleClient import ru.radiationx.data.ads.AdsConfigApi import ru.radiationx.data.ads.AdsConfigRepository @@ -125,15 +127,35 @@ import ru.radiationx.data.repository.ScheduleRepository import ru.radiationx.data.repository.SearchRepository import ru.radiationx.data.repository.TeamsRepository import ru.radiationx.data.repository.YoutubeRepository +import ru.radiationx.data.sslcompat.SslCompat import ru.radiationx.data.system.ApiUtils import ru.radiationx.data.system.AppCookieJar import ru.radiationx.quill.QuillModule import toothpick.InjectConstructor import javax.inject.Provider -class DataModule : QuillModule() { +class DataModule(context: Context) : QuillModule() { init { + + + instance { + val rawCertResources = listOf( + R.raw.gsr4, + R.raw.gtsr1, + R.raw.gtsr2, + R.raw.gtsr3, + R.raw.gtsr4, + R.raw.isrg_root_x1, + R.raw.isrg_root_x2, + ) + val connectionSpecs = listOf( + ConnectionSpec.COMPATIBLE_TLS, + ConnectionSpec.CLEARTEXT + ) + SslCompat(context, rawCertResources, connectionSpecs) + } + instance { Moshi.Builder().build() } diff --git a/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt index 01dcb41ce..eaec168e9 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt @@ -8,9 +8,10 @@ import okhttp3.logging.HttpLoggingInterceptor import ru.radiationx.data.SharedBuildConfig import ru.radiationx.data.datasource.remote.address.ApiConfig import ru.radiationx.data.datasource.remote.interceptors.UnauthorizedInterceptor +import ru.radiationx.data.sslcompat.SslCompat +import ru.radiationx.data.sslcompat.appendSslCompat import ru.radiationx.data.system.AppCookieJar import ru.radiationx.data.system.Client -import ru.radiationx.data.system.appendConnectionSpecs import ru.radiationx.data.system.appendTimeouts import java.net.InetSocketAddress import java.net.Proxy @@ -23,10 +24,11 @@ class ApiOkHttpProvider @Inject constructor( private val apiConfig: ApiConfig, private val sharedBuildConfig: SharedBuildConfig, private val unauthorizedInterceptor: UnauthorizedInterceptor, + private val sslCompat: SslCompat, ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() - .appendConnectionSpecs() + .appendSslCompat(sslCompat) .appendTimeouts() .apply { val availableAddress = diff --git a/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt index b694e9a36..2cb3040e2 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt @@ -5,7 +5,8 @@ import com.chuckerteam.chucker.api.ChuckerInterceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import ru.radiationx.data.SharedBuildConfig -import ru.radiationx.data.system.appendConnectionSpecs +import ru.radiationx.data.sslcompat.SslCompat +import ru.radiationx.data.sslcompat.appendSslCompat import ru.radiationx.data.system.appendTimeouts import javax.inject.Inject import javax.inject.Provider @@ -14,10 +15,11 @@ import javax.inject.Provider class MainOkHttpProvider @Inject constructor( private val context: Context, private val sharedBuildConfig: SharedBuildConfig, + private val sslCompat: SslCompat ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() - .appendConnectionSpecs() + .appendSslCompat(sslCompat) .appendTimeouts() .addNetworkInterceptor { val hostAddress = diff --git a/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt index 6ed35cecb..465fa829c 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt @@ -4,15 +4,19 @@ import android.content.Context import com.chuckerteam.chucker.api.ChuckerInterceptor import okhttp3.OkHttpClient import ru.radiationx.data.SharedBuildConfig +import ru.radiationx.data.sslcompat.SslCompat +import ru.radiationx.data.sslcompat.appendSslCompat import javax.inject.Inject import javax.inject.Provider class PlayerOkHttpProvider @Inject constructor( private val context: Context, private val sharedBuildConfig: SharedBuildConfig, + private val sslCompat: SslCompat, ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() + .appendSslCompat(sslCompat) .apply { if (sharedBuildConfig.debug) { addNetworkInterceptor(ChuckerInterceptor.Builder(context).build()) diff --git a/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt index 0041b87ed..06a4575fb 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt @@ -5,15 +5,20 @@ import com.chuckerteam.chucker.api.ChuckerInterceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import ru.radiationx.data.SharedBuildConfig +import ru.radiationx.data.sslcompat.SslCompat +import ru.radiationx.data.sslcompat.appendSslCompat import javax.inject.Inject import javax.inject.Provider + class SimpleOkHttpProvider @Inject constructor( private val context: Context, private val sharedBuildConfig: SharedBuildConfig, + private val sslCompat: SslCompat, ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() + .appendSslCompat(sslCompat) .apply { if (sharedBuildConfig.debug) { addNetworkInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)) diff --git a/data/src/main/java/ru/radiationx/data/sslcompat/CompatSSLSocketFactory.kt b/data/src/main/java/ru/radiationx/data/sslcompat/CompatSSLSocketFactory.kt new file mode 100644 index 000000000..210c7be3a --- /dev/null +++ b/data/src/main/java/ru/radiationx/data/sslcompat/CompatSSLSocketFactory.kt @@ -0,0 +1,77 @@ +package ru.radiationx.data.sslcompat + +import okhttp3.TlsVersion +import java.io.IOException +import java.net.InetAddress +import java.net.Socket +import java.net.UnknownHostException +import javax.net.ssl.SSLSocket +import javax.net.ssl.SSLSocketFactory + +class CompatSSLSocketFactory( + private val mSSLSocketFactory: SSLSocketFactory, + private val tlsVersions: List, +) : SSLSocketFactory() { + + private val protocols = tlsVersions.map { it.javaName }.toTypedArray() + + override fun getDefaultCipherSuites(): Array { + return mSSLSocketFactory.defaultCipherSuites + } + + override fun getSupportedCipherSuites(): Array { + return mSSLSocketFactory.supportedCipherSuites + } + + @Throws(IOException::class) + override fun createSocket(): Socket { + return mSSLSocketFactory.createSocket().enableProtocols() + } + + @Throws(IOException::class) + override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket { + return mSSLSocketFactory.createSocket(s, host, port, autoClose).enableProtocols() + } + + @Throws(IOException::class, UnknownHostException::class) + override fun createSocket(host: String, port: Int): Socket { + return mSSLSocketFactory.createSocket(host, port).enableProtocols() + } + + @Throws(IOException::class, UnknownHostException::class) + override fun createSocket( + host: String, + port: Int, + localHost: InetAddress, + localPort: Int, + ): Socket { + return mSSLSocketFactory.createSocket(host, port, localHost, localPort).enableProtocols() + } + + @Throws(IOException::class) + override fun createSocket(host: InetAddress, port: Int): Socket { + return mSSLSocketFactory.createSocket(host, port).enableProtocols() + } + + @Throws(IOException::class) + override fun createSocket( + address: InetAddress, + port: Int, + localAddress: InetAddress, + localPort: Int, + ): Socket { + return mSSLSocketFactory.createSocket( + address, + port, + localAddress, + localPort + ).enableProtocols() + } + + private fun Socket.enableProtocols(): Socket { + if (this is SSLSocket) { + enabledProtocols = protocols + } + return this + } +} diff --git a/data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt b/data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt new file mode 100644 index 000000000..3c12be9b0 --- /dev/null +++ b/data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt @@ -0,0 +1,62 @@ +package ru.radiationx.data.sslcompat + +import android.annotation.SuppressLint +import android.util.Log +import java.security.KeyStore +import java.security.cert.CertificateException +import java.security.cert.X509Certificate +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager + +@SuppressLint("CustomX509TrustManager") +class CompatTrustManager( + private val defaultManager: X509TrustManager, + private val additionalKeyStores: List, +) : X509TrustManager { + + private val managers = mutableListOf(defaultManager) + + init { + for (keyStore in additionalKeyStores) { + val algorithm = TrustManagerFactory.getDefaultAlgorithm() + val factory = TrustManagerFactory.getInstance(algorithm) + factory.init(keyStore) + factory.trustManagers.forEach { manager -> + if (manager is X509TrustManager) { + managers.add(manager) + } + } + } + } + + /* + * Delegate to the default trust manager. + */ + @Throws(CertificateException::class) + override fun checkClientTrusted(chain: Array, authType: String) { + defaultManager.checkClientTrusted(chain, authType) + } + + /* + * Loop over the trustmanagers until we find one that accepts our server + */ + @Throws(CertificateException::class) + override fun checkServerTrusted(chain: Array, authType: String) { + for (tm in managers) { + try { + tm.checkServerTrusted(chain, authType) + return + } catch (e: CertificateException) { + // ignore + } + } + throw CertificateException() + } + + override fun getAcceptedIssuers(): Array { + val certificates = managers.flatMap { + it.acceptedIssuers.toList() + } + return certificates.toTypedArray() + } +} diff --git a/data/src/main/java/ru/radiationx/data/sslcompat/SslCompat.kt b/data/src/main/java/ru/radiationx/data/sslcompat/SslCompat.kt new file mode 100644 index 000000000..7514bf3aa --- /dev/null +++ b/data/src/main/java/ru/radiationx/data/sslcompat/SslCompat.kt @@ -0,0 +1,86 @@ +package ru.radiationx.data.sslcompat + +import android.content.Context +import android.os.Build +import okhttp3.ConnectionSpec +import org.conscrypt.Conscrypt +import java.security.KeyStore +import java.security.Provider +import java.security.Security +import java.security.cert.CertificateFactory +import javax.inject.Inject +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + + +class SslCompat @Inject constructor( + private val context: Context, + private val rawCertResources: List, + private val connectionSpecs: List, +) { + + val data: Result by lazy { + runCatching { + val conscrypt = Conscrypt.newProvider() + Security.insertProviderAt(conscrypt, 1) + + val trustManager = createTrustManager() + val sslContext = SSLContext.getInstance("TLS", conscrypt).apply { + init(null, arrayOf(trustManager), null) + } + val tlsVersions = connectionSpecs.flatMap { + it.tlsVersions.orEmpty() + } + val socketFactory = CompatSSLSocketFactory(sslContext.socketFactory, tlsVersions) + Data(connectionSpecs, conscrypt, trustManager, socketFactory) + }.onFailure { + it.printStackTrace() + } + } + + private fun createTrustManager(): X509TrustManager { + val defaultTrustManager = Conscrypt.getDefaultX509TrustManager() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return defaultTrustManager + } + val compatKeyStore = createCompatKeyStore() + return CompatTrustManager( + defaultManager = defaultTrustManager, + additionalKeyStores = listOfNotNull(compatKeyStore) + ) + } + + private fun createCompatKeyStore(): KeyStore? { + val keyStoreType = KeyStore.getDefaultType() + val keyStore = KeyStore.getInstance(keyStoreType).apply { + load(null, null) + } + + val factory = CertificateFactory.getInstance("X.509") + rawCertResources.forEach { rawResource -> + val alias = try { + context.resources.getResourceName(rawResource) + } catch (ex: Exception) { + "$rawResource" + } + val certificate = context.resources.openRawResource(rawResource).use { + factory.generateCertificate(it) + } + keyStore.setCertificateEntry(alias, certificate) + } + return keyStore + } + + class Data( + val connectionSpec: List, + val conscrypt: Provider, + val trustManager: X509TrustManager, + val socketFactory: SSLSocketFactory, + ) { + override fun toString(): String { + return conscrypt.toString() + } + } +} \ No newline at end of file diff --git a/data/src/main/java/ru/radiationx/data/sslcompat/SslCompatExt.kt b/data/src/main/java/ru/radiationx/data/sslcompat/SslCompatExt.kt new file mode 100644 index 000000000..e140153f4 --- /dev/null +++ b/data/src/main/java/ru/radiationx/data/sslcompat/SslCompatExt.kt @@ -0,0 +1,16 @@ +package ru.radiationx.data.sslcompat + +import okhttp3.OkHttpClient +import timber.log.Timber + + +fun OkHttpClient.Builder.appendSslCompat(sslCompat: SslCompat): OkHttpClient.Builder { + val data = sslCompat.data.getOrNull() ?: return this + try { + connectionSpecs(data.connectionSpec) + sslSocketFactory(data.socketFactory, data.trustManager) + } catch (e: Exception) { + Timber.e(e) + } + return this +} \ No newline at end of file diff --git a/data/src/main/res/raw/gsr4.der b/data/src/main/res/raw/gsr4.der new file mode 100644 index 0000000000000000000000000000000000000000..f63cc7f924172a45ebf672c6576a7e9e4a9ac88a GIT binary patch literal 480 zcmXqLV!UI}#Mr!mnTe5!iI<7_Y28=*$$yt}DsEb4z{SR))#h=|mW7$gAiz+?K#7ex zl!aSZ+C3*fDKRHFGd)ki)!A7gC_leM!P!wkS0Tv6P}o2aq>4+J3!w@o$c!v#AScdi zXk=(;XlwumQR2MD76yifmIlU#CI;qFG_Wz!K$wjk>?0;dsMnYo*_oXfSauw%yxgCo z$~RxnQc{L_$+}rjE!jE6P9<;L+4iF={9yPwr(eYjzo$C=mpjO?DX*CC|8d2q5=&Oy zEVN&7W#_pb*~Lx<4hDQckIV8iGX7@)1{h_y@Rylk9WZ60mkc^MgbSQ!l54Y>_C z*_cCF*o2uvgAJ7o6hIs^_~4w?6yx`?qIX(j5dts9?CHP&lRfo3d zG|o*As(SQFug!VAJS?Sunt#SR+v$f|`VZ`JIKUmV_gnjB)0{OD0rf2pe9lg~tnku! zPteTuMq4#Et7>iW_*wMuRLb{fO#;0y<{s+m_+^pS^(Xl!~ z4Gz`MomVfHn*JhtGLG*m`4gpX>Ru18*I){|57KSbo)-cVpKg=6i-~ z&y>H)ueQ5(f1;AmvwgpA82u4Q76~l67kf=?@?OmhqKvuyiM{nBcABIUY6+59)ZM&W71soX)!QwD(&}?>R^8FNF%{scg7b z`0LBTU1#1(fBo0!c!Obvt<{NjRWr;bSf?^EGcqtPb~11<-~%R8S$;;w|12!PoX}># z58?}h_^bxZK*~TCB*4ca#v<}$y|!ARl(vU@tA0?L&ifCY!i9FosSKD^fT@g;Vg4#7 zwcFgY_x+cfHc8KJQL0zgj9p5nYY$r8XyRf!_HF6C1Agskl?#6^i}Ze>H}RBymNqRAM!>Yr6LCcDTtNaO-vld6hJ*>-V_`NzKtK3lvg z`XE?3_q0h+@3XRO@niSfR;%}UYL})p3MejnW%p&B>i-(id*^yo?N22YaDCV3Ok+P` zFZ1WaZC8W)NgW5b^3Ody-|Jpvr2El!C9`Ku%inVCe8j$>x4O#9rts1u=7sV`?02P~ zIGy8Ra7}F3%B>KoS+VzL=;`F6Ha=gr{lA?RZ!iwNGE|Hh;l? zr{z}lF{fiU6v%3ue7#b}$@99xCzAEEvD4v1Q32_4-*ZYmS6V;0)@~1e_1+WY;JGvJGqdz zfxlX;Tx9?M+niH2P2QsrA^P%mq4Vc^0*4vytk9ae?a(#*3E5@0bN==0`BY%ab7!Wc V>7DB*+>)=F6|M{ay}c>-4gj1STZjMv literal 0 HcmV?d00001 diff --git a/data/src/main/res/raw/gtsr2.der b/data/src/main/res/raw/gtsr2.der new file mode 100644 index 0000000000000000000000000000000000000000..726e5443ce691f16ae9e24844bbfc53fe3a9bc79 GIT binary patch literal 1371 zcmXqLVhuNFVzytv%*4pV#LL9|bluTj7FDU$f~uure698*&?P zvN4CUun9AT1{*3FD1bOz!jkU!`RO^S3L!CsYPX($*IK(K0eNdA_hVrHO#`? z?jgYnLHYS53PDB&a^k#(W(HsRjFZy#D4q8EAu&9^+d z{hh{|_ovG;M0Va^%a}7${)m$7{C`E|4|Jl}KVjMLU$j8VWX222j5kwNtIHNX%iP&2 zxwo_D-`clkuZs8lZ+>*T_TZ&+UYmPa=XHBdzJD=Ws7s5Rd40zryAv0L=dOMC#I?FJ^m~_lQc#P~=0gtC zA9u;SPh3_Kb?L{(WDS8Gn>lxcl{+j_n^Ae}-+k40c25`7UrFQ-^tQ3@x%IH14O)v|TQ;;X-l4~8e+)d(vUUf16DoF|Y| zptQqhPw&ran=48l@>+LZXYY2|c-pwo$Zh>vua7-1O03pAmr^QjxMc8M)#*ly|>{&JG%i!!ky!*uOwbzO^(PYF}npXFW0sJKbl=>C&gF1eA>v0oV$ z$jm-gF}-)f|Jf&2ZCNiEXc>3&clz3^(Smc0W=Ze8x+{9=$XA7rS1e4^ZFOOe7a3cQL=K=vrg{P zlv7t%@8LWsvGD(ukcF)_e??T7H??10vYm;Uk%4islYxT)A26B9@-s62XJG;6gf;_y z5MLO?XEk63QU+f&W>uiW6_xs2JYKZM>@8ZK~jHPzrMT%zLNbH}EM zJwEMDt-wE?#LcWz-#uK{$iL#p@qXVG%1mn6vduxE=eFuIoN91*@UUb}!q@hOx$DeQ zCdO{aka*RVc(Q!O^dl#aFZ~nh{rN-v&Qx3Vj0hC7sBl^}Md+D&f(-o)ba$Ss@Yr*86v-8V0Wz`)EvSw+QbNo88QDcHy z1c%L|x>eV0^LH6`aIwBt-@4HA-2&H;>SyYyGZhq9EZ*~5V%qMYe4e+>fj%!IA1Ndp zJ(N1BspqqRJYU!JedSHL_WiQ4dpyWmW#+fgj zCCp!M-hDA5$?DM&?q_qA~*kHg>T4nHbqvwHsL&l$etkSmgbU z+l@857Pj0weOxiZvGY~fTy5{xW3oQ#d+OB!i}D}Mzqa_)8CMav?zQjh+Irq>tkeI$ zvT&|Miz|y`XrA^fwffx?7$18}?s^h3&6RWG)tregYg>f-X7%tk@ECo2puX72z`=kI z=y6$oM#ldvEWm(mGvEjDg+Y8)17;v)APW-UV-aH!Irvd+*8<&jt&Qg|f7BA%yMrtJ z?;hlkXU=3WNMkZ&`1Vj?sz`On;t67c`&&-D%Fh;`C+9XpVfOsXyf@ce)<0YGBU&$X z_x`(+zAxdqWB70pP|Kg`X=zWL#e|#h9>1@@PsHLa`T6NNsR|)QrNt!*!Kp=MnaQce3O+v0h9U++AT`Xw-0mU4 z3PJh#B?>_%26E!OhGqt4Mn(o;5GBrQjLfB;MP&v8Z0un7GcmHUYB#blC^087uzW5l zUY^mNu+Djh>Cr}mYH!cEzYiv!dv?!lZ}!zh3Db2BcZ + + + + + From b70bfa4c4bef74732c7fe69b99cb7ebe7c4a4ed9 Mon Sep 17 00:00:00 2001 From: RadiationX Date: Mon, 1 Apr 2024 23:11:48 +0500 Subject: [PATCH 2/5] fix: tv build --- .../main/java/ru/radiationx/anilibria/App.kt | 2 +- .../ru/radiationx/data/system/OkHttpConfig.kt | 20 ------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/app-tv/src/main/java/ru/radiationx/anilibria/App.kt b/app-tv/src/main/java/ru/radiationx/anilibria/App.kt index fb0aef56c..28943b7e0 100644 --- a/app-tv/src/main/java/ru/radiationx/anilibria/App.kt +++ b/app-tv/src/main/java/ru/radiationx/anilibria/App.kt @@ -78,7 +78,7 @@ class App : Application() { private fun initDependencies() { Toothpick.setConfiguration(Configuration.forProduction()) - Quill.getRootScope().installModules(AppModule(this), DataModule()) + Quill.getRootScope().installModules(AppModule(this), DataModule(this)) } diff --git a/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt b/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt index e0f323c4e..672431801 100644 --- a/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt +++ b/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt @@ -5,26 +5,6 @@ import okhttp3.ConnectionSpec import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit -fun OkHttpClient.Builder.appendConnectionSpecs(): OkHttpClient.Builder { - val cipherSuites = mutableListOf() - val suites = ConnectionSpec.MODERN_TLS.cipherSuites - suites?.also { cipherSuites.addAll(it) } - - if (!cipherSuites.contains(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)) { - cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA) - } - - if (!cipherSuites.contains(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)) { - cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA) - } - - val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .cipherSuites(*cipherSuites.toTypedArray()) - .build() - connectionSpecs(listOf(spec, ConnectionSpec.CLEARTEXT)) - return this -} - fun OkHttpClient.Builder.appendTimeouts(): OkHttpClient.Builder { callTimeout(25, TimeUnit.SECONDS) connectTimeout(15, TimeUnit.SECONDS) From b4e36aceabddded6b7f6404674eed5045514ea33 Mon Sep 17 00:00:00 2001 From: RadiationX Date: Mon, 1 Apr 2024 23:35:18 +0500 Subject: [PATCH 3/5] feat: move tv to media3 and add okhttp support --- app-mobile/build.gradle | 3 - .../ru/radiationx/anilibria/AppBuildConfig.kt | 7 +- .../ru/radiationx/anilibria/di/AppModule.kt | 5 +- .../ui/activities/player/PlayerHolder.kt | 1 + app-tv/build.gradle | 6 +- .../ru/radiationx/anilibria/AppBuildConfig.kt | 7 +- .../screen/player/BasePlayerFragment.kt | 71 ++++++------------- .../anilibria/screen/player/PlayerFragment.kt | 4 +- .../screen/player/VideoPlayerGlue.kt | 4 +- data/build.gradle | 4 ++ .../ru/radiationx/data/SharedBuildConfig.kt | 1 + .../java/ru/radiationx/data/di/DataModule.kt | 4 ++ .../data}/player/PlayerDataSourceProvider.kt | 5 +- 13 files changed, 57 insertions(+), 65 deletions(-) rename {app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities => data/src/main/java/ru/radiationx/data}/player/PlayerDataSourceProvider.kt (94%) diff --git a/app-mobile/build.gradle b/app-mobile/build.gradle index 1d600c8e7..387805cda 100644 --- a/app-mobile/build.gradle +++ b/app-mobile/build.gradle @@ -102,10 +102,7 @@ dependencies { implementation "androidx.media3:media3-exoplayer:$androidx_media3_version" implementation "androidx.media3:media3-exoplayer-hls:$androidx_media3_version" - implementation "androidx.media3:media3-datasource-okhttp:$androidx_media3_version" implementation "androidx.media3:media3-session:$androidx_media3_version" - implementation "androidx.media3:media3-datasource-cronet:$androidx_media3_version" - implementation "org.bsc.util:minitemplator-repackaged:$minitemplator_version" diff --git a/app-mobile/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt b/app-mobile/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt index 357fd8dbf..bfa9c27d1 100644 --- a/app-mobile/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt +++ b/app-mobile/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt @@ -1,9 +1,14 @@ package ru.radiationx.anilibria +import android.content.Context import ru.radiationx.data.SharedBuildConfig import javax.inject.Inject -class AppBuildConfig @Inject constructor() : SharedBuildConfig { +class AppBuildConfig @Inject constructor( + private val context: Context, +) : SharedBuildConfig { + + override val applicationName: String = context.getString(R.string.app_name) override val applicationId: String = BuildConfig.APPLICATION_ID diff --git a/app-mobile/src/main/java/ru/radiationx/anilibria/di/AppModule.kt b/app-mobile/src/main/java/ru/radiationx/anilibria/di/AppModule.kt index 662ff1b03..c09f1aabf 100644 --- a/app-mobile/src/main/java/ru/radiationx/anilibria/di/AppModule.kt +++ b/app-mobile/src/main/java/ru/radiationx/anilibria/di/AppModule.kt @@ -15,7 +15,7 @@ import ru.radiationx.anilibria.ads.NativeAdsRepository import ru.radiationx.anilibria.navigation.CiceroneHolder import ru.radiationx.anilibria.presentation.common.IErrorHandler import ru.radiationx.anilibria.presentation.common.ILinkHandler -import ru.radiationx.anilibria.ui.activities.player.PlayerDataSourceProvider +import ru.radiationx.data.player.PlayerDataSourceProvider import ru.radiationx.anilibria.ui.common.ErrorHandler import ru.radiationx.anilibria.ui.common.LinkRouter import ru.radiationx.anilibria.ui.common.Templates @@ -118,9 +118,6 @@ class AppModule(application: Application) : QuillModule() { singleImpl() singleImpl() } - - /* Player */ - single() } } \ No newline at end of file diff --git a/app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities/player/PlayerHolder.kt b/app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities/player/PlayerHolder.kt index b4e63fa74..dadebabd7 100644 --- a/app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities/player/PlayerHolder.kt +++ b/app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities/player/PlayerHolder.kt @@ -7,6 +7,7 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.DefaultMediaSourceFactory import androidx.media3.session.MediaSession import ru.radiationx.data.entity.common.PlayerTransport +import ru.radiationx.data.player.PlayerDataSourceProvider import java.util.UUID import javax.inject.Inject diff --git a/app-tv/build.gradle b/app-tv/build.gradle index f4ce357ea..010e3ea2d 100644 --- a/app-tv/build.gradle +++ b/app-tv/build.gradle @@ -70,9 +70,9 @@ dependencies { implementation "com.github.stephanenicolas.toothpick:ktp:$toothpick_version" kapt "com.github.stephanenicolas.toothpick:toothpick-compiler:$toothpick_version" - - implementation "com.google.android.exoplayer:exoplayer:$exoplayer_leanback_version" - implementation "com.google.android.exoplayer:extension-leanback:$exoplayer_leanback_version" + implementation "androidx.media3:media3-ui-leanback:$androidx_media3_version" + implementation "androidx.media3:media3-exoplayer:$androidx_media3_version" + implementation "androidx.media3:media3-exoplayer-hls:$androidx_media3_version" implementation "com.github.mintrocket.MintPermissions:mintpermissions:$mintpermissions_version" implementation "com.github.mintrocket.MintPermissions:mintpermissions-flows:$mintpermissions_version" diff --git a/app-tv/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt b/app-tv/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt index 5ff38140e..05a1c8fab 100644 --- a/app-tv/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt +++ b/app-tv/src/main/java/ru/radiationx/anilibria/AppBuildConfig.kt @@ -1,9 +1,14 @@ package ru.radiationx.anilibria +import android.content.Context import ru.radiationx.data.SharedBuildConfig import javax.inject.Inject -class AppBuildConfig @Inject constructor() : SharedBuildConfig { +class AppBuildConfig @Inject constructor( + private val context: Context, +) : SharedBuildConfig { + + override val applicationName: String = context.getString(R.string.app_name) override val applicationId: String = BuildConfig.APPLICATION_ID diff --git a/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/BasePlayerFragment.kt b/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/BasePlayerFragment.kt index 22f7ac604..48bf56385 100644 --- a/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/BasePlayerFragment.kt +++ b/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/BasePlayerFragment.kt @@ -1,5 +1,3 @@ -@file:Suppress("DEPRECATION") - package ru.radiationx.anilibria.screen.player import android.annotation.SuppressLint @@ -13,45 +11,29 @@ import androidx.leanback.app.VideoSupportFragmentGlueHost import androidx.leanback.widget.ArrayObjectAdapter import androidx.leanback.widget.ClassPresenterSelector import androidx.leanback.widget.ListRow -import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.MediaItem -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.SimpleExoPlayer -import com.google.android.exoplayer2.ext.leanback.LeanbackPlayerAdapter -import com.google.android.exoplayer2.source.MediaSource -import com.google.android.exoplayer2.source.MediaSourceFactory -import com.google.android.exoplayer2.source.ProgressiveMediaSource -import com.google.android.exoplayer2.source.dash.DashMediaSource -import com.google.android.exoplayer2.source.hls.HlsMediaSource -import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource -import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector -import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory -import com.google.android.exoplayer2.util.Util +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.common.util.UnstableApi +import androidx.media3.datasource.DefaultDataSource +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory +import androidx.media3.ui.leanback.LeanbackPlayerAdapter import ru.radiationx.anilibria.ui.presenter.cust.CustomListRowPresenter +import ru.radiationx.data.player.PlayerDataSourceProvider +import ru.radiationx.quill.get +@UnstableApi open class BasePlayerFragment : VideoSupportFragment() { - protected var playerGlue: VideoPlayerGlue? = null private set - protected var player: SimpleExoPlayer? = null + protected var player: ExoPlayer? = null private set protected var skipsPart: PlayerSkipsPart? = null private set - private val dataSourceFactory by lazy { - val userAgent = Util.getUserAgent(requireActivity(), "VideoPlayerGlue") - DefaultDataSourceFactory(requireContext(), userAgent) - } - private val dashMediaSourceFactory by lazy { DashMediaSource.Factory(dataSourceFactory) } - private val ssMediaSourceFactory by lazy { SsMediaSource.Factory(dataSourceFactory) } - private val hlsMediaSourceFactory by lazy { HlsMediaSource.Factory(dataSourceFactory) } - private val otherMediaSourceFactory by lazy { ProgressiveMediaSource.Factory(dataSourceFactory) } - @SuppressLint("RestrictedApi") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -132,11 +114,16 @@ open class BasePlayerFragment : VideoSupportFragment() { if (player != null) { throw RuntimeException("Player already initialized") } - val bandwidthMeter = DefaultBandwidthMeter.Builder(requireContext()).build() - val trackSelector = DefaultTrackSelector(requireContext(), AdaptiveTrackSelection.Factory()) - val player = SimpleExoPlayer.Builder(requireContext()) - .setBandwidthMeter(bandwidthMeter) - .setTrackSelector(trackSelector) + + val dataSourceProvider = get() + val dataSourceType = dataSourceProvider.get() + val dataSourceFactory = DefaultDataSource.Factory(requireContext(), dataSourceType.factory) + val mediaSourceFactory = DefaultMediaSourceFactory(requireContext()).apply { + setDataSourceFactory(dataSourceFactory) + } + val player = ExoPlayer.Builder(requireContext()) + .setMediaSourceFactory(mediaSourceFactory) + .setHandleAudioBecomingNoisy(true) .build() player.addListener(object : Player.Listener { @@ -172,20 +159,8 @@ open class BasePlayerFragment : VideoSupportFragment() { } protected fun preparePlayer(url: String) { - val mediaSource = getMediaSource(url) - player?.prepare(mediaSource, false, false) - } - - private fun getMediaSource(url: String): MediaSource = Uri.parse(url).let { - getMediaSourceFactory(it).createMediaSource(MediaItem.fromUri(it)) + player?.setMediaItem(MediaItem.fromUri(Uri.parse(url)), false) + player?.prepare() } - private fun getMediaSourceFactory(uri: Uri): MediaSourceFactory = - when (val type = Util.inferContentType(uri)) { - C.TYPE_DASH -> dashMediaSourceFactory - C.TYPE_SS -> ssMediaSourceFactory - C.TYPE_HLS -> hlsMediaSourceFactory - C.TYPE_OTHER -> otherMediaSourceFactory - else -> throw IllegalStateException("Unsupported type: $type") - } } \ No newline at end of file diff --git a/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/PlayerFragment.kt b/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/PlayerFragment.kt index e551aa432..3df46620f 100644 --- a/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/PlayerFragment.kt +++ b/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/PlayerFragment.kt @@ -4,7 +4,8 @@ package ru.radiationx.anilibria.screen.player import android.os.Bundle import android.view.View -import com.google.android.exoplayer2.PlaybackParameters +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.util.UnstableApi import kotlinx.coroutines.flow.filterNotNull import ru.radiationx.data.entity.domain.types.EpisodeId import ru.radiationx.data.entity.domain.types.ReleaseId @@ -14,6 +15,7 @@ import ru.radiationx.shared.ktx.android.getExtraNotNull import ru.radiationx.shared.ktx.android.putExtra import ru.radiationx.shared.ktx.android.subscribeTo +@UnstableApi class PlayerFragment : BasePlayerFragment() { companion object { diff --git a/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/VideoPlayerGlue.kt b/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/VideoPlayerGlue.kt index 664fe0407..dea6f180e 100644 --- a/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/VideoPlayerGlue.kt +++ b/app-tv/src/main/java/ru/radiationx/anilibria/screen/player/VideoPlayerGlue.kt @@ -27,7 +27,8 @@ import androidx.leanback.widget.PlaybackControlsRow.MultiAction import androidx.leanback.widget.PlaybackControlsRow.RewindAction import androidx.leanback.widget.PlaybackControlsRow.SkipNextAction import androidx.leanback.widget.PlaybackControlsRow.SkipPreviousAction -import com.google.android.exoplayer2.ext.leanback.LeanbackPlayerAdapter +import androidx.media3.common.util.UnstableApi +import androidx.media3.ui.leanback.LeanbackPlayerAdapter import ru.radiationx.data.entity.common.PlayerQuality import java.util.concurrent.TimeUnit @@ -48,6 +49,7 @@ import java.util.concurrent.TimeUnit * Note that the superclass, [PlaybackTransportControlGlue], manages the playback controls * row. */ +@UnstableApi class VideoPlayerGlue( context: Context, playerAdapter: LeanbackPlayerAdapter, diff --git a/data/build.gradle b/data/build.gradle index d62d54bec..9eccbd875 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -45,6 +45,10 @@ dependencies { api "com.squareup.moshi:moshi-adapters:$moshi_version" ksp "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" + implementation "androidx.media3:media3-exoplayer:$androidx_media3_version" + implementation "androidx.media3:media3-datasource-okhttp:$androidx_media3_version" + implementation "androidx.media3:media3-datasource-cronet:$androidx_media3_version" + implementation "com.github.stephanenicolas.toothpick:ktp:$toothpick_version" kapt "com.github.stephanenicolas.toothpick:toothpick-compiler:$toothpick_version" diff --git a/data/src/main/java/ru/radiationx/data/SharedBuildConfig.kt b/data/src/main/java/ru/radiationx/data/SharedBuildConfig.kt index 754b6f53a..776e72f60 100644 --- a/data/src/main/java/ru/radiationx/data/SharedBuildConfig.kt +++ b/data/src/main/java/ru/radiationx/data/SharedBuildConfig.kt @@ -1,6 +1,7 @@ package ru.radiationx.data interface SharedBuildConfig { + val applicationName:String val applicationId: String val versionName: String val versionCode: Int diff --git a/data/src/main/java/ru/radiationx/data/di/DataModule.kt b/data/src/main/java/ru/radiationx/data/di/DataModule.kt index 2b9653cc0..1ba3e9d3b 100644 --- a/data/src/main/java/ru/radiationx/data/di/DataModule.kt +++ b/data/src/main/java/ru/radiationx/data/di/DataModule.kt @@ -113,6 +113,7 @@ import ru.radiationx.data.interactors.ReleaseInteractor import ru.radiationx.data.interactors.ReleaseUpdateMiddleware import ru.radiationx.data.migration.MigrationDataSource import ru.radiationx.data.migration.MigrationDataSourceImpl +import ru.radiationx.data.player.PlayerDataSourceProvider import ru.radiationx.data.repository.AuthRepository import ru.radiationx.data.repository.CheckerRepository import ru.radiationx.data.repository.ConfigurationRepository @@ -283,6 +284,9 @@ class DataModule(context: Context) : QuillModule() { single() single() single() + + /* Player */ + single() } diff --git a/app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities/player/PlayerDataSourceProvider.kt b/data/src/main/java/ru/radiationx/data/player/PlayerDataSourceProvider.kt similarity index 94% rename from app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities/player/PlayerDataSourceProvider.kt rename to data/src/main/java/ru/radiationx/data/player/PlayerDataSourceProvider.kt index cdf2af061..cde8392fb 100644 --- a/app-mobile/src/main/java/ru/radiationx/anilibria/ui/activities/player/PlayerDataSourceProvider.kt +++ b/data/src/main/java/ru/radiationx/data/player/PlayerDataSourceProvider.kt @@ -1,4 +1,4 @@ -package ru.radiationx.anilibria.ui.activities.player +package ru.radiationx.data.player import android.content.Context import android.os.Build @@ -9,7 +9,6 @@ import androidx.media3.datasource.cronet.CronetDataSource import androidx.media3.datasource.okhttp.OkHttpDataSource import okhttp3.OkHttp import org.chromium.net.CronetEngine -import ru.radiationx.anilibria.R import ru.radiationx.data.SharedBuildConfig import ru.radiationx.data.datasource.holders.PreferencesHolder import ru.radiationx.data.di.providers.PlayerOkHttpProvider @@ -71,7 +70,7 @@ class PlayerDataSourceProvider @Inject constructor( } private fun createUserAgent(transport: String): String { - val appInfo = "${context.getString(R.string.app_name)}/${buildConfig.versionName}" + val appInfo = "${buildConfig.applicationName}/${buildConfig.versionName}" val appIdInfo = "${buildConfig.applicationId}/${buildConfig.versionCode}" val androidInfo = "Android ${Build.VERSION.RELEASE}/${Build.VERSION.SDK_INT}" val deviceInfo = "${Build.MANUFACTURER}/${Build.MODEL}" From 2425d254d59638d5aceab94b2609b2b1fe23ddf3 Mon Sep 17 00:00:00 2001 From: RadiationX Date: Tue, 2 Apr 2024 14:57:54 +0500 Subject: [PATCH 4/5] feat: add sslcompat analytics --- .../analytics/features/SslCompatAnalytics.kt | 21 +++++++++++++++++++ .../java/ru/radiationx/data/di/DataModule.kt | 2 ++ .../data/di/providers/ApiOkHttpProvider.kt | 4 ++++ .../data/di/providers/MainOkHttpProvider.kt | 6 +++++- .../data/di/providers/PlayerOkHttpProvider.kt | 4 ++++ .../data/di/providers/SimpleOkHttpProvider.kt | 4 ++++ .../ru/radiationx/data/system/OkHttpConfig.kt | 12 +++++++++-- 7 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 data/src/main/java/ru/radiationx/data/analytics/features/SslCompatAnalytics.kt diff --git a/data/src/main/java/ru/radiationx/data/analytics/features/SslCompatAnalytics.kt b/data/src/main/java/ru/radiationx/data/analytics/features/SslCompatAnalytics.kt new file mode 100644 index 000000000..b0f5e743e --- /dev/null +++ b/data/src/main/java/ru/radiationx/data/analytics/features/SslCompatAnalytics.kt @@ -0,0 +1,21 @@ +package ru.radiationx.data.analytics.features + +import ru.radiationx.data.analytics.AnalyticsSender +import ru.radiationx.data.sslcompat.SslCompat +import toothpick.InjectConstructor + +@InjectConstructor +class SslCompatAnalytics( + private val sender: AnalyticsSender, +) { + + private var sentError: Throwable? = null + + fun oneShotError(data: Result) { + val error = data.exceptionOrNull() ?: return + if (error == sentError) return + sentError = error + sender.error("sslCompatError", error.message.orEmpty(), error) + } + +} \ No newline at end of file diff --git a/data/src/main/java/ru/radiationx/data/di/DataModule.kt b/data/src/main/java/ru/radiationx/data/di/DataModule.kt index 1ba3e9d3b..2875ffafc 100644 --- a/data/src/main/java/ru/radiationx/data/di/DataModule.kt +++ b/data/src/main/java/ru/radiationx/data/di/DataModule.kt @@ -40,6 +40,7 @@ import ru.radiationx.data.analytics.features.PlayerAnalytics import ru.radiationx.data.analytics.features.ReleaseAnalytics import ru.radiationx.data.analytics.features.ScheduleAnalytics import ru.radiationx.data.analytics.features.SettingsAnalytics +import ru.radiationx.data.analytics.features.SslCompatAnalytics import ru.radiationx.data.analytics.features.TeamsAnalytics import ru.radiationx.data.analytics.features.UpdaterAnalytics import ru.radiationx.data.analytics.features.WebPlayerAnalytics @@ -252,6 +253,7 @@ class DataModule(context: Context) : QuillModule() { /* Analytics */ single() + single() single() single() single() diff --git a/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt index eaec168e9..a108f2c2f 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/ApiOkHttpProvider.kt @@ -6,12 +6,14 @@ import okhttp3.Credentials import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import ru.radiationx.data.SharedBuildConfig +import ru.radiationx.data.analytics.features.SslCompatAnalytics import ru.radiationx.data.datasource.remote.address.ApiConfig import ru.radiationx.data.datasource.remote.interceptors.UnauthorizedInterceptor import ru.radiationx.data.sslcompat.SslCompat import ru.radiationx.data.sslcompat.appendSslCompat import ru.radiationx.data.system.AppCookieJar import ru.radiationx.data.system.Client +import ru.radiationx.data.system.appendSslCompatAnalytics import ru.radiationx.data.system.appendTimeouts import java.net.InetSocketAddress import java.net.Proxy @@ -25,9 +27,11 @@ class ApiOkHttpProvider @Inject constructor( private val sharedBuildConfig: SharedBuildConfig, private val unauthorizedInterceptor: UnauthorizedInterceptor, private val sslCompat: SslCompat, + private val sslCompatAnalytics: SslCompatAnalytics ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() + .appendSslCompatAnalytics(sslCompat, sslCompatAnalytics) .appendSslCompat(sslCompat) .appendTimeouts() .apply { diff --git a/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt index 2cb3040e2..7e30ca07b 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/MainOkHttpProvider.kt @@ -5,8 +5,10 @@ import com.chuckerteam.chucker.api.ChuckerInterceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import ru.radiationx.data.SharedBuildConfig +import ru.radiationx.data.analytics.features.SslCompatAnalytics import ru.radiationx.data.sslcompat.SslCompat import ru.radiationx.data.sslcompat.appendSslCompat +import ru.radiationx.data.system.appendSslCompatAnalytics import ru.radiationx.data.system.appendTimeouts import javax.inject.Inject import javax.inject.Provider @@ -15,10 +17,12 @@ import javax.inject.Provider class MainOkHttpProvider @Inject constructor( private val context: Context, private val sharedBuildConfig: SharedBuildConfig, - private val sslCompat: SslCompat + private val sslCompat: SslCompat, + private val sslCompatAnalytics: SslCompatAnalytics ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() + .appendSslCompatAnalytics(sslCompat, sslCompatAnalytics) .appendSslCompat(sslCompat) .appendTimeouts() .addNetworkInterceptor { diff --git a/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt index 465fa829c..5ca8333c7 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/PlayerOkHttpProvider.kt @@ -4,8 +4,10 @@ import android.content.Context import com.chuckerteam.chucker.api.ChuckerInterceptor import okhttp3.OkHttpClient import ru.radiationx.data.SharedBuildConfig +import ru.radiationx.data.analytics.features.SslCompatAnalytics import ru.radiationx.data.sslcompat.SslCompat import ru.radiationx.data.sslcompat.appendSslCompat +import ru.radiationx.data.system.appendSslCompatAnalytics import javax.inject.Inject import javax.inject.Provider @@ -13,9 +15,11 @@ class PlayerOkHttpProvider @Inject constructor( private val context: Context, private val sharedBuildConfig: SharedBuildConfig, private val sslCompat: SslCompat, + private val sslCompatAnalytics: SslCompatAnalytics ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() + .appendSslCompatAnalytics(sslCompat, sslCompatAnalytics) .appendSslCompat(sslCompat) .apply { if (sharedBuildConfig.debug) { diff --git a/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt b/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt index 06a4575fb..c30337882 100644 --- a/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt +++ b/data/src/main/java/ru/radiationx/data/di/providers/SimpleOkHttpProvider.kt @@ -5,8 +5,10 @@ import com.chuckerteam.chucker.api.ChuckerInterceptor import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import ru.radiationx.data.SharedBuildConfig +import ru.radiationx.data.analytics.features.SslCompatAnalytics import ru.radiationx.data.sslcompat.SslCompat import ru.radiationx.data.sslcompat.appendSslCompat +import ru.radiationx.data.system.appendSslCompatAnalytics import javax.inject.Inject import javax.inject.Provider @@ -15,9 +17,11 @@ class SimpleOkHttpProvider @Inject constructor( private val context: Context, private val sharedBuildConfig: SharedBuildConfig, private val sslCompat: SslCompat, + private val sslCompatAnalytics: SslCompatAnalytics, ) : Provider { override fun get(): OkHttpClient = OkHttpClient.Builder() + .appendSslCompatAnalytics(sslCompat, sslCompatAnalytics) .appendSslCompat(sslCompat) .apply { if (sharedBuildConfig.debug) { diff --git a/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt b/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt index 672431801..15e5eeee6 100644 --- a/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt +++ b/data/src/main/java/ru/radiationx/data/system/OkHttpConfig.kt @@ -1,10 +1,18 @@ package ru.radiationx.data.system -import okhttp3.CipherSuite -import okhttp3.ConnectionSpec import okhttp3.OkHttpClient +import ru.radiationx.data.analytics.features.SslCompatAnalytics +import ru.radiationx.data.sslcompat.SslCompat import java.util.concurrent.TimeUnit +fun OkHttpClient.Builder.appendSslCompatAnalytics( + sslCompat: SslCompat, + sslCompatAnalytics: SslCompatAnalytics, +): OkHttpClient.Builder { + sslCompatAnalytics.oneShotError(sslCompat.data) + return this +} + fun OkHttpClient.Builder.appendTimeouts(): OkHttpClient.Builder { callTimeout(25, TimeUnit.SECONDS) connectTimeout(15, TimeUnit.SECONDS) From 718b0f997c7056b2b9c7b3a456d2360516ca16ab Mon Sep 17 00:00:00 2001 From: RadiationX Date: Tue, 2 Apr 2024 15:15:43 +0500 Subject: [PATCH 5/5] fix: trustmanager exception --- .../ru/radiationx/data/sslcompat/CompatTrustManager.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt b/data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt index 3c12be9b0..f0803e7bf 100644 --- a/data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt +++ b/data/src/main/java/ru/radiationx/data/sslcompat/CompatTrustManager.kt @@ -1,7 +1,6 @@ package ru.radiationx.data.sslcompat import android.annotation.SuppressLint -import android.util.Log import java.security.KeyStore import java.security.cert.CertificateException import java.security.cert.X509Certificate @@ -42,15 +41,16 @@ class CompatTrustManager( */ @Throws(CertificateException::class) override fun checkServerTrusted(chain: Array, authType: String) { - for (tm in managers) { + var latestException: CertificateException? = null + for (manager in managers) { try { - tm.checkServerTrusted(chain, authType) + manager.checkServerTrusted(chain, authType) return } catch (e: CertificateException) { - // ignore + latestException = e } } - throw CertificateException() + throw latestException ?: CertificateException("Not found any trusted server in managers") } override fun getAcceptedIssuers(): Array {