This adds a feature flag, disabled by default, which disables the 'don't receive push notifications from other accounts used in this device' rule, which is useful in general, but problematic for testing.
Thread notification tags have the form "roomId|threadId". clearReadRoomsNotifications
was building a RoomId from the whole tag, which is only validated in debug builds, so
in release an invalid room id reached RoomListService.room(). Add
NotificationCreator.getMessageNotificationRoomId(), the inverse of messageTag(), which
only keeps the part before '|' and checks it is a valid room id.
Also make RoomListServiceInterface.roomOrNull take a RoomId instead of a String.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The push history was written to the database synchronously, from the app coroutine scope which runs on the main thread, for every received push. Make PushHistoryService.onPushResult suspend and run all the database queries on the IO dispatcher.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The matrixmedia modules were using the `libraries.matrix.ui.media` package
(shared with the matrixui module) and `libraries.matrix.ui.media.*`
namespaces. Use `libraries.matrixmedia.api`, `libraries.matrixmedia.impl`
and `libraries.matrixmedia.test` instead, matching the module paths.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Remove dependencies flagged as unused by the Dependency Analysis Gradle Plugin
(`./gradlew buildHealth`) and explicitly declared in module build files.
Removals were skipped when the dependency provides transitively something the
module (or a consumer of an `api` dependency) uses directly, as well as for
runtime-only dependencies (element-call-embedded, sentry-android,
rustls-platform-verifier). Dependencies added by the convention plugin helpers
(composeDependencies, testCommonDependencies) are left untouched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
detekt's default configuration excludes test source sets from
`TooManyFunctions` and `StringLiteralDuplication`, but not from
`LargeClass`, so large test classes were reported and had to carry a
`@Suppress("LargeClass")` annotation.
Add the same kind of exclusion to the `LargeClass` rule in
`tools/detekt/detekt.yml` and remove the 24 now-redundant suppressions
from test files. The two remaining suppressions are in production code,
where the rule still applies.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Keep the push foreground service alive while notifications are fetched
* Release the push foreground service as soon as results are handed over
---------
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
Sharing from another app listed Element X once, with no way to pick a
conversation: the Direct Share row never showed any.
The app already publishes a long-lived, conversation-categorised shortcut per
room, which is most of what the sharing shortcuts API needs. The part that was
missing is the declaration that ties those shortcuts to the share intent: the
launcher activity had no `android.app.shortcuts` meta-data, so there was no
`<share-target>` for the system to match them against.
It now ships one, matching the activity that already receives ACTION_SEND, and
the shortcuts carry the category it names.
The system offers these from Android 10 onwards, where sharing shortcuts are
handled natively. Reaching further back would additionally need the
androidx.sharetarget library, which is not added here.
Part of #3835
Every @BindingContainer now lives in a di subpackage, in a file named after
it. Containers that were declared inside a shared file (SimplePlayer.kt,
LockScreenConfig.kt, VoiceMessagePresenter.kt, TimelineItemPresenterFactories.kt,
MediaItemPresenterFactories.kt) are extracted into their own file.
Method names are made consistent too:
- @Binds functions are named bind<ConcreteParameterType>, fixing three that
named something else (notably RoomAliasResolverBindingContainer, whose
provider was still called providesJoinRoomPresenterFactory).
- @Provides functions are named after what they provide, and all use the
provides prefix rather than a mix of provide and provides.
ANDROID_KEYSTORE moves back to the cryptography impl package, next to
KeyStoreSecretKeyRepository which uses it; it is not DI specific and would
otherwise be unreachable from there.
No behaviour change: Metro discovers containers via @ContributesTo and does
not care about method names, so the only call site to follow was in
DefaultVoiceRecorderTest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Metro's @BindingContainer replaced Dagger's @Module, but the declarations
kept the old Module suffix. Rename them (and their dedicated files) so the
type name matches the annotation, matching UserStatusBindingContainer which
already followed this convention.
No behaviour change: Metro discovers containers via @ContributesTo, so there
were only two call sites to update, both in tests.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Explain a rate limiting push gateway in the notification troubleshooter
The push loop back test printed whatever HTTP status the gateway
answered, so a user throttled by the public ntfy.sh gateway was told
"Error: HTTP 507", which reads as a server fault rather than as a quota
they can wait out or move away from.
Map the two statuses that mean the notification was throttled onto a
typed failure and give the troubleshooter a message that says what to do.
* Import strings from localazy, remove `temporary.xml` in `:libraries:push:impl`
---------
Co-authored-by: Jorge Martín <jorgem@element.io>
Document PushService, GetCurrentPushProvider, NotificationCleaner and
NotificationBitmapLoader along with their members, and add the missing @param
entries across the notification interfaces.
The NotificationCleaner docs record the behaviour shared by all its methods,
which is not visible in the signatures: each one also removes the grouping
summary notification once it has no children left.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A room notification is rebuilt from the one currently on screen: its messaging
style is extracted and the incoming events are appended. Every event was
appended unconditionally, so an event re-delivered while its notification was
still showing was added again, and the notification re-alerted each time.
Skip events whose id is already present in the existing messaging style, and
set onlyAlertOnce when the batch adds nothing new. Smart reply failures are
exempt, because that path deliberately reuses the same synthesised event id to
append the "message not sent" line. Filtering is per event rather than per
message so an image and its caption stay a unit.
This fixes the visible duplication, not the re-delivery that triggers it, and
does not help once the notification has been dismissed.
With several accounts logged in, sending a message from one of them raises
a notification on every other account that shares the room. The device
notifies its owner about something the owner has just done, and the more
accounts share a room the more copies arrive.
Resolve the set of logged-in user ids once per push batch and drop an event
whose sender is one of the other accounts, using the filtered-out result
the notification pipeline already understands, so it is recorded in push
history rather than turned into a fallback notification.
The receiving session is removed from that set, which keeps the change a
strict no-op for a single account and protects the local echo that the
reply action pushes back through the same path. Filtering happens at the
one point that sees every kind of push, so messages, polls, call invites,
ringing calls and room invites are all covered.
Filtering is unconditional and has no setting; multiple accounts sit
behind an experimental flag that is off by default, so nothing changes for
a stock build.
A redaction push whose redacts field the SDK could not map was reported as
an unknown error. The result processor only suppresses filtered-out and
redacted events; every other exception becomes a visible "you have N
notifications" fallback and also raises the battery optimisation banner.
So a deleted message could produce a notification about itself, which is
the opposite of what a redaction means, plus an unrelated banner.
The branch now throws EventFilteredOut, which the processor already drops
silently, and logs at warning level so the SDK gap still shows up in push
history.
Relates to https://github.com/element-hq/element-x-android/issues/5766
This can only happen when the app was killed by the OS and then you received some notification.
This notification action creates a new `DefaultSessionPreferencesStore` when trying to fetch `isSendPublicReadReceiptsEnabled`, which would contain a different session coroutine scope than the one created when instantiating the navigation graph (`@SessionCoroutineScope`), resulting in flows that never emit any items and close automatically as 'completed'.
This in turn prevented the timeline items subscription in `TimelinePresenter` from progressing, since we're also checking `isRenderReadReceiptsEnabled` there, which is closed and in turn closes the timeline items flow.
The homeserver's new MSCxxxx knock push rule notifies members who can
act on a knock; the push resolver previously filtered every
m.room.member state event out, so the push produced no notification.
Split RoomMemberContent out of the filtered-out catch-all and render
membership=knock as "Requested to join" from the knocking user
(other membership changes stay filtered out).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>