Skip to content

Merge upstream AnkiDroid - #57

Merged
float3 merged 192 commits into
float3:ankiquestfrom
ankidroid:main
Oct 5, 2026
Merged

float3 merged 192 commits into
float3:ankiquestfrom
ankidroid:main

Conversation

@float3

@float3 float3 commented Sep 30, 2026

Copy link
Copy Markdown
Owner

33 new commits from ankidroid/Anki-Android main.

Merge with a merge commit, never squash or rebase, so ankiquest keeps upstream's history.

If this conflicts, merge ankidroid/Anki-Android main into a branch from ankiquest, resolve there, and merge that branch instead; this pull request then shows as merged.

david-allison and others added 30 commits September 29, 2026 16:21
Part of 19572

Assisted-by: GPT-6
This enables `UnusedResources` and `StringFormatMatches` to run
 cross-module, which will be necessary when our strings move to
 `:common:android`

Part of 21500

Assisted-by: Claude Fable 5
`sdkmanager` can fail with "Error on ZipFile unknown archive" before the
emulator starts.

Pre-install the emulator to work around this while upstream fixes the bug

Fixes 21885
Related to https://redirect.github.com/ReactiveCircus/android-emulator-runner/issues/496

Assisted-by: GPT-6
This allows searching when tags are loading, or with an empty collection.

Part of 10397

Assisted-by: GPT-6
In preparation for better handling of progress, errors and overlap protection.

Part of 10397

Assisted-by: GPT-6
Stops an operation from clearing progress while another is running.

This now ignores requests made while an operation is in progress
 (the UI should block operations when this occurs).

* Remember the unfinished job
* Check completion via 'isCompleted'
* set the instance variable for the next job before running it
* State is only modified for accepted requests

Part of 10397

Assisted-by: GPT-6
e.g. a tag rename

Part of 10397

Assisted-by: GPT-6
A screen rotation could hide a snackbar before a user had time to see it

Part of 10397

Assisted-by: GPT-6
Gradle task validation failed when lint and jacocoUnitTestReport ran in
one invocation because report tasks scanned all of build/ for coverage.
RuntimeEnvironment.setQualifiers(+night) recreates ActivityThread leftovers
(often DeckPicker), which flakes ThemesTest mid-suite. Add an after-test
guard and dispose the current offenders.

tearDown force-removes owned controllers only; clearAll would hide
ActivityScenario leftovers from NoLiveRobolectricActivitiesRule.

Preserve original test failures alongside leak reports via
MultipleFailureException, and use a low Rule.order so the check runs
outside other rules after their activity cleanup finishes.

Dispose AppCompat delegates during leak cleanup, and report owned
activity cleanup failures after remaining teardown finishes.

Assisted-by: GPT-6
Co-authored-by: Cursor <cursoragent@cursor.com>
checkDependencies rejects setting WrongThread to informational while :common leaves that Android check disabled. Informational enables it without failing :common:lint.

Co-authored-by: Cursor <cursoragent@cursor.com>
For a locale-only tag such as {{tts fr_FR:Front}}, voiceForTag fell
back on the first voice matching the language without checking
availability. Android can list voices flagged KEY_FEATURE_NOT_INSTALLED,
so an uninstalled voice could be chosen and fail on playback with
ERROR_NOT_INSTALLED_YET even when an installed voice for that language
was available.

The language fallback now prefers an installed voice, only settling for
an unavailable one when nothing else matches.

The fix and its tests come from David Allison's review suggestion,
generated with GPT-6.

Co-authored-by: David Allison <62114487+david-allison@users.noreply.github.com>
Assisted-by: GPT-6 [fix and tests]
An Activity can report `STARTED` after a `finish()` call: if a WebView
renderer callback executed, if this opens a dialog, it crashes.

In these exceptional cases, skip displaying the dialog.

Fixes 6244

Assisted-by: GPT-6
* Manage Fields
* Deck Picker + Study Options

Part of 21794

Assisted-by: GPT-6
FAB is still in the background

Part of 21794

Assisted-by: GPT-6
Content was covered when entering custom study options

Part of 21794

Assisted-by: GPT-6
WebView text-only zoom (`textZoom`) does not scale em-based layouts (used by MathJax).

It would have required fragile, font-specific workarounds to fix on the MathJax level:
* `text-size-adjust` had no effect.
* `font-size-adjust` required compensation for each font.

Instead, set `textZoom` to 100 (from 130 [system-dependent]) and scale using CSS zoom.
This scales the text size and layout together.

BREAKING CHANGE:
Some layouts are changed by this, see my list on 20776

Fixes 20776

Assisted-by: GPT-6
Part of 20776

Assisted-by: GPT-6
Keeps a strong reference to a pending snackbar so it can't be GCed

The reference is removed when the snackbar is shown or dismissed
 (without clearing a newer replacement).

Fixes 22076

Assisted-by: GPT-6
`AppLifecycleObserver.onStop` blocked on the collection queue,
This blocked WorkManager's timeout callback, crashing the app if
`dataSync` timed out.

```
android.app.RemoteServiceException$ForegroundServiceDidNotStopInTimeException: A foreground service of type dataSync did not stop within its timeout: ComponentInfo{com.ichi2.anki/androidx.work.impl.foreground.SystemForegroundService}
	at android.app.ActivityThread.generateForegroundServiceDidNotStopInTimeException(ActivityThread.java:2875)
	at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:2837)
	at android.app.ActivityThread.-$$Nest$mthrowRemoteServiceException(Unknown Source:0)
```

Fixes 19749

Assisted-by: GPT-6
With the keyboard up in landscape, Filter by tag froze AnkiDroid for
seconds at a time and could end in "AnkiDroid isn't responding". The
dialog had so little height left that the tag list was laid out with a
negative height, which RecyclerView read as a huge one, so it built a
row for every tag, again on each keystroke.

The list now sits in its own container, so it shrinks to nothing
instead.

Part of 22082

Assisted-by: Opus 5.5
On a phone in landscape, opening the keyboard squeezed the tags dialog
into the strip above it: the toolbar was cut off and no tags were
visible.

On short screens the dialog now keeps its size and the keyboard covers
its lower part, so the search field and the first matches stay in view,
and the buttons come back once the keyboard closes. Rotating with the
dialog open switches between the two.

Fixes 22082

Assisted-by: Opus 5.5
Add a ViewModelSavedStateHandle alias and a factory that supplies an empty
set of default arguments to AndroidX. Keep the remaining creation extras
intact so saved-state ownership and restoration still use AndroidX.

Document that the alias alone does not filter arguments and previously
saved values remain intact. Launch inputs belong in constructor parameters.
Existing ViewModels are not migrated in this preparatory change.

Test activity and fragment owners with EmptyApplication, including an
unfiltered model that must still receive the original owner arguments.
Both cases fail when the factory copies those arguments.

Part of 21953

Assisted-by: GPT-6
Part of 21953

Assisted-by: GPT-6
ExportReadyViewModel copied any intent extras from AnkiActivity into its
instance state, potentially triggering a `TransactionTooLargeException`.

Use our new `savedStateViewModelFactory` to avoid this.

Part of 21953

Assisted-by: GPT-6
Calling `.cancel()` on the progress monitor did not wait for completion,
so a notification + 'Cancel', associated with a completed WorkManager
could appear.

Fix: make the monitor a child of the sync operation via `coroutineScope`. The scope will
 then wait for the monitor to finish before cleaning up notifications.

Fixes 19830

Assisted-by: GPT-6
ShaanNarendran and others added 27 commits October 4, 2026 00:19
13 files

AnkiDroid is standardizing licenses to SPDX.

Issue 20954

This change was not performed using LLMs and I have verified that it is
correct
build(deps): bump actions/setup-java from 6.0.0 to 6.0.1

Bumps [actions/setup-java](https://github.com/actions/setup-java) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](actions/setup-java@v6...de7274f)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

build(deps): bump com.google.testparameterinjector:test-parameter-injector

Bumps com.google.testparameterinjector:test-parameter-injector from 1.23 to 1.24.

---
updated-dependencies:
- dependency-name: com.google.testparameterinjector:test-parameter-injector
  dependency-version: '1.24'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

build(deps): bump the non-breaking group

Bumps the non-breaking group in /tools/localization with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [dotenv](https://github.com/motdotla/dotenv) | `18.0.2` | `18.0.4` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.6.2` | `26.6.3` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.70.1` | `8.71.0` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.70.1` | `8.71.0` |
| [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) | `18.3.0` | `18.4.0` |
| [prettier](https://github.com/prettier/prettier) | `3.9.8` | `3.9.9` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.12` | `29.4.14` |

Updates `dotenv` from 18.0.2 to 18.0.4
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](motdotla/dotenv@v18.0.2...v18.0.4)

Updates `@types/node` from 26.6.2 to 26.6.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@typescript-eslint/eslint-plugin` from 8.70.1 to 8.71.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.70.1 to 8.71.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.0/packages/parser)

Updates `eslint-plugin-n` from 18.3.0 to 18.4.0
- [Release notes](https://github.com/eslint-community/eslint-plugin-n/releases)
- [Changelog](https://github.com/eslint-community/eslint-plugin-n/blob/master/CHANGELOG.md)
- [Commits](eslint-community/eslint-plugin-n@v18.3.0...v18.4.0)

Updates `prettier` from 3.9.8 to 3.9.9
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/prettier@3.9.8...3.9.9)

Updates `ts-jest` from 29.4.12 to 29.4.14
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](kulshekhar/ts-jest@v29.4.12...v29.4.14)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 18.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: non-breaking
- dependency-name: "@types/node"
  dependency-version: 26.6.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.71.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: non-breaking
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.71.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: non-breaking
- dependency-name: eslint-plugin-n
  dependency-version: 18.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: non-breaking
- dependency-name: prettier
  dependency-version: 3.9.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking
- dependency-name: ts-jest
  dependency-version: 29.4.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: non-breaking
...

Signed-off-by: dependabot[bot] <support@github.com>

build(deps): bump dawidd6/action-download-artifact from 25 to 26

Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 25 to 26.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](dawidd6/action-download-artifact@634d83b...27e4ae6)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-version: '26'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

build(deps): bump gradle/actions/setup-gradle from 6.3.0 to 6.4.0

Bumps [gradle/actions/setup-gradle](https://github.com/gradle/actions) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](gradle/actions@9c97196...3f5f9ad)

---
updated-dependencies:
- dependency-name: gradle/actions/setup-gradle
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

build(deps): bump dawidd6/action-download-artifact from 26 to 27

Fixes a bug in GitHub which meant the latest workflow was not returned, breaking
our screenshot baselines.

dawidd6/action-download-artifact@eab87c9

Fixes 21888
See https://redirect.github.com/dawidd6/action-download-artifact/pull/432

Assisted-by: GPT-6
My own copyright lines were not added to the licenses header.
A concurrent scroll and call to `clearCardsList` could cause an inconsistent state:

* The adapter thinks it has rows
* but the rows were cleared

To fix this, use and update a snapshot of the rows
in the adapter, to ensure they're consistent.

Fixes 17759

Assisted-by: GPT-6
Using 'Edit Tags' so there are intermediate states:

With BOTH tags `B` and `B`::child` selected on a note.

* Deselect `B`
* B appears partially checked, as the child remained selected
* Submit
* `B` remains selected.

The user's intention was to remove `B`, but this didn't occur, as we used the
visible state of the tag rather than our concept of 'preserved tags'.

This fix separates out the display state, so we don't over-select tags

Assisted-by: GPT-6
`TMPDIR` can be unwritable (`/data/local/tmp`).

In this case, set the 'overwrite' flag to true, and overwrite the
envvar with `context.cacheDir.path` when we start up.

Fixes 19545

Assisted-by: GPT-6
* different heights
* 200% font sizes

Prep for 18524

Assisted-by: GPT-6
Handle the padding and deck name

Part of 18524

Assisted-by: GPT-6
Names and card counts are centered within each row

Part of 18524

Assisted-by: GPT-6
* Measure the rows so only complete rows are shown
* Center the list
* Refresh the list if widget is resized

Part of 18524

Assisted-by: GPT-6
If there's not enough space for the first row, shrink its text
so it is visible.

Fixes 18524

Assisted-by: GPT-6
As the API uses a content provider, a query could theoretically hit
the backend before `Application.onCreate`'s init routine.

Assisted-by: GPT-6
The exception is user-facing and non-actionable by us.
Filters `BackendSchedulerUpgradeRequiredException`.

Fixes 17385

Assisted-by: GPT-6
When an in-flight request to the API failed, it could
be picked up by the media handler, showing an unexpected
`.bin` file failing to load.

Fixes 16510

Assisted-by: GPT-6
Hardening for 16510

Assisted-by: GPT-6
Mostly for media throttling

Part of 16510

Assisted-by: GPT-6
Assisted-by: Claude Sonnet 5 [tests]
This is very heavily documented as there are subtle issues, such as
`ViewModel.onCleared` not necessarily being a permanent dismissal.

As part of our IdsFile changes, where we want to keep a file on disk
and reference this in our activity state, but also allow multiple
concurrent files on disk in case an activity exists twice in the task
graph.

Part of 19572

Assisted-by: GPT-6
* Delete only after the replacement is written
* Delete the final snapshot via `onPermanentDismissal`

Part of 19572

Assisted-by: Claude Fable 5.1
Assisted-by: GPT-6
Buggy if the build logic classloaders are reused between builds,
potentially using outdated data from the cache.

Caused a 0.004 ms saving (warmed-lookup estimate) - not worth keeping

Assisted-by: GPT-6
Previously, if our `alert` implementation failed, we returned `false`,
which had the WebView show the default.

Now, we cancel the alert, confirming the request without a fallback.

This could fail for the same reason - for example, I suspect this was caused
by our Congrats page continuing to refresh (and failing), when the app is backgrounded
and can no longer show alerts.

**Bug Reporting**
If we hit a window-limit failure, ensure we're only reporting it if silent mode is enabled
otherwise, this shows a dialog itself.

This is defensive handling for a legacy bug, it has likely already been resolved
by Anki updates the congrats screen and our lifecycle fixes.

Part of 15658

Assisted-by: GPT-6
Restore the expanded add menu views and Back behavior when DeckPicker is recreated.

GPT-5.6 added the recreation handling and regression test.

Fixes #22241

Assisted-by: GPT-5.6
The sort chip is icon only, so TalkBack announced it as an unlabelled
button, and the flipped icon was the only hint of the sort direction.

Give it a content description with the same text as the sort snackbar,
such as "Sort by Created · Newest first" or "No sorting". The ViewModel
now keeps the current sort as state, so the label is right as soon as
the browser opens, and it is rebuilt after a language change.

Assisted-by: Claude Opus 5.5
Cards and notes mode keep separate sort settings, but switching modes
didn't reload them. The sort chip kept showing the old mode's direction,
and its TalkBack label still named the old mode's sort.

Assisted-by: Claude Opus 5.5
@float3
float3 merged commit 6befcc5 into float3:ankiquest Oct 5, 2026
4 checks passed
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.