komikku/app/src/main/java/eu/kanade/tachiyomi/App.kt

342 lines
13 KiB
Kotlin
Raw Normal View History

package eu.kanade.tachiyomi
import android.app.ActivityManager
import android.app.Application
import android.app.PendingIntent
import android.content.BroadcastReceiver
2016-10-15 11:12:16 +02:00
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
2019-08-07 21:47:43 +02:00
import android.os.Build
2019-04-14 05:47:57 +02:00
import android.os.Environment
import android.webkit.WebView
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
2020-05-04 00:34:46 +02:00
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.disk.DiskCache
import coil.util.DebugLogger
2019-04-14 05:47:57 +02:00
import com.elvishew.xlog.LogConfiguration
import com.elvishew.xlog.LogLevel
import com.elvishew.xlog.XLog
import com.elvishew.xlog.printer.AndroidPrinter
import com.elvishew.xlog.printer.Printer
2019-04-14 19:57:14 +02:00
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
2019-04-14 05:47:57 +02:00
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
2020-08-24 03:57:06 +02:00
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.ms_square.debugoverlay.DebugOverlay
import com.ms_square.debugoverlay.modules.FpsModule
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferenceValues
2020-05-04 00:34:46 +02:00
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.util.preference.asImmediateFlow
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.notification
import exh.debug.DebugToggles
2019-04-14 05:47:57 +02:00
import exh.log.CrashlyticsPrinter
import exh.log.EHDebugModeOverlay
2019-04-14 16:46:06 +02:00
import exh.log.EHLogLevel
2020-11-11 23:28:09 +01:00
import exh.log.EnhancedFilePrinter
import exh.log.XLogLogcatLogger
import exh.log.xLogD
import exh.log.xLogE
2020-08-22 02:26:56 +02:00
import exh.syDebugVersion
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
2021-11-13 23:43:40 +01:00
import logcat.LogPriority
import logcat.LogcatLogger
import org.conscrypt.Conscrypt
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
2020-02-22 04:58:19 +01:00
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.security.Security
2020-11-11 23:28:09 +01:00
import java.text.SimpleDateFormat
import java.util.Locale
2021-12-25 18:05:06 +01:00
import kotlin.time.Duration.Companion.days
open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
private val preferences: PreferencesHelper by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver()
override fun onCreate() {
super<Application>.onCreate()
// if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
2019-04-14 18:48:59 +02:00
setupExhLogging() // EXH logging
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
2020-08-24 03:57:06 +02:00
if (!BuildConfig.DEBUG) addAnalytics()
// TLS 1.3 support for Android < 10
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Security.insertProviderAt(Conscrypt.newProvider(), 1)
}
// Avoid potential crashes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = getProcessName()
if (packageName != process) WebView.setDataDirectorySuffix(process)
}
Injekt.importModule(AppModule(this))
setupNotificationChannels()
2020-05-04 00:34:46 +02:00
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
setupDebugOverlay()
}
2020-02-22 04:58:19 +01:00
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
// Show notification to disable Incognito Mode when it's enabled
preferences.incognitoMode().asFlow()
.onEach { enabled ->
val notificationManager = NotificationManagerCompat.from(this)
if (enabled) {
disableIncognitoReceiver.register()
val notification = notification(Notifications.CHANNEL_INCOGNITO_MODE) {
setContentTitle(getString(R.string.pref_incognito_mode))
setContentText(getString(R.string.notification_incognito_text))
setSmallIcon(R.drawable.ic_glasses_24dp)
setOngoing(true)
val pendingIntent = PendingIntent.getBroadcast(
this@App,
0,
Intent(ACTION_DISABLE_INCOGNITO_MODE),
PendingIntent.FLAG_ONE_SHOT
)
setContentIntent(pendingIntent)
}
notificationManager.notify(Notifications.ID_INCOGNITO_MODE, notification)
} else {
disableIncognitoReceiver.unregister()
notificationManager.cancel(Notifications.ID_INCOGNITO_MODE)
}
}
.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
preferences.themeMode()
.asImmediateFlow {
AppCompatDelegate.setDefaultNightMode(
when (it) {
PreferenceValues.ThemeMode.light -> AppCompatDelegate.MODE_NIGHT_NO
PreferenceValues.ThemeMode.dark -> AppCompatDelegate.MODE_NIGHT_YES
PreferenceValues.ThemeMode.system -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
)
}.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
/*if (!LogcatLogger.isInstalled && preferences.verboseLogging()) {
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
}*/
}
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this).apply {
val callFactoryInit = { Injekt.get<NetworkHelper>().client }
val diskCacheInit = { CoilDiskCache.get(this@App) }
components {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
add(TachiyomiImageDecoder.Factory())
add(MangaCoverFetcher.Factory(lazy(callFactoryInit), lazy(diskCacheInit)))
add(MangaCoverKeyer())
}
callFactory(callFactoryInit)
diskCache(diskCacheInit)
crossfade((300 * this@App.animatorDurationScale).toInt())
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
if (preferences.verboseLogging()) logger(DebugLogger())
}.build()
}
2020-08-24 03:57:06 +02:00
private fun addAnalytics() {
if (syDebugVersion != "0") {
Firebase.analytics.setUserProperty("preview_version", syDebugVersion)
2020-08-24 03:57:06 +02:00
}
}
override fun onStop(owner: LifecycleOwner) {
if (!AuthenticatorUtil.isAuthenticating && preferences.lockAppAfter().get() >= 0) {
2020-02-22 19:30:36 +01:00
SecureActivityDelegate.locked = true
}
}
override fun getPackageName(): String {
try {
// Override the value passed as X-Requested-With in WebView requests
val stackTrace = Thread.currentThread().stackTrace
for (element in stackTrace) {
if ("org.chromium.base.BuildInfo".equals(element.className, ignoreCase = true)) {
if ("getAll".equals(element.methodName, ignoreCase = true)) {
return WebViewUtil.SPOOF_PACKAGE_NAME
}
break
}
}
} catch (e: Exception) {
}
return super.getPackageName()
}
protected open fun setupNotificationChannels() {
try {
Notifications.createChannels(this)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
}
}
2019-04-14 05:47:57 +02:00
// EXH
private fun setupExhLogging() {
2019-04-14 18:48:59 +02:00
EHLogLevel.init(this)
val logLevel = when {
EHLogLevel.shouldLog(EHLogLevel.EXTREME) -> LogLevel.ALL
EHLogLevel.shouldLog(EHLogLevel.EXTRA) || BuildConfig.DEBUG -> LogLevel.DEBUG
else -> LogLevel.WARN
2019-04-14 05:47:57 +02:00
}
val logConfig = LogConfiguration.Builder()
2020-05-04 00:34:46 +02:00
.logLevel(logLevel)
2020-11-25 21:57:05 +01:00
.disableStackTrace()
.disableBorder()
2020-05-04 00:34:46 +02:00
.build()
2019-04-14 05:47:57 +02:00
val printers = mutableListOf<Printer>(AndroidPrinter())
2020-05-04 00:34:46 +02:00
val logFolder = File(
Environment.getExternalStorageDirectory().absolutePath + File.separator +
getString(R.string.app_name),
"logs"
)
2019-04-14 05:47:57 +02:00
2020-11-11 23:28:09 +01:00
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
printers += EnhancedFilePrinter
.Builder(logFolder.absolutePath) {
fileNameGenerator = object : DateFileNameGenerator() {
override fun generateFileName(logLevel: Int, timestamp: Long): String {
2020-11-11 23:28:09 +01:00
return super.generateFileName(
logLevel,
timestamp
) + "-${BuildConfig.BUILD_TYPE}.log"
}
2020-05-04 00:34:46 +02:00
}
flattener { timeMillis, level, tag, message ->
"${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(level)}/$tag: $message"
}
2021-06-02 06:10:36 +02:00
cleanStrategy = FileLastModifiedCleanStrategy(7.days.inWholeMilliseconds)
backupStrategy = NeverBackupStrategy()
2020-11-11 23:28:09 +01:00
}
2019-04-14 05:47:57 +02:00
// Install Crashlytics in prod
2020-05-04 00:34:46 +02:00
if (!BuildConfig.DEBUG) {
2019-04-14 05:47:57 +02:00
printers += CrashlyticsPrinter(LogLevel.ERROR)
}
XLog.init(
2020-05-04 00:34:46 +02:00
logConfig,
*printers.toTypedArray()
2019-04-14 05:47:57 +02:00
)
xLogD("Application booting...")
xLogD(
"""
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})
Preview build: $syDebugVersion
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
Android build ID: ${Build.DISPLAY}
Device brand: ${Build.BRAND}
Device manufacturer: ${Build.MANUFACTURER}
Device name: ${Build.DEVICE}
Device model: ${Build.MODEL}
Device product name: ${Build.PRODUCT}
""".trimIndent()
2020-08-22 02:26:56 +02:00
)
2019-04-14 05:47:57 +02:00
}
// EXH
private fun setupDebugOverlay() {
try {
DebugOverlay.Builder(this)
2020-05-04 00:34:46 +02:00
.modules(FpsModule(), EHDebugModeOverlay(this))
.bgColor(Color.parseColor("#7F000000"))
.notification(false)
.allowSystemLayer(false)
.build()
.install()
} catch (e: IllegalStateException) {
// Crashes if app is in background
xLogE("Failed to initialize debug overlay, app in background?", e)
}
}
private inner class DisableIncognitoReceiver : BroadcastReceiver() {
private var registered = false
override fun onReceive(context: Context, intent: Intent) {
preferences.incognitoMode().set(false)
}
fun register() {
if (!registered) {
registerReceiver(this, IntentFilter(ACTION_DISABLE_INCOGNITO_MODE))
registered = true
}
}
fun unregister() {
if (registered) {
unregisterReceiver(this)
registered = false
}
}
}
}
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"
/**
* Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it.
*/
internal object CoilDiskCache {
private const val FOLDER_NAME = "image_cache"
private var instance: DiskCache? = null
@Synchronized
fun get(context: Context): DiskCache {
return instance ?: run {
val safeCacheDir = context.cacheDir.apply { mkdirs() }
// Create the singleton disk cache instance.
DiskCache.Builder()
.directory(safeCacheDir.resolve(FOLDER_NAME))
.build()
.also { instance = it }
}
}
}