diff --git a/app/src/main/java/eu/darken/myperm/App.kt b/app/src/main/java/eu/darken/myperm/App.kt index 74b9e5c..60111ab 100644 --- a/app/src/main/java/eu/darken/myperm/App.kt +++ b/app/src/main/java/eu/darken/myperm/App.kt @@ -1,12 +1,21 @@ package eu.darken.myperm import android.app.Application +import android.os.DeadObjectException +import android.os.TransactionTooLargeException import coil.Coil import coil.ImageLoaderFactory import dagger.hilt.android.HiltAndroidApp import eu.darken.myperm.common.debug.autoreport.AutomaticBugReporter -import eu.darken.myperm.common.debug.logging.* +import eu.darken.myperm.common.debug.logging.LogCatLogger +import eu.darken.myperm.common.debug.logging.Logging +import eu.darken.myperm.common.debug.logging.Logging.Priority.WARN +import eu.darken.myperm.common.debug.logging.asLog +import eu.darken.myperm.common.debug.logging.log +import eu.darken.myperm.common.debug.logging.logTag import eu.darken.myperm.common.debug.recording.core.RecorderModule +import eu.darken.myperm.common.error.causes +import eu.darken.myperm.settings.core.GeneralSettings import javax.inject.Inject @HiltAndroidApp @@ -15,6 +24,7 @@ open class App : Application() { @Inject lateinit var bugReporter: AutomaticBugReporter @Inject lateinit var recorderModule: RecorderModule @Inject lateinit var imageLoaderFactory: ImageLoaderFactory + @Inject lateinit var generalSettings: GeneralSettings override fun onCreate() { super.onCreate() @@ -27,9 +37,39 @@ open class App : Application() { Coil.setImageLoader(imageLoaderFactory) + // https://github.com/d4rken-org/permission-pilot/issues/186 + Thread.setDefaultUncaughtExceptionHandler(ipcGuard) + log(TAG) { "onCreate() done! ${Exception().asLog()}" } } + private val ipcGuard = object : Thread.UncaughtExceptionHandler { + + private val defaultHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler() + + override fun uncaughtException(thread: Thread, throwable: Throwable) { + val ipcMessages = setOf( + "Package manager has died", + "DeadSystemException" + ) + val ipcExceptions = setOf( + DeadObjectException::class, + TransactionTooLargeException::class, + ) + val isIpcIssue = ipcMessages.any { it == throwable.message } + || throwable.causes.any { cause -> ipcExceptions.any { it.isInstance(cause) } } + + if (isIpcIssue) { + log(TAG, WARN) { "Crashing due to IPC buffer overflow!" } + if (generalSettings.ipcParallelisation.value == 0) { + log(TAG, WARN) { "Reducing `ipcParallelisation` from AUTO (0) to 1" } + generalSettings.ipcParallelisation.value = 1 + } + } + defaultHandler?.uncaughtException(thread, throwable) + } + } + companion object { internal val TAG = logTag("App") } diff --git a/app/src/main/java/eu/darken/myperm/apps/core/features/InstallerInfo.kt b/app/src/main/java/eu/darken/myperm/apps/core/features/InstallerInfo.kt index 951fc80..63150e9 100644 --- a/app/src/main/java/eu/darken/myperm/apps/core/features/InstallerInfo.kt +++ b/app/src/main/java/eu/darken/myperm/apps/core/features/InstallerInfo.kt @@ -5,6 +5,7 @@ import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.os.Build +import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import eu.darken.myperm.R import eu.darken.myperm.apps.core.Pkg @@ -54,11 +55,13 @@ fun Installed.isSideloaded(): Boolean { suspend fun PackageInfo.getInstallerInfo( ipcFunnel: IPCFunnel, ): InstallerInfo = if (hasApiLevel(Build.VERSION_CODES.R)) { + @Suppress("NewApi") getInstallerInfoApi30(ipcFunnel) } else { getInstallerInfoLegacy(ipcFunnel) } +@RequiresApi(Build.VERSION_CODES.R) private suspend fun PackageInfo.getInstallerInfoApi30(ipcFunnel: IPCFunnel): InstallerInfo { val sourceInfo = try { ipcFunnel.packageManager.getInstallSourceInfo(packageName) diff --git a/app/src/main/java/eu/darken/myperm/common/IPCFunnel.kt b/app/src/main/java/eu/darken/myperm/common/IPCFunnel.kt index 89b8a91..68e4a14 100644 --- a/app/src/main/java/eu/darken/myperm/common/IPCFunnel.kt +++ b/app/src/main/java/eu/darken/myperm/common/IPCFunnel.kt @@ -6,15 +6,20 @@ import android.content.pm.LauncherActivityInfo import android.content.pm.PackageInfo import android.content.pm.PermissionGroupInfo import android.content.pm.PermissionInfo +import android.os.Build import android.os.UserHandle import android.os.UserManager +import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import dagger.hilt.android.qualifiers.ApplicationContext import eu.darken.myperm.apps.core.getPackageInfo2 import eu.darken.myperm.apps.core.getPermissionInfo2 import eu.darken.myperm.apps.core.tryCreateUserHandle +import eu.darken.myperm.common.debug.logging.Logging.Priority.WARN import eu.darken.myperm.common.debug.logging.log +import eu.darken.myperm.common.debug.logging.logTag import eu.darken.myperm.permissions.core.Permission +import eu.darken.myperm.settings.core.GeneralSettings import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import javax.inject.Inject @@ -23,15 +28,24 @@ import javax.inject.Singleton @Singleton class IPCFunnel @Inject constructor( @ApplicationContext private val context: Context, + private val generalSettings: GeneralSettings, ) { - private val execLock = Semaphore( - when { - hasApiLevel(31) -> 3 - hasApiLevel(29) -> 2 - else -> 1 - }.also { log { "IPCFunnel init with parallelization set to $it" } } - ) + private val execLock by lazy { + val parallel = generalSettings.ipcParallelisation.value + Semaphore( + when { + parallel != 0 -> { + log(TAG, WARN) { "AUTO disabled, using $parallel" } + parallel + } + + hasApiLevel(31) -> 3 + hasApiLevel(29) -> 2 + else -> 1 + }.also { log { "IPCFunnel init with parallelization set to $it" } } + ) + } suspend fun execute(block: suspend IPCFunnel.() -> R): R = execLock.withPermit { block(this) } @@ -47,6 +61,7 @@ class IPCFunnel @Inject constructor( service.getInstalledPackages(flags) } + @RequiresApi(Build.VERSION_CODES.R) suspend fun getInstallSourceInfo(packageName: String) = ipcFunnel.execute { service.getInstallSourceInfo(packageName) } @@ -120,4 +135,8 @@ class IPCFunnel @Inject constructor( service.tryCreateUserHandle(handle) } } + + companion object { + private val TAG = logTag("IPCFunnel") + } } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/myperm/settings/core/GeneralSettings.kt b/app/src/main/java/eu/darken/myperm/settings/core/GeneralSettings.kt index 536ad93..398442c 100644 --- a/app/src/main/java/eu/darken/myperm/settings/core/GeneralSettings.kt +++ b/app/src/main/java/eu/darken/myperm/settings/core/GeneralSettings.kt @@ -9,7 +9,11 @@ import eu.darken.myperm.apps.ui.list.AppsFilterOptions import eu.darken.myperm.apps.ui.list.AppsSortOptions import eu.darken.myperm.common.debug.autoreport.DebugSettings import eu.darken.myperm.common.debug.logging.logTag -import eu.darken.myperm.common.preferences.* +import eu.darken.myperm.common.preferences.PreferenceStoreMapper +import eu.darken.myperm.common.preferences.Settings +import eu.darken.myperm.common.preferences.createFlowPreference +import eu.darken.myperm.common.preferences.moshiReader +import eu.darken.myperm.common.preferences.moshiWriter import eu.darken.myperm.permissions.ui.list.PermsFilterOptions import eu.darken.myperm.permissions.ui.list.PermsSortOptions import javax.inject.Inject @@ -51,6 +55,8 @@ class GeneralSettings @Inject constructor( moshiWriter(moshi), ) + val ipcParallelisation = preferences.createFlowPreference("core.ipc.parallelisation", 0) + override val preferenceDataStore: PreferenceDataStore = PreferenceStoreMapper( debugSettings.isAutoReportingEnabled )