Files
vpnhotspotmod/mobile/src/main/java/be/mygod/vpnhotspot/App.kt
2023-02-11 15:19:40 -05:00

148 lines
6.6 KiB
Kotlin

package be.mygod.vpnhotspot
import android.annotation.SuppressLint
import android.app.Application
import android.content.ActivityNotFoundException
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.location.LocationManager
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.widget.Toast
import androidx.annotation.Size
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager
import be.mygod.librootkotlinx.NoShellException
import be.mygod.vpnhotspot.net.DhcpWorkaround
import be.mygod.vpnhotspot.room.AppDatabase
import be.mygod.vpnhotspot.root.RootManager
import be.mygod.vpnhotspot.util.DeviceStorageApp
import be.mygod.vpnhotspot.util.Services
import be.mygod.vpnhotspot.widget.SmartSnackbar
import com.google.firebase.analytics.ktx.ParametersBuilder
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.google.firebase.ktx.initialize
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.*
class App : Application() {
companion object {
@SuppressLint("StaticFieldLeak")
lateinit var app: App
}
@SuppressLint("RestrictedApi")
override fun onCreate() {
super.onCreate()
app = this
deviceStorage = DeviceStorageApp(this)
// alternative to PreferenceManager.getDefaultSharedPreferencesName(this)
deviceStorage.moveSharedPreferencesFrom(this, PreferenceManager(this).sharedPreferencesName)
deviceStorage.moveDatabaseFrom(this, AppDatabase.DB_NAME)
BootReceiver.migrateIfNecessary()
Services.init { this }
// overhead of debug mode is minimal: https://github.com/Kotlin/kotlinx.coroutines/blob/f528898/docs/debugging.md#debug-mode
System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
Firebase.initialize(deviceStorage)
when (val codename = Build.VERSION.CODENAME) {
"REL" -> { }
else -> FirebaseCrashlytics.getInstance().apply {
setCustomKey("codename", codename)
setCustomKey("preview_sdk", Build.VERSION.PREVIEW_SDK_INT)
}
}
Timber.plant(object : Timber.DebugTree() {
@SuppressLint("LogNotTimber")
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (t == null) {
if (priority != Log.DEBUG || BuildConfig.DEBUG) Log.println(priority, tag, message)
FirebaseCrashlytics.getInstance().log("${"XXVDIWEF".getOrElse(priority) { 'X' }}/$tag: $message")
} else {
if (priority >= Log.WARN || priority == Log.DEBUG) {
Log.println(priority, tag, message)
Log.w(tag, message, t)
}
if (priority >= Log.INFO && t !is NoShellException) {
FirebaseCrashlytics.getInstance().recordException(t)
}
}
}
})
ServiceNotification.updateNotificationChannels()
if (DhcpWorkaround.shouldEnable) DhcpWorkaround.enable(true)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
ServiceNotification.updateNotificationChannels()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
if (level == TRIM_MEMORY_RUNNING_CRITICAL || level >= TRIM_MEMORY_BACKGROUND) GlobalScope.launch {
RootManager.closeExisting()
}
}
/**
* This method is used to log "expected" and well-handled errors, i.e. we care less about logs, etc.
* logException is inappropriate sometimes because it flushes all logs that could be used to investigate other bugs.
*/
fun logEvent(@Size(min = 1L, max = 40L) event: String, block: ParametersBuilder.() -> Unit = { }) {
val builder = ParametersBuilder()
builder.block()
Timber.i(if (builder.bundle.isEmpty) event else "$event, extras: ${builder.bundle}")
Firebase.analytics.logEvent(event, builder.bundle)
}
/**
* LOH also requires location to be turned on. So does p2p for some reason. Source:
* https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/53e0284/service/java/com/android/server/wifi/WifiServiceImpl.java#1204
* https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/53e0284/service/java/com/android/server/wifi/WifiSettingsStore.java#228
*/
inline fun <reified T> startServiceWithLocation(context: Context) {
if (Build.VERSION.SDK_INT < 33 && location?.isLocationEnabled != true) try {
context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
Toast.makeText(context, R.string.tethering_location_off, Toast.LENGTH_LONG).show()
} catch (e: ActivityNotFoundException) {
app.logEvent("location_settings") { param("message", e.toString()) }
SmartSnackbar.make(R.string.tethering_location_off).show()
} else context.startForegroundService(Intent(context, T::class.java))
}
lateinit var deviceStorage: Application
val english by lazy {
createConfigurationContext(Configuration(resources.configuration).apply {
setLocale(Locale.ENGLISH)
})
}
val pref by lazy { PreferenceManager.getDefaultSharedPreferences(deviceStorage) }
val clipboard by lazy { getSystemService<ClipboardManager>()!! }
val location by lazy { getSystemService<LocationManager>() }
val hasTouch by lazy { packageManager.hasSystemFeature("android.hardware.faketouch") }
val customTabsIntent by lazy {
CustomTabsIntent.Builder().apply {
setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM)
setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_LIGHT, CustomTabColorSchemeParams.Builder().apply {
setToolbarColor(resources.getColor(R.color.light_colorPrimary, theme))
}.build())
setColorSchemeParams(CustomTabsIntent.COLOR_SCHEME_DARK, CustomTabColorSchemeParams.Builder().apply {
setToolbarColor(resources.getColor(R.color.dark_colorPrimary, theme))
}.build())
}.build()
}
}