Skip to content

chore: merge develop into develop-v2 - #1761

Open
PratimMallick wants to merge 63 commits into
develop-v2from
develop-v2-merge-develop
Open

chore: merge develop into develop-v2 #1761
PratimMallick wants to merge 63 commits into
develop-v2from
develop-v2-merge-develop

Conversation

@PratimMallick

@PratimMallick PratimMallick commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Goal

closes AND-1364

Land the already-completed merge of develop into develop-v2, plus the follow-up commits that fixed compilation / unit-test breakages left after that merge.

Base: origin/develop-v2 @ 35e58369a8 (Expose core StreamConnectionState as the public connection state (#1739))
Head: develop-v2-merge-develop @ 95eadcd411

Actual diff vs develop-v2 (from git)

Measured with git diff --shortstat origin/develop-v2...HEAD and git log --first-parent origin/develop-v2..HEAD:

Metric Value
First-parent commits on this branch 3 (see below)
Total commits reachable (origin/develop-v2..HEAD) 63
Files changed 332 (130 added, 193 modified, 9 renames)
Lines +15,569 / −4,585

First-parent history (what this PR adds on top of develop-v2)

95eadcd411 chore(ui-compose): refresh API dump after develop merge
bf78092d02 fix(core): resolve develop merge compile/runtime conflicts for v2
f2fc274db5 Merge branch 'develop' of github.com:GetStream/stream-video-android into develop-v2
  • f2fc274db5 — merge commit by @PratimMallick (parents: 35e58369a8 develop-v2 + 8a5591287a develop)
  • bf78092d02 / 95eadcd411 — follow-up compile / API-dump fixes after the merge (22 files, +162/−211)

What develop brought in (merge second parent 8a5591287a)

Non-exhaustive list from git log --no-merges origin/develop-v2..f2fc274db5^2 (the develop tip merged in):

Largest buckets in the tree diff: generated OpenAPI models (~12%), compose preview moves to src/debug, analytics packages, workflow updates.

Conflict resolutions in the merge commit (f2fc274db5)

From the merge commit message # Conflicts: list, and verified against git show --cc f2fc274db5 / parent file contents:

1. gradle/libs.versions.toml

  • Kept develop-v2 Kotlin/KSP (kotlin = "2.2.0", ksp = "2.2.0-2.0.2") and coroutines (1.10.2) over develop’s 1.9.25 / 1.9.0.
  • Kept develop’s added kotlinxDatetime = "0.6.1".

2. ClientState.kt

  • Kept develop-v2 public connection: StateFlow<StreamConnectionState> + updateUser + user from userRepository.userFlow.
  • Merge result still left _connection typed as legacy ConnectionState / PreConnect, and updateUser writing _user (broken leftovers — fixed in bf78092d02).

3. StreamVideoBuilder.kt

  • Kept develop-v2 guest adoption via BindableWritableUserRepository.bind { client.state.updateUser(...) } (dropped develop’s setupGuestUser / anonymous auth-type branch at this site).
  • Kept develop’s broader auto-connect gate: Authenticated || Guest + always honor autoRegisterPushDevice when that block runs.
  • Merge result still constructed CoordinatorConnectionModule(..., user = user, ...) while the module now required userRepository (fixed in bf78092d02).

4. StreamVideoClient.kt

  • Kept develop-v2 streamClient.subscribe(...) + re-watch on streamClient.connectionState.
  • Dropped develop’s collectors on coordinatorConnectionModule.socketConnection.{events,state,errors}.
  • Kept develop’s setupGuestUser / createGuestUser / guestUserJob await in registerPushDevice (with TODO in merge).
  • Merge result still called coordinatorConnectionModule.socketConnection.state() for analytics (invalid because socketConnection is Unit — fixed in bf78092d02).

5. CoordinatorConnectionModule.kt

  • Ctor param: took develop’s userRepository: UserRepository (not develop-v2’s user: User).
  • Socket ownership: kept develop-v2 ConnectionModuleDeclaration<..., Unit, ...> and socketConnection: Unit = Unit (did not restore develop’s CoordinatorSocketConnection).

6. StreamVideoClientTest.kt

  • Combined develop-v2 StreamClient harness/streamClient tests with develop guest/guestUserJob / setupGuestUser / CreateGuestResponse tests.
  • Merge left prepareClient(user=...) ignoring user (initialUser = mockk(relaxed = true)), which broke anonymous-user tests (fixed in bf78092d02).

7. CallLobby.kt

  • Took develop’s videoPreviewModifier + participantLabelContent APIs.
  • Kept develop-v2 io.getstream.webrtc.VideoTrack in most call sites; one org.webrtc.VideoTrack call site remained until bf78092d02.

8. VideoRenderer.kt

9. stream-video-android-ui-compose.api

  • Conflict listed in merge; merge result matched develop’s dump length (1594 lines vs develop-v2’s 1912). Re-dumped after compile fixes in 95eadcd4111530 lines.

Follow-up fixes after the merge (bf78092d02, 95eadcd411)

These are the only commits that change behavior/compile state after f2fc274db5. Actual file list from git diff --name-status f2fc274db5..HEAD:

File What changed (from the diff)
ClientState.kt _connection: MutableStateFlow<StreamConnectionState>(Idle); updateUseruserRepository.setUser
StreamVideoBuilder.kt Create StreamUserRepositoryImpl first; pass userRepository= into module + client; guest sink renamed to guestUserAdoptionSink
StreamVideoClient.kt Add CreateGuestRequest/Response + UserRequest imports; analytics observes streamClient.connectionState
CoordinatorAnalytics.kt (+ test) startObserver(StateFlow<StreamConnectionState>) instead of VideoSocketState
DegradationPreferenceMapper.kt (+ test) org.webrtcio.getstream.webrtc; MAINTAIN_FRAMERATE_AND_RESOLUTIONDISABLED
Analytics / reporter sources + tests org.webrtc.PeerConnectionio.getstream.webrtc.PeerConnection
CallLobby.kt, compose debug previews remaining org.webrtc.VideoTrackio.getstream.webrtc.VideoTrack
StreamVideoClientTest.kt prepareClient(user) now sets initialUser = user; add ProductvideoApi + streamClient for guest setup test
ClientStateUpdateUserTest.kt Mock returns real StreamUserRepositoryImpl
stream-video-android-ui-compose.api apiDump refresh (95eadcd411)

Testing

  • ./gradlew :stream-video-android-core:compileDebugKotlin
  • ./gradlew :stream-video-android-ui-compose:compileDebugKotlin
  • ./gradlew :demo-app:compileDevelopmentDebugKotlin
  • ./gradlew :stream-video-android-core:testDebugUnitTest — 980 passed, 0 failed (50 skipped)
  • ./gradlew apiCheck (pre-push)
  • spotlessApply on touched modules

☑️Contributor Checklist

General

  • I have signed the Stream CLA (required)
  • Assigned a person / code owner group (required)
  • Thread with the PR link started in a respective Slack channel (required internally)
  • PR targets the develop-v2 branch
  • PR is linked to the GitHub issue it resolves

Code & documentation

  • Changelog is updated with client-facing changes
  • New code is covered by unit tests
  • Comparison screenshots added for visual changes
  • Affected documentation updated (KDocs, docusaurus, tutorial)
  • Tutorial starter kit updated
  • Examples/guides starter kits updated (stream-video-examples)

☑️Reviewer Checklist

  • XML sample runs & works
  • Compose sample runs & works
  • Tutorial starter kit
  • Example starter kits work
  • UI Changes correct (before & after images)
  • Bugs validated (bugfixes)
  • New feature tested and works
  • Release notes and docs clearly describe changes
  • All code we touched has new or updated KDocs
  • Check the SDK Size Comparison table in the CI logs

🎉 GIF

Skipped — merge + compile-fix PR.

PratimMallick and others added 30 commits May 26, 2026 16:18
* upgrade to m145 webrtc and noise-cancellation

* Update to webrtc m145 and corresponding noiseCancellation lib

* Remove the snapshot repo resolution
…ng (#1699)

* feat(core): pick up SFU DegradationPreference and add WebRTC mapper

Regenerate SFU protos to pick up the new DegradationPreference enum and
its degradation_preference fields on PublishOption and VideoSender (plus
TrackInfo.self_sub_audio_video), and refresh the public API dump.

Add toRtcDegradationPreference() converting the SFU enum to
org.webrtc.RtpParameters.DegradationPreference, returning null for
UNSPECIFIED so callers can keep the current value. Includes unit tests
covering every enum variant.

Co-authored-by: Cursor <cursoragent@cursor.com>

* publisher changes for applying degradation preferences

* Remove duplicate handling of ChangePublishQualityEvent from callState. This event directly gets handled by RtcSession handleEvent method

* Added test for the two call sites where degradation Preference is getting set to test for the case where sfu sends the same degrdation preference which is already set in the transcevier sender param

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
* demo: Add logic to add custom user

* chore(demo-app): gate add user dialog to development flavor

Hide the new add-user button and popup outside the development flavor
so the production demo app doesn't expose internal user injection.

---------

Co-authored-by: Aleksandar Apostolov <apostolov.alexandar@gmail.com>
When toggling a single track (e.g. muting the mic while the camera stays
on, or turning the camera off while the mic stays on), RtcSession sent the
full declarative mute-state map for all track types. Re-asserting an
unchanged track as un-muted made the SFU re-emit a redundant
TrackPublishedEvent for that track. That event carries a potentially stale
published_tracks snapshot which re-enabled the just-disabled track, freezing
the local self-view on the last frame / showing the avatar while no frames
arrive.

Send only the mute state of the track that actually changed, matching the
web SDK. On SFU (re)connect/migration each enabled track is re-signalled
individually via listenToMediaChanges, so the full state is still restored.

Co-authored-by: Cursor <cursoragent@cursor.com>
…1705)

deleteDevice previously only purged the cached Device on API success. When
DELETE /devices returned 404 or the network failed, the stale token sat in
deviceTokenStorage. The next createDevice for a different user short-circuited
on token equality (when autoRegisterPushDevice=true) and returned Success
without calling POST /devices, silently leaving the new user without a device
row server-side and breaking incoming-call push.

Local cleanup now runs unconditionally and is guarded so a storage failure
doesn't mask the API outcome. CancellationException is re-thrown to preserve
structured concurrency.

Three regression tests added.

AND-1214
… device registration (#1703)

Guest user setup runs asynchronously: StreamVideoBuilder.build returns immediately
while setupGuestUser kicks off a background createGuest call to fetch the JWT.
Any authenticated request that fires in that window goes out with stream-auth-type
"anonymous" and no Authorization header, so the backend silently registers it
against the wrong identity. The customer-visible effect is push device
registration succeeding under !anon and incoming-call pushes never reaching
the guest user.

apiCall now awaits guestUserJob before invoking the request block, with a
self-job guard so createGuestUser — which also goes through apiCall — does not
await its own enclosing job and deadlock.

Adds two regression tests: one for the wait, one for the deadlock guard.

AND-1202

Co-authored-by: Rahul Kumar Lohra <tgunix@gmail.com>
* fix(core): adopt response.user from createGuest to keep guest identity in sync

The createGuest endpoint returns the server-resolved user (which may differ
from what was passed in — e.g. normalized id). The SDK previously kept only
the access token and left its in-memory user as the builder's input, so the
WS auth payload and the JWT user_id claim could disagree.

setupGuestUser now also updates client.user from response.user (matching the
JS SDK's connectUser(response.user, response.access_token) semantics).
userId becomes a computed property so every existing reader of client.userId
picks up the new identity automatically. CoordinatorSocketConnection.user
turns into a var so its onCreated() auth payload reads the latest user.

Adds three regression tests: userId reactivity, the var update inside the
socket connection's connect path, and a full setupGuestUser flow with the
api mocked to return a different user id than the input.

AND-1202

* fix(core): mirror adopted guest user into ClientState.user

ClientState._user was snapshotted from the integrator-supplied user at
construction, so observers of state.user kept the old id after
setupGuestUser adopted the server-issued one. Propagate the adopted
user via a new internal ClientState.setUser.
* refactor(core): introduce UserRepository as single source of truth for SDK user

Replaces the parallel `var user` fields in `StreamVideoClient` and
`CoordinatorSocketConnection` (and the `_user` mirror in `ClientState`) with
a single `UserRepository`:

- `UserRepository` — public, read-only access via `user` / `userFlow`.
- `WritableUserRepository` — internal sub-interface with `setUser`. Only
  `StreamVideoClient` holds a write reference, so identity updates go through
  one path.
- `StreamUserRepositoryImpl` — in-memory impl backed by a `MutableStateFlow`.

`StreamVideoBuilder` constructs one instance and shares it between the client
(writer) and the coordinator socket / `ClientState` (readers). `setupGuestUser`
writes the adopted user to the repo once; readers pick it up automatically
without any local copy to keep in sync. `connect()`/`reconnect()` no longer
mutate a snapshot of the user on the socket — they only forward the call to
`internalSocket`, and `onCreated()` reads from the repository when building
the WS auth payload.

* test(core): add direct unit tests for StreamUserRepositoryImpl

Covers seed-from-constructor, user/userFlow reads, setUser write, emission
to active StateFlow collectors, and replacement semantics. Lifts coverage
on the new repository from indirect-only (via StreamVideoClient tests) to
full coverage of the impl.

* Auto-connect and register push device for guest users (#1707)

* feat(core): auto-connect and register push device for guest users

StreamVideoBuilder previously only ran the auto-register-push and
auto-connect block for UserType.Authenticated. Guest users fell through,
forcing every Guest integrator to write the same boilerplate (manual
registerPushDevice + connect after build) — boilerplate the iOS and JS
SDKs don't require.

Widen the gate to include UserType.Guest. registerPushDevice() and
connectAsync() inside StreamVideoClient already await guestUserJob, so
both are safe to fire from the builder block before /video/guest
completes. Anonymous users still don't have an identity to register a
device against, so they remain excluded.

AND-1202

* fix(core): wait for guestUserJob before registering push device

StreamNotificationManager.createDevice() goes straight to api.createDevice()
without the apiCall {} wrapper, so the guestUserJob await guard added in #1703
doesn't cover it. registerPushDevice() now waits for guest setup itself before
delegating, so the push generator can't fire createDevice() before the
coordinator's auth headers flip from anonymous to JWT.
* fix: include internal audio switch to fix concurrency issue

* fix: include aar
The KDoc claimed `logOut` clears internal user state, removes push
notification devices, and clears call state. The actual implementation
only writes null to the local DeviceTokenStorage — no `DELETE /devices`,
no socket disconnect, no in-memory clear.

The name and the historical doc invite a customer to ship broken
user-switching: anyone reading the API surface would reasonably assume a
clean slate. Surfaced while diagnosing a customer integration where push
delivery silently failed across user transitions.

Annotate the interface declaration and the StreamVideoClient override
with `@Deprecated`. Update the KDoc to describe current behavior
accurately. Point `ReplaceWith` at `StreamVideo.removeClient()`, which
triggers a real `cleanup()` and uninstalls the singleton. Customers who
need to remove the server-side device row should call `deleteDevice()`
before `removeClient()`.

No binary signature change — `@Deprecated` is annotation-only, so the
public `.api` file is unchanged.

AND-1217

Co-authored-by: Rahul Kumar Lohra <tgunix@gmail.com>
…ce (#1711)

Assign the DataStore.updateData() result to a local in
DeviceTokenStorage.updateUserDevice so the suspend function is compiled
as a state machine that returns Unit. Without it the compiler
tail-call-optimizes the call and propagates the DevicePreferences result
up the updateDevice suspend chain, which can surface as
"DevicePreferences cannot be cast to kotlin.Unit" at the caller once R8
inlines the chain.

Co-authored-by: Cursor <cursoragent@cursor.com>
…unt (#1712)

Post-join, the SFU healthcheck delivers the authoritative participant count.
Coordinator session events (participant_joined/left, counts_updated, anything
carrying a CallSessionResponse) carry a smaller, stale snapshot that disagrees
at scale. The previous guard checked only !is RealtimeConnection.Joined — a
transient state immediately replaced by Connected — so every coordinator
session event re-wrote the count, producing wild swings during livestreams
(e.g. 25k -> 32k -> 42k -> 28k in seconds).

Broaden the guard to cover the entire in-call lifetime (Joined, Connected,
Reconnecting, Migrating). Pre-join, the session-derived path now uses
max(byRoleCount, participants.size) for monotonicity during fast joins,
matching the stream-video-js SDK.

AND-926
)

* fix(core): prevent "MediaSource has been disposed" crash on leave

Guards the lazy audio/video source and track creation/disposal in
MediaManagerImpl with a single reentrant lock, and adds a terminal
`released` flag so the mic/camera mute paths no-op after cleanup instead
of lazily resurrecting native objects.

The crash occurred when a call was left while the first AudioSwitch setup
was still in flight: cleanup() disposed the audio source on one thread
while the deferred mic-disable callback recreated the audio track from
that disposed source on stream-audio-thread.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(core): stub runOnAudioTrackIfAvailable in MicrophoneManager tests

enable()/disable() now route the track toggle through
mediaManager.runOnAudioTrackIfAvailable instead of the audioTrack getter,
so the test helper stubs the new helper to invoke its block with the mock
track. Fixes the 5 failing MicrophoneManagerTest verifications.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…elease of 1.26.0 (#1719)

* Revert " Include internal audio switch to fix concurrency issue (#1710)"

This reverts commit 9a8da36.

* chore(release): reset version to 1.25.0 to allow re-release of 1.26.0

The 1.26.0 Maven publish failed due to the local AAR introduced in #1710.
Resetting gradle.properties to 1.25.0 so the release workflow can re-tag
and publish 1.26.0 cleanly from the reverted develop state.
The "setting up call" foreground-service notification was built without a
small icon for any non-incoming trigger (outgoing/ongoing/livestream). The
non-deprecated getSettingUpCallNotification(trigger, callId) delegated its
else branch to the deprecated no-arg overload, which never called
setSmallIcon. A small icon is mandatory for foreground-service
notifications, so Android 13+ rejected it with
CannotPostForegroundServiceNotificationException ("Bad notification for
startForeground") when the call foreground service started.

Extract a non-deprecated buildSettingUpCallNotification() helper that always
sets setSmallIcon(R.drawable.stream_video_ic_call), and have both the
non-deprecated else branch and the deprecated overload delegate to it. Add a
regression test covering the non-incoming (outgoing) trigger path.

Co-authored-by: Cursor <cursoragent@cursor.com>
* update open api generated models

* update code gen script
* update open api generated models

* update code gen script

* fix: self cancelling coroutine code

* fix: fix self cancelling coroutine code
…e call (3/4) (#1715)

* update open api generated models

* update code gen script

* fix: self cancelling coroutine code

* internal: add call leave reason

* internal: update Call Leave Reason LLC

* fix: fix unit tests

* 📝 CodeRabbit Chat: Implement requested code changes

* Update stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallLeaveReason.kt

* chore: send correct leave reason from StreamCallActivity.kt

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Aleksandar Apostolov <apostolov.alexandar@gmail.com>
rahul-lohra and others added 22 commits June 23, 2026 10:12
…ging call flows (#1729)

* temp: force-commit

* fix: invoke updateRingingState() when setting active call

* fix: add canRunService on incoming calls

* demo-app: add screen to configure call settings

* fix: refactor

* fix: refactor

* fix: remove unused classes

* fix: fix unit-test
* improve: fix sending correct ice state

* improve: correctly update analytics stage

* improve: refactor

* fix: remove grace period to wait for ice state

* fix: refactor

* fix: refactor

---------

Co-authored-by: Aleksandar Apostolov <apostolov.alexandar@gmail.com>
* improvement: video analytics refactor

* fix: fix unit test
* temp: force-commit

* fix: refactor

* fix: remove unused classes

* fix: Fix a crash when application put to background restriction by rendering normal notification instead of CallStyle notification

* fix: add safe null-check

* fix: moved file to utils
)

* temp: force-commit

* fix: invoke updateRingingState() when setting active call

* fix: add canRunService on incoming calls

* demo-app: add screen to configure call settings

* fix: refactor

* fix: refactor

* fix: remove unused classes

* fix: Fix a crash when application put to background restriction by rendering normal notification instead of CallStyle notification

* fix: add safe null-check

* fix: fix unit-test

* fix: fix notification cleaning from leave button in notification

* fix: add todos for future fixes
The lobby video preview Box was hardcoded to a responsive height
(180/280/200dp by screen size and orientation), fillMaxWidth, and a 12dp
rounded corner clip with no way to override size or shape. iOS sizes the
preview via the parent's GeometryReader; React drives size via
className on VideoPreview. Android was the outlier.

Add a videoPreviewModifier parameter so callers can override the box
modifier directly. The default preserves the previous responsive height,
full width, and 12dp clip, so existing callers see no change.
* fix(core): report SFU WebSocket connection/join timeouts accurately

Splits the SFU socket connect into a transport-open phase (bounded by
OkHttp's connect timeout) and a distinct join-response phase (bounded by
a dedicated timer via a new WebSocketConnected state), so a silent SFU no
longer hangs the join. Surfaces real transport failure messages and HTTP
status codes on the resulting NetworkError, routes all recoverable errors
through a single DisconnectedTemporarily state carrying the exact
code/reason, and maps both timeout flavours to REQUEST_TIMEOUT in analytics
(join-response via error code, transport via SocketTimeoutException cause).

Also moves join-error analytics to the join flow only, adds a per-session
SFU WS retry counter, and wires connectionTimeoutInMs from the builder to
both the OkHttp client and the join-response deadline.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Remove !! operator on session.value and send a proper Failure when session.value was cleared to null while connecting to sfu was still in progress

* Update comment

* fix(core): address PR review comments for SFU WS timeout handling

- Rename SfuSocketStateEvent.WebSocketConnected to WebSocketEstablished to
  avoid the naming collision with the SfuSocketState.WebSocketConnected state.
- Guard monitorSession() in _join recovery so it only runs when the original
  session is reused, preventing double-registration after rejoin/migrate.
- Default connectionTimeoutInMs to 5s and fix stale KDoc.
- Rename triggersReconnect() to canTriggersReconnect() for clarity.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(core): classify OkHttp InterruptedIOException timeouts as REQUEST_TIMEOUT

OkHttp connect/call timeouts surface as InterruptedIOException("timeout"),
not SocketTimeoutException, so SFU join analytics were incorrectly reporting
SFU_ERROR despite the failure reason being "timeout".

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(core): add safety timeout to connectInternal SFU socket wait

Wrap the wait for a terminal SfuSocketState in withTimeoutOrNull so
connectInternal always returns even if the socket state machine never
reaches Connected or Disconnected. On timeout, return a recoverable
Failure with REQUEST_TIMEOUT so higher-level reconnect logic can escalate.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Rahul Kumar Lohra <tgunix@gmail.com>
* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* CI: add least-privilege permissions to GitHub Actions workflows

Add explicit workflow-level permissions blocks to resolve CodeQL
actions/missing-workflow-permissions alerts. Scopes are derived from
workflow operations (git push, artifact upload, Danger, release lanes).

Refs: APPSEC-164

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: move workflow permissions to job level (SonarCloud)

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: address CodeRabbit review feedback and fix workflow YAML

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud

* ci: repair workflow permissions blocks for actionlint/SonarCloud
… on retries only (#1746)

- RetryRule: each failed attempt that is retried is written as its own Allure result sharing the real test's historyId, with that attempt's steps, error and artifacts, so TestOps groups attempts as retries and can flag flaky tests
- RetryRule: screen recording runs only on retry attempts; previously every test recorded an 8 Mbit/s video that was discarded on pass
- RetryRule: the recording file uses methodName instead of displayName (the display name's parentheses break when the recording commands go through the device shell), and a recording stop failure no longer replaces the test result
- RetryRule: move to io.getstream.video.android.rules (the file declared a chat package), remove the unused Retry annotation, simplify DatabaseOperations
- Wait: waitForText and waitForCount poll at 50ms instead of spinning the CPU until timeout; remove the unused waitForTextToChange
- Allurefile: name launches 'Cron checks' only for real scheduled events, so manual dispatches stay distinguishable in TestOps
- Fastfile: batch_tests splits round-robin, always yielding exactly batch_count groups; the previous rounded-up slicing could produce fewer groups and crash on nil for the last batch

Ported from GetStream/stream-chat-android#6568.
…1741)

* fix(core): recover initial join when connect safety-timeout leaves no reconnect

When RtcSession.connectInternal hits its own safety-timeout, the socket is
left in a non-terminal state that stateJob ignores, so no Call.reconnect is
ever launched and _join's didReconnectSucceed() would block forever.

- Add SfuConnectionResult.Failure.reconnectTriggered so the join flow can tell
  whether a recovery loop has already been started by stateJob.
- On the safety-timeout path, tear down the abandoned (still-in-flight) socket
  so a late Connected/JoinResponse can't resurface and resurrect the session.
- In Call._join, trigger a REJOIN ourselves when a recoverable failure reports
  no reconnect was triggered; otherwise await the existing loop.
- Rename canTriggersReconnect() to triggersStateJobReconnect() for clarity.
- demo-app: align connectionTimeoutInMs with the SDK default (5s).

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(core): type SFU connect failures instead of a reconnect flag

Address PR review: reconnect-orchestration state should not live on the
SfuConnectionResult.Failure DTO. Replace the boolean flag with a typed error
so callers of connectInternal branch on the failure kind.

- Add sealed SfuConnectException with Timeout (connect safety-timeout tore down
  a stuck socket; no recovery started) and Disconnected (terminal SFU state).
- connectInternal now returns these typed errors instead of Exception("msg").
- Drop SfuConnectionResult.Failure.hasReconnectStarted; _join self-triggers a
  REJOIN only when the error is SfuConnectException.Timeout, else awaits the
  loop stateJob already started.
- Update tests to construct/assert the typed errors.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Replace SfuConnectException with SfuConnectFailureCause code (#1744)

* fix: Refactor with SfuConnectFailureCause code

* fix: Add kdoc

* fix: Replace Singletone shared Call.testInstanceProvider.rtcSessionCreator with  Call..unitTestRtcSessionFactory

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Aleksandar Apostolov <apostolov.alexandar@gmail.com>
Co-authored-by: Rahul Kumar Lohra <tgunix@gmail.com>
…atency (#1751)

* test(e2e): harden recording and outgoing-call waits against backend latency

The composite recorder can take 20-30s to start and emit call.recording_started,
and the outgoing ringing screen only renders after the call create/ring network
round-trip. The 5s default (recording icon) and 10s (outgoing decline button)
waits time out before the UI appears, causing deterministic findObject NPEs in
testParticipantRecordsCall and testUserRejectsTheOutgoingAudioCall.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(e2e): extend recording-consent and stop waits to 30s

The recording-consent dialog (acceptCallRecording/declineCallRecording) and the
recording-icon disappear assert are all gated on backend recorder events
(call.recording_started / call.recording_stopped), which can take 20-30s. The
10s/5s waits time out first, so testParticipantRecordsCall still failed at
acceptCallRecording after the initial icon-wait bump. Extend these waits to 30s.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(e2e): record for 60s so the recording icon is observable

Composite recording spins up ~10-15s after the request, so recording for only
15s left a ~5s client-visible window that closed before the 3-view assertion
loop could observe the icon. Record for 60s and keep the buddy in the call for
120s (matching the ReconnectionTests pattern) so the icon is reliably visible,
then extend the "recording disappeared" wait to 70s to cover the longer run.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: pass OpenAPI generator config via --opt flags

The chat-side Kotlin generator now takes its configuration through the generic
`--opt key=value` flag (via the Configurable interface) instead of bespoke named
flags. Update the generation script accordingly:

- Convert the generate-client invocation to `--opt key=value` form.
- Drop `--model-dir` (the generator hardcodes the models directory; the flag was
  a no-op) and its now-unused MODEL_DIR variable / argument.
- classes-to-skip is space-separated (the --opt slice flag splits comma lists).
- Point REFERENCE_VALUE at `master`, since the generator changes land there.

Generated output is unchanged (byte-identical vs the previous generator).

* fix: use the renamed android-sdk generator option

The backend generator's SDK option was renamed androidSdk -> android-sdk during
review before landing on master. Update the invocation to match, otherwise
generate-client fails with `kotlin: unknown option "androidSdk"`.

* chore: drop retired --model-dir from the openapi generator gradle task

generate_openapi_v2.sh no longer accepts --model-dir (the backend generator
hardcodes the models directory and never honored the flag), so the Gradle
caller passing it would fail with "Unknown argument". Remove the argument and
the now-unused modelsDir property.

* fix: default the openapi generator to clone master

The Gradle task's default refValue still pointed at the merged (now-deleted)
feature branch, so `generateOpenApiClient` would fail at `git clone --branch`.
Default to master, matching the script's own default and the intended source.
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@9c091bb...3d3c42e)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Gianmarco <47775302+gpunto@users.noreply.github.com>
… loop (#1757)

Three transceiver-lifecycle bugs in Publisher could strand live sendonly
m-lines that SetPublisher.tracks never announces, causing the SFU to
force-rejoin the publisher (VID-1376):

- syncPublishOptions: the cleanup loop compared the cache against itself
  (always true), so every transceiver was torn down on each
  ChangePublishOptions event. Now keep transceivers whose option is still
  requested by the SFU.
- publishStreamInternal fallback: replaced the old transceiver without
  stopping it, leaving an orphaned m-line. Now stop() the old one first.
- addTransceiver: silently overwrote the cache entry for the same
  [track_type, publish_option_id], stranding the old transceiver. Now
  stop() any pre-existing transceiver before adding.

All paths use stop() only (never dispose()) on a live PeerConnection; the
PC owns the native transceiver/sender and frees it safely at teardown.
Disposing mid-session is a use-after-free on network_thread (SIGSEGV).

Adds unit tests covering all three cases.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nto develop-v2

# Conflicts:
#	gradle/libs.versions.toml
#	stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt
#	stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt
#	stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt
#	stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt
#	stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/StreamVideoClientTest.kt
#	stream-video-android-ui-compose/api/stream-video-android-ui-compose.api
#	stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/lobby/CallLobby.kt
#	stream-video-android-ui-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/video/VideoRenderer.kt
Keep UserRepository + StreamConnectionState + StreamClient as v2 SSOT while
adopting develop analytics/degradation-preference changes under io.getstream.webrtc.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@PratimMallick
PratimMallick requested a review from a team as a code owner August 4, 2026 08:16
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

PR checklist ❌

The following issues were detected:

  • Section Implementation is missing or empty.

What we check

  1. Title is concise (5–18 words) unless labeled pr:ignore-for-release.
  2. At least one pr: label exists (e.g., pr:bug, pr:new-feature).
  3. Sections ### Goal, ### Implementation, and ### Testing contain content. Bot-authored PRs are exempt.
  4. PR description references an issue (Linear ticket like AND-123, a Linear URL, or a GitHub Closes #N). Bot-authored PRs are exempt.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-video-android-core 12.27 MB 12.41 MB 0.14 MB 🟢
stream-video-android-ui-xml 5.68 MB 5.73 MB 0.05 MB 🟢
stream-video-android-ui-compose 6.20 MB 5.76 MB -0.44 MB 🚀

@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
55.6% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@PratimMallick PratimMallick added the pr:internal Internal or infra-only changes label Aug 4, 2026
@PratimMallick PratimMallick changed the title chore(core): merge develop into develop-v2 and resolve conflicts chore: merge develop into develop-v2 (with follow-up compile fixes) Aug 4, 2026
@PratimMallick PratimMallick changed the title chore: merge develop into develop-v2 (with follow-up compile fixes) chore: merge develop into develop-v2 Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:internal Internal or infra-only changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants