diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index b3cef73967..689a2a13e9 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { anvil(projects.anvilcodegen) implementation(projects.libraries.androidutils) implementation(projects.libraries.core) + implementation(projects.libraries.network) implementation(projects.libraries.architecture) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index 91f761bda9..2cedce18a8 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.core.extensions.toOnOff import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.network.useragent.UserAgentProvider import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import okhttp3.Call @@ -64,6 +65,7 @@ class DefaultBugReporter @Inject constructor( private val crashDataStore: CrashDataStore, private val coroutineDispatchers: CoroutineDispatchers, private val okHttpClient: Provider, + private val userAgentProvider: UserAgentProvider, /* private val activeSessionHolder: ActiveSessionHolder, private val versionProvider: VersionProvider, @@ -222,7 +224,7 @@ class DefaultBugReporter @Inject constructor( val builder = BugReporterMultipartBody.Builder() .addFormDataPart("text", text) .addFormDataPart("app", rageShakeAppNameForReport(reportType)) - // .addFormDataPart("user_agent", matrix.getUserAgent()) + .addFormDataPart("user_agent", userAgentProvider.provide()) .addFormDataPart("user_id", userId) .addFormDataPart("can_contact", canContact.toString()) .addFormDataPart("device_id", deviceId) diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 5709b5a6d7..f9ab87d5dc 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(libs.matrix.sdk) implementation(projects.libraries.di) implementation(projects.libraries.androidutils) + implementation(projects.libraries.network) implementation(projects.services.toolbox.api) api(projects.libraries.matrix.api) implementation(libs.dagger) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index ed4d4ecf6a..3907acf730 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.RustMatrixClient +import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.services.toolbox.api.systemclock.SystemClock @@ -56,6 +57,7 @@ class RustMatrixAuthenticationService @Inject constructor( private val coroutineDispatchers: CoroutineDispatchers, private val sessionStore: SessionStore, private val clock: SystemClock, + private val userAgentProvider: UserAgentProvider, ) : MatrixAuthenticationService { private val authService: RustAuthenticationService = RustAuthenticationService( @@ -84,6 +86,7 @@ class RustMatrixAuthenticationService @Inject constructor( .basePath(baseDirectory.absolutePath) .homeserverUrl(sessionData.homeserverUrl) .username(sessionData.userId) + .userAgent(userAgentProvider.provide()) .use { it.build() } client.restoreSession(sessionData.toSession()) createMatrixClient(client) diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt index 52c04e6717..c36a0411e5 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.network.interceptors.FormattedJsonHttpLogger +import io.element.android.libraries.network.interceptors.UserAgentInterceptor import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -35,10 +36,12 @@ object NetworkModule { @SingleIn(AppScope::class) fun providesOkHttpClient( buildMeta: BuildMeta, + userAgentInterceptor: UserAgentInterceptor, ): OkHttpClient = OkHttpClient.Builder().apply { connectTimeout(30, TimeUnit.SECONDS) readTimeout(60, TimeUnit.SECONDS) writeTimeout(60, TimeUnit.SECONDS) + addInterceptor(userAgentInterceptor) if (buildMeta.isDebuggable) addInterceptor(providesHttpLoggingInterceptor()) }.build() diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt new file mode 100644 index 0000000000..315835d584 --- /dev/null +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.network.headers + +internal object HttpHeaders { + const val Authorization = "Authorization" + const val UserAgent = "User-Agent" +} diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/UserAgentInterceptor.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/UserAgentInterceptor.kt new file mode 100644 index 0000000000..4c3f0419d8 --- /dev/null +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/UserAgentInterceptor.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.network.interceptors + +import io.element.android.libraries.network.headers.HttpHeaders +import io.element.android.libraries.network.useragent.UserAgentProvider +import okhttp3.Interceptor +import okhttp3.Response +import javax.inject.Inject + +class UserAgentInterceptor @Inject constructor( + private val userAgentProvider: UserAgentProvider, +) : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val newRequest = chain.request() + .newBuilder() + .header(HttpHeaders.UserAgent, userAgentProvider.provide()) + .build() + return chain.proceed(newRequest) + } +} diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/DefaultUserAgentProvider.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/DefaultUserAgentProvider.kt new file mode 100644 index 0000000000..7b0b5bedcc --- /dev/null +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/DefaultUserAgentProvider.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.network.useragent + +import android.os.Build +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultUserAgentProvider @Inject constructor( + private val buildMeta: BuildMeta, +) : UserAgentProvider { + private val userAgent: String by lazy { buildUserAgent() } + + override fun provide(): String = userAgent + + /** + * Create an user agent with the application version. + * Ex: Element X/1.5.0 (Xiaomi Mi 9T; Android 11; RKQ1.200826.002; Sdk 0.1.0) + */ + private fun buildUserAgent(): String { + val appName = buildMeta.applicationName + val appVersion = buildMeta.versionName + val deviceManufacturer = Build.MANUFACTURER + val deviceModel = Build.MODEL + val androidVersion = Build.VERSION.RELEASE + val deviceBuildId = Build.DISPLAY + val matrixSdkVersion = "TODO" + + return buildString { + append(appName) + append("/") + append(appVersion) + append(" (") + append(deviceManufacturer) + append(" ") + append(deviceModel) + append("; ") + append("Android ") + append(androidVersion) + append("; ") + append(deviceBuildId) + append("; ") + append("Sdk ") + append(matrixSdkVersion) + append(")") + } + } +} diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/SimpleUserAgentProvider.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/SimpleUserAgentProvider.kt new file mode 100644 index 0000000000..319eeeb65b --- /dev/null +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/SimpleUserAgentProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.network.useragent + +class SimpleUserAgentProvider( + private val userAgent: String = "User agent" +) : UserAgentProvider { + override fun provide(): String = userAgent +} diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/UserAgentProvider.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/UserAgentProvider.kt new file mode 100644 index 0000000000..0266b518cb --- /dev/null +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/UserAgentProvider.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.network.useragent + +interface UserAgentProvider { + fun provide(): String +} diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 6ac7599d4a..8063ac9b0a 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.architecture) implementation(projects.libraries.core) + implementation(projects.libraries.network) implementation(projects.libraries.dateformatter.impl) implementation(projects.libraries.eventformatter.impl) implementation(projects.features.invitelist.impl) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index 0f6bbe23d0..21d6648a41 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -29,6 +29,7 @@ import androidx.core.view.WindowCompat import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService +import io.element.android.libraries.network.useragent.SimpleUserAgentProvider import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore import io.element.android.services.toolbox.impl.systemclock.DefaultSystemClock import kotlinx.coroutines.runBlocking @@ -45,7 +46,8 @@ class MainActivity : ComponentActivity() { appCoroutineScope = Singleton.appScope, coroutineDispatchers = Singleton.coroutineDispatchers, sessionStore = InMemorySessionStore(), - clock = DefaultSystemClock() + clock = DefaultSystemClock(), + userAgentProvider = SimpleUserAgentProvider("MinimalSample") ) }