Skip to content

Improve intent handling - #6989

Merged
AndyScherzinger merged 75 commits into
masterfrom
improveIntentHandling
Nov 3, 2026
Merged

AndyScherzinger merged 75 commits into
masterfrom
improveIntentHandling

Conversation

@mahibi

@mahibi mahibi commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Reworks how the app is opened: from links, shares, shortcuts and notifications. The goal is more consistent navigation and more reliable behaviour.

Changes

  • Entry points: the launcher, links, shares, shortcuts and notifications now go through a common internal path into the conversation list. This replaces several separate code paths.
  • Links: nextcloudtalk:// links are handled in one place. For conversations and calls the user hasn't joined yet, the app asks before continuing. The splash screen now shows the app logo when the app is
    opened from a link.
  • Sharing: shared files and text are handled more robustly. Shared content is always shown in the preview before it is sent. Temporary copies of shared files are cleaned up.
  • Shortcuts: home screen and direct share shortcuts open the conversation once the conversation list is loaded. If the conversation no longer exists, a hint is shown.
  • Uploads: files picked in the chat can still be uploaded after the app was in the background, for example when the upload waited for the network.
  • App lock: fixed a loop where pressing Back on the lock screen or on the device credential screen showed the lock again instead of leaving the app.
  • Cleanup: removed the old MainActivity routing and some unused extras, and simplified the shortcut and focus handling.

Testing

  • Unit tests added for routing, shares, shortcuts, file handling and uploads.
  • Tested manually on a device:
    • opening links while the app is running and while it is closed
    • sharing from other apps
    • home screen and direct share shortcuts
    • App lock with and without a fingerprint
    • uploading picked files while offline

🏁 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

mahibi added 30 commits November 3, 2026 11:22
ShareActivity is now the entry point of the share sheet and its direct share targets. It copies the shared files into the cache of the app, so the chat and the upload don't depend on the read access of the other app and no media permission is needed, and opens the list in share mode with that content. A direct share target is only preselected if the app published it, uris and files that can't be read are left out, and the copies are kept until their upload is done.

The chosen chat opens in the running task of the app, or in a new one with the conversation list below it, and the attachment preview asks before uploading instead of a dialog in the list.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…MainActivity

MainActivity only routed the intents it got on to other screens. It is removed:
the launcher activity is now an alias of the conversation list under its old name,
which home-screen icons and pinned shortcuts refer to, and a launcher tap brings
the task of the app to the front by itself.

- Entry points: the conversation list has an internal alias for the intents the
  app starts itself (InternalEntryPoint). Deep links are taken through
  DeepLinkActivity.
- Deep links and contact chats: DeepLinkActivity, the entry point for
  nextcloudtalk:// links and the chat action of phone book contacts, passes their
  uri on to the list. The list routes them in DeepLinkRoutingViewModel: it asks
  before joining a conversation that is not in the local list and before starting
  a conversation with a contact, doesn't use an account scheduled for deletion,
  and keeps a question across configuration changes and process death. A link
  that opens no conversation does nothing.
- Notifications: a notification opens its chat, the invitations or the list of
  its account directly, as a new back stack built with TaskStackBuilder, with the
  list of the account below. Their pending intents and the microphone action of
  picture-in-picture are immutable. A room token is checked with the token
  pattern.
- Shortcuts: the conversation shortcuts of the list and of notifications open the
  deep link of their conversation, and old ones that only had extras are given it.
- Tasks: the app opens its chats in the running task of the app, removes the
  tasks of the app that are only left in the recents, and tolerates a task that
  was removed meanwhile (AppTasks). The app is restarted in a fresh task, and
  without an account the list opens the login. The crash screen also tolerates
  removed tasks when it removes the other tasks of the app.
- Screen lock: the key is created once when the app starts with the conversation
  list instead of whenever it is resumed, and the check creates it when it is
  missing, e.g. when the app is started at a chat of a notification.
- The unused start of a call from the extras of a notification, the layout of
  MainActivity and ActionBarProvider are removed.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The launch screen was the window background of the application theme. The conversation list, the launcher activity, now uses the splash screen of androidx.core:core-splashscreen, which Android only shows when the app is launched with the list, e.g. not when a notification opens a chat. A deep link that starts the app keeps it until its chat covers the list or it asks whether to join, at most 2 seconds. Switching the account no longer replaces the list at the root of the task, which Android showed with the color of the splash screen: the list switches itself in place. The theme is taken from #6937.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The conversation list, which routes a deep link, showed the lock screen and
routed the link at the same time. The routing now waits until the list is
resumed after unlocking.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
BaseActivity.startActivity turned any ACTION_VIEW of a call link of the
own server into a ChatActivity intent, also the explicit forward of a link
to DeepLinkActivity, which then skipped the routing of the link. Explicit
intents now start the screen they name unchanged.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…nlocked

The pending shortcut token was used by the room list collector, which runs
regardless of the lifecycle of the list. The token is now only used while
the list is resumed and not covered by the lock screen, as DeepLinkRouting
does. After unlocking, the list is resumed and the room list emitted again,
so the conversation opens then.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
isTrustedShareUri only treated a uri as the app's own when its authority
equaled the package name, which missed the other providers of the app,
e.g. of the ImagePicker library, and uris with a user id prefix.

The user id prefix is now ignored, and only the shared attachments and
copies of shared files of its FileProvider are taken from the own app.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…ain intent

onRestart relaunched the list with a copy of its own intent. If the list
was created for a deep link, the new list routed that link again: a
declined "Join conversation?" was asked again, the "no account" toast
showed again, or the old chat reopened and switched the default account
back. For a contact chat link Talk can't read, the read permission grant
made startActivity throw a SecurityException.

Forwarding and sharing are already excluded there, so the new list gets a
plain intent with only the account.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
BaseActivity writes the default account into a launcher intent, so that a
recreation after a configuration change stays on that account. But the
recreation reuses the same intent, and removeExtrasOfApp removed the
account again because the component is the launcher alias. The list then
bound the default account of that time, while its view models stayed on
the previous one: e.g. after picking another account while sharing,
rotating and going back, the list showed the conversations of one account
with the other account, and opened them with it.

The intent is now set to the one of the internal entry point with the
account, as showNewIntent does. The intents of other apps that the list
doesn't show were passed on before, and their extras of the app removed.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…s chat opens

The deep link routing made the account of the conversation active while
its chat was still held back, e.g. by the lock screen covering the list.
After unlocking, the list of the previous account ran onRestart first,
saw the new default account and relaunched for it with its old intent.
The pending chat was dropped with the finishing list, so only the list of
the other account showed. The same happened when the default changed
while the list was stopped.

The account is no longer made active there. The list of the account that
the chat opens above makes it active in onCreate.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…or deep link started

The launcher is an alias of the conversation list with the standard launch
mode. A task that a notification or DeepLinkActivity started has no
MAIN/LAUNCHER root intent, so a launcher tap added another list on top of
it instead of bringing it to the front, and each list ran the start-up
code again.

A list that the launcher starts on top of a running task of the app, not
as its root, now finishes right away, which shows that task. A list that
another app starts in its own task with the launcher intent is still
shown. taskIdCompat moves from CallActivity to AppTasks for that check.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Whether the app was locked depended on the lock timeout, and going to the
background reset the time of the last use also while the app was locked.

The app is now locked until LockedActivity is unlocked by authenticating:
while it is locked, every screen that is resumed shows the lock screen
again. The time of the last use isn't reset while the app is locked, so it
is still locked after its process was killed.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
ShareActivity copies each shared file into the cache and passes on the
FileProvider uri of the copy. FileUtils.getFileFromUri, used by the
attachment preview and UploadAndShareFilesWorker, copied every content
uri into the cache again, and that second copy was never deleted. So a
shared file needed twice its size in the cache, and a share could fail
when free space ran short.

getFileFromUri now returns the file behind an own FileProvider uri of the
copies of shared files or the shared attachments, resolved like the check
of shared uris, instead of copying it again. isSharedAttachmentProviderPath
becomes sharedAttachmentProviderFile, which returns that file.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…ng it

Leaving or deleting a conversation restarted the app with the launcher
list, which shows the default account. A chat opened from a notification,
the media notification or the audio player of another account doesn't
make that account the default anymore, so afterwards the list of the
default account showed. The same happened after a call of another account
when no task of the app was left.

Leaving or deleting a conversation now recreates the list in the task for
the conversation's account (createAccountSwitchIntent with CLEAR_TOP),
which also closes the screens above it. NEW_TASK isn't used there: with
the empty task affinity it would start a second task when the list in the
task was started from the launcher. A call without a task of the app left
starts the list of its account in a new task.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The tasks of the app were found by the alias in the base intent of their
root. Before Android 10, the base intent of a task that an alias started
names the target of the alias instead, so no task of the app was found:
openChatInAppTask always took its fallback, which closed the screens open
in the app task instead of opening the chat on top, and CallActivity could
take a share task as the primary task and remove the real app task.

The root is now taken from origActivity, which names the alias on every
version, before the base intent.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…ecents

Before Android 10, a task relaunched from the recents gets its intent with
the target of the alias it was started through, so the list of a deep link
that DeepLinkActivity passed on took it for a VIEW intent of another app.
It passed the old link on to DeepLinkActivity again, and the forward
dropped FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY, which bypassed the history
guard of DeepLinkRouting: the link was routed again.

A VIEW intent launched from the recents is no longer passed on, as its
link was taken already. The list shows without routing it.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…rtcut

The conversation shortcuts, also those of notifications, opened their
conversation through a nextcloudtalk:// deep link, which names only the
host and user name of the account. With two accounts of the same user on
one host, e.g. on another path or port, a shortcut of one account opened
or asked to join the conversation on the other. The update of the
shortcuts of earlier versions turned exact notification shortcuts into
such links.

The system starts a shortcut as the app that published it, so the
shortcuts now target the internal entry point of the conversation list
with the account and room token as extras, like the direct share targets.
The shortcuts of earlier versions, with extras or a deep link, are given
this intent, with the account and conversation of their id. Only legacy
home screen shortcuts, which the launcher starts as itself, keep the deep
link.

The list now takes the room token of a shortcut once: it opened the
conversation again whenever the list was resumed, e.g. when going back
from it. A token not opened yet is kept with the instance state.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The shared files were copied in a blocking loop. Leaving ShareActivity
cancelled the coroutine, but every file was still copied to the end and
then left in the cache, as copyToCache also caught the cancellation. A
recreation of ShareActivity, e.g. after the font scale or locale changed,
started a second full copy into a new directory.

The copy now stops between files and buffers when it is cancelled, and
removes the copies of the share. ShareActivity handles all configuration
changes itself, as it only shows a progress indicator.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The copies of shared files were only pruned when a later share with a
file started. Prune the done shares also at app start, and remove the
copies of a share right away when it is left in the conversation list
or in the attachment preview, unless an upload of them is unfinished.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…eated

After confirming to start a conversation with a contact, a failed
createRoom, e.g. offline or rejected by the server, or a response
without a conversation token, silently stayed in the conversation list.
Let the failure reach the routing, which shows the error, as for any
failed step.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The list is no longer a single singleTask activity, so the setup meant
for once per start of the app, the push registration and the key of the
screen lock, ran for every new list, e.g. for a notification, a share or
another account. Run it once per process.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The user id of a contact chat was read from any data row of the passed
uri, e.g. the email address of a contact, which has the form of a user
id at a server as well. Read it only from a row of Talk's contact chat
type.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
A list that finishes in onCreate, e.g. for a launcher tap on a task of
the app or an intent it passes on, has neither its account nor its deep
link routing. The system doesn't pass new intents to a finishing
activity; guard onNewIntent anyway instead of reading them uninitialized.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The system clears the cache when the storage runs low, also while the
uploads of the copies still wait for the network. Keep the copies in
the files directory instead, which the app prunes itself, and exclude
them from backups.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…st intent

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…pository

The routing of contact chats built the create room request itself, as
ContactsRepository.createRoom already does.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
setUpBoundUserOrFinish already opens the login when there is no
account, so the list doesn't read the accounts on the main thread for it
beforehand.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The room token of the chat intent was checked with the token pattern
where it enters the app, but not by the chat itself. A chat started
without a valid token now finishes instead of crashing, and a room
switch only goes to a valid token.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
mahibi added 18 commits November 3, 2026 11:22
…user

getFileFromUri didn't recognise a uri with the user id prefix of the own user
("0@authority") as one of the app's copies and copied it into the cache by
its name, where a file of the same name would have been taken instead, and
its upload got no tag of its share, so its copy could be removed while the
upload still waited. They now drop a prefix of the own user. A uri of
another user, e.g. of the app in another profile, isn't taken for one of the
app's copies.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…move

The removal of the copies asked WorkManager for the uploads of each share
on its own, one after another, when the app starts and when a share starts.
It now asks once for all of them.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
… and shared files

AGENTS.md asks for brief comments matching the surrounding code.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…the recents

A task a shortcut started keeps its intent, so when its list was gone and
the task was relaunched from the recents, the conversation of the shortcut
was opened again.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
When the default account changed while the list was in the background, the
list switched to it in onRestart, before onNewIntent() delivered e.g. a
shortcut or deep link, which was then lost with the old list. The check is
now armed when the list is stopped and done on resume, and not for a new
intent, which chooses its account itself. A list that switches doesn't load
its data anymore.

It waits while the lock screen covers the list: the list of the default
account would start above it and open a second lock screen.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Leaving the app while the lock screen is shown, e.g. for the screen lock of
the device, set the lock check for its return, which the screen below then
ran after unlocking: with no timeout it locked again right away.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
A list that passed a share on to ShareActivity still removed the copies its
uris named when it finished, which could be those of another share in
progress.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The routing of deep links and the list each read the flag of a relaunch from
the recents themselves, which all have to agree on.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…Android 12 on

From Android 12 on, cloud backups and device transfers of an app targeting
it ignore the full backup rules and only follow the data extraction rules,
so the copies were backed up.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The conversation of a shortcut that wasn't listed yet opened whenever a sync
added it while the list stayed shown, also minutes later. It now opens at
the latest when the sync of the list ends, reading the stored conversations,
or a snackbar tells that it wasn't found, also on a slow network. A search
started meanwhile drops it, so it doesn't open over what the user does.

The list keeps it in its saved state only when it is recreated, not when it
was left: before Android 9, the state is saved before the list is stopped,
which restored a conversation the user had left.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…r room

Opening the chat of an audio message from the player of a chat put the new
chat on top of the shown one, so chats piled up, each joined to its room. The
chat of another room now replaces the shown one. A thread and the chat of
its room stay, as Back leads from one to the other.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The call recognised the task of a share by the action of its intent, beside
AppTasks, which tells it by its root.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…lf shortcut

The note to self shortcut opened its chat with the focus in the message
input, which isn't needed. The extra, and the state the conversation list
kept for it while the conversation of a shortcut was pending, are removed.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
It holds the conversation of any shortcut that started the list, the
conversation shortcuts as well as the direct share targets.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
The photo picker and the file picker grant the read access to a picked file
only to the chat that picked it, until it is closed or the process is
killed. An upload that waited for the network, e.g. while the app was in
the background, then couldn't read its file and failed with a
SecurityException. The access is now persisted when the file is sent, and
released when its upload ends or is cancelled, unless another upload of
the file still needs it, and for leftovers when the app starts.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Since Android 13, the system shows the splash screen of a start that isn't
one of the launcher as a plain color, without the icon of the app. The
conversation list keeps it while it routes a link that started the app, so
a link showed a blue screen without the logo. DeepLinkActivity now asks for
the splash screen with the icon.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…ithout unlocking

Without a fingerprint, or after the biometric prompt failed, the lock screen
shows the screen lock of the device. Leaving it with Back returned to the
lock screen, which asked again right away, so the screen lock came back and
the app couldn't be left. It now leaves the app like Back on the lock
screen. Closing the biometric prompt with Back keeps the lock screen instead
of asking for the screen lock of the device, and its callbacks run on the
main thread.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
…a link

A nextcloudtalk:// link to a known conversation opened its chat right away,
and the chat of a voice room joins its call as soon as it has joined the
room. Such a link now opens the voice room through the conversation list,
like a conversation shortcut, which asks whether to join the call, as when
it is opened from the list.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
@mahibi mahibi added this to the 26.0.0 milestone Nov 3, 2026
@mahibi mahibi self-assigned this Nov 3, 2026
…list

LoginIT started the app through the launcher intent into an
ActivityScenario<Activity>, which has no defaultAccountProvider, so the
android tests didn't compile. The launcher alias opens the conversation
list, so the scenario is typed with ConversationsListActivity.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
room-testing-android and junit-ktx were dependencies of the app, so it
shipped junit and hamcrest-core 1.3. The android test APK leaves out what
the app already has and is held to its versions, so it lacked
org.hamcrest.Matchers and Espresso's withId() failed with a
NoClassDefFoundError. Both are only used by the android tests and are now
dependencies of those. The changed dependencies need the checksum of
core-common-2.1.0.pom.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Lint takes RecentTaskInfo.origActivity for the field of TaskInfo, which was
added in API 29, and reports it as a new API. RecentTaskInfo declares the
field itself since API 1 and only inherits it since API 29, and the bytecode
reads it from RecentTaskInfo, so it exists on every supported version. The
check is suppressed for the getter that reads it.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Result.Retry is restricted to the library group of WorkManager, so lint
reported its use in UploadAndShareFilesWorker. The result is now compared
with Result.retry(), which equals every retry, so the access to the picked
file is still kept for a retry and released otherwise.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Codacy

Lint

TypemasterPR
Warnings1414
Errors00

SpotBugs

CategoryBaseNew
Bad practice77
Correctness88
Dodgy code4040
Internationalization33
Malicious code vulnerability33
Performance88
Security1111
Total8080

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

📱 QA build

Download app-qa-debug.apk
QR code Open the QR code for this download
Commit 5135a16
Version 6989
Available until 7 days after this build

The QA build installs alongside a released Nextcloud app, so you can keep
using your existing install while testing.

Downloading the file requires a GitHub account, so open this link on the
device you want to test on, or transfer the APK to it.

@AndyScherzinger
AndyScherzinger merged commit 3342da7 into master Nov 3, 2026
20 of 22 checks passed
@AndyScherzinger
AndyScherzinger deleted the improveIntentHandling branch November 3, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants