/*
*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* 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 .
*/
package com.nextcloud.talk.application
import android.app.Application
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.emoji.bundled.BundledEmojiCompatConfig
import androidx.emoji.text.EmojiCompat
import androidx.lifecycle.LifecycleObserver
import androidx.multidex.MultiDex
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import coil.Coil
import coil.ImageLoader
import com.bluelinelabs.logansquare.LoganSquare
import com.facebook.cache.disk.DiskCacheConfig
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.components.filebrowser.webdav.DavUtils
import com.nextcloud.talk.jobs.AccountRemovalWorker
import com.nextcloud.talk.jobs.CapabilitiesWorker
import com.nextcloud.talk.jobs.PushRegistrationWorker
import com.nextcloud.talk.jobs.SignalingSettingsWorker
import com.nextcloud.talk.models.ExternalSignalingServer
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.capabilities.Capabilities
import com.nextcloud.talk.models.json.push.PushConfigurationState
import com.nextcloud.talk.newarch.di.module.CommunicationModule
import com.nextcloud.talk.newarch.di.module.NetworkModule
import com.nextcloud.talk.newarch.di.module.StorageModule
import com.nextcloud.talk.newarch.features.conversationsList.di.module.ConversationsListModule
import com.nextcloud.talk.newarch.local.dao.UsersDao
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.other.UserStatus.*
import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.OkHttpNetworkFetcherWithCache
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.webrtc.MagicWebRTCUtils
import com.vanniktech.emoji.EmojiManager
import com.vanniktech.emoji.googlecompat.GoogleCompatEmojiProvider
import de.cotech.hw.SecurityKeyManager
import de.cotech.hw.SecurityKeyManagerConfig
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import org.conscrypt.Conscrypt
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.webrtc.PeerConnectionFactory
import org.webrtc.voiceengine.WebRtcAudioManager
import org.webrtc.voiceengine.WebRtcAudioUtils
import java.security.Security
import java.util.concurrent.TimeUnit
class NextcloudTalkApplication : Application(), LifecycleObserver {
//region Getters
val userUtils: UserUtils by inject()
val imageLoader: ImageLoader by inject()
val appPreferences: AppPreferences by inject()
val okHttpClient: OkHttpClient by inject()
val usersDao: UsersDao by inject()
//endregion
//region private methods
private fun initializeWebRtc() {
try {
if (MagicWebRTCUtils.HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) {
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true)
}
if (!MagicWebRTCUtils.OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true)
}
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions.builder(this)
.setEnableVideoHwAcceleration(
MagicWebRTCUtils.shouldEnableVideoHardwareAcceleration()
)
.createInitializationOptions()
)
} catch (e: UnsatisfiedLinkError) {
Log.w(TAG, e)
}
}
//endregion
//region Overridden methods
override fun onCreate() {
sharedApplication = this
val securityKeyManager = SecurityKeyManager.getInstance()
val securityKeyConfig = SecurityKeyManagerConfig.Builder()
.setEnableDebugLogging(BuildConfig.DEBUG)
.build()
securityKeyManager.init(this, securityKeyConfig)
initializeWebRtc()
DisplayUtils.useCompatVectorIfNeeded()
startKoin()
DavUtils.registerCustomFactories()
Coil.setDefaultImageLoader(imageLoader)
migrateUsers()
setAppTheme(appPreferences.theme)
super.onCreate()
val imagePipelineConfig = ImagePipelineConfig.newBuilder(this)
.setNetworkFetcher(OkHttpNetworkFetcherWithCache(okHttpClient))
.setMainDiskCacheConfig(
DiskCacheConfig.newBuilder(this)
.setMaxCacheSize(0)
.setMaxCacheSizeOnLowDiskSpace(0)
.setMaxCacheSizeOnVeryLowDiskSpace(0)
.build()
)
.build()
Fresco.initialize(this, imagePipelineConfig)
Security.insertProviderAt(Conscrypt.newProvider(), 1)
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
.build()
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java)
.build()
val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder(
CapabilitiesWorker::class.java,
12, TimeUnit.HOURS
)
.build()
val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
.build()
WorkManager.getInstance()
.enqueue(pushRegistrationWork)
WorkManager.getInstance()
.enqueue(accountRemovalWork)
WorkManager.getInstance()
.enqueue(signalingSettingsWork)
WorkManager.getInstance()
.enqueueUniquePeriodicWork(
"DailyCapabilitiesUpdateWork", ExistingPeriodicWorkPolicy.REPLACE,
periodicCapabilitiesUpdateWork
)
val config = BundledEmojiCompatConfig(this)
config.setReplaceAll(true)
val emojiCompat = EmojiCompat.init(config)
EmojiManager.install(GoogleCompatEmojiProvider(emojiCompat))
}
override fun onTerminate() {
super.onTerminate()
sharedApplication = null
}
//endregion
//region Protected methods
protected fun startKoin() {
startKoin {
androidContext(this@NextcloudTalkApplication)
androidLogger()
modules(listOf(CommunicationModule, StorageModule, NetworkModule, ConversationsListModule))
}
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
fun migrateUsers() {
if (!appPreferences.migrationToRoomFinished) {
GlobalScope.launch {
val users: List = userUtils.users as List
var userNg: UserNgEntity
val newUsers = mutableListOf()
for (user in users) {
userNg = UserNgEntity(user.id, user.userId, user.username, user.baseUrl)
userNg.token = user.token
userNg.displayName = user.displayName
try {
userNg.pushConfiguration =
LoganSquare.parse(user.pushConfigurationState, PushConfigurationState::class.java)
} catch (e: Exception) {
// no push
}
if (user.capabilities != null) {
userNg.capabilities = LoganSquare.parse(user.capabilities, Capabilities::class.java)
}
userNg.clientCertificate = user.clientCertificate
try {
userNg.externalSignaling =
LoganSquare.parse(user.externalSignalingServer, ExternalSignalingServer::class.java)
} catch (e: Exception) {
// no external signaling
}
if (user.current) {
userNg.status = ACTIVE
} else {
if (user.scheduledForDeletion) {
userNg.status = PENDING_DELETE
} else {
userNg.status = DORMANT
}
}
newUsers.add(userNg)
}
usersDao.saveUsers(*newUsers.toTypedArray())
appPreferences.migrationToRoomFinished = true
}
}
}
companion object {
private val TAG = NextcloudTalkApplication::class.java.simpleName
//region Singleton
//endregion
var sharedApplication: NextcloudTalkApplication? = null
protected set
//endregion
//region Setters
fun setAppTheme(theme: String) {
when (theme) {
"night_no" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"night_yes" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
"battery_saver" -> AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
)
else ->
// will be "follow_system" only for now
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
}
//endregion
}