diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/BatteryOptimizationBanner.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/BatteryOptimizationBanner.kt index 19f63cf20d..d2dda01659 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/BatteryOptimizationBanner.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/BatteryOptimizationBanner.kt @@ -7,13 +7,13 @@ package io.element.android.features.roomlist.impl.components -import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.element.android.libraries.designsystem.components.Announcement import io.element.android.libraries.designsystem.components.AnnouncementType import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents import io.element.android.libraries.push.api.battery.BatteryOptimizationState import io.element.android.libraries.push.api.battery.aBatteryOptimizationState @@ -22,7 +22,6 @@ internal fun BatteryOptimizationBanner( state: BatteryOptimizationState, modifier: Modifier = Modifier, ) { - val activity = LocalActivity.current Announcement( modifier = modifier.roomListBannerPadding(), // TODO Localazy @@ -30,8 +29,8 @@ internal fun BatteryOptimizationBanner( description = "To be sure to receive all the notifications, it can help to disable the battery optimization for this application.", type = AnnouncementType.Actionable( actionText = "Yes, disable", - onActionClick = { state.openSettings(activity) }, - onDismissClick = state.dismiss, + onActionClick = { state.eventSink(BatteryOptimizationEvents.DoAction) }, + onDismissClick = { state.eventSink(BatteryOptimizationEvents.Dismiss) }, ), ) } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt new file mode 100644 index 0000000000..2ee8022827 --- /dev/null +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.api.battery + +sealed interface BatteryOptimizationEvents { + data object Dismiss : BatteryOptimizationEvents + data object DoAction : BatteryOptimizationEvents +} diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt index 42e65c2bcb..7e7c2b2bd6 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt @@ -7,10 +7,7 @@ package io.element.android.libraries.push.api.battery -import android.app.Activity - data class BatteryOptimizationState( val shouldDisplayBanner: Boolean, - val dismiss: () -> Unit, - val openSettings: (Activity?) -> Unit, + val eventSink: (BatteryOptimizationEvents) -> Unit, ) diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt index da1639ed7a..d6d4e5774b 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt @@ -7,14 +7,10 @@ package io.element.android.libraries.push.api.battery -import android.app.Activity - fun aBatteryOptimizationState( shouldDisplayBanner: Boolean = false, - dismiss: () -> Unit = {}, - openSettings: (Activity?) -> Unit = {}, + eventSink: (BatteryOptimizationEvents) -> Unit = {}, ) = BatteryOptimizationState( shouldDisplayBanner = shouldDisplayBanner, - dismiss = dismiss, - openSettings = openSettings, + eventSink = eventSink, ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt index dd114aed4b..8adcbb2572 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt @@ -8,7 +8,6 @@ package io.element.android.libraries.push.impl.battery import android.annotation.SuppressLint -import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -19,6 +18,7 @@ import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher import timber.log.Timber import javax.inject.Inject @@ -39,16 +39,16 @@ interface BatteryOptimization { * This will open the system settings where the user can disable battery optimizations. * See https://developer.android.com/training/monitoring-device-state/doze-standby#exemption-cases * - * @param activity The activity from which to start the settings intent. * @return true if the intent was successfully started, false if the activity was not found */ - fun requestDisablingBatteryOptimization(activity: Activity?): Boolean + fun requestDisablingBatteryOptimization(): Boolean } @ContributesBinding(AppScope::class) class AndroidBatteryOptimization @Inject constructor( @ApplicationContext private val context: Context, + private val externalIntentLauncher: ExternalIntentLauncher, ) : BatteryOptimization { override fun isIgnoringBatteryOptimizations(): Boolean { return context.getSystemService() @@ -56,13 +56,12 @@ class AndroidBatteryOptimization @Inject constructor( } @SuppressLint("BatteryLife") - override fun requestDisablingBatteryOptimization(activity: Activity?): Boolean { - activity ?: return false + override fun requestDisablingBatteryOptimization(): Boolean { val intent = Intent() intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent.data = ("package:" + context.packageName).toUri() return try { - activity.startActivity(intent) + externalIntentLauncher.launch(intent) true } catch (exception: ActivityNotFoundException) { Timber.w(exception, "Cannot request ignoring battery optimizations.") diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt index 46bd00ab3a..857b820ad4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.lifecycle.compose.LifecycleResumeEffect import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents import io.element.android.libraries.push.api.battery.BatteryOptimizationState import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore import io.element.android.libraries.push.impl.store.PushDataStore @@ -45,22 +46,26 @@ class BatteryOptimizationPresenter @Inject constructor( onPauseOrDispose {} } - return BatteryOptimizationState( - shouldDisplayBanner = localShouldDisplayBanner && storeShouldDisplayBanner && !isSystemIgnoringBatteryOptimizations, - dismiss = { - coroutineScope.launch { + fun handleEvents(event: BatteryOptimizationEvents) { + when (event) { + BatteryOptimizationEvents.Dismiss -> coroutineScope.launch { mutableBatteryOptimizationStore.onOptimizationBannerDismissed() } - }, - openSettings = { activity -> - isRequestSent = true - if (batteryOptimization.requestDisablingBatteryOptimization(activity).not()) { - // If not able to perform the request, ensure that we do not display the banner again - coroutineScope.launch { - mutableBatteryOptimizationStore.onOptimizationBannerDismissed() + BatteryOptimizationEvents.DoAction -> { + isRequestSent = true + if (batteryOptimization.requestDisablingBatteryOptimization().not()) { + // If not able to perform the request, ensure that we do not display the banner again + coroutineScope.launch { + mutableBatteryOptimizationStore.onOptimizationBannerDismissed() + } } } } + } + + return BatteryOptimizationState( + shouldDisplayBanner = localShouldDisplayBanner && storeShouldDisplayBanner && !isSystemIgnoringBatteryOptimizations, + eventSink = ::handleEvents, ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt index 6a31b399f3..7188aecaa3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.push.impl.battery import androidx.lifecycle.Lifecycle import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.push.api.battery.BatteryOptimizationEvents import io.element.android.libraries.push.impl.push.FakeMutableBatteryOptimizationStore import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore import io.element.android.libraries.push.impl.store.InMemoryPushDataStore @@ -96,7 +97,7 @@ class BatteryOptimizationPresenterTest { assertThat(initialState.shouldDisplayBanner).isFalse() val displayedItem = awaitItem() assertThat(displayedItem.shouldDisplayBanner).isTrue() - displayedItem.dismiss() + displayedItem.eventSink(BatteryOptimizationEvents.Dismiss) onOptimizationBannerDismissedResult.assertions().isCalledOnce() } } @@ -122,7 +123,7 @@ class BatteryOptimizationPresenterTest { assertThat(initialState.shouldDisplayBanner).isFalse() val displayedItem = awaitItem() assertThat(displayedItem.shouldDisplayBanner).isTrue() - displayedItem.openSettings(null) + displayedItem.eventSink(BatteryOptimizationEvents.DoAction) requestDisablingBatteryOptimizationResult.assertions().isCalledOnce() onOptimizationBannerDismissedResult.assertions().isCalledOnce() } @@ -148,7 +149,7 @@ class BatteryOptimizationPresenterTest { assertThat(initialState.shouldDisplayBanner).isFalse() val displayedItem = awaitItem() assertThat(displayedItem.shouldDisplayBanner).isTrue() - displayedItem.openSettings(null) + displayedItem.eventSink(BatteryOptimizationEvents.DoAction) requestDisablingBatteryOptimizationResult.assertions().isCalledOnce() batteryOptimization.isIgnoringBatteryOptimizationsResult = true lifeCycleOwner.givenState(Lifecycle.State.RESUMED) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt index 7bf4564bb8..0adb3d2520 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt @@ -7,7 +7,6 @@ package io.element.android.libraries.push.impl.battery -import android.app.Activity import io.element.android.tests.testutils.lambda.lambdaError class FakeBatteryOptimization( @@ -18,7 +17,7 @@ class FakeBatteryOptimization( return isIgnoringBatteryOptimizationsResult } - override fun requestDisablingBatteryOptimization(activity: Activity?): Boolean { + override fun requestDisablingBatteryOptimization(): Boolean { return requestDisablingBatteryOptimizationResult() } }