Skip to content

Notification settings: pick custom sounds for messages and call ringtones#6702

Open
jennaharris7 wants to merge 12 commits intoelement-hq:developfrom
jennaharris7:feature/notification-sound-picker
Open

Notification settings: pick custom sounds for messages and call ringtones#6702
jennaharris7 wants to merge 12 commits intoelement-hq:developfrom
jennaharris7:feature/notification-sound-picker

Conversation

@jennaharris7
Copy link
Copy Markdown

@jennaharris7 jennaharris7 commented Apr 30, 2026

Content

Notification sound picking moves in-app for both messages and incoming calls.

For message notifications, tapping the row opens a dialog with four options:

  • Element default — Element X bundled sound
  • Element fade — Element X bundled sound
  • System default — Android's notification tone
  • Choose another sound… — opens the system ringtone picker

The current bundled "Element default" is always reachable through the dialog, so users can return to it after experimenting with custom tones. Silent is reachable inside the system picker.

Open to feedback on the dialog's option set, labels, and presentation — happy to iterate on the UX based on review.

For incoming call ringtones, tapping the row opens the system picker directly — there's no bundled call ringtone to surface as a preset.

Custom picks are copied into app-private storage and re-exposed through the app's FileProvider (DefaultNotificationSoundCopier), so a chosen sound can't disappear when the source app is uninstalled or the URI grant lapses.

Users on prior builds who never picked a sound keep hearing the bundled tone — a one-shot migration on first launch flips their stored state to Element default and bumps the channel version. Gated on version == 0, so it runs at most once.

Channel recreation

Android forbids mutating an existing channel's sound, so each pick increments a per-slot version counter and creates a new channel id ({base}_v{n}); the previous versioned channel is deleted. Persistence and the version bump happen in a single transaction.

Display name

The row shows the friendly tone name, falling back through Ringtone.getTitle()OpenableColumns.DISPLAY_NAME of the source URI → localised "Custom". The live resolver skips our own FileProvider URIs, where getTitle() would otherwise surface our internal filename (call_sound.ogg).

Failure path

If the copy fails, an inline dismissible error appears below the row and the persisted state is left untouched.

Motivation and context

Closes #6672.

Today, changing the notification sound requires leaving Element X to dig through Android's per-channel system settings, which most users never discover. Bringing the picker in-app makes the setting discoverable, gives messages and calls independent controls, and lets users return to the bundled Element default sound at any time.

Screenshots / GIFs

**Subject to design review
screenshot1screenshot2
screenshot3screenshot4
screenshot5screenshot6

Tests

  • Open Settings → Notifications. The Sounds section shows two rows: Message sound (defaults to "Element default") and Call ringtone (defaults to "System default").
  • Tap Message sound → dialog with three options. Pick Choose another sound… → system picker opens. Pick a tone → row reads the tone's friendly name; sending a message plays it.
  • Tap Message soundElement default → row reads "Element default"; sending a message plays the bundled message.mp3.
  • Tap Message soundElement fade → row reads "Element fade"; sending a message plays the bundled element_fade.mp3.
  • Tap Message soundSystem default → row reads "System default"; sending a message plays Android's notification tone.
  • Reach Silent via "Choose another sound…" → row reads "Silent"; messages produce no sound.
  • Tap Call ringtone → system picker opens directly. Verify Default / Silent / Custom all behave the same way; a placed call rings with the chosen tone.
  • Force-stop and re-open → all picks persist.
  • Upgrade-from-prior-build: install the previous build with the message sound left at its default, install this build over the top → row silently switches to "Element default"; the channel still plays message.mp3.

Tested devices

  • Physical
  • Emulator
  • OS version(s): Samsung Galaxy S24, Android 16

Checklist

  • This PR was made with the help of AI:
    • Yes. In this case, please request a review by Copilot.
    • No.
  • Changes have been tested on an Android device or Android emulator with API 24
  • UI change has been tested on both light and dark themes
  • Accessibility has been taken into account. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
  • Pull request is based on the develop branch
  • Pull request title will be used in the release note, it clearly defines what will change for the user
  • Pull request includes screenshots or videos if containing UI changes
  • You've made a self review of your PR

@jennaharris7 jennaharris7 requested a review from a team as a code owner April 30, 2026 20:30
@jennaharris7 jennaharris7 requested review from bmarty and removed request for a team April 30, 2026 20:30
@github-actions
Copy link
Copy Markdown
Contributor

Thank you for your contribution! Here are a few things to check in the PR to ensure it's reviewed as quickly as possible:

  • If your pull request adds a feature or modifies the UI, this should have an equivalent pull request in the Element X iOS repo unless it only affects an Android-only behaviour or is behind a disabled feature flag, since we need parity in both clients to consider a feature done. It will also need to be approved by our product and design teams before being merged, so it's usually a good idea to discuss the changes in a Github issue first and then start working on them once the approach has been validated.
  • Your branch should be based on origin/develop, at least when it was created.
  • The title of the PR will be used for release notes, so it needs to describe the change visible to the user.
  • The test pass locally running ./gradlew test.
  • The code quality check suite pass locally running ./gradlew runQualityChecks.
  • If you modified anything related to the UI, including previews, you'll have to run the Record screenshots GH action in your forked repo: that will generate compatible new screenshots. However, given Github Actions limitations, it will prevent the CI from running temporarily, until you upload a new commit after that one. To do so, just pull the latest changes and push an empty commit.

@github-actions github-actions Bot added the Z-Community-PR Issue is solved by a community member's PR label Apr 30, 2026
Resolves merge conflict in NotificationSettingsViewTest.kt by porting
the two new sound-picker tests to the v2 testing API
(runAndroidComposeUiTest) introduced upstream in commit 11b9efa.
- libraries:preferences:api now uses io.element.android-compose-library
  so NotificationSound's @immutable annotation can resolve, matching the
  pattern in libraries:matrix:api.
- Rename SoundUnavailableBannerStateProvider to
  NotificationSoundUnavailableStateProvider so the class name contains
  the parameter type as required by KonsistClassNameTest.
The three new sound-unavailable tests pushed the class past Detekt's
default LargeClass threshold. Matches the suppression pattern already
used on five presenter test classes under features/messages/impl/test.
…on mode

rememberLauncherForActivityResult requires a LocalActivityResultRegistryOwner,
which Paparazzi-driven preview snapshot tests don't provide, causing
NotificationSettingsViewPreview to throw IllegalStateException for every
preview parameter. Extract the click handlers into a helper that returns a
no-op when LocalInspectionMode.current is true; the runtime path is unchanged.
@bmarty
Copy link
Copy Markdown
Member

bmarty commented May 4, 2026

Thanks! A question before I review the PR: would it be possible, when the user selects a sound, to copy the selected file in the private application storage, so that the sound file can never be deleted / lost / updated? All the resilience part of this PR could then be removed, that would simplify the PR a lot. Also it will prevent the file to be updated (by a third party app), and so the sound to change without Element app to be aware of, even with the resilience system.

@jennaharris7
Copy link
Copy Markdown
Author

jennaharris7 commented May 5, 2026

We can absolutely do that - how do we want to handle the current message.mp3 in the app? This will be the starting point for users, do we want to show the label for this to be "Default", or "Custom" (and leave the default label for the true system default?) I've code the code changes in place (barring a change to the message.mp3 label), I just want to run it through some testing before I push

@bmarty
Copy link
Copy Markdown
Member

bmarty commented May 5, 2026

how do we want to handle the current message.mp3 in the app? This will be the starting point for users, do we want to show the label for this to be "Default", or "Custom" (and leave the default label for the true system default?)

Maybe something like:

  • "Default" for the system default
  • "Element" or "Element's sound" for the Element default sound (could be actually the app name "$AppName's sound")

@mxandreas any thoughts?

@mxandreas
Copy link
Copy Markdown
Member

I have taken a quick look at it, and please see also my comment on the iOS PR for more context: element-hq/element-x-ios#5527 (comment)

  1. It looks like we're in agreement to use Element Default for the default tone.
  2. iOS also offers the alternative Element tone, I think Android should have it available as well, and I proposed to rename it Element Fade (open for better names).
  3. iOS does not currently let you to customize ringtone for calls - does anyone know if that is something that is platform-specific? E.g. not something that is possible or good practice to do on iOS?
  4. Regarding the copy, I'd make both platforms to either use "sound" or "tone" - both are used, there seem to be no clear winner here but perhaps sound is a bit more modern and use.

@mredig
Copy link
Copy Markdown

mredig commented May 6, 2026

  1. Regarding the copy, I'd make both platforms to either use "sound" or "tone" - both are used, there seem to be no clear winner here but perhaps sound is a bit more modern and use.

iOS PR author here - I used 'tone' because it's the terminology used on iOS.

Screenshot 2026-05-06 at 9 49 07 AM

I don't know what Android uses, but I would suggest remaining consistent with the platform.

@jennaharris7
Copy link
Copy Markdown
Author

I have taken a quick look at it, and please see also my comment on the iOS PR for more context: element-hq/element-x-ios#5527 (comment)

  1. It looks like we're in agreement to use Element Default for the default tone.
  2. iOS also offers the alternative Element tone, I think Android should have it available as well, and I proposed to rename it Element Fade (open for better names).
  3. iOS does not currently let you to customize ringtone for calls - does anyone know if that is something that is platform-specific? E.g. not something that is possible or good practice to do on iOS?
  4. Regarding the copy, I'd make both platforms to either use "sound" or "tone" - both are used, there seem to be no clear winner here but perhaps sound is a bit more modern and use.

I've gone ahead and added the Element fade tone in the selection picker - subject to name or ui changes (Android does use the term "Sound", which differs from iOS)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Z-Community-PR Issue is solved by a community member's PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add in-app settings link to system picker for app-wide message sound and call ringtone

4 participants