From f916e4e3d4e991eeeb5980b48fb2b2ff4dec00d6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 16 Apr 2025 16:37:32 +0200 Subject: [PATCH] Push: improve Push history screen, log and stored data (#4601) * Add adb tools to help with doze mode and app standby * Add info about the device state when an error occurs in push. * Keep more events in the DB. * Push history: add confirmation dialog when resetting the data * Push history: add a filter to see only the errors * Update screenshots * Push history: print out invalid/ignored data received. * Increase log level for push, to make such log more visible. It also appears that sometimes Timber.d are not present in the rageshakes. * Log priority * Do not include device state for invalid/ignored event. * Fix tests. * Fix format issue. * Fix mistake in code blocks and do not filter when not necessary. * Improve formatting and add missing unit test. * Reduce nesting of blocks. --------- Co-authored-by: ElementBot --- .../impl/history/DefaultPushHistoryService.kt | 34 ++++++++- .../push/impl/history/PushHistoryService.kt | 9 ++- .../push/impl/push/DefaultPushHandler.kt | 4 +- .../impl/history/FakePushHistoryService.kt | 5 +- .../push/impl/push/DefaultPushHandlerTest.kt | 28 ++++---- .../libraries/push/test/FakePushService.kt | 2 +- .../push/test/test/FakePushHandler.kt | 6 +- .../pushproviders/api/PushHandler.kt | 1 + .../VectorFirebaseMessagingService.kt | 7 +- .../VectorFirebaseMessagingServiceTest.kt | 15 +++- .../VectorUnifiedPushMessagingReceiver.kt | 7 +- .../VectorUnifiedPushMessagingReceiverTest.kt | 2 +- .../impl/history/PushHistoryEvents.kt | 4 +- .../impl/history/PushHistoryPresenter.kt | 36 ++++++++-- .../impl/history/PushHistoryState.kt | 3 + .../impl/history/PushHistoryStateProvider.kt | 8 +++ .../impl/history/PushHistoryView.kt | 70 +++++++++++++++++-- .../impl/history/PushHistoryPresenterTest.kt | 62 ++++++++++++++-- .../impl/history/PushHistoryViewTest.kt | 35 +++++++++- ....impl.history_PushHistoryView_Day_0_en.png | 4 +- ....impl.history_PushHistoryView_Day_1_en.png | 4 +- ....impl.history_PushHistoryView_Day_2_en.png | 3 + ...mpl.history_PushHistoryView_Night_0_en.png | 4 +- ...mpl.history_PushHistoryView_Night_1_en.png | 4 +- ...mpl.history_PushHistoryView_Night_2_en.png | 3 + tools/adb/disable_app_standby.sh | 18 +++++ tools/adb/disable_doze_mode.sh | 16 +++++ tools/adb/enable_app_standby.sh | 18 +++++ tools/adb/enable_doze_mode.sh | 16 +++++ tools/adb/print_device_state.sh | 18 +++++ 30 files changed, 388 insertions(+), 58 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en.png create mode 100755 tools/adb/disable_app_standby.sh create mode 100755 tools/adb/disable_doze_mode.sh create mode 100755 tools/adb/enable_app_standby.sh create mode 100755 tools/adb/enable_doze_mode.sh create mode 100755 tools/adb/print_device_state.sh diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt index ba0fe826eb..23ae48ea31 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt @@ -7,8 +7,13 @@ package io.element.android.libraries.push.impl.history +import android.content.Context +import android.os.Build +import android.os.PowerManager +import androidx.core.content.getSystemService import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId @@ -21,15 +26,38 @@ import javax.inject.Inject class DefaultPushHistoryService @Inject constructor( private val pushDatabase: PushDatabase, private val systemClock: SystemClock, + @ApplicationContext context: Context, ) : PushHistoryService { + private val powerManager = context.getSystemService() + private val packageName = context.packageName + override fun onPushReceived( providerInfo: String, eventId: EventId?, roomId: RoomId?, sessionId: SessionId?, hasBeenResolved: Boolean, + includeDeviceState: Boolean, comment: String?, ) { + val finalComment = buildString { + append(comment.orEmpty()) + if (includeDeviceState && powerManager != null) { + // Add info about device state + append("\n") + append(" - Idle: ${powerManager.isDeviceIdleMode}\n") + append(" - Power Save Mode: ${powerManager.isPowerSaveMode}\n") + append(" - Ignoring Battery Optimizations: ${powerManager.isIgnoringBatteryOptimizations(packageName)}\n") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + append(" - Device Light Idle Mode: ${powerManager.isDeviceLightIdleMode}\n") + append(" - Low Power Standby Enabled: ${powerManager.isLowPowerStandbyEnabled}\n") + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + append(" - Exempt from Low Power Standby: ${powerManager.isExemptFromLowPowerStandby}\n") + } + } + }.takeIf { it.isNotEmpty() } + pushDatabase.pushHistoryQueries.insertPushHistory( PushHistory( pushDate = systemClock.epochMillis(), @@ -38,11 +66,11 @@ class DefaultPushHistoryService @Inject constructor( roomId = roomId?.value, sessionId = sessionId?.value, hasBeenResolved = if (hasBeenResolved) 1 else 0, - comment = comment, + comment = finalComment, ) ) - // Keep only the last 100 events - pushDatabase.pushHistoryQueries.removeOldest(100) + // Keep only the last 1_000 events + pushDatabase.pushHistoryQueries.removeOldest(1_000) } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt index b66fd0317b..31d431bb2c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt @@ -22,19 +22,22 @@ interface PushHistoryService { roomId: RoomId?, sessionId: SessionId?, hasBeenResolved: Boolean, + includeDeviceState: Boolean, comment: String?, ) } fun PushHistoryService.onInvalidPushReceived( providerInfo: String, + data: String, ) = onPushReceived( providerInfo = providerInfo, eventId = null, roomId = null, sessionId = null, hasBeenResolved = false, - comment = "Invalid push data", + includeDeviceState = false, + comment = "Invalid or ignored push data:\n$data", ) fun PushHistoryService.onUnableToRetrieveSession( @@ -48,6 +51,7 @@ fun PushHistoryService.onUnableToRetrieveSession( roomId = roomId, sessionId = null, hasBeenResolved = false, + includeDeviceState = true, comment = "Unable to retrieve session: $reason", ) @@ -63,6 +67,7 @@ fun PushHistoryService.onUnableToResolveEvent( roomId = roomId, sessionId = sessionId, hasBeenResolved = false, + includeDeviceState = true, comment = "Unable to resolve event: $reason", ) @@ -78,6 +83,7 @@ fun PushHistoryService.onSuccess( roomId = roomId, sessionId = sessionId, hasBeenResolved = true, + includeDeviceState = false, comment = buildString { append("Success") if (comment.isNullOrBlank().not()) { @@ -94,5 +100,6 @@ fun PushHistoryService.onDiagnosticPush( roomId = null, sessionId = null, hasBeenResolved = true, + includeDeviceState = false, comment = "Diagnostic push", ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index 697ff67759..1c7de16505 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -72,9 +72,9 @@ class DefaultPushHandler @Inject constructor( } } - override suspend fun handleInvalid(providerInfo: String) { + override suspend fun handleInvalid(providerInfo: String, data: String) { incrementPushDataStore.incrementPushCounter() - pushHistoryService.onInvalidPushReceived(providerInfo) + pushHistoryService.onInvalidPushReceived(providerInfo, data) } /** diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt index a88ab7ccc4..50a06756dc 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt @@ -19,8 +19,9 @@ class FakePushHistoryService( RoomId?, SessionId?, Boolean, + Boolean, String? - ) -> Unit = { _, _, _, _, _, _ -> lambdaError() } + ) -> Unit = { _, _, _, _, _, _, _ -> lambdaError() } ) : PushHistoryService { override fun onPushReceived( providerInfo: String, @@ -28,6 +29,7 @@ class FakePushHistoryService( roomId: RoomId?, sessionId: SessionId?, hasBeenResolved: Boolean, + includeDeviceState: Boolean, comment: String?, ) { onPushReceivedResult( @@ -36,6 +38,7 @@ class FakePushHistoryService( roomId, sessionId, hasBeenResolved, + includeDeviceState, comment ) } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt index 4df8cd2ecd..77b71c0c69 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt @@ -60,7 +60,7 @@ class DefaultPushHandlerTest { @Test fun `check handleInvalid behavior`() = runTest { val incrementPushCounterResult = lambdaRecorder {} - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -68,12 +68,12 @@ class DefaultPushHandlerTest { incrementPushCounterResult = incrementPushCounterResult, pushHistoryService = pushHistoryService, ) - defaultPushHandler.handleInvalid(A_PUSHER_INFO) + defaultPushHandler.handleInvalid(A_PUSHER_INFO, "data") incrementPushCounterResult.assertions() .isCalledOnce() onPushReceivedResult.assertions() .isCalledOnce() - .with(value(A_PUSHER_INFO), value(null), value(null), value(null), value(false), value("Invalid push data")) + .with(value(A_PUSHER_INFO), value(null), value(null), value(null), value(false), value(false), value("Invalid or ignored push data:\ndata")) } @Test @@ -85,7 +85,7 @@ class DefaultPushHandlerTest { } val onNotifiableEventReceived = lambdaRecorder {} val incrementPushCounterResult = lambdaRecorder {} - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -133,7 +133,7 @@ class DefaultPushHandlerTest { unread = 0, clientSecret = A_SECRET, ) - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -176,7 +176,7 @@ class DefaultPushHandlerTest { unread = 0, clientSecret = A_SECRET, ) - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -221,7 +221,7 @@ class DefaultPushHandlerTest { unread = 0, clientSecret = A_SECRET, ) - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -263,7 +263,7 @@ class DefaultPushHandlerTest { unread = 0, clientSecret = A_SECRET, ) - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -290,7 +290,7 @@ class DefaultPushHandlerTest { .isNeverCalled() onPushReceivedResult.assertions() .isCalledOnce() - .with(any(), value(AN_EVENT_ID), value(A_ROOM_ID), value(A_USER_ID), value(false), any()) + .with(any(), value(AN_EVENT_ID), value(A_ROOM_ID), value(A_USER_ID), value(false), value(true), any()) } @Test @@ -314,7 +314,7 @@ class DefaultPushHandlerTest { > { _, _, _, _, _, _, _, _ -> } val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda) val onNotifiableEventReceived = lambdaRecorder {} - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -359,7 +359,7 @@ class DefaultPushHandlerTest { Unit, > { _, _, _, _, _, _, _, _ -> } val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda) - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -403,7 +403,7 @@ class DefaultPushHandlerTest { Unit, > { _, _, _, _, _, _, _, _ -> } val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda) - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -444,7 +444,7 @@ class DefaultPushHandlerTest { ) val onRedactedEventReceived = lambdaRecorder { } val incrementPushCounterResult = lambdaRecorder {} - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) @@ -476,7 +476,7 @@ class DefaultPushHandlerTest { clientSecret = A_SECRET, ) val diagnosticPushHandler = DiagnosticPushHandler() - val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _ -> } + val onPushReceivedResult = lambdaRecorder { _, _, _, _, _, _, _ -> } val pushHistoryService = FakePushHistoryService( onPushReceivedResult = onPushReceivedResult, ) diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt index a875de68de..5c3c01e8e8 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt @@ -89,7 +89,7 @@ class FakePushService( pushCounterFlow.value = counter } - override suspend fun resetPushHistory() { + override suspend fun resetPushHistory() = simulateLongTask { resetPushHistoryResult() } } diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt index 933790d3e9..1279d6e5a0 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt @@ -13,13 +13,13 @@ import io.element.android.tests.testutils.lambda.lambdaError class FakePushHandler( private val handleResult: (PushData, String) -> Unit = { _, _ -> lambdaError() }, - private val handleInvalidResult: (String) -> Unit = { lambdaError() }, + private val handleInvalidResult: (String, String) -> Unit = { _, _ -> lambdaError() }, ) : PushHandler { override suspend fun handle(pushData: PushData, providerInfo: String) { handleResult(pushData, providerInfo) } - override suspend fun handleInvalid(providerInfo: String) { - handleInvalidResult(providerInfo) + override suspend fun handleInvalid(providerInfo: String, data: String) { + handleInvalidResult(providerInfo, data) } } diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt index 46dca54f5b..a9dfd76c2c 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt @@ -15,5 +15,6 @@ interface PushHandler { suspend fun handleInvalid( providerInfo: String, + data: String, ) } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt index 3fd2e77f22..d7c7777168 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt @@ -31,20 +31,23 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { } override fun onNewToken(token: String) { - Timber.tag(loggerTag.value).d("New Firebase token") + Timber.tag(loggerTag.value).w("New Firebase token") coroutineScope.launch { firebaseNewTokenHandler.handle(token) } } override fun onMessageReceived(message: RemoteMessage) { - Timber.tag(loggerTag.value).d("New Firebase message") + Timber.tag(loggerTag.value).w("New Firebase message. Priority: ${message.priority}/${message.originalPriority}") coroutineScope.launch { val pushData = pushParser.parse(message.data) if (pushData == null) { Timber.tag(loggerTag.value).w("Invalid data received from Firebase") pushHandler.handleInvalid( providerInfo = FirebaseConfig.NAME, + data = message.data.keys.joinToString("\n") { + "$it: ${message.data[it]}" + }, ) } else { pushHandler.handle( diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt index f47b614619..6b8c19934f 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt @@ -32,13 +32,24 @@ import org.robolectric.RobolectricTestRunner class VectorFirebaseMessagingServiceTest { @Test fun `test receiving invalid data`() = runTest { - val lambda = lambdaRecorder {} + val lambda = lambdaRecorder { _, _ -> } val vectorFirebaseMessagingService = createVectorFirebaseMessagingService( pushHandler = FakePushHandler(handleInvalidResult = lambda) ) - vectorFirebaseMessagingService.onMessageReceived(RemoteMessage(Bundle())) + vectorFirebaseMessagingService.onMessageReceived( + message = RemoteMessage( + Bundle().apply { + putString("a", "A") + putString("b", "B") + } + ) + ) runCurrent() lambda.assertions().isCalledOnce() + .with( + value(FirebaseConfig.NAME), + value("a: A\nb: B"), + ) } @Test diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt index 328f5be8c7..684e08be5b 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt @@ -46,13 +46,14 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { * @param instance connection, for multi-account */ override fun onMessage(context: Context, message: ByteArray, instance: String) { - Timber.tag(loggerTag.value).d("New message") + Timber.tag(loggerTag.value).w("New message") coroutineScope.launch { val pushData = pushParser.parse(message, instance) if (pushData == null) { Timber.tag(loggerTag.value).w("Invalid data received from UnifiedPush") pushHandler.handleInvalid( providerInfo = "${UnifiedPushConfig.NAME} - $instance", + data = String(message), ) } else { pushHandler.handle( @@ -68,7 +69,7 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { * You should send the endpoint to your application server and sync for missing notifications. */ override fun onNewEndpoint(context: Context, endpoint: String, instance: String) { - Timber.tag(loggerTag.value).i("onNewEndpoint: $endpoint") + Timber.tag(loggerTag.value).w("onNewEndpoint: $endpoint") coroutineScope.launch { val gateway = unifiedPushGatewayResolver.getGateway(endpoint) .let { gatewayResult -> @@ -109,7 +110,7 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { * Called when this application is unregistered from receiving push messages. */ override fun onUnregistered(context: Context, instance: String) { - Timber.tag(loggerTag.value).d("Unifiedpush: Unregistered") + Timber.tag(loggerTag.value).w("Unifiedpush: Unregistered") /* val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME pushDataStore.setFdroidSyncBackgroundMode(mode) diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt index b445e73fec..bcacdc3d7a 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt @@ -90,7 +90,7 @@ class VectorUnifiedPushMessagingReceiverTest { @Test fun `onMessage invalid invokes the push handler invalid method`() = runTest { val context = InstrumentationRegistry.getInstrumentation().context - val handleInvalidResult = lambdaRecorder { } + val handleInvalidResult = lambdaRecorder { _, _ -> } val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver( pushHandler = FakePushHandler( handleInvalidResult = handleInvalidResult, diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt index 7b6d5f616d..c18a480899 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt @@ -8,5 +8,7 @@ package io.element.android.libraries.troubleshoot.impl.history sealed interface PushHistoryEvents { - data object Reset : PushHistoryEvents + data class SetShowOnlyErrors(val showOnlyErrors: Boolean) : PushHistoryEvents + data class Reset(val requiresConfirmation: Boolean) : PushHistoryEvents + data object ClearDialog : PushHistoryEvents } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt index d7b0592b5b..ec6f2f0abb 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt @@ -10,11 +10,15 @@ package io.element.android.libraries.troubleshoot.impl.history import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.push.api.PushService import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -25,14 +29,36 @@ class PushHistoryPresenter @Inject constructor( override fun present(): PushHistoryState { val coroutineScope = rememberCoroutineScope() val pushCounter by pushService.pushCounter.collectAsState(0) - val pushHistory by remember { - pushService.getPushHistoryItemsFlow() + var showOnlyErrors: Boolean by remember { mutableStateOf(false) } + val pushHistory by remember(showOnlyErrors) { + pushService.getPushHistoryItemsFlow().map { + if (showOnlyErrors) { + it.filter { item -> item.hasBeenResolved.not() } + } else { + it + } + } }.collectAsState(emptyList()) + var resetAction: AsyncAction by remember { mutableStateOf(AsyncAction.Uninitialized) } fun handleEvents(event: PushHistoryEvents) { when (event) { - PushHistoryEvents.Reset -> coroutineScope.launch { - pushService.resetPushHistory() + is PushHistoryEvents.SetShowOnlyErrors -> { + showOnlyErrors = event.showOnlyErrors + } + is PushHistoryEvents.Reset -> { + if (event.requiresConfirmation) { + resetAction = AsyncAction.ConfirmingNoParams + } else { + resetAction = AsyncAction.Loading + coroutineScope.launch { + pushService.resetPushHistory() + resetAction = AsyncAction.Uninitialized + } + } + } + PushHistoryEvents.ClearDialog -> { + resetAction = AsyncAction.Uninitialized } } } @@ -40,6 +66,8 @@ class PushHistoryPresenter @Inject constructor( return PushHistoryState( pushCounter = pushCounter, pushHistoryItems = pushHistory.toImmutableList(), + showOnlyErrors = showOnlyErrors, + resetAction = resetAction, eventSink = ::handleEvents ) } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt index 113f7d0f19..fda9c6e479 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt @@ -7,11 +7,14 @@ package io.element.android.libraries.troubleshoot.impl.history +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.push.api.history.PushHistoryItem import kotlinx.collections.immutable.ImmutableList data class PushHistoryState( val pushCounter: Int, val pushHistoryItems: ImmutableList, + val showOnlyErrors: Boolean, + val resetAction: AsyncAction, val eventSink: (PushHistoryEvents) -> Unit, ) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt index 7482e22ef7..da37700a93 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.troubleshoot.impl.history import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId @@ -36,16 +37,23 @@ open class PushHistoryStateProvider : PreviewParameterProvider ) ) ), + aPushHistoryState( + resetAction = AsyncAction.ConfirmingNoParams, + ), ) } fun aPushHistoryState( pushCounter: Int = 0, pushHistoryItems: List = emptyList(), + showOnlyErrors: Boolean = false, + resetAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (PushHistoryEvents) -> Unit = {}, ) = PushHistoryState( pushCounter = pushCounter, pushHistoryItems = pushHistoryItems.toImmutableList(), + showOnlyErrors = showOnlyErrors, + resetAction = resetAction, eventSink = eventSink, ) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt index 76b5ff2bf0..36340518cb 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt @@ -24,6 +24,10 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -31,16 +35,20 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.DropdownMenu +import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -57,6 +65,8 @@ fun PushHistoryView( onItemClick: (SessionId, RoomId, EventId) -> Unit, modifier: Modifier = Modifier, ) { + var showMenu by remember { mutableStateOf(false) } + Scaffold( modifier = modifier .fillMaxSize() @@ -77,12 +87,42 @@ fun PushHistoryView( ) }, actions = { - TextButton( - text = stringResource(CommonStrings.action_reset), - onClick = { - state.eventSink(PushHistoryEvents.Reset) - }, - ) + IconButton(onClick = { showMenu = !showMenu }) { + Icon( + imageVector = CompoundIcons.OverflowVertical(), + contentDescription = stringResource(id = CommonStrings.a11y_user_menu), + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + ) { + DropdownMenuItem( + text = { Text("Show only errors") }, + trailingIcon = if (state.showOnlyErrors) { + { + Icon( + imageVector = CompoundIcons.CheckCircleSolid(), + contentDescription = null, + modifier = Modifier.size(16.dp), + ) + } + } else { + null + }, + onClick = { + showMenu = false + state.eventSink(PushHistoryEvents.SetShowOnlyErrors(state.showOnlyErrors.not())) + }, + ) + DropdownMenuItem( + text = { Text(stringResource(id = CommonStrings.action_reset)) }, + onClick = { + showMenu = false + state.eventSink(PushHistoryEvents.Reset(requiresConfirmation = true)) + }, + ) + } } ) }, @@ -95,6 +135,22 @@ fun PushHistoryView( onItemClick = onItemClick, ) } + + AsyncActionView( + async = state.resetAction, + onSuccess = {}, + confirmationDialog = { + ConfirmationDialog( + content = "", + title = stringResource(CommonStrings.dialog_title_confirmation), + submitText = stringResource(CommonStrings.action_reset), + cancelText = stringResource(CommonStrings.action_cancel), + onSubmitClick = { state.eventSink(PushHistoryEvents.Reset(requiresConfirmation = false)) }, + onDismiss = { state.eventSink(PushHistoryEvents.ClearDialog) }, + ) + }, + onErrorDismiss = {}, + ) } @Composable diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt index ad1d9c512e..313735b6e6 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt @@ -10,12 +10,12 @@ package io.element.android.libraries.troubleshoot.impl.history import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.test.FakePushService import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.test import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -27,6 +27,8 @@ class PushHistoryPresenterTest { val initialState = awaitItem() assertThat(initialState.pushCounter).isEqualTo(0) assertThat(initialState.pushHistoryItems).isEmpty() + assertThat(initialState.showOnlyErrors).isFalse() + assertThat(initialState.resetAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -49,7 +51,7 @@ class PushHistoryPresenterTest { } @Test - fun `present - reset`() = runTest { + fun `present - reset and cancel`() = runTest { val resetPushHistoryResult = lambdaRecorder { } val pushService = FakePushService( resetPushHistoryResult = resetPushHistoryResult, @@ -59,12 +61,64 @@ class PushHistoryPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(PushHistoryEvents.Reset) - runCurrent() + initialState.eventSink(PushHistoryEvents.Reset(requiresConfirmation = true)) + assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.ConfirmingNoParams) + initialState.eventSink(PushHistoryEvents.ClearDialog) + assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.Uninitialized) + resetPushHistoryResult.assertions().isNeverCalled() + } + } + + @Test + fun `present - reset and confirm`() = runTest { + val resetPushHistoryResult = lambdaRecorder { } + val pushService = FakePushService( + resetPushHistoryResult = resetPushHistoryResult, + ) + val presenter = createPushHistoryPresenter( + pushService = pushService, + ) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(PushHistoryEvents.Reset(requiresConfirmation = true)) + assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.ConfirmingNoParams) + initialState.eventSink(PushHistoryEvents.Reset(requiresConfirmation = false)) + assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.Loading) + assertThat(awaitItem().resetAction).isEqualTo(AsyncAction.Uninitialized) resetPushHistoryResult.assertions().isCalledOnce() } } + @Test + fun `present - set show only errors`() = runTest { + val pushService = FakePushService() + val presenter = createPushHistoryPresenter( + pushService = pushService, + ) + presenter.test { + val initialState = awaitItem() + assertThat(initialState.showOnlyErrors).isFalse() + val item = aPushHistoryItem(hasBeenResolved = true) + val itemError = aPushHistoryItem(hasBeenResolved = false) + pushService.emitPushHistoryItems(listOf(item, itemError)) + awaitItem().let { state -> + assertThat(state.pushHistoryItems).containsExactly(item, itemError) + state.eventSink(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = true)) + } + skipItems(1) + awaitItem().let { state -> + assertThat(state.showOnlyErrors).isTrue() + assertThat(state.pushHistoryItems).containsExactly(itemError) + state.eventSink(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = false)) + } + skipItems(1) + awaitItem().let { state -> + assertThat(state.showOnlyErrors).isFalse() + assertThat(state.pushHistoryItems).containsExactly(item, itemError) + } + } + } + private fun createPushHistoryPresenter( pushService: PushService = FakePushService(), ): PushHistoryPresenter { diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt index fdb10e318f..5c98b2c21a 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.troubleshoot.impl.history import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -46,12 +47,44 @@ class PushHistoryViewTest { eventSink = eventsRecorder, ), ) + val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu) + rule.onNodeWithContentDescription(menuContentDescription).performClick() rule.clickOn(CommonStrings.action_reset) - eventsRecorder.assertSingle(PushHistoryEvents.Reset) + eventsRecorder.assertSingle(PushHistoryEvents.Reset(requiresConfirmation = true)) // Also check that the push counter is rendered rule.onNodeWithText("123").assertExists() } + @Test + fun `clicking on show only errors sends a PushHistoryEvents(true)`() { + val eventsRecorder = EventsRecorder() + rule.setPushHistoryView( + aPushHistoryState( + showOnlyErrors = false, + eventSink = eventsRecorder, + ), + ) + val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu) + rule.onNodeWithContentDescription(menuContentDescription).performClick() + rule.onNodeWithText("Show only errors").performClick() + eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = true)) + } + + @Test + fun `clicking on show only errors sends a PushHistoryEvents(false)`() { + val eventsRecorder = EventsRecorder() + rule.setPushHistoryView( + aPushHistoryState( + showOnlyErrors = true, + eventSink = eventsRecorder, + ), + ) + val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu) + rule.onNodeWithContentDescription(menuContentDescription).performClick() + rule.onNodeWithText("Show only errors").performClick() + eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = false)) + } + @Test fun `clicking on an invalid event has no effect`() { val eventsRecorder = EventsRecorder(expectEvents = false) diff --git a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en.png index fe1f4cb059..f8ec437752 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04cc3a21bceeee5727df5728d34eb7618a5a90949352cd02f4eaa2be77c47492 -size 13565 +oid sha256:673fc1787251cbd1f0d6ea6971c1c7a35b4a990e47adbf5b3e2ac442cf74f809 +size 12656 diff --git a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en.png index a1e58baca5..9afa0c1a04 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a8c0a12277b4335b1d01d3f5f206704b37c4cfb3693ec7d9a26ce15215ba800 -size 45566 +oid sha256:5a778dc2f98f1ad4ee901e4ca0d9b593cb78f44ab67337350349869f4b211a51 +size 44773 diff --git a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en.png new file mode 100644 index 0000000000..3f76ccdc77 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b32c39383aef9ed41ba65109d4ee5e97f057221474aeeb90522c161d3b0c2409 +size 21014 diff --git a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en.png index aa1f275616..8bd1f33ecb 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d877e4d537fa0990dd0f2a5e14f2d00cf8dcd45a4c27fac6c0188ef2b441756 -size 13163 +oid sha256:97c2af47763d65926e407c94901e1b0a691218b1cf152203fc4fd2f396c17e43 +size 12270 diff --git a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en.png index d61baf4fc4..b103e9e094 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f110d1684003cdd1d6e47f765bbf4f55e2b0f3ba732926df08d06297b36bb7b -size 44157 +oid sha256:bfb8a1f9bd060141b0f20490d4066724f34d1560296235f5e45f98de9d3d7aad +size 43310 diff --git a/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en.png new file mode 100644 index 0000000000..909315d240 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af4e8fb83c87caccd084c0321a5e47451c1a8bba3eed1e948e95d6184667f710 +size 19339 diff --git a/tools/adb/disable_app_standby.sh b/tools/adb/disable_app_standby.sh new file mode 100755 index 0000000000..311921aa33 --- /dev/null +++ b/tools/adb/disable_app_standby.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# 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. + +# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_your_app_with_app_standby + +echo " => Standby OFF" + +set -x +package_name="io.element.android.x.debug" +adb shell dumpsys battery reset +adb shell am set-inactive "${package_name}" false +adb shell am get-inactive "${package_name}" + +tools/adb/print_device_state.sh diff --git a/tools/adb/disable_doze_mode.sh b/tools/adb/disable_doze_mode.sh new file mode 100755 index 0000000000..b0f00a76eb --- /dev/null +++ b/tools/adb/disable_doze_mode.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# 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. + +# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_doze + +echo " => Disable doze mode" + +set -x +adb shell dumpsys deviceidle unforce +adb shell dumpsys battery reset + +tools/adb/print_device_state.sh diff --git a/tools/adb/enable_app_standby.sh b/tools/adb/enable_app_standby.sh new file mode 100755 index 0000000000..393bfa14b9 --- /dev/null +++ b/tools/adb/enable_app_standby.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# 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. + +# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_your_app_with_app_standby + +echo " => Standby ON" + +set -x +package_name="io.element.android.x.debug" +adb shell dumpsys battery unplug +adb shell am set-inactive "${package_name}" true +adb shell am get-inactive "${package_name}" + +tools/adb/print_device_state.sh diff --git a/tools/adb/enable_doze_mode.sh b/tools/adb/enable_doze_mode.sh new file mode 100755 index 0000000000..5eb9d23c92 --- /dev/null +++ b/tools/adb/enable_doze_mode.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# 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. + +# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_doze + +echo " => Enable doze mode" + +set -x +adb shell dumpsys battery unplug +adb shell dumpsys deviceidle force-idle + +tools/adb/print_device_state.sh diff --git a/tools/adb/print_device_state.sh b/tools/adb/print_device_state.sh new file mode 100755 index 0000000000..4d0061fe75 --- /dev/null +++ b/tools/adb/print_device_state.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# 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. + +# Ref: https://developer.android.com/training/monitoring-device-state/doze-standby#testing_doze + +echo " => Device state" + +set -x +adb shell dumpsys deviceidle get light +adb shell dumpsys deviceidle get deep +adb shell dumpsys deviceidle get force +adb shell dumpsys deviceidle get screen +adb shell dumpsys deviceidle get charging +adb shell dumpsys deviceidle get network