From 706a7f9c8ff2e5d3af6661f653f9bee28e27d25e Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Sun, 30 Mar 2025 21:08:19 +0200 Subject: [PATCH] fix crash when there are 2 or more unifiedpush distributors (#5015) `UnifiedPush.registerAppWithDialog` will show a dialog when there are 2 or more distributors, for that it needs an activity context but we passed an application one. I think this is improved in a newer Unified push library version, will upgrade soon. In the meantime this change fixes the crash. ``` android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running? at android.view.ViewRootImpl.setView(ViewRootImpl.java:1314) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:421) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:149) at android.app.Dialog.show(Dialog.java:352) at org.unifiedpush.android.connector.UnifiedPush.registerAppWithDialog(UnifiedPush.kt:139) at org.unifiedpush.android.connector.UnifiedPush.registerAppWithDialog$default(UnifiedPush.kt:67) at com.keylesspalace.tusky.components.systemnotifications.NotificationService.setupPushNotificationsForAccount(NotificationService.kt:775) at com.keylesspalace.tusky.components.systemnotifications.NotificationService.access$setupPushNotificationsForAccount(NotificationService.kt:76) at com.keylesspalace.tusky.components.systemnotifications.NotificationService$setupPushNotificationsForAccount$1.invokeSuspend(Unknown Source:15) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7932) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942) ``` --- .../com/keylesspalace/tusky/MainActivity.kt | 4 +++- .../com/keylesspalace/tusky/MainViewModel.kt | 14 ++++--------- .../NotificationService.kt | 21 +++++++------------ 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 65deb667d..99f98dcf7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -184,7 +184,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { private val requestNotificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { - viewModel.setupNotifications() + viewModel.setupNotifications(this) } } @@ -213,6 +213,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED ) { requestNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } else { + viewModel.setupNotifications(this) } if (explodeAnimationWasRequested()) { diff --git a/app/src/main/java/com/keylesspalace/tusky/MainViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/MainViewModel.kt index 696db0d7c..0d4865754 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainViewModel.kt @@ -27,7 +27,6 @@ import com.keylesspalace.tusky.appstore.NewNotificationsEvent import com.keylesspalace.tusky.appstore.NotificationsLoadingEvent import com.keylesspalace.tusky.components.systemnotifications.NotificationService import com.keylesspalace.tusky.db.AccountManager -import com.keylesspalace.tusky.db.entity.AccountEntity import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.entity.Status @@ -97,8 +96,6 @@ class MainViewModel @Inject constructor( accountManager.updateAccount(activeAccount, userInfo) shareShortcutHelper.updateShortcuts() - - setupNotifications(activeAccount) }, { throwable -> Log.e(TAG, "Failed to fetch user info.", throwable) @@ -161,19 +158,16 @@ class MainViewModel @Inject constructor( } } - fun setupNotifications(account: AccountEntity? = null) { + fun setupNotifications(activity: MainActivity) { // TODO this is only called on full app (re) start; so changes in-between (push distributor uninstalled/subscription changed, or // notifications fully disabled) will get unnoticed; and also an app restart cannot be easily triggered by the user. - if (account != null) { - // TODO it's quite odd to separate channel creation (for an account) from the "is enabled by channels" question below - - notificationService.createNotificationChannelsForAccount(account) - } + // TODO it's quite odd to separate channel creation (for an account) from the "is enabled by channels" question below + notificationService.createNotificationChannelsForAccount(activeAccount) if (notificationService.areNotificationsEnabledBySystem()) { viewModelScope.launch { - notificationService.setupNotifications(account) + notificationService.setupNotifications(activity) } } else { viewModelScope.launch { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationService.kt b/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationService.kt index b4ccdf9fc..4282bcec9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/systemnotifications/NotificationService.kt @@ -1,6 +1,7 @@ package com.keylesspalace.tusky.components.systemnotifications import android.Manifest +import android.app.Activity import android.app.NotificationChannel import android.app.NotificationChannelGroup import android.app.NotificationManager @@ -111,11 +112,11 @@ class NotificationService @Inject constructor( } } - suspend fun setupNotifications(account: AccountEntity?) { + suspend fun setupNotifications(activity: Activity) { resetPushWhenDistributorIsMissing() if (arePushNotificationsAvailable()) { - setupPushNotifications(account) + setupPushNotifications(activity) } // At least as a fallback and otherwise as main source when there are no push distributors installed: @@ -731,20 +732,14 @@ class NotificationService @Inject constructor( fun arePushNotificationsAvailable(): Boolean = UnifiedPush.getDistributors(context).isNotEmpty() - private suspend fun setupPushNotifications(account: AccountEntity?) { - val relevantAccounts: List = if (account != null) { - listOf(account) - } else { - accountManager.accounts - } - - relevantAccounts.forEach { + private suspend fun setupPushNotifications(activity: Activity) { + accountManager.accounts.forEach { val notificationGroupEnabled = Build.VERSION.SDK_INT < 28 || notificationManager.getNotificationChannelGroup(it.identifier)?.isBlocked == false val shouldEnable = it.notificationsEnabled && notificationGroupEnabled if (shouldEnable) { - setupPushNotificationsForAccount(it) + setupPushNotificationsForAccount(activity, it) Log.d(TAG, "Enabled push notifications for account ${it.id}.") } else { disablePushNotificationsForAccount(it) @@ -753,7 +748,7 @@ class NotificationService @Inject constructor( } } - private suspend fun setupPushNotificationsForAccount(account: AccountEntity) { + private suspend fun setupPushNotificationsForAccount(activity: Activity, account: AccountEntity) { val currentSubscription = getActiveSubscription(account) if (currentSubscription != null) { @@ -772,7 +767,7 @@ class NotificationService @Inject constructor( // make sure this is done in any inconsistent case (is not too often and doesn't hurt). unregisterPushEndpoint(account) - UnifiedPush.registerAppWithDialog(context, account.id.toString(), features = arrayListOf(UnifiedPush.FEATURE_BYTES_MESSAGE)) + UnifiedPush.registerAppWithDialog(activity, account.id.toString(), features = arrayListOf(UnifiedPush.FEATURE_BYTES_MESSAGE)) // Will lead to call of registerPushEndpoint() } }