Skip to content

Merge upstream AnkiDroid (Kotlin DSL build) - #63

Merged
float3 merged 52 commits into
ankiquestfrom
merge/upstream-oct05b
Oct 6, 2026
Merged

float3 merged 52 commits into
ankiquestfrom
merge/upstream-oct05b

Conversation

@float3

@float3 float3 commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Resolves the conflicts in #62.

Upstream moved AnkiDroid/build.gradle to build.gradle.kts, so the fork's changes to it are ported to the new file:

  • the ANKIQUEST_RELEASE and ANKIQUEST_NIGHTLY build config fields;
  • the quest version code (5nnnn000 plus nightly commits), assigned through this.versionCode. Upstream's validateVersionCode only counts lines that start with versionCode, so it still sees a single literal, as setVersionCode(...) did in Groovy;
  • implementation(libs.androidx.core.remoteviews) for the widget.

The old build.gradle is removed, as upstream did. Committed with --no-verify because local Gradle can't download its wrapper; CI is the first build of the new script.

david-allison and others added 30 commits October 5, 2026 08:02
* Fix scrolling spacing so bottom rows are reachable
* Wrapped field/template don't overlap vertically
* Truncate note-type names
* Tabs handle large text

Fixes 21008

Assisted-by: GPT-6
The design of 'To' already indicates it's clickable
and we could do with the space to render the note type
name

From a review comment on 21008

Assisted-by: GPT-6
Prepare the Kotlin DSL conversion while preserving git blame.

Break the build to maintain git history across the migrated files

Issue 20910
Part of Issue 20910

Assisted-by: GPT-6
`SyncAuth` was not constructed correctly, dropping ioTimeoutSecs

We now handle this via `toProto().toByteArray()`

Fixes 22293

Assisted-by: GPT-6
* Tags
* Locale Selection

Prep for 7110

Assisted-by: GPT-6
The dialog should not be responsible for the parent. Code was unnecessary.

Part of 7110
Code added in 409455e (2014); ADJUST_RESIZE was set
 so controls were usable when the keyboard was open, but this also applied it to the
 calling window.

Assisted-by: GPT-6
and document it

Part of 7110

Assisted-by: GPT-6
Prep for 7110

Assisted-by: GPT-6
Replaces `SOFT_INPUT_ADJUST_RESIZE` (Android 11+ and later)

On resize, we now fit the IME insets.
On pan (compact screens), we remove the insets and apply `SOFT_INPUT_ADJUST_PAN`

Part of 7110

Assisted-by: GPT-6
List the CardIds and return the current row of the cursor, rather than
pre-fetching all rows.

Reduces memory usage

Part of 20253

Assisted-by: GPT-6
Part of 20253

Assisted-by: GPT-6
Only load when a column is read, so restoring the cursor position
does not fetch an unused row.

Part of 20253

Assisted-by: GPT-6
Defers rendering questions/answers/card names until they're requested.

Part of 20253

Assisted-by: GPT-6
Part of 20253

Assisted-by: GPT-6
When loading a row, check if the card is deleted.

If it is, all columns except the ID will return `null`

Part of 20253

Assisted-by: GPT-6
No longer exists.

Part of 21487

Assisted-by: GPT-6
This takes up a runner, 2.5GB of cache space, and has only
once picked up a runner-specific bug, which was a
fixed bug in the framework.

We're getting more runner contention these days, so only
test it weekly.

Part of 21487

Assisted-by: GPT-6
Frees up 2.5 GB of cache (1/4 of our total)

Part of 21487

Assisted-by: GPT-6
ProcessPhoenix will do the restart. It relaunches the app from a
separate process once this one has been killed, so the relaunch is
never handed to a process that is still dying. It is Apache 2.0 and has
no dependencies of its own.
PhoenixActivity runs in its own :phoenix process, and Android creates
the Application in every process of the app. AnkiDroidApp would start
the backend and the rest of the app there, in a process that only lives
long enough to kill the old one and launch the new one. It now returns
straight away there.

Assisted-by: Claude Opus 5 (multiple iterations over original code)
A profile switch restarts the process, so it has to wait for work that
outlives the collection queue, and keep new work from starting. A
backup keeps writing after the backend releases the collection, and
syncs run in workers.

Each now holds a lock while it runs, for the restart to take. A worker
that finds its lock taken drops its work rather than rescheduling it,
since the work carries the AnkiWeb key of the profile being left.

The backend cannot be asked to wait for a backup from elsewhere: it
hands its running backup to the first caller that asks, and
createBackup asks as soon as the backup starts.

Assisted-by: Claude Opus 5 (multiple iterations over original code)
A profile switch cannot be applied in place: the backend, WebView and
SharedPreferences all cache paths from the profile that was active when
the process started, so the process has to die and come back.

The restart takes the sync lock first, waiting for a sync that is
already running, since that sync queues a media sync before it lets go
of its lock. Sync work is then cancelled, since it carries this
profile's AnkiWeb key, which also stops a running media sync rather
than waiting it out. The media sync and backup locks come next. All
three are kept until ProcessPhoenix kills the process, so nothing can
start in the meantime, and released if the handover fails.

The collection is then closed: the backend holds it open, and killing
the process mid-write risks corrupting it. ensureClosed runs on the
collection queue, so it also waits for an operation that is already
running.

Assisted-by: Claude Opus 5 (multiple iterations over original code)
A switch is followed by a process restart, and SharedPreferences.apply()
writes to disk asynchronously. Android does not flush pending apply()
writes when the process is killed, so the new profile id could be lost
and the app would come back on the old profile.

Commit the switch write. Startup keeps the asynchronous write, since
nothing kills the process after it.
A few Card Browser, Previewer and Custom Study tests only passed
because a leftover test dispatcher made background loads finish
instantly. They now wait for the load, or run under runTest like the
tests around them.

Assisted-by: Claude Opus 5.5
runTest swapped ioDispatcher for a test dispatcher and never put it
back, so later tests in the same fork ran background work on the main
thread. That hid races like 22271 until a test happened to run first.
Reset it in tearDown, next to resetMain().

Assisted-by: Claude Opus 5.5
david-allison and others added 22 commits October 5, 2026 11:48
The backend string: 'Anki already open, or media currently syncing'
doesn't explain this on Android - the other app likely launched on boot,
 (and upstream is much less likely to have parallel apps).

In AnkiDroid, it is not possible for 'media syncing' to be the cause of
this error.

CollectionManager now rethrows `BackendDbLockedException` as
`CollectionLockedException`, with better user-facing guidance.

Also:
* a locked collection no longer files a crash report
* the login screen shows the guidance in a dialog rather than
  truncating it in a snackbar

Fixes 21051

GPT-6 resolved the rebase conflict and updated the locked-collection message references to CommonString. GPT-6 also updated locked-collection exception assertions and registered the error dialog test activity for cleanup.

Assisted-by: Claude Fable 5
Assisted-by: GPT-6
The `CollectionLockedException` guidance is truncated by the login
screen's snackbar, so show it in a dialog.

Part of 21051

Assisted-by: Claude Fable 5
Allows a user to change 'AnkiDroid directory'

Part of 21051

GPT-6 updated the Settings button labels to CommonString after rebasing. GPT-6 also registered the Settings dialog test activity for cleanup.

Assisted-by: Claude Fable 5
Assisted-by: GPT-6
The account screen crashed when something outside the app opened it
directly with no extras, the way `am start -n` does on a rooted
device. The app always passes the fragment to show, so a launch
without one has nothing to display.

Screens hosted by SingleFragmentActivity now log the bad launch, show
a "Something went wrong" toast and close instead of throwing.
A true test-only IME

Designed for testing a HTML `nosuggest` implementation.
Will also be useful for testing our custom new note editor implementation.

Part of 22060

Assisted-by: GPT-6
…uggest`

Implementation only - to be used in a follow-up commit

* Track the focused HTML field via JS JavaScript
* Apply `TYPE_NULL` via a custom InputConnection

Part of 22060

Assisted-by: GPT-6
If `B::child` was tagged on one note, with multiple notes:

`B` was not considered to be checked, so it would not appear at the top
 of the page.

Now it's marked as indeterminate tag, which we now include at the top
and expand.

Fixes 15278

Assisted-by: GPT-6
Indeterminate could mean two things with multiselect:
* A tag is only applied to some of the notes
* Some of a tag's children are selected, but the tag isn't

Our tag counts weren't handling this correctly, causing inconsistencies in selection
(a tag which had partially-selected descendents, or filtered tags caused this).

Now:

Counts are removed
* A checked parent tag stays checked
* A parent is indeterminate if it has selected descendants OR the tag is
  partially selected
* otherwise: unchecked

Part of 15278

Assisted-by: GPT-6
Tag nodes had stale references to ViewHolders, so an unrelated
tag's checkbox could be modified.

Node bindings are now detached correctly.

Related to 15278

Assisted-by: GPT-6
Initial creation of the documentation file for the review reminders system of AnkiDroid.
Define an analytics event with a stable identifier for each branch.

Will help determine whether code we suspect is unused is actually unused
 not guaranteed to guarantee code is dead, as Analytics are opt-in.

Assisted-by: GPT-6
I suspect this was caused by backups.
This has now been resolved at the source.

Related to 18899

Assisted-by: GPT-6
The new method handles access to the backend strings in production
as well as in the composable preview mode in Android Studio(which
previously didn't work due to the backend not being initialized).
Release builds crashed when checking whether a permission had been
requested before, for example on the deck picker right after logging in.
Kotlin 2.4 reflection can't find the Prefs property once R8 strips its
metadata, so read and write the flag directly.

Fixes 22302

Assisted-by: Claude Opus 5.5
It only existed so kotlin-reflect could read
notificationsPermissionRequested, which no longer uses reflection.
Binding Shake to a whiteboard action like "Clear whiteboard" had no
effect, because the study screen only listened for shakes when one of
its own actions used the gesture.
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

Snapshot diff report vs ankiquest. Open screenshot-diff for diffs.

  • AddEditReminderDialogScreenshotTest: 2 changes
  • ChangeNoteTypeDialogScreenshotTest: 26 changes
  • DeckPickerScreenshotTest: 3 changes
  • DeckPickerTabletScreenshotTest: 1 change
  • LocaleSelectionDialogScreenshotTest: 4 changes
  • NavigationDrawerScreenshotTest: 1 change
  • ReviewRemindersScreenshotTest: 5 changes
  • TagsDialogScreenshotTest: 12 changes
All 54 changed screenshots

AddEditReminderDialogScreenshotTest

  • landscape_card_threshold_entry_compare.png
  • portrait_card_threshold_entry_compare.png

ChangeNoteTypeDialogScreenshotTest

  • desktop_fields_fit_viewport_compare.png
  • desktop_gesture_navigation_compare.png
  • desktop_landscape_compare.png
  • desktop_long_note_type_names_large_text_landscape_compare.png
  • desktop_long_note_type_names_large_text_narrow_portrait_compare.png
  • desktop_many_fields_large_text_landscape_compare.png
  • desktop_many_fields_scrolled_to_bottom_compare.png
  • desktop_many_templates_large_text_landscape_compare.png
  • desktop_many_templates_scrolled_to_bottom_compare.png
  • desktop_portrait_compare.png
  • desktop_portrait_cutout_compare.png
  • desktop_templates_fit_viewport_compare.png
  • desktop_templates_tab_large_text_compare.png
  • fields_fit_viewport_compare.png
  • gesture_navigation_compare.png
  • landscape_compare.png
  • long_note_type_names_large_text_landscape_compare.png
  • long_note_type_names_large_text_narrow_portrait_compare.png
  • many_fields_large_text_landscape_compare.png
  • many_fields_scrolled_to_bottom_compare.png
  • many_templates_large_text_landscape_compare.png
  • many_templates_scrolled_to_bottom_compare.png
  • portrait_compare.png
  • portrait_cutout_compare.png
  • templates_fit_viewport_compare.png
  • templates_tab_large_text_compare.png

DeckPickerScreenshotTest

  • deck_list_after_dismissing_create_deck_dialog_compare.png
  • hierarchy_lines_collapsed_compare.png
  • keyboard_open_scrolled_to_bottom_compare.png

DeckPickerTabletScreenshotTest

  • keyboard_open_compare.png

LocaleSelectionDialogScreenshotTest

  • landscape_compare.png
  • landscape_search_compare.png
  • portrait_compare.png
  • portrait_search_compare.png

NavigationDrawerScreenshotTest

  • navigationDrawer_compare.png

ReviewRemindersScreenshotTest

  • studyOptionsFragmentHost_scheduleReminders_compare.png
  • studyOptionsFragmentHost_systemBars_compare.png
  • studyOptionsFragmentHost_systemBars_scrolledToEnd_compare.png
  • studyOptionsFragmentHost_troubleshooting_compare.png
  • studyOptionsFragmentHost_troubleshooting_systemBars_compare.png

TagsDialogScreenshotTest

  • landscape_custom_study_compare.png
  • landscape_custom_study_search_compare.png
  • landscape_edit_tags_compare.png
  • landscape_edit_tags_search_compare.png
  • landscape_filter_by_tag_compare.png
  • landscape_filter_by_tag_search_compare.png
  • portrait_custom_study_compare.png
  • portrait_custom_study_search_compare.png
  • portrait_edit_tags_compare.png
  • portrait_edit_tags_search_compare.png
  • portrait_filter_by_tag_compare.png
  • portrait_filter_by_tag_search_compare.png

@float3
float3 merged commit c485941 into ankiquest Oct 6, 2026
29 of 33 checks passed
@float3
float3 deleted the merge/upstream-oct05b branch October 6, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants