From 5ddbbe0e22ebf4ddb9ee797a0a45d64539dc947c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= <30429749+saleniuk@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:38:20 +0100 Subject: [PATCH] fix: changes to hopefully improve startup and ANRs [WPB-6048] (#2607) Co-authored-by: Yamil Medina --- app/build.gradle.kts | 1 + .../com/wire/android/util/DataDogLogger.kt | 2 +- .../com/wire/android/util/DataDogLogger.kt | 2 +- app/src/main/AndroidManifest.xml | 39 ++++----- .../com/wire/android/WireApplication.kt | 85 +++++++++---------- .../initializer/FirebaseInitializer.kt | 40 +++++++++ .../initializer/InitializerEntryPoint.kt | 37 ++++++++ .../com/wire/android/ui/WireActivity.kt | 45 ++++++---- .../ui/calling/ProximitySensorManager.kt | 11 +-- .../res/drawable/ic_launcher_wire_logo.xml | 39 +++++++++ .../com/wire/android/util/DataDogLogger.kt | 2 +- gradle/libs.versions.toml | 2 + 12 files changed, 214 insertions(+), 91 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/initializer/FirebaseInitializer.kt create mode 100644 app/src/main/kotlin/com/wire/android/initializer/InitializerEntryPoint.kt create mode 100644 app/src/main/res/drawable/ic_launcher_wire_logo.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cd0d0540d8..e146779449 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -70,6 +70,7 @@ dependencies { implementation(libs.androidx.splashscreen) implementation(libs.androidx.exifInterface) implementation(libs.androidx.biometric) + implementation(libs.androidx.startup) implementation(libs.ktx.dateTime) implementation(libs.material) diff --git a/app/src/dev/kotlin/com/wire/android/util/DataDogLogger.kt b/app/src/dev/kotlin/com/wire/android/util/DataDogLogger.kt index 8eaf326532..fd84a0bfcc 100644 --- a/app/src/dev/kotlin/com/wire/android/util/DataDogLogger.kt +++ b/app/src/dev/kotlin/com/wire/android/util/DataDogLogger.kt @@ -29,8 +29,8 @@ object DataDogLogger : LogWriter() { private val logger = Logger.Builder() .setNetworkInfoEnabled(true) - .setLogcatLogsEnabled(true) .setLogcatLogsEnabled(false) // we already use platformLogWriter() along with DataDogLogger, don't need duplicates in LogCat + .setDatadogLogsEnabled(true) .setBundleWithTraceEnabled(true) .setLoggerName("DATADOG") .build() diff --git a/app/src/internal/kotlin/com/wire/android/util/DataDogLogger.kt b/app/src/internal/kotlin/com/wire/android/util/DataDogLogger.kt index 8eaf326532..fd84a0bfcc 100644 --- a/app/src/internal/kotlin/com/wire/android/util/DataDogLogger.kt +++ b/app/src/internal/kotlin/com/wire/android/util/DataDogLogger.kt @@ -29,8 +29,8 @@ object DataDogLogger : LogWriter() { private val logger = Logger.Builder() .setNetworkInfoEnabled(true) - .setLogcatLogsEnabled(true) .setLogcatLogsEnabled(false) // we already use platformLogWriter() along with DataDogLogger, don't need duplicates in LogCat + .setDatadogLogsEnabled(true) .setBundleWithTraceEnabled(true) .setLoggerName("DATADOG") .build() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f60f99354d..f76f5432ac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -227,7 +227,16 @@ android:host="e2ei" android:scheme="wire" /> + + + + + + - - - - - - - - - diff --git a/app/src/main/kotlin/com/wire/android/WireApplication.kt b/app/src/main/kotlin/com/wire/android/WireApplication.kt index 7924cbf315..5554a2e561 100644 --- a/app/src/main/kotlin/com/wire/android/WireApplication.kt +++ b/app/src/main/kotlin/com/wire/android/WireApplication.kt @@ -24,14 +24,11 @@ import android.os.Build import android.os.StrictMode import androidx.work.Configuration import co.touchlab.kermit.platformLogWriter -import com.google.firebase.FirebaseApp -import com.google.firebase.FirebaseOptions import com.wire.android.datastore.GlobalDataStore import com.wire.android.di.ApplicationScope import com.wire.android.di.KaliumCoreLogic import com.wire.android.util.DataDogLogger import com.wire.android.util.LogFileWriter -import com.wire.android.util.extension.isGoogleServicesAvailable import com.wire.android.util.getGitBuildId import com.wire.android.util.lifecycle.ConnectionPolicyManager import com.wire.android.workmanager.WireWorkerFactory @@ -39,6 +36,7 @@ import com.wire.kalium.logger.KaliumLogLevel import com.wire.kalium.logger.KaliumLogger import com.wire.kalium.logic.CoreLogger import com.wire.kalium.logic.CoreLogic +import dagger.Lazy import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first @@ -50,29 +48,29 @@ class WireApplication : Application(), Configuration.Provider { @Inject @KaliumCoreLogic - lateinit var coreLogic: CoreLogic + lateinit var coreLogic: Lazy @Inject - lateinit var logFileWriter: LogFileWriter + lateinit var logFileWriter: Lazy @Inject - lateinit var connectionPolicyManager: ConnectionPolicyManager + lateinit var connectionPolicyManager: Lazy @Inject - lateinit var wireWorkerFactory: WireWorkerFactory + lateinit var wireWorkerFactory: Lazy @Inject - lateinit var globalObserversManager: GlobalObserversManager + lateinit var globalObserversManager: Lazy @Inject - lateinit var globalDataStore: GlobalDataStore + lateinit var globalDataStore: Lazy @Inject @ApplicationScope lateinit var globalAppScope: CoroutineScope override fun getWorkManagerConfiguration(): Configuration { return Configuration.Builder() - .setWorkerFactory(wireWorkerFactory) + .setWorkerFactory(wireWorkerFactory.get()) .build() } @@ -81,23 +79,19 @@ class WireApplication : Application(), Configuration.Provider { enableStrictMode() - if (this.isGoogleServicesAvailable()) { - val firebaseOptions = FirebaseOptions.Builder() - .setApplicationId(BuildConfig.FIREBASE_APP_ID) - .setGcmSenderId(BuildConfig.FIREBASE_PUSH_SENDER_ID) - .setApiKey(BuildConfig.GOOGLE_API_KEY) - .setProjectId(BuildConfig.FCM_PROJECT_ID) - .build() - FirebaseApp.initializeApp(this, firebaseOptions) - } + globalAppScope.launch { + initializeApplicationLoggingFrameworks() - initializeApplicationLoggingFrameworks() - connectionPolicyManager.startObservingAppLifecycle() + appLogger.i("$TAG app lifecycle") + connectionPolicyManager.get().startObservingAppLifecycle() - // TODO: Can be handled in one of Sync steps - coreLogic.updateApiVersionsScheduler.schedulePeriodicApiVersionUpdate() + appLogger.i("$TAG api version update") + // TODO: Can be handled in one of Sync steps + coreLogic.get().updateApiVersionsScheduler.schedulePeriodicApiVersionUpdate() - globalObserversManager.observe() + appLogger.i("$TAG global observers") + globalObserversManager.get().observe() + } } private fun enableStrictMode() { @@ -121,29 +115,27 @@ class WireApplication : Application(), Configuration.Provider { } } - private fun initializeApplicationLoggingFrameworks() { - globalAppScope.launch { - // 1. Datadog should be initialized first - ExternalLoggerManager.initDatadogLogger(applicationContext, globalDataStore) - // 2. Initialize our internal logging framework - val isLoggingEnabled = globalDataStore.isLoggingEnabled().first() - val config = if (isLoggingEnabled) { - KaliumLogger.Config.DEFAULT.apply { - setLogLevel(KaliumLogLevel.VERBOSE) - setLogWriterList(listOf(DataDogLogger, platformLogWriter())) - } - } else { - KaliumLogger.Config.disabled() + private suspend fun initializeApplicationLoggingFrameworks() { + // 1. Datadog should be initialized first + ExternalLoggerManager.initDatadogLogger(applicationContext, globalDataStore.get()) + // 2. Initialize our internal logging framework + val isLoggingEnabled = globalDataStore.get().isLoggingEnabled().first() + val config = if (isLoggingEnabled) { + KaliumLogger.Config.DEFAULT.apply { + setLogLevel(KaliumLogLevel.VERBOSE) + setLogWriterList(listOf(DataDogLogger, platformLogWriter())) } - // 2. Initialize our internal logging framework - AppLogger.init(config) - CoreLogger.init(config) - // 3. Initialize our internal FILE logging framework - logFileWriter.start() - // 4. Everything ready, now we can log device info - appLogger.i("Logger enabled") - logDeviceInformation() + } else { + KaliumLogger.Config.disabled() } + // 2. Initialize our internal logging framework + AppLogger.init(config) + CoreLogger.init(config) + // 3. Initialize our internal FILE logging framework + logFileWriter.get().start() + // 4. Everything ready, now we can log device info + appLogger.i("Logger enabled") + logDeviceInformation() } private fun logDeviceInformation() { @@ -169,7 +161,7 @@ class WireApplication : Application(), Configuration.Provider { override fun onLowMemory() { super.onLowMemory() appLogger.w("onLowMemory called - Stopping logging, buckling the seatbelt and hoping for the best!") - logFileWriter.stop() + logFileWriter.get().stop() } private companion object { @@ -190,5 +182,6 @@ class WireApplication : Application(), Configuration.Provider { values().firstOrNull { it.level == value } ?: TRIM_MEMORY_UNKNOWN } } + private const val TAG = "WireApplication" } } diff --git a/app/src/main/kotlin/com/wire/android/initializer/FirebaseInitializer.kt b/app/src/main/kotlin/com/wire/android/initializer/FirebaseInitializer.kt new file mode 100644 index 0000000000..2a5f62a33a --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/initializer/FirebaseInitializer.kt @@ -0,0 +1,40 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.initializer + +import android.content.Context +import androidx.startup.Initializer +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.wire.android.BuildConfig +import com.wire.android.util.extension.isGoogleServicesAvailable + +class FirebaseInitializer : Initializer { + override fun create(context: Context) { + if (context.isGoogleServicesAvailable()) { + val firebaseOptions = FirebaseOptions.Builder() + .setApplicationId(BuildConfig.FIREBASE_APP_ID) + .setGcmSenderId(BuildConfig.FIREBASE_PUSH_SENDER_ID) + .setApiKey(BuildConfig.GOOGLE_API_KEY) + .setProjectId(BuildConfig.FCM_PROJECT_ID) + .build() + FirebaseApp.initializeApp(context, firebaseOptions) + } + } + override fun dependencies(): List>> = emptyList() // no dependencies on other libraries +} diff --git a/app/src/main/kotlin/com/wire/android/initializer/InitializerEntryPoint.kt b/app/src/main/kotlin/com/wire/android/initializer/InitializerEntryPoint.kt new file mode 100644 index 0000000000..365ce3eac6 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/initializer/InitializerEntryPoint.kt @@ -0,0 +1,37 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.initializer + +import android.content.Context +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface InitializerEntryPoint { + + companion object { + // a helper method to resolve the InitializerEntryPoint from the context + fun resolve(context: Context): InitializerEntryPoint { + val appContext = context.applicationContext ?: throw IllegalStateException() + return EntryPointAccessors.fromApplication(appContext, InitializerEntryPoint::class.java) + } + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt index 5e7381dae3..e1b4e05ae2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt @@ -97,6 +97,7 @@ import com.wire.android.util.debug.FeatureVisibilityFlags import com.wire.android.util.debug.LocalFeatureVisibilityFlags import com.wire.android.util.deeplink.DeepLinkResult import com.wire.android.util.ui.updateScreenSettings +import dagger.Lazy import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest @@ -117,7 +118,7 @@ class WireActivity : AppCompatActivity() { lateinit var proximitySensorManager: ProximitySensorManager @Inject - lateinit var lockCodeTimeManager: LockCodeTimeManager + lateinit var lockCodeTimeManager: Lazy private val viewModel: WireActivityViewModel by viewModels() @@ -133,26 +134,39 @@ class WireActivity : AppCompatActivity() { private var shouldKeepSplashOpen = true override fun onCreate(savedInstanceState: Bundle?) { + + appLogger.i("$TAG splash install") // We need to keep the splash screen open until the first screen is drawn. // Otherwise a white screen is displayed. // It's an API limitation, at some point we may need to remove it - installSplashScreen().setKeepOnScreenCondition { - shouldKeepSplashOpen - } + val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) - proximitySensorManager.initialize() + splashScreen.setKeepOnScreenCondition { shouldKeepSplashOpen } + lifecycle.addObserver(currentScreenManager) WindowCompat.setDecorFitsSystemWindows(window, false) - viewModel.observePersistentConnectionStatus() - val startDestination = when (viewModel.initialAppState) { - InitialAppState.NOT_MIGRATED -> MigrationScreenDestination - InitialAppState.NOT_LOGGED_IN -> WelcomeScreenDestination - InitialAppState.LOGGED_IN -> HomeScreenDestination - } - setComposableContent(startDestination) { - shouldKeepSplashOpen = false - handleDeepLink(intent, savedInstanceState) + appLogger.i("$TAG proximity sensor") + proximitySensorManager.initialize() + + lifecycleScope.launch { + + appLogger.i("$TAG persistent connection status") + viewModel.observePersistentConnectionStatus() + + appLogger.i("$TAG start destination") + val startDestination = when (viewModel.initialAppState) { + InitialAppState.NOT_MIGRATED -> MigrationScreenDestination + InitialAppState.NOT_LOGGED_IN -> WelcomeScreenDestination + InitialAppState.LOGGED_IN -> HomeScreenDestination + } + + appLogger.i("$TAG composable content") + setComposableContent(startDestination) { + appLogger.i("$TAG splash hide") + shouldKeepSplashOpen = false + handleDeepLink(intent, savedInstanceState) + } } } @@ -412,7 +426,7 @@ class WireActivity : AppCompatActivity() { super.onResume() lifecycleScope.launch { - lockCodeTimeManager.observeAppLock() + lockCodeTimeManager.get().observeAppLock() // Listen to one flow in a lifecycle-aware manner using flowWithLifecycle .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED) .first().let { @@ -506,6 +520,7 @@ class WireActivity : AppCompatActivity() { companion object { private const val HANDLED_DEEPLINK_FLAG = "deeplink_handled_flag_key" + private const val TAG = "WireActivity" } } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ProximitySensorManager.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ProximitySensorManager.kt index cd688f2372..d72a972fb9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ProximitySensorManager.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ProximitySensorManager.kt @@ -31,6 +31,7 @@ import com.wire.android.di.KaliumCoreLogic import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.feature.session.CurrentSessionResult import com.wire.kalium.logic.feature.session.CurrentSessionUseCase +import dagger.Lazy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject @@ -39,8 +40,8 @@ import javax.inject.Singleton @Singleton class ProximitySensorManager @Inject constructor( private val context: Context, - private val currentSession: CurrentSessionUseCase, - @KaliumCoreLogic private val coreLogic: CoreLogic, + private val currentSession: Lazy, + @KaliumCoreLogic private val coreLogic: Lazy, @ApplicationScope private val appCoroutineScope: CoroutineScope ) { @@ -69,11 +70,11 @@ class ProximitySensorManager @Inject constructor( override fun onSensorChanged(event: SensorEvent) { appCoroutineScope.launch { - coreLogic.globalScope { - when (val currentSession = currentSession()) { + coreLogic.get().globalScope { + when (val currentSession = currentSession.get().invoke()) { is CurrentSessionResult.Success -> { val userId = currentSession.accountInfo.userId - val isCallRunning = coreLogic.getSessionScope(userId).calls.isCallRunning() + val isCallRunning = coreLogic.get().getSessionScope(userId).calls.isCallRunning() val distance = event.values.first() val shouldTurnOffScreen = distance == NEAR_DISTANCE && isCallRunning appLogger.i( diff --git a/app/src/main/res/drawable/ic_launcher_wire_logo.xml b/app/src/main/res/drawable/ic_launcher_wire_logo.xml new file mode 100644 index 0000000000..7b6912bb1b --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_wire_logo.xml @@ -0,0 +1,39 @@ + + + + + + + + + diff --git a/app/src/staging/kotlin/com/wire/android/util/DataDogLogger.kt b/app/src/staging/kotlin/com/wire/android/util/DataDogLogger.kt index 8eaf326532..fd84a0bfcc 100644 --- a/app/src/staging/kotlin/com/wire/android/util/DataDogLogger.kt +++ b/app/src/staging/kotlin/com/wire/android/util/DataDogLogger.kt @@ -29,8 +29,8 @@ object DataDogLogger : LogWriter() { private val logger = Logger.Builder() .setNetworkInfoEnabled(true) - .setLogcatLogsEnabled(true) .setLogcatLogsEnabled(false) // we already use platformLogWriter() along with DataDogLogger, don't need duplicates in LogCat + .setDatadogLogsEnabled(true) .setBundleWithTraceEnabled(true) .setLoggerName("DATADOG") .build() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 90f4c21fb1..6969257997 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,6 +36,7 @@ androidx-splashscreen = "1.0.1" androidx-workManager = "2.8.1" androidx-browser = "1.5.0" androidx-biometric = "1.1.0" +androidx-startup = "1.1.1" # Compose composeBom = "2023.10.01" # TODO check if in new version [anchoredDraggable] is available @@ -158,6 +159,7 @@ androidx-exifInterface = { module = "androidx.exifinterface:exifinterface", vers androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-splashscreen" } androidx-profile-installer = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" } androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "androidx-biometric" } +androidx-startup = { group = "androidx.startup", name = "startup-runtime", version.ref = "androidx-startup" } # Dependency Injection hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }