Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
251 commits
Select commit Hold shift + click to select a range
95653a8
fix(change-note-type): stop mappings being clipped
david-allison Oct 3, 2026
11498e7
fix(change-note-type): remove destination down arrow
david-allison Oct 4, 2026
2ff52b0
rename: build.gradle => gradle.kts [breaks build]
david-allison Oct 3, 2026
1940c93
refactor(build): convert `build.gradle` to kts
david-allison Oct 3, 2026
471eb7c
fix(sync): use network timeout in background syncs
david-allison Oct 3, 2026
61747de
test(dialogs): screenshots for keyboard-resizing dialogs
david-allison Oct 3, 2026
7e571ef
fix(tags): stop overriding activity keyboard settings
david-allison Oct 3, 2026
b076787
refactor: make 'resizeDialogWhenSoftInputShown' dialog-only
david-allison Oct 3, 2026
38c8b09
refactor: extract `Int.setBitFlag`
david-allison Oct 3, 2026
76745d9
fix(dialogs): fit dialogs to IME insets
david-allison Oct 3, 2026
e3cb98d
fix(api): load card search results lazily
david-allison Oct 3, 2026
169d07b
refactor(api): extract card template name lookup
david-allison Oct 3, 2026
4e9a6cf
perf(api): load cursor rows on read, not move
david-allison Oct 3, 2026
d9697dd
perf(api): compute only requested card columns
david-allison Oct 3, 2026
39919a7
fix(api): validate column names during query
david-allison Oct 3, 2026
9b4bd95
fix(api): handle deleted cards
david-allison Oct 3, 2026
394126d
ci(emulator): remove legacy 'inputs.clearCaches' reference
david-allison Oct 2, 2026
da13c67
ci(emulator): add a scheduled weekly run (03:17 UTC)
david-allison Oct 2, 2026
ba6a9c5
ci(emulator): make 16 KB emulator scheduled-only
david-allison Oct 2, 2026
9305e99
ci: skip caching the 16 KB emulator snapshot
david-allison Oct 2, 2026
afed0e1
Updated strings from Crowdin
Oct 5, 2026
babc513
build(deps): add ProcessPhoenix
criticalAY Sep 11, 2026
b6dea19
feat(profiles): skip app setup in the ProcessPhoenix process
criticalAY Sep 11, 2026
2688fd2
feat(profiles): hold a lock while background work runs
criticalAY Sep 11, 2026
2112273
feat(profiles): restart the app after closing the collection
criticalAY Sep 11, 2026
54cd8d4
fix(profiles): persist a profile switch synchronously
criticalAY Sep 10, 2026
4baba72
feat(profiles): implement the profile switch safety checks
criticalAY Sep 10, 2026
8b18055
test: stop relying on instant background loads
criticalAY Oct 3, 2026
8f08439
fix(tests): reset ioDispatcher after each test
criticalAY Oct 3, 2026
35e1439
fix: explain 'Anki already open' errors
david-allison Aug 15, 2026
ccf39a8
fix(login): locked-collection error in a dialog
david-allison Aug 15, 2026
188cd78
feat: Settings button on locked-collection dialogs
david-allison Aug 15, 2026
76f9c30
Add Han Unification detection in note editor (Stage 1)
xenonnn4w Nov 11, 2025
ff2286b
fix: close screens launched without a fragment instead of crashing
criticalAY Oct 2, 2026
1662c1b
test: add a controllable Android input method
david-allison Oct 2, 2026
0a6c70e
feat(reviewer): prep for "Type answer into the card" support for `nos…
david-allison Oct 2, 2026
3bf38b4
Updated strings from Crowdin
Oct 5, 2026
9890107
fix(tags): prioritize partially selected tags
david-allison Oct 3, 2026
073d1b4
fix(tags): correct parent tag states
david-allison Oct 3, 2026
0771d22
fix(tags): issues with RecyclerView row reuse
david-allison Oct 3, 2026
70d076c
docs: review reminders documentation
ericli3690 Sep 7, 2026
5b4f4d7
fix: handle backup-related crashes in Intent handlers
david-allison Oct 4, 2026
8a08014
feat(analytics): dead code reporting
david-allison Oct 4, 2026
165aa4e
chore: check if WorkManager failure handling is dead code
david-allison Oct 4, 2026
8b56eeb
Add tr() extension for backend strings access in composable code
lukstbit Oct 4, 2026
b5969cd
Add trCase() extension for handling sentence case strings in composab…
lukstbit Oct 4, 2026
e4c9100
fix(permissions): read permission flags without kotlin-reflect
criticalAY Oct 5, 2026
45c3933
build(proguard): drop the PrefsRepository keep rule
criticalAY Oct 5, 2026
cf4fef5
fix(whiteboard): shake gesture did nothing
criticalAY Oct 5, 2026
1eaca72
feat: Replace androidx core-ktx dependency with core
devchaos0 Oct 2, 2026
7b37e0a
docs: convert licenses to SPDX format
david-allison Oct 6, 2026
371da79
Updated strings from Crowdin
Oct 7, 2026
2ca9686
Removed duplicate yarn exclusion in .gitignore
Rizos-Vassili Oct 7, 2026
ffb5478
fix(deck-picker): do not block the main thread on startup during sync
criticalAY Jul 1, 2026
da23305
Added a test case to ensure that parent tags are case-insensitive
Rizos-Vassili Oct 7, 2026
f3a4772
refactor: use Duration for delays
MichaelJamesL Sep 30, 2026
f1e2b0e
docs: convert licenses to SPDX format
david-allison Oct 7, 2026
bc03621
fix(tts): move cancellation cleanup to IO dispatcher
david-allison Oct 7, 2026
edbb283
fix: handle exceptions loading TTS engines
david-allison Oct 7, 2026
03e55d9
docs: convert licenses to SPDX format
david-allison Oct 8, 2026
f1c17e7
docs: fix some drawable licenses
david-allison Oct 8, 2026
158f4c9
docs: convert some XML licenses to SPDX
david-allison Oct 8, 2026
2361c35
docs: identify 21 Material icons
david-allison Oct 8, 2026
32aaf4c
rename: move Robolectric downloader into buildSrc [breaks build]
david-allison May 3, 2026
05de71b
refactor(build): convert robolectricDownloader to a convention plugin
david-allison May 3, 2026
1d04dcf
fix(whiteboard): hidden whiteboard swallowed shakes and keys
criticalAY Oct 5, 2026
d744a38
fix: bring back SLF4J logging
criticalAY Oct 8, 2026
71979b2
docs(analytics): drop the slf4j-timber advice
criticalAY Oct 8, 2026
2a6c29c
fix(coroutines): skip `launchCatchingTask` if activity is invalid
david-allison Oct 7, 2026
741b25c
refactor(build): convert lint.gradle to kts
david-allison Oct 10, 2026
83e81aa
refactor(build): inline jacocoSupport.gradle
david-allison Oct 10, 2026
deaa6dc
perf(startup): make twoLetterSystemLocaleMapping test-only
david-allison Oct 10, 2026
f74b3d1
Updated strings from Crowdin
Oct 10, 2026
509e2ec
fix the issue of LocaleSelectionDialog with no name along with the test
Phoenix1808 Oct 7, 2026
d7dc416
test(multimedia): image preview/editor
david-allison Oct 5, 2026
55c588a
ui(multimedia): move image actions to a Compose bottom toolbar
david-allison Oct 5, 2026
5d3647c
ui(multimedia): move 'Done' to a Compose app bar action
david-allison Oct 5, 2026
115331b
chore(icons): add photo_library & SPDX attribution
david-allison Oct 5, 2026
58a50c7
refactor: extract theme wrapper for Composable previews
david-allison Oct 6, 2026
bcab277
fix(deckpicker): fix 'one-way sync' dialog on tablets
david-allison Oct 9, 2026
fe6d088
fix(deck-picker): fix layout listener leaking memory
david-allison Oct 9, 2026
35dd964
fix(study-screen): remove baseline gap on audio button
david-allison Oct 8, 2026
9ca5533
docs: add [L]GPL SPDX headers for post-2017 files
david-allison Oct 10, 2026
f832b0f
test: remove MathJaxClozeTest
david-allison Oct 10, 2026
341ca99
docs: add SPDX header for MetaDB
david-allison Oct 10, 2026
fafa3d7
fix: pane drag direction in RTL
david-allison Oct 6, 2026
8b74d7a
refactor: rename panes to start/end
david-allison Oct 6, 2026
1840192
fix(study-options): don't refresh while syncing
david-allison Oct 7, 2026
1a1dab3
Updated strings from Crowdin
Oct 11, 2026
dcd46b3
fix(profiles): skip profile setup in the restart process
criticalAY Oct 8, 2026
3cbd0b9
feat(anki-common): add property delegates for Config
david-allison Sep 20, 2026
36cdfe1
refactor(libanki): delegate 'adding defaults mode'
david-allison Sep 20, 2026
97ce1f8
fix(tests): wait for preview before screenshot
david-allison Oct 6, 2026
1e1db76
Fix: inconsistent string for redone action
snowtimeglass Oct 9, 2026
0dbd463
Bumped version to 2.26.0alpha3
mikehardy Oct 11, 2026
e488cf5
test(whiteboard): gestures in stylus mode
david-allison Oct 8, 2026
9f7b1bc
fix(study-screen): stylus mode - send gestures to whiteboard
david-allison Oct 9, 2026
9340137
test: fix flaky SyncConflictTest
david-allison Oct 11, 2026
bd1c8f0
test: ignore theme-related duplicate translations
david-allison Oct 11, 2026
3b6f7a3
docs: identify 95 Material icons
david-allison Oct 9, 2026
8c28f9d
refactor(locale): extract AnkiBackendLanguage
david-allison Oct 9, 2026
701c54a
fix(locale): normalize and validate backend languages
david-allison Oct 9, 2026
57f6687
refactor: extract mayLaunchUrl from AnkiActivity
david-allison Oct 7, 2026
50aba25
fix(custom-tabs): retain connection until unbound
david-allison Oct 7, 2026
ad99ca1
fix(custom-tabs): avoid retrying session creation
david-allison Oct 7, 2026
15887f9
refactor(custom-tabs): extract initialization helpers
david-allison Oct 7, 2026
d87a450
fix(custom-tabs): use IO dispatcher
david-allison Oct 7, 2026
b66106a
fix(custom-tabs): preload URLs in the background
david-allison Oct 7, 2026
b177547
refactor: split automatic sync status handling
david-allison Oct 11, 2026
ebc6db7
test: coverage for auto sync exit behavior
david-allison Oct 11, 2026
4117d66
fix: skip auto-sync if the collection is closed
david-allison Oct 11, 2026
e8cd35d
fix: cancelability of error dialogs
david-allison Oct 10, 2026
0c65341
fix(tests): wait for browser search
david-allison Oct 6, 2026
0444e7a
test: DeckAdapterTest fix flake - wait for deck rows
david-allison Oct 6, 2026
7d29ba8
refactor(config): delegate Config.ignoreAccentsInSearch
david-allison Oct 11, 2026
ff23274
refactor(config): delegate Config.pasteImagesAsPng
david-allison Oct 11, 2026
06f176f
refactor(config): delegate Config.hideAudioPlayButtons
david-allison Oct 11, 2026
f4e4311
refactor(config): delegate Config.cardStateCustomizer
david-allison Oct 11, 2026
4bb74f1
refactor(config): delegate Config.cardsOrNotes
david-allison Oct 11, 2026
514c513
refactor(config): delegate Config.currentDeckId
david-allison Oct 11, 2026
6c21814
refactor(config): delegate Config.schedulerVersion
david-allison Oct 11, 2026
3aaf8d8
refactor(config): delegate Config.rolloverHour
david-allison Oct 11, 2026
a2c0121
refactor(config): delegate Config.showIntervalsOnButtons
david-allison Oct 11, 2026
a70850b
refactor(config): delegate Config.showRemainingDueCounts
david-allison Oct 11, 2026
3b4190d
refactor(config): delegate Config.fsrsEnabled
david-allison Oct 11, 2026
a729eb3
refactor(config): delegate Config.flagLabels
david-allison Oct 11, 2026
f397a4f
fix(study-options): avoid duplicate ChangeManager subscriptions
david-allison Oct 11, 2026
9c57a5e
fix(study-options): reuse fragment when selecting a deck
david-allison Oct 11, 2026
1bdbe0c
perf(deck-description): use fast line-breaking when resizing
david-allison Oct 11, 2026
a6bc9f1
refactor(tests): extract snackbar helpers
david-allison Oct 9, 2026
dc1527e
test(browser): add coverage for post-search snackbar
david-allison Oct 9, 2026
4b58815
perf(lint): reject invalid Locale receivers
david-allison Oct 11, 2026
678b1b9
perf(lint): reuse translation text during typo checks
david-allison Oct 11, 2026
88f3dc6
perf(lint): avoid repeated format validation work
david-allison Oct 11, 2026
c6f1805
perf(lint): check SPDX headers before legacy licenses
david-allison Oct 11, 2026
f14b652
refactor: extract card template change tracking
david-allison Oct 9, 2026
1bb558c
feat(crash-reporting): report ANRs
david-allison Oct 7, 2026
5b2e50d
test(crash-reporting): guard against ANR trace format changes
david-allison Oct 8, 2026
2599ce2
test(startup): extract storage helpers
david-allison Sep 5, 2026
9c5f077
feat(deck-picker): decide & persist collection path
david-allison Aug 16, 2026
bd03751
feat(permissions): persist the storage decision
david-allison Aug 16, 2026
f96d686
refactor(sync): strongly type 'MeteredSyncPermission'
david-allison Oct 5, 2026
9cce6e2
refactor(sync-media): prep: metered connections handling
david-allison Oct 6, 2026
04506c9
fix(sync): handle network restriction changes
david-allison Oct 5, 2026
1e92d5f
fix(reviewer): mark `nosuggest` HTML answer fields
david-allison Oct 6, 2026
c3fe246
fix(reviewer): support `nosuggest` for HTML typed answers
david-allison Oct 6, 2026
741c722
refactor(pages): strongly type SvelteKit routes
david-allison Oct 6, 2026
153ee75
fix(congrats): strip CSP to allow for `bridgeCommand`
david-allison Oct 6, 2026
c6da9fa
fix(browser): stop copying activity launch extras
david-allison Oct 6, 2026
8ae5424
fix(previewer): stop copying activity launch extras
david-allison Oct 6, 2026
b5ea6d4
test: fixtures for saved state tests
david-allison Oct 6, 2026
9964e36
fix: don't copy intent extras to ViewModel state
david-allison Oct 6, 2026
03c859a
fix(note-editor): when adding, do not persist 'type' until saving
david-allison Sep 20, 2026
4a248f8
refactor(deck-picker): move requestRenameSelectedDeck to ViewModel
david-allison Oct 8, 2026
b92b1d7
refactor(deck-picker): move requestStudySelectedDeck to ViewModel
david-allison Oct 8, 2026
fd1fb1a
refactor(deck-picker): move deck menu visibility to ViewModel
david-allison Oct 8, 2026
33e54d6
refactor(recorder): add VoiceRecorder
criticalAY Oct 7, 2026
94dafbb
fix(study-screen): stop leaking check pronunciation recordings
criticalAY Oct 7, 2026
5e81fcf
refactor: rename cacheDir to idsFileDirectory
david-allison Oct 5, 2026
2e9c9c3
refactor: define IdsFile purpose
david-allison Oct 5, 2026
1b3b665
fix: use internal cache for IdsFile
david-allison Oct 5, 2026
da548bc
refactor: remove IdsFile storage policy
david-allison Oct 5, 2026
118ac26
fix: pass IdsFile.Purpose at call sites added on main
mikehardy Oct 12, 2026
0bda333
fix(reminders): use Delete label in delete reminder confirmation dialog
anugrah-pal Sep 23, 2026
9df7b01
refactor: simplify binding construction
Arthur-Milchior Oct 6, 2026
91de7dd
test: simplify binding assertions
Arthur-Milchior Oct 6, 2026
8292f17
Bumped version to 2.26.0alpha4
mikehardy Oct 12, 2026
92336a9
refactor(media-check): handle errors in ViewModel
criticalAY Oct 1, 2026
5475a4e
build(ci): add scheduled API 37 emulator run
david-allison Oct 6, 2026
c854131
build(ci): output emulator startup and kernel logs
david-allison Oct 6, 2026
534dcae
refactor: add 'ImportResolution' domain type
david-allison Oct 2, 2026
11e98aa
refactor: resolve import metadata before copying
david-allison Oct 2, 2026
0006b07
fix(crash-reporting): use stable stack traces
david-allison Oct 12, 2026
dd597b4
build(deps): bump handlebars from 4.7.9 to 4.7.10 in /tools/localization
dependabot[bot] Oct 12, 2026
fdf63d9
fix(deck-picker): don't crash on removed menu items
david-allison Oct 12, 2026
950e0c2
test(media-sync): use main looper for foreground callbacks
david-allison Oct 12, 2026
3cb2e1b
docs(README): update to 'official'
david-allison Oct 12, 2026
d27ba4f
test(deck-picker): coverage for closing FAB closing add menu on touch…
david-allison Oct 12, 2026
98e3c15
fix(deck-picker): close FAB when opening note editor
david-allison Oct 12, 2026
6c48d74
fix(coroutines): stop reporting crashes on lifecycle issues
david-allison Oct 12, 2026
bfae2d0
docs(crash-reporting): onlyIfSilent
david-allison Oct 12, 2026
e0422c9
fix(tags): add spacing above 'no tags' state
david-allison Oct 12, 2026
94edca4
feat(ci): summarize unit test failures
david-allison Oct 12, 2026
a6cb379
test: coverage for Akebi API observer bug
david-allison Oct 12, 2026
2084c62
test: add a release smoke test
criticalAY Oct 7, 2026
104963f
ci(emulator): run the release smoke test
criticalAY Oct 7, 2026
329bb78
refactor(test): extract EditorInfo.matches
david-allison Oct 12, 2026
c557f51
refactor(test): Extract ImeResponse
david-allison Oct 12, 2026
0a25256
test: ensure IME connection is responsive
david-allison Oct 12, 2026
84cd198
fix: detect duplicate ChangeManager subscriptions
criticalAY Oct 12, 2026
ce555c0
feat(deckpicker): improve collection file too large dialog
Ranjan-3105 Sep 28, 2026
885d02f
fix: avoid duplicate uncompressed label in sync tests
Ranjan-3105 Sep 29, 2026
4be5a87
fix: handle localized collection too large errors
Ranjan-3105 Sep 29, 2026
dfa0b9f
test(sync): cover collection too large error languages
Ranjan-3105 Sep 30, 2026
533d9a1
fix: improve collection too large sync dialog
Ranjan-3105 Oct 1, 2026
26550ff
fix(deck-picker): retain maintenance prompts
n1snt Oct 12, 2026
c22ed83
fix: restore deck search after recreation
n1snt Oct 2, 2026
b323397
fix(decks): reject blank subdeck names
CapThunder19 Sep 23, 2026
5927366
fix(change-note-type): put tab icons before their labels
criticalAY Oct 13, 2026
74a08f5
fix(change-note-type): match text size in both columns
criticalAY Oct 13, 2026
3fd3b57
Updated strings from Crowdin
Oct 13, 2026
902235c
Add test for CardBrowserFragmentViewModel.openDeckSelectionDialog
GuTS805 Aug 8, 2026
503be75
refactor: use CommonString for all common strings
david-allison Oct 12, 2026
16b54ff
refactor: move CrowdIn string resources to :common:android
david-allison Aug 30, 2026
254366c
chore(i18n): point translation tooling at :common:android
david-allison Aug 30, 2026
8ce3e85
refactor(recorder): add VoicePlayer
criticalAY Oct 7, 2026
fc8da00
fix(study-screen): release the check pronunciation player
criticalAY Oct 7, 2026
6338099
fix(ci): avoid duplicate runs for Crowdin
david-allison Oct 13, 2026
58ed057
test(manage-space): add coverage for a missing external files dir
criticalAY Oct 13, 2026
9e5e2c7
fix(empty-cards): reuse search after recreation
n1snt Sep 30, 2026
1c80f43
refactor(widgets): move `AddNoteWidget` into `:widgets`
david-allison Oct 13, 2026
ea9a1ec
refactor(widgets): extract DeckWidgetData to `:widgets`
david-allison Oct 13, 2026
49f0498
refactor: move reviewer colors to `common:android`
david-allison Oct 13, 2026
5ad1f39
refactor(widgets): move `DeckPickerWidget` resources into `:widgets`
david-allison Oct 13, 2026
9f44b67
refactor(widgets): move `CardAnalysisWidget` resources into `:widgets`
david-allison Oct 13, 2026
67ce6ee
refactor: move scheduleAllNotifications out of AlarmManagement
criticalAY Oct 13, 2026
e19ebe4
refactor: move AlarmManagement to :common:android
criticalAY Oct 13, 2026
180cde4
refactor(widgets): move `AnkiDroidWidgetSmall` resources into `:widgets`
david-allison Oct 13, 2026
1309545
refactor(widgets): move card analysis config layout into `:widgets`
david-allison Oct 13, 2026
73c7fc6
refactor: move `FixedTextView` to `:common:android`
david-allison Oct 13, 2026
b3d4d84
refactor: move shared add icon to `:common:android`
david-allison Oct 13, 2026
c5094a5
refactor(widgets): move deck picker config layout into `:widgets`
david-allison Oct 13, 2026
865d86f
refactor(note-editor): add AudioRecordingViewModel
criticalAY Oct 12, 2026
86b0c8f
fix(multimedia): use AudioRecordingViewModel
criticalAY Oct 12, 2026
f44015a
build(release): show the stable changelog in test builds
criticalAY Oct 13, 2026
01408e9
fix(progress): give each observer its own loading dialog
criticalAY Sep 12, 2026
b22c3a1
fix(progress): block touches as soon as an operation starts
criticalAY Oct 12, 2026
a19feb0
feat(deck-picker): show progress from the ViewModel
criticalAY Sep 12, 2026
9ca2655
feat(profiles): expose the profile manager on the application
criticalAY Sep 10, 2026
6db3070
feat(profiles): populate the list from the profile registry
criticalAY Sep 10, 2026
104d3e8
fix(tts): report `ERROR_NETWORK_TIMEOUT`
david-allison Oct 13, 2026
4f3407b
fix(tts): `ERROR_NETWORK` typo
david-allison Oct 13, 2026
876cba8
fix(reviewer): disabled HTML autofocus
david-allison Oct 13, 2026
31839f6
test: fix flaky test: sync conflict dialog lookup
david-allison Oct 13, 2026
5ee5750
fix(deck-picker): keep selected deck after deletion
sanjaysargam Sep 18, 2026
6c2c498
refactor(shared-decks): extract AnkiWeb login-state helpers
sanjaysargam Sep 29, 2026
001b19d
fix(shared-decks): open the AnkiWeb login page after a rate-limited d…
sanjaysargam Sep 29, 2026
fbb3acd
refactor(shared-decks): reuse the login-required snackbar
sanjaysargam Sep 29, 2026
148cd89
refactor(shared-decks): replace onDownloadFinished booleans with Down…
sanjaysargam Sep 29, 2026
8168252
fix(shared-decks): open the AnkiWeb login page after a rate-limited d…
sanjaysargam Oct 2, 2026
d18a97d
build(ci): only keep compare images in screenshot-diff
criticalAY Oct 13, 2026
bb2c918
fix(widget): handle storage errors in the permission receiver
criticalAY Oct 13, 2026
9528b4d
fix(widget): update the Add Note widget once storage is granted
criticalAY Oct 13, 2026
9b03a1c
fix(card-browser): full-width display order sheet in landscape
criticalAY Oct 13, 2026
0c21982
fix(study-screen): avoid white flash in dark theme
criticalAY Oct 13, 2026
6efec70
fix(widgets): AppWidgetManager can be null
david-allison Oct 14, 2026
8140f3b
lint: require `getAppWidgetManager`
david-allison Oct 14, 2026
328b42e
feat(manage-space): show progress from the ViewModel
criticalAY Oct 14, 2026
3f7a13d
fix(recorder): keep recordings out of the cache
criticalAY Oct 12, 2026
2ff269e
fix(recorder): clean up old recordings at startup
criticalAY Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later

"""Report failures and skipped tests from Android's JUnit XML using the standard library."""
"""Report failures and skipped tests from JUnit XML using the standard library."""

import argparse
import html
import os
from pathlib import Path
Expand All @@ -25,11 +26,11 @@ def read_ignored_tests(log_file):
return ignored - failed


def main(results_dir, log_file=None):
def main(results_dir, log_file=None, pattern="TEST-*.xml", title="Test failures"):
failures = []
skipped = []
ignored = read_ignored_tests(log_file)
for report in sorted(results_dir.rglob("TEST-*.xml")):
for report in sorted(results_dir.rglob(pattern)):
try:
root = ET.parse(report).getroot()
except (ET.ParseError, OSError) as error:
Expand Down Expand Up @@ -68,11 +69,11 @@ def main(results_dir, log_file=None):
print(failure, end="\n\n")

if skipped:
title = f"Skipped tests ({len(skipped)})"
skipped_title = f"Skipped tests ({len(skipped)})"
if os.environ.get("GITHUB_ACTIONS") == "true":
print(f"::group::{title}")
print(f"::group::{skipped_title}")
else:
print(title, end="\n\n")
print(skipped_title, end="\n\n")
for skip in skipped:
print(skip, end="\n\n")
if os.environ.get("GITHUB_ACTIONS") == "true":
Expand All @@ -81,11 +82,11 @@ def main(results_dir, log_file=None):
if (failures or skipped) and os.environ.get("GITHUB_STEP_SUMMARY"):
with open(os.environ["GITHUB_STEP_SUMMARY"], "a", encoding="utf-8") as summary:
if failures:
summary.write("### Emulator test failures\n\n")
summary.write(f"### {title}\n\n")
for failure in failures:
summary.write(f"<pre>{html.escape(failure)}</pre>\n\n")
if skipped:
summary.write(f"<details><summary>{title}</summary>\n\n")
summary.write(f"<details><summary>{skipped_title}</summary>\n\n")
for skip in skipped:
summary.write(f"<pre>{html.escape(skip)}</pre>\n\n")
summary.write("</details>\n\n")
Expand All @@ -94,4 +95,10 @@ def main(results_dir, log_file=None):


if __name__ == "__main__":
sys.exit(main(Path(sys.argv[1]), Path(sys.argv[2]) if len(sys.argv) > 2 else None))
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("results_dir", type=Path)
parser.add_argument("log_file", type=Path, nargs="?")
parser.add_argument("--pattern", default="TEST-*.xml", help="Recursive report glob relative to results_dir")
parser.add_argument("--title", default="Test failures", help="Failure heading in the job summary")
args = parser.parse_args()
sys.exit(main(args.results_dir, args.log_file, args.pattern, args.title))
2 changes: 1 addition & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Alternately, you may run the actions on your fork of `Anki-Android`.
| [Lint (JavaScript)](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/lint.yml) | See script | Prettier, lint & code formatting |
| [Build Logic](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/lint.yml) | `./gradlew -p buildSrc test` and `tools/validate-version-code.sh` | Version-code bump checks |
| [Unit Tests](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/tests_unit.yml) | `./gradlew jacocoUnitTestReport --daemon` | Unit tests for the Android Project |
| [Emulator Tests](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/tests_emulator.yml) | `TEST_RELEASE_BUILD=true ./gradlew jacocoAndroidTestReport --daemon` | Emulator tests for the Android Project.<br/>CI runs `bash .github/disable_art_metrics.sh` and `adb shell settings put global hide_error_dialogs 1` first |
| [Emulator Tests](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/tests_emulator.yml) | `TEST_RELEASE_BUILD=true ./gradlew jacocoAndroidTestReport --daemon` | Emulator tests for the Android Project.<br/>CI runs `bash .github/disable_art_metrics.sh` and `adb shell settings put global hide_error_dialogs 1` first.<br/>Then runs `ReleaseSmokeTest` from `:baselineprofile` with `TEST_RELEASE_BUILD=false` |
| [CodeQL](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/codeql.yml) | N/A | GitHub-only check.<br/>[Docs](https://codeql.github.com/) |

## Other Workflows
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
- 'gh-readonly-queue/**'
# Dependabot branches are already checked by the pull_request trigger.
- 'dependabot/**'
# Crowdin translations are already checked by the pull_request trigger.
- 'i18n_sync'
merge_group:

permissions:
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ jobs:
gunzip *.gz
shell: bash

- name: Clone ankidroiddocs for the changelog
run: git clone git@github.com:ankidroid/ankidroiddocs ../ankidroiddocs
shell: bash

- name: Build and Release public release
if: "${{ github.event.inputs.public != '' }}"
run: |
pushd ../
git clone git@github.com:ankidroid/ankidroiddocs
popd
./tools/release.sh public
run: ./tools/release.sh public
shell: bash

- name: Build and Release test release
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/screenshot_compare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,16 @@ jobs:
SUBDIR: ${{ github.event_name == 'pull_request' && format('screenshot-diff-pr-{0}', github.event.number) || format('screenshot-diff-{0}', github.ref_name) }}
run: |
# Trim 'AnkiDroid/build/outputs/roborazzi' from the artifact
# Only keep diagnostic content for failing cases: each <class>/diffs/ becomes <class>/
# Only keep the _compare images of failing cases: <class>/diffs/*_compare.png moves to <class>/
mkdir -p "screenshot-diff/$SUBDIR"
shopt -s nullglob
for diffs_dir in AnkiDroid/build/outputs/roborazzi/*/diffs; do
class_name="$(basename "$(dirname "$diffs_dir")")"
mv "$diffs_dir" "screenshot-diff/$SUBDIR/$class_name"
compares=("$diffs_dir"/*_compare.png)
if (( ${#compares[@]} )); then
mkdir -p "screenshot-diff/$SUBDIR/$class_name"
mv "${compares[@]}" "screenshot-diff/$SUBDIR/$class_name/"
fi
done

- name: Upload screenshot diffs
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sync_translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ jobs:
cd ./tools/localization
yarn start extract

- name: Update translation to AnkiDroid res
- name: Update translations in common resources
run: |
cd ./tools/localization
yarn start update

- name: Check for changes and handle results
id: tr_check
run: |
git add docs/marketing/localized_description AnkiDroid/src/main/res
git add docs/marketing/localized_description common/android/src/main/res
if git diff --cached --quiet; then
echo "HAS_CHANGES=false" >> $GITHUB_OUTPUT
echo "Translation Sync Result: No new translation found on Crowdin. No Pull Request necessary" >> $GITHUB_STEP_SUMMARY
Expand Down
64 changes: 41 additions & 23 deletions .github/workflows/tests_emulator.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: 🛠️ Emulator Tests

on:
schedule:
# Weekly emulator coverage, Monday at 03:17 UTC.
- cron: '17 3 * * 1'
workflow_dispatch:
inputs:
iterations:
Expand All @@ -15,13 +18,15 @@ on:
- 'gh-readonly-queue/**'
# Dependabot branches are already checked by the pull_request trigger.
- 'dependabot/**'
# Crowdin translations are already checked by the pull_request trigger.
- 'i18n_sync'
merge_group:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name == 'schedule' && 'scheduled' || 'ci' }}
cancel-in-progress: true

jobs:
Expand Down Expand Up @@ -56,22 +61,21 @@ jobs:
console.log('iterationArray is: ' + iterationArray)
}

// If we are running on a schedule it's our periodic passive scan for flakes
// Goal is to run enough iterations on all systems that we have confidence there are no flakes
if (context.eventName === "schedule") {
const iterationCount = 15
for (let i = 0; i < iterationCount; i++) {
iterationArray.push(i);
}
}

// TODO Refactor to make these dynamic with a minSdk/max bracket only on schedule, not push
let apiLevel = [36];

// These are used for performance tuning what emulator to use.
// try different architectures, targets, delays etc
let arch = ['x86_64'];
let target = ['google_apis', 'google_apis_ps16k'];
// Keep API 37 and the 16 KB emulator off the per-change CI path.
// Scheduled and manual runs exercise both API levels and targets.
if (context.eventName !== 'schedule' && context.eventName !== 'workflow_dispatch') {
target = ['google_apis'];
} else {
// Keep the minor version: SDK 37 packages use android-37.0.
apiLevel.push('37.0');
}
let firstBootDelay = [600];

return {
Expand All @@ -94,6 +98,14 @@ jobs:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
TEST_RELEASE_BUILD: true
# Capture startup and kernel logs before ADB is ready; -logcat conflicts with -show-kernel.
EMULATOR_OPTIONS: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -verbose -show-kernel
# API 37 otherwise defaults to 96 MiB and panics during init with an OOM.
EMULATOR_RAM_SIZE: ${{ matrix.api-level == '37.0' && '2560M' || '' }}
# Keep the SDK package at 37.0, but ensure the AVD is recognized as API 37.
# API 3 fallback disables the DMA readback required by API 37's graphics mapper.
EMULATOR_PRE_LAUNCH_SCRIPT: >-
if [ '${{ matrix.api-level }}' = '37.0' ]; then sed -i 's/^target=.*/target=android-37/' "$ANDROID_AVD_HOME/test.ini" "$ANDROID_AVD_HOME/test.avd/config.ini"; fi
strategy:
fail-fast: false
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
Expand Down Expand Up @@ -138,17 +150,16 @@ jobs:
# Comment this and the with: above out for performance testing on a branch
cache-read-only: ${{ github.ref != 'refs/heads/main' }}

# This appears to be 'Cache Size: ~1230 MB (1290026823 B)' based on watching action logs
# Repo limit is 10GB; branch caches are independent; branches may read default branch cache.
# We don't want branches to evict main branch snapshot, so save on main, read-only all else
# Cache only the per-change emulator: the 16 KB snapshot costs about 2.5 GiB.
- name: AVD cache
if: matrix.target == 'google_apis'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-v2-${{ github.event.inputs.clearCaches }}
key: avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-v2-
restore-keys: |
avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{matrix.target}}-v2

Expand All @@ -159,7 +170,7 @@ jobs:
timeout_minutes: 15
retry_wait_seconds: 60
max_attempts: 3
command: ./gradlew packagePlayRelease packagePlayReleaseAndroidTest --daemon
command: ./gradlew packagePlayRelease packagePlayReleaseAndroidTest :baselineprofile:packageBenchmarkBenchmark --daemon

- name: Install Android Emulator
# Retry to work around https://github.com/ReactiveCircus/android-emulator-runner/issues/496
Expand Down Expand Up @@ -191,16 +202,18 @@ jobs:
"${ANDROID_HOME:?}/system-images/android-${SDK_API_LEVEL:?}/${SDK_TARGET:?}/${SDK_ARCH:?}/.installer"

- name: AVD Boot and Snapshot Creation
# Only generate a snapshot for saving if we are on main branch with a cache miss
# Warm up the uncached 16 KB emulator on every run; otherwise only on main cache misses.
# Comment the if out to generate snapshots on branch for performance testing
if: "${{ github.event.inputs.clearCaches != '' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}"
if: "${{ matrix.target == 'google_apis_ps16k' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}"
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
with:
api-level: ${{ matrix.api-level }}
force-avd-creation: false
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
emulator-options: ${{ env.EMULATOR_OPTIONS }}
ram-size: ${{ env.EMULATOR_RAM_SIZE }}
pre-emulator-launch-script: ${{ env.EMULATOR_PRE_LAUNCH_SCRIPT }}
sdcard-path-or-size: 100M
disable-animations: true
# Give the emulator a little time to run and do first boot stuff before taking snapshot
Expand All @@ -212,10 +225,10 @@ jobs:

# This step is separate so pure install time may be calculated as a step
- name: Emulator Snapshot After Firstboot Warmup
# Only generate a snapshot for saving if we are on main branch with a cache miss
# Always warm up the 16 KB emulator. The default emulator only needs it on main cache misses.
# Switch the if statements via comment if generating snapshots for performance testing
# if: matrix.first-boot-delay != '0'
if: "${{ github.event.inputs.clearCaches != '' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}"
if: "${{ matrix.target == 'google_apis_ps16k' || (steps.avd-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/main') }}"
env:
FIRST_BOOT_DELAY: ${{ matrix.first-boot-delay }}
uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0
Expand All @@ -224,7 +237,9 @@ jobs:
force-avd-creation: false
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
emulator-options: ${{ env.EMULATOR_OPTIONS }}
ram-size: ${{ env.EMULATOR_RAM_SIZE }}
pre-emulator-launch-script: ${{ env.EMULATOR_PRE_LAUNCH_SCRIPT }}
sdcard-path-or-size: 100M
disable-animations: true
# Restart zygote to win a config race / Give emulator time to run and do first boot stuff before taking snapshot
Expand All @@ -245,7 +260,9 @@ jobs:
force-avd-creation: false
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
emulator-options: -no-snapshot-save ${{ env.EMULATOR_OPTIONS }}
ram-size: ${{ env.EMULATOR_RAM_SIZE }}
pre-emulator-launch-script: ${{ env.EMULATOR_PRE_LAUNCH_SCRIPT }}
sdcard-path-or-size: 100M
disable-animations: true
# update workflows/README.md when modifying this script
Expand All @@ -257,10 +274,11 @@ jobs:
bash .github/adb_retry.sh emu screenrecord start --time-limit 1800 video.webm
sleep 5
bash .github/with_emulator_log.sh adb-log.txt python3 .github/run_emulator_tests.py
TEST_RELEASE_BUILD=false ./gradlew :baselineprofile:connectedBenchmarkBenchmarkAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.ichi2.anki.baselineprofile.ReleaseSmokeTest -Pandroid.testInstrumentationRunnerArguments.releaseSmokeTest=true --daemon

- name: Report Emulator Test Results
if: always() && hashFiles('AnkiDroid/build/outputs/androidTest-results/connected/**/TEST-*.xml') != ''
run: python3 .github/report_emulator_test_failures.py AnkiDroid/build/outputs/androidTest-results/connected adb-log.txt
run: python3 .github/report_test_failures.py AnkiDroid/build/outputs/androidTest-results/connected adb-log.txt --title "Emulator test failures"

- name: Configure NDK tools PATH for alignment checking
run: |
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/tests_unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ on:
- 'gh-readonly-queue/**'
# Dependabot branches are already checked by the pull_request trigger.
- 'dependabot/**'
# Crowdin translations are already checked by the pull_request trigger.
- 'i18n_sync'
merge_group:

permissions:
Expand Down Expand Up @@ -177,6 +179,10 @@ jobs:
if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.iteration == 1 && matrix.os == 'ubuntu-latest'
run: ./gradlew testFullDebugUnitTest -PemptyApplication --daemon

- name: Report Unit Test Results
if: always() && hashFiles('**/build/test-results/**/TEST-*.xml') != ''
run: python3 .github/report_test_failures.py . --pattern "build/test-results/**/TEST-*.xml" --title "Unit test failures"

- name: Store Logcat as Artifact
# cancelled() handles test timeouts
# remove when test timeouts cause a failure()
Expand Down
1 change: 1 addition & 0 deletions .idea/dictionaries/android.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<w>FILESIZE</w>
<w>ONEPLUS</w>
<w>Roborazzi</w>
<w>Supernote</w>
<w>allempty</w>
<w>apkgfileprovider</w>
<w>asynctasks</w>
Expand Down
Loading
Loading