* Make PIP react to camera orientation changes in the other participant's device
Adds a `controls.onPipMediaOrientationUpdate` callback for the WebView that will trigger a new `pipParamsBuilder.setAspectRatioFromOrientation(orientation)` method
* Fix warning about `PictureInPictureParams.Builder.setAspectRatio` only being available on API 26+
RootFlowNodeTest was declaring its own no-op fake, while the oauth test
module already provides one. Use it, and move it out of the customtab
package, since it is not specific to the custom tabs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fakes were declared in RootFlowNodeTest, so other tests could not use
them. Move each of them next to the API it fakes, following the existing
convention of a createNodeResult lambda defaulting to lambdaError():
- FakeLoginEntryPoint in features/login/test
- FakeBugReportEntryPoint in features/rageshake/test
- FakeSignedOutEntryPoint in features/signedout/test (new module)
- FakeAccountSelectEntryPoint in libraries/accountselect/test (new module)
The login and rageshake test modules did not have Appyx on their
classpath, so add the architecture module to their dependencies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tapping a room alias whose server is down left the resolver screen on a
spinner for as long as the screen stayed open, with no way forward other
than going back.
Bound the resolution with the ten second timeout already used when
joining a room by address, so the retry dialog is shown instead. The fake
client's resolve lambda becomes suspending so a server that never answers
can be simulated.
* Keep the send button when the composer is emptied while editing
rememberEndButtonParams checked whether the composer had sendable text
before it checked whether the user was editing a message, so clearing
the text of a message being edited replaced the send button with the
voice recorder button. Starting a recording from there abandons the
edit.
Check the editing branch first, as the formatting composer already does,
and let it render a disabled send button when there is nothing to send.
* Update screenshots
* Request media audio focus when playing a voice message
Voice message playback asked for audio focus with
USAGE_VOICE_COMMUNICATION, the same bucket as Element Call and voice
message recording, while the player itself renders on the media stream.
Audio policy layers act on the declared usage, so a car head unit
switches to its call audio context and routes the playback away while
the player keeps running.
Declare USAGE_MEDIA (and STREAM_MUSIC on the legacy path) for playback,
which is what the player actually renders. Recording and Element Call
keep asking for voice communication focus.
* Keep playing a voice message through a short sound from another app
A notification, a navigation prompt or any other app taking focus for a
moment sends AUDIOFOCUS_LOSS_TRANSIENT or AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,
and DefaultMediaPlayer pauses on it. The voice message stops part way
through and the user has to find it again and restart it.
Recording already ignores those two, because the capture path is not
affected by focus. Give voice message playback the same treatment, moving
the requester test into a named function rather than growing the inline
condition. A permanent AUDIOFOCUS_LOSS, a real phone call, still pauses,
and the media viewer is untouched.
* Model the fake voice recorder on a cancellable recording job
FakeVoiceRecorder pushed a whole recording synchronously from a single
suspending call, which made it both weaker and stricter than
DefaultVoiceRecorder.
Every level emission carried the same elapsed time and the emissions were
coalesced by Molecule, so no intermediate recording state was observable and
the level-limit test had to skip half the emissions to compensate. Each level
now advances the test time source and suspends for its share of the recording
duration, so the states arrive one at a time and that magic number is gone.
Call verification moved to the constructor lambdas the rest of the repo uses,
in place of hand-rolled counters and assertCalls. The counters discarded the
argument, so nothing pinned that cancelling a recording stops it with
cancelled = true; that is now asserted. Truth is no longer needed by the
fixture module.
A second startRecord() no longer raises an error where production only warns
and returns, and stopping or deleting terminates an in-flight emission loop
the way cancelling the real recording job does, so a stale recording state can
no longer arrive after the recording has finished.
* Keep each recording's level loop to its own recording
The level loop checked a single isRecording flag, which cannot tell "this
recording was cancelled" from "a later recording has started". After a cancel
followed by a new start, the first loop resumed, saw the flag set again, and
advanced the shared test time source, so the new recording reported the elapsed
time of both. Each recording now carries an id and its loop stops as soon as
that id is no longer the active one.
* Rename `UrlContentFetcher` to `ClientUrlContentFetcher` and add missing base URL data (server name, homeserver URL)
* Avoid passing the `homeserverUrl/serverName` around.
Calculate the URL in `DefaultRemoteEnterpriseConfigRetriever` using the newly added `homserver` and `server` values as the possible base URLs, depending on the remote config source
* Make sure we don't perform blocking requests to fetch the remote config when restoring a session
Add a `needsRefresh` parameter to `RemoteEnterpriseConfigResult.NotFound` to handle this, and store a `no-config` value in the store to indicate an endpoint has been checked and no config was found
* Bump enterprise module
* Don't restart the sync service if the client is shutting down
This was causing issues when clearing the cache:
1. The client would ask the SDK to clear the caches.
2. The SDK would stop the sync service, clear the caches and set the `pos` value of sliding sync to 0 so the HS returns all the required state the next time the client is used.
3. However, between the sync service being stopped with the `pos=0` change and the client being destroyed, there was enough time for the `SyncOrchestrator` to decide the sync service was stopped for no good reason since there is network connectivity and the app is on foreground, so it restarted the sync, with `pos=0`.
4. This value was persisted, and the next time the new client did an 'initial sync' it had a `pos != 0`, which the HS uses to avoid returning some required state it already fetched.
5. The room list now lacks state events for lots of rooms and room names, avatars, members, etc. are now missing.
---------
Co-authored-by: Benoit Marty <benoitm@element.io>
* Resolve the real image format before choosing the encoder
The upload pre-processor picks the encoder from the mime type it is
given. When an image is shared from another app that type is often
broader than the image really is, so a transparent PNG shared as a
wildcard image type is re-encoded as JPEG and its alpha channel is
flattened to black.
Read the format from the image header when the declared type is not
already a concrete PNG or JPEG, and use that to decide both whether the
image can be compressed at all and which encoder to use.
* Cover the mime type fallbacks the wildcard resolution introduced
resolveImageMimeType now decides whether an image is compressible, so two of
its outcomes had no test: a GIF shared with a wildcard type has to be
recognised and left uncompressed rather than recompressed into a still, and a
file the decoder cannot read at all has to fall back to the type it was shared
with.
The isMimeTypeImage() guard on the decoded type is dropped with them. The
BitmapFactory decoders only ever report an image type in outMimeType, so the
guard could not fire and there was no way to exercise it.
* Update dependency org.matrix.rustcomponents:sdk-android to v26.08.17
* Fix API breaks:
- `ClientBuildException.WellKnownLookupDisabled` should be handled now.
- `Client.sendReply` returns a `Result<SendHandle>` now. We can ignore it like we do for `Client.send`.
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
* Extract `IsEnterpriseBuild` use case
Having it inside `EnterpriseService` made having DI cycles with the `BuildMeta` provider too easy
* Rename `EnterpriseService.defaultHomeserverList()` to `homeserverAllowList()`
This seems more aligned with the way this list works
* Move most of the logic from `:libraries:wellknown` to `:enterprise:feature:enterprise`, both `shared` and `impl` modules
Ideally I would have added everything into `impl`, but some of the classes needed to be shared between the different `:enterprise:features` modules, and those are in the `shared` module, lacking a better name/strategy
* Add `EnterpriseService.isElementProEnforced`
With this, we don't need to provide the `RemoteEnterpriseConfig` anymore to any consumers, everything goes through `EnterpriseService` or other providers
* Remove `EnterpriseService.overriddenElementWellKnown`
This leaks the remote enterprise config/element well known contents to the app in a public interface.
* Create `CustomMapTilerConfigProvider` to avoid leaking the `RemoteEnterpriseConfig` in public APIs
This commit also starts moving some of the well-known related values to the enterprise modules
* Add missing docs to `EnterpriseService` and `SessionEnterpriseService`
* Fix tests
* Fix lint issues
* Bump enterprise module
* 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>
Images are downscaled against a 640px reference, which makes inSampleSize
halve the source one step further than the documented intent: the KDoc
says images should be sent at up to 1280x1280, but a 12000x3916 photo is
sent as 3006x979. Text in screenshots and detail in photos is lost at
that size.
Raise the reference to 1280. inSampleSize is a power of two, so this
moves each image one step up rather than to an exact size.
Selecting a Light or Dark theme makes AppCompatDelegate rewrite the
activity configuration to the forced night mode. isSystemInDarkTheme
reads that same configuration, so switching back to System kept showing
the previously forced theme.
Read the night mode from the application configuration instead, which
AppCompat leaves untouched and itself uses to expand
MODE_NIGHT_FOLLOW_SYSTEM. The activity configuration is still used as
the recomposition key so a system-wide theme change is picked up.
A slash is valid in the local part of a user id, but in a permalink it
also separates the identifier from an event id, so the SDK could not tell
the two apart and the link fell back to opening matrix.to in a browser.
Only a user id can start with an @, so recover it before handing the URI
to the SDK, and only when the identifier really contains a slash so every
other permalink keeps taking the same path.
A m.key.verification.request sent from a client that supports in-chat
verification carries a plain text body for clients that do not. Element X
has no msgtype of its own for it, so it fell through to OtherMessageType
and the timeline printed that body, telling the user their client does
not support in-chat key verification right after a verification they had
just completed.
Drop the event when building the timeline items, as Element X iOS does.
Adding or editing a media caption from the plain composer sent the
caption as text only: the composer has no HTML to pass, and the media
caption edit was built by hand with a null formatted body, so the
markdown was never rendered. Sending a caption with the same markdown
does render it, because the SDK builds the formatted body there.
Build the edit with the SDK's createCaptionEdit, which runs the caption
through the same markdown conversion the send path uses.