Skip to content

feat(chat): record video messages from the message input - #6818

Open
ToteMeiSter wants to merge 13 commits into
nextcloud:masterfrom
ToteMeiSter:feat/video-message-record
Open

ToteMeiSter wants to merge 13 commits into
nextcloud:masterfrom
ToteMeiSter:feat/video-message-record

Conversation

@ToteMeiSter

Copy link
Copy Markdown
Contributor

Refs #6812

This is a feature PR; the issue is not approved yet, the PR is opened for discussion.

🖼️ Screenshots

🏚️ Before 🏡 After
The record button records voice only A tap on the record button switches between voice and video; holding it records a video message with a live preview

Feature

A short tap on the record button in the message input switches between voice and video mode (the mode is stored in the preferences and shown by the icon and a hint popup). In video mode:

  • holding the button records with CameraX (front camera, 720p with fallback, about 2.5 Mbit/s, at most 120 s, then it stops and is sent);
  • sliding left cancels, sliding up locks; gestures, lock state, timer and the locked recording view are shared with the voice recording;
  • a live preview is shown above the input and the camera can be switched during the recording;
  • the recording is not lost on a screen rotation: the recorder lives in ChatViewModel like the voice recorder and the camera is bound to a lifecycle of the recorder, not to the activity;
  • a recording which the camera cuts off is shown in the attachment preview instead of being lost;
  • the finished mp4 goes through the regular file upload (a normal video attachment, not a special message type).

Structure

The PR is a stack. It contains the two voice recording fixes as the first commits:

After they are merged only the four commits of the feature remain:

  1. feat(chat): record video messages from the message input
  2. fix(chat): start recording only on hold and show the record hint as a popup
  3. fix(chat): keep a video recording over a screen rotation
  4. fix(chat): block taps and TalkBack focus on the video recording preview

How to check on a device

  1. Open a chat, tap the record button: the mode switches and a hint is shown.
  2. In video mode hold the button: the preview appears and the recording runs. Release to send, slide left to cancel, slide up to lock.
  3. In a locked recording rotate the device: recording and preview go on. Switch the camera during a recording.
  4. A very short recording shows a hint and is not sent.

Tested by the author on a Huawei DEL-LX9 with a fork build that has this feature together with other fork changes; this branch itself was built and unit-tested.

🚧 TODO

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

The change was written with the help of Claude Code (Anthropic). The author reviewed the code, built it and ran the unit tests, and checked the behavior on a device with the fork build.

The observer of the recording state is registered again when the chat
activity is recreated, for example on a screen rotation. LiveData then
delivers the current value to it and the device vibrated as if a recording
had just started or ended.

Vibrate only when the state differs from the one the activity already knew.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A rotation stops the chat activity. ChatViewModel.onStop then stopped the
MediaRecorder, while the locked and in-progress state of the recording stayed.
The screen still showed a running recording and a truncated file was sent.

The view model now keeps the recorder running when the stopping activity is
changing its configuration. It reads this from the lifecycle owner: ON_STOP
reaches the observer before the body of Activity.onStop runs (API 29+), so a
flag set by the activity in onStop would come too late.
The recorder is still stopped when the user leaves the chat.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
A short tap on the record button switches between voice and video mode. The
mode is stored in AppPreferences and shown by the button icon and a snackbar
hint. The result is a regular video attachment.

In video mode holding the button records with CameraX (front camera, 720p
with fallback, about 2.5 Mbit/s, at most 120 s, then it stops and is sent).
Sliding left cancels, sliding up locks. The gestures, the lock state, the
timer and the locked recording view are shared with the voice recording. The
preview is shown above the input and the camera can be switched during the
recording. An unfinished recording is cancelled and the camera released when
the chat is paused. Too short recordings show a hint, camera errors a
message. The finished mp4 goes through the regular file upload.

Refs nextcloud#6812

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
… popup

The record button used to start a recording on the first touch, so a short
tap that should switch the mode started a recording first. Recording now
starts only when the button is held. A tap shows the record hint as a popup
above the button instead of a snackbar, placed in the window of the button
and kept above it after layout changes.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The activity is recreated on rotation while the video recorder belonged to
it: onPause cancelled the recording and deleted its file, and binding the
camera to the next activity made the CameraX recorder configure itself in a
state where that is not allowed.

- The video recorder lives in ChatViewModel like the voice recorder. The
  activity attaches its preview and callback, and the recorder lets go of
  them when the activity is destroyed. A result that arrives without an
  activity is delivered to the next one.
- The camera is bound to a lifecycle of the recorder itself, not to the one
  of the activity, so it stays bound while the activity is recreated.
- A new activity picks up the recording and locks it when it was held, so the
  timer, stop, send and cancel are shown.
- The recording is cancelled when the user leaves the chat, not on a
  configuration change.
- A recording cut off by the camera is not lost but shown in the attachment
  preview.
- The video is recorded in the rotation of the sensor.
- A recording is never stopped while a switched camera settles.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
The container of the video recording preview did not consume touches, so
taps on it reached the chat below. Make it consume them and hide it from
TalkBack, where it is only decoration.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d8965fe7-b923-4df9-97d5-2e79c0a35e01
📥 Commits

Reviewing files that changed from the base of the PR and between 2e9ec35 and 59de428.

📒 Files selected for processing (5)
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/VideoMessageRecorder.kt
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/nextcloud/talk/chat/VideoRecordingRecreationTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/res/values/strings.xml

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The chat recording flow now supports video messages alongside voice recordings. Taps switch between voice and video modes, while holds start recording after a threshold. Video capture uses CameraX and supports camera switching, orientation handling, and activity recreation. The change adds mode-specific permissions and controls, persists the selected input mode, and adds tests for gestures, recording outcomes, lifecycle behavior, and preview placement.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 59de4

The change adds video message recording to the chat input, and the earlier review concerns appear to be fixed. No remaining merge-blocking issue is evident from the supplied review, though real-device camera behavior was only tested by the author.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 59de4

Recording remains permission-gated and tied to the selected chat. However, sending during a camera switch can leave capture running briefly after the send action, and recording completion during screen recreation can bypass chat-readiness checks. These lifecycle risks affect privacy and recovery of sensitive recordings.

Retained concerns

  • Medium · security · inferred: Sending during a camera switch records the SEND decision immediately but defers the actual stop until the settling timer expires. Persistent camera/audio capture is not frozen at that decision, and a later background cancellation cannot override SEND. Consequently, the uploaded recording can include audiovisual content captured after the user requested sending, including after leaving the chat. The delay addresses a CameraX shutdown race, but does not preserve the user's capture endpoint.
  • Medium · reliability · inferred: The recreation flow defers an already-pending result until chat metadata is ready, but attaches an active recorder regardless of readiness. If that recorder finalizes before metadata arrives, its callback immediately uploads or previews the file. The recorder has already cleared its output and does not retain the result when a callback exists. Upload requires a current conversation, and preview requires initialized capabilities, so this ordering can fail after sensitive-file ownership has been relinquished, defeating the intended recovery handoff.
Security review details

Security Blast Radius

  • inferred — The identified privacy exposure concerns camera and microphone content from a user's recording session, delivered through the selected chat's normal attachment path. Unintended trailing content could reach that chat's recipients. The inspected change does not establish cross-account access, elevated privileges, or a remote capture endpoint.

Security Findings and Attack Paths

  • inferred — The privacy path requires an authorized user to record, switch cameras, and send during the settling window. SEND is committed before capture stops, so subsequent content may be uploaded without a new confirmation. This is an introduced capture-consent boundary risk, not evidence that an external attacker can bypass Android permissions.

Trust Boundaries and Controls

  • observed — Local account resolution remains ahead of chat initialization, while capture is reached through the user hold and permission checks. Upload dispatch supplies the activity's room token. The new recording methods are internal application APIs rather than exported Android component entrypoints.

Resilience and Maintainability Implications

  • observed — Discard-first finalization cannot become SEND, and releasing the recorder deletes pending output and suppresses delivery. These controls contain ordinary cancellation and teardown. They do not freeze capture during deferred stopping or make an attached completion callback readiness-aware.

Hardening Proposals

  • proposed — Separate the user's capture endpoint from deferred encoder finalization: freeze capture at the accepted stop decision using a CameraX-supported mechanism, and validate send-then-background behavior on devices. Keep completed-file ownership in chat state until a readiness-aware consumer accepts delivery, including when an active recorder finishes during recreation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.23% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 212 functions across 24 files. (1 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: recording video messages from the chat input.
Description check ✅ Passed The description explains the feature, includes before-and-after behavior, TODOs, testing steps, dependencies, and checklist details. The milestone checkbox remains unchecked, but the description is ot…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 21.23% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 212 functions across 24 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b493e8b5-ae18-41b3-a265-55028ef6b613
📥 Commits

Reviewing files that changed from the base of the PR and between 1e9c2f1 and 4528d9e.

📒 Files selected for processing (27)
  • app/build.gradle.kts
  • app/src/main/java/com/nextcloud/talk/chat/CameraLens.kt
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordButtonGesture.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordHintPopup.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordInputMode.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordingStopCoordinator.kt
  • app/src/main/java/com/nextcloud/talk/chat/VideoMessageRecorder.kt
  • app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt
  • app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java
  • app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt
  • app/src/main/res/drawable/bg_record_hint.xml
  • app/src/main/res/drawable/ic_record_hint_arrow.xml
  • app/src/main/res/layout/activity_chat.xml
  • app/src/main/res/layout/view_message_input.xml
  • app/src/main/res/layout/view_record_hint.xml
  • app/src/main/res/values/dimens.xml
  • app/src/main/res/values/strings.xml
  • app/src/test/java/com/nextcloud/talk/chat/RecordButtonGestureTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/RecordHintPopupPlacementTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/RecordingStopCoordinatorTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/ShouldVibrateOnRecordingChangeTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoMessageRecordingTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoRecorderLifecycleTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoRecordingRecreationTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt Outdated
@mahibi

mahibi commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Hi @ToteMeiSter thank you for your contributions.
We will review them soon

…g start

The start time was taken on ACTION_DOWN, but the recording starts only after
the 400-600 ms hold threshold, so a voice message was judged too short
(or long enough) by a duration which included the hold. Take the time in
beginRecording() after the recording has started and use the monotonic clock.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…alized it

Send or delete of a locked video recording cleared the lock and the
in-progress state at once, while CameraX was still finalizing the file. A new
recording started in that window was silently dropped in beginRecording().
For video the state is now released in onVideoRecordingFinished(), which
CameraX reaches for every outcome (send, cancel, error, too short). Audio, and
a video recorder which is already idle, are still cleared at once.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…rded frame

The locked video recording preview was a fixed 180x240dp box floating over the
message bubbles, squashed to about 2:1 in landscape. Now a scrim dims the chat
pane (it blocks touches and is hidden from TalkBack), and the preview is centered
in it in the aspect of the recorded frame, fitted with margins, at most 75% of
the width and 480dp on the longest side. The placement is a pure function
(videoPreviewPlacement) with unit tests; it is recomputed when the pane changes
size and when the recorder learns the frame aspect from CameraX.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
…entation, hide chat from TalkBack

- Center by Gravity.CENTER and change only width and height: the margin
  comparison never settled in RTL and re-laid out the chat every frame.
- The frame aspect is turned into screen coordinates by the difference of the
  recording and the display rotation (screenFrameAspect), so the frame matches
  the picture with auto-rotate off and after a turn during the recording.
- While recording, the siblings of the scrim are hidden from TalkBack and get
  their previous values back afterwards.
- The preview size is recalculated after a camera switch.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
In video mode the locked recording panel is one row: delete, red dot in a
progress ring of the 120 s limit, timer, send. The voice panel is unchanged.
The preview keeps a smaller gap on a short area.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
- Hide the whole recording indicator from TalkBack, so the progress ring
  does not announce its percentage every second.
- Relative padding of the timer, so the gap stays between dot and timer in RTL.
- No inset around the progress ring, so it fills its 24dp.
- The ring scale is set in code only (PROGRESS_MAX).

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1db88cfb-cd9e-4493-9d4e-d139a302b51f
📥 Commits

Reviewing files that changed from the base of the PR and between 4528d9e and 2e9ec35.

📒 Files selected for processing (16)
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputVoiceRecordingFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/RecordButtonGesture.kt
  • app/src/main/java/com/nextcloud/talk/chat/VideoMessageRecorder.kt
  • app/src/main/java/com/nextcloud/talk/chat/VideoPreviewPlacement.kt
  • app/src/main/res/drawable/video_recording_dot.xml
  • app/src/main/res/layout/activity_chat.xml
  • app/src/main/res/layout/fragment_message_input_voice_recording.xml
  • app/src/main/res/values/colors.xml
  • app/src/main/res/values/dimens.xml
  • app/src/test/java/com/nextcloud/talk/chat/RecordButtonGestureTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoFrameAspectTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoPreviewPlacementCompactPanelTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoPreviewPlacementTest.kt
  • app/src/test/java/com/nextcloud/talk/chat/VideoRecordingRecreationTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/test/java/com/nextcloud/talk/chat/RecordButtonGestureTest.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt Outdated
A video recording result that waited for a new activity was delivered from onCreate, before currentConversation
and spreedCapabilities were set: the send path and the preview path could throw. The pending result is now
delivered once the initial capabilities state has initialized both; a running recording still reattaches at once.

The delete and send buttons of the compact video row announced the voice recording labels to TalkBack.

Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Assisted-by: Claude-Code:claude-opus-5-5
@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

Update: four commits were added on top of the original change. They refine the video message UI of this PR and were tested on a device (Huawei DEL-LX9) with a fork build:

  • 5af90c0 — dim the chat behind the recording and fit the preview to the recorded frame;
  • 0367437 — center the preview, follow the screen orientation, hide the chat from TalkBack while recording;
  • c0587e2 — one-line recording panel for video messages with a larger preview;
  • 2e9ec35 — review fixes of that panel.

They stay in this PR instead of a separate one because they change the same recording UI that this PR introduces; a follow-up PR on top of an unmerged one would need the same code reviewed twice. The CodeRabbit findings are fixed in 9447da7, 18b8f2a and 59de428 (answers in the threads). All commits are carried up the stack into #6819 and #6820.

This comment was prepared with the help of an AI assistant (Claude Code) and reviewed before posting.

@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Labels

3. to review Waiting for reviews AI assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants