# Code map Generated from the source by `tools/make-code-map.py` - do not edit by hand. Regenerate it after a change (`python3 tools/make-code-map.py`); `--check` exits non-zero when this file is stale. 79 source files, 13040 lines - 35 test files, 396 tests. The behaviour index at the end lists every test name, and in this project a test name is a sentence about what the app does, so it doubles as the specification. Start at `AGENTS.md`. What is done, and why: `docs/ROADMAP.md` and `CHANGELOG.md`. What was verified by hand rather than by a test: `docs/EDGE_CASE_TESTING.md`. ## core/ Pure Kotlin, **no Android imports**: paths, file names, kinds, sorting, formatting, matching, the batch-rename planner | file | lines | what it holds | tests | |---|---|---|---| | `ArchiveFormats.kt` | 102 | Which archive formats the app can handle, by file name. ArchiveFormat, ArchiveFormats | `ArchivePathTest` (18) | | `ArchivePath.kt` | 43 | Guards archive extraction against writing outside the destination folder. ArchivePath | `ArchivePathTest` (18) | | `BatchRename.kt` | 125 | Renames a batch of files to one name with a number after each: Holiday (1).jpg, Holiday (2).jpg, and so on. BatchRenameMove, BatchRenamePlan, BatchRename | `BatchRenameTest` (16) | | `FileCategory.kt` | 53 | A library the home screen can open: everything on the device of a given kind, rather than everything in a given folder. FileCategory | `FileCategoryTest` (6) | | `FileEntry.kt` | 29 | One row in a directory listing. FileEntry, isHiddenName() | `FileEntryTest` (8) | | `FileKind.kt` | 73 | The coarse category a file belongs to, derived from its name. FileKind | `FileKindTest` (6) | | `FileNames.kt` | 13 | Whether name can be the name of a file in a folder. isUsableName() | `FileNamesTest` (4) | | `FileSize.kt` | 27 | Human-readable byte sizes for the file list ("1.5 MB"). FileSize | `FileSizeTest` (2) | | `MimeTypes.kt` | 110 | Best-effort MIME lookup for the "open with" intent. MimeTypes | `FileEntryTest` (8) | | `NameAllocator.kt` | 30 | Picks a name that is not already in use, in the familiar "name (1)" style. NameAllocator | `NameMatcherTest` (10) | | `NameMatcher.kt` | 63 | The in-folder search matcher. NameMatcher | `NameMatcherTest` (10) | | `SortSpec.kt` | 82 | A complete, serialisable description of the ordering of a directory listing. SortKey, SortDirection, SortSpec, NaturalOrder | `SortSpecTest` (8) | | `SortedMerge.kt` | 42 | Adds a freshly read batch to an already sorted list, in one pass. SortedMerge | `SortedMergeTest` (4) | | `StoragePath.kt` | 89 | A normalized, absolute, backend-agnostic storage path. StoragePath | `StoragePathTest` (8) | | `ThumbnailKey.kt` | 22 | The identity of a thumbnail: which file it came from, and which version of that file. ThumbnailKey | `ThumbnailKeyTest` (4) | | `Timestamps.kt` | 20 | Timestamps for the details sheet. Timestamps | - | | `TransferPlanner.kt` | 105 | Turns "copy/paste these items into that folder" into a per-item decision, before a single byte is written. ConflictPolicy, TransferAction, TransferSource, TransferItem, TransferPlanner | `TransferPlannerTest` (5) | ## data/ Storage backends (all-files access and SAF), volumes, incoming shares, the SQLite search index, trash index and favourite list | file | lines | what it holds | tests | |---|---|---|---| | `AllFilesSource.kt` | 154 | The "All files access" backend: plain java.io.File over absolute paths. AllFilesSource, File() | `AllFilesSourceTest` (14) | | `ApkInspector.kt` | 100 | What an APK says about itself, read without installing it. ApkInfo, interface(), PackageManagerApkInspector | - | | `BookmarkStore.kt` | 34 | The folders the user starred. BookmarkStore | - | | `IncomingSource.kt` | 48 | Something another app has handed to this one. IncomingSource, ContentResolverIncomingSource | - | | `MediaStoreDeleter.kt` | 73 | A best-effort DeleteRequestFactory backed by MediaStore. interface(), MediaStoreDeleter | - | | `SafDocumentIds.kt` | 40 | Maps a Storage Access Framework tree onto StoragePaths. SafDocumentIds | `SafDocumentIdsTest` (6) | | `SafGrant.kt` | 41 | Picking and keeping a folder tree for the SAF fallback. SafGrant | - | | `SafSource.kt` | 187 | The Storage Access Framework fallback backend. SafSource | `SafSourceTest` (5) | | `SearchIndex.kt` | 196 | A by-name index of what is on the volumes, so a search does not have to walk them. IndexedFile, VolumeStamp, SearchIndex | - | | `SqliteBookmarkStore.kt` | 118 | The starred folders, on SQLite. SqliteBookmarkStore | `SqliteBookmarkStoreTest` (9) | | `SqliteSearchIndex.kt` | 438 | The search index, on SQLite. SqliteSearchIndex | `SqliteSearchIndexTest` (27) | | `SqliteTrashIndex.kt` | 163 | The trash index, on SQLite. SqliteTrashIndex | `SqliteTrashIndexTest` (7) | | `StorageAccess.kt` | 80 | The Android-facing side of the storage-access decision. StorageAccess | - | | `StorageAccessPolicy.kt` | 68 | The decision rules behind StorageAccess. StorageAccessMode, StorageAccessPolicy | `StorageAccessPolicyTest` (6) | | `StorageLocation.kt` | 87 | Everything the browser needs to know about where files live and what it may touch. StorageLocation, StorageProvider | - | | `StorageSource.kt` | 124 | The primitive file operations a storage backend must provide. StorageException, StorageSource | - | | `TrashIndex.kt` | 61 | The durable memory of what is in the trash. TrashRecord, TrashIndex | - | | `VolumeRepository.kt` | 121 | Discovers the device's storage volumes and reports their space. VolumeInfo, SpaceInfo, VolumeRepository | `VolumeRepositoryTest` (6) | ## domain/ Operations over a source: copy/move/delete/rename, archives, search walking, duplicate finding, trash, usage analysis | file | lines | what it holds | tests | |---|---|---|---| | `ArchiveEngine.kt` | 658 | Creates and extracts archives, on top of the storage backends. ArchiveEntry, ArchiveEngine | `ArchiveEngineTest` (30) | | `DuplicateFinder.kt` | 202 | Finds files that hold the same bytes. DuplicateGroup, DuplicateProgress, DuplicateScan, DuplicateFinder | `DuplicateFinderTest` (8) | | `FileOperations.kt` | 272 | Copy, move and delete, assembled from the StorageSource primitives. FileOperations | `FileOperationsTest` (19) | | `FileSearch.kt` | 79 | Recursive name search. FileSearch | `FileSearchTest` (8) | | `OperationModels.kt` | 42 | The kind of long-running operation a progress notification can describe. OperationKind, OperationProgress, OperationFailure, OperationResult | - | | `OperationNotifier.kt` | 25 | Keeps the foreground service in step with a running operation. OperationNotifier | - | | `SearchIndexer.kt` | 173 | Fills a SearchIndex by walking a volume. SearchIndexer | `SearchIndexerTest` (14) | | `Trash.kt` | 338 | A staging folder that makes a delete undoable. TrashedEntry, TrashBatch, Trash | `TrashTest` (19) | | `UsageAnalysis.kt` | 95 | Works out where the space went. UsageNode, UsageAnalysis | `UsageAnalysisTest` (6) | ## service/ The foreground service that keeps a long operation alive and reports it | file | lines | what it holds | tests | |---|---|---|---| | `FileOpNotifications.kt` | 64 | Builds the progress notification for a running file operation. FileOpNotifications | - | | `FileOpService.kt` | 107 | Keeps a long copy, move or delete alive while the app is backgrounded, and mirrors the engine's progress into an ongoing notification. FileOpService | `FileOpServiceTest` (4) | | `ServiceOperationNotifier.kt` | 19 | The production OperationNotifier: forwards to FileOpService, which owns the foreground notification and keeps the process alive. ServiceOperationNotifier | - | ## text/ Domain enum to string-resource mapping | file | lines | what it holds | tests | |---|---|---|---| | `TextRes.kt` | 58 | Maps domain enums onto their resource ids. OperationKind(), FileKind(), FileCategory() | - | ## ui/browser/ The browser screen: view model, state, list and grid, dialogs, selection bar | file | lines | what it holds | tests | |---|---|---|---| | `BrowserComponents.kt` | 317 | The thumbnail an entry's leading visual gets in a list row. ListThumbnailSize, GridThumbnailSize, GridCellMinSize, EntryIcon(), FileEntry(), FileEntry(), VIDEO_FRAME_MILLIS, iconFor(), iconFor(), EntryRow(), EntryGridCell(), FileEntry() | - | | `BrowserContent.kt` | 463 | Everything below the app bar: the permission prompt, the roots, or a directory. WIDE_LAYOUT_MIN_WIDTH, MasterPaneWidth, BrowserContent(), LazyListScope(), LIBRARY_SECTION_KEY, LazyListScope(), BOOKMARK_SECTION_KEY, DirectoryView(), FileEntry(), IndexStatusRow(), FileEntry() | - | | `BrowserDetailPane.kt` | 131 | The second pane of the wide layout: what is inside the folder the user picked, or a preview and the details of the file they picked. DetailPane(), FolderPreview(), ImagePreview() | - | | `BrowserDialogs.kt` | 279 | Renaming several files at once, with the numbering worked out before anything moves. BatchRenameDialog(), PREVIEW_NAMES, batchRenameNote(), NameDialog(), ConfirmDialog(), ConflictDialog() | `BrowserDialogsTest` (5) | | `BrowserRoots.kt` | 270 | A row on the root screen: a volume, with its free space when the platform reports it. RootRow(), itemCountLabel(), EmptyState(), LibrarySectionHeader(), BookmarkSectionHeader(), SectionHeader(), BookmarkRow(), LibraryRow(), Breadcrumbs() | `BrowserRootsTest` (4) | | `BrowserScreen.kt` | 591 | BrowserScreen() | - | | `BrowserTopBar.kt` | 238 | The browser's app bar: title, up, the favourite star, and the search / sort / overflow actions. BrowserTopBar(), SEARCH_FIELD_WIDTH | `BrowserTopBarTest` (4) | | `BrowserUiState.kt` | 311 | One immutable description of everything the browser shows. ViewMode, SearchScope, Clipboard, UiMessage, ConflictPrompt, RootItem, Bookmark, DetailFocus, Undo, UndoMove, Overlay, BrowserUiState | - | | `BrowserViewModel.kt` | 2237 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (90) | | `DetailsSheet.kt` | 109 | Name, location, size, type and modified date for one entry. DetailsSheet(), DetailLines(), DetailLine() | - | | `EntryIntents.kt` | 58 | Wraps an entry's path in a content:// URI another app may read. contentUriFor(), viewIntent(), shareIntent() | - | | `IncomingBar.kt` | 60 | The bar shown while another app is waiting on this one. IncomingBar() | - | | `IncomingRequest.kt` | 46 | What another app asked this one to do. IncomingRequest | - | | `SelectionBar.kt` | 176 | The contextual action bar shown while entries are selected. StorageAccessPrompt(), SelectionBar() | `SelectionBarTest` (2) | | `SortSheet.kt` | 128 | Sort key, direction, hidden files and list/grid — all in one sheet. sortKeyLabel(), SortSheet() | - | ## ui/viewer/ The built-in viewers (text, image, PDF, media, markup, archive contents) | file | lines | what it holds | tests | |---|---|---|---| | `ApkInfoScreen.kt` | 201 | What an APK says about itself, read from its own manifest without installing it. ApkInfoScreen(), ApkDetails(), ApkLine() | - | | `ArchivePreviewScreen.kt` | 198 | What an archive holds, read from its index without unpacking any of it. ArchivePreviewScreen(), ArchiveEntryRow(), INDENT_DP | - | | `ImageViewerScreen.kt` | 64 | The built-in image viewer. ImageViewerScreen() | - | | `MarkupPreviewScreen.kt` | 116 | Markup drawn by the platform's own web engine: rendered HTML, and the SVG that the raster image viewer cannot decode at all. MarkupPreviewScreen(), ENCODING | - | | `MediaPlayerScreen.kt` | 250 | Audio and video, played from the file itself. MediaPlayerScreen(), VideoPreview(), AudioPreview(), Unplayable(), timeLabel(), POSITION_POLL_MILLIS | - | | `PdfViewerScreen.kt` | 276 | A PDF, drawn a page at a time by the platform's own renderer. OpenPdf, PdfViewerScreen(), PdfPages(), PdfPage(), PageBar() | - | | `TextEditorScreen.kt` | 79 | A plain text editor for the text formats the browser classifies as FileKind.TEXT. TextEditorScreen() | - | ## ui/usage/ The storage-usage screen | file | lines | what it holds | tests | |---|---|---|---| | `UsageScreen.kt` | 146 | Where the space went: each child of the current folder with its recursive size, biggest first, and a bar showing its share of the folder. UsageScreen(), UsageRow() | - | ## ui/trash/ The trash screen | file | lines | what it holds | tests | |---|---|---|---| | `TrashScreen.kt` | 192 | What the trash still holds, and the way to put it back. TrashScreen(), BatchHeader(), TrashEntryRow() | - | ## ui/duplicates/ The duplicate-finder screen | file | lines | what it holds | tests | |---|---|---|---| | `DuplicateScreen.kt` | 246 | The duplicate scan: what was compared, what matched, and which copies to remove. DuplicateScreen(), ScanRow(), LazyListScope(), GroupHeader(), EmptyDuplicates() | - | ## ui/theme/ Compose theme, colours, type | file | lines | what it holds | tests | |---|---|---|---| | `Color.kt` | 27 | BluePrimaryLight, BlueOnPrimaryLight, BlueContainerLight, BlueOnContainerLight, TealSecondaryLight, SurfaceLight, SurfaceVariantLight, OnSurfaceLight, ErrorLight, BluePrimaryDark, BlueOnPrimaryDark, BlueContainerDark, BlueOnContainerDark, TealSecondaryDark, SurfaceDark, SurfaceVariantDark, OnSurfaceDark, ErrorDark | - | | `Theme.kt` | 64 | App theme. LightColors, DarkColors, FileManagerTheme() | - | | `Type.kt` | 9 | The app sticks with the Material 3 default type scale; a dedicated object keeps a single place to tune typography later. FileManagerTypography | - | ## ./ | file | lines | what it holds | tests | |---|---|---|---| | `FileManagerApplication.kt` | 151 | Application entry point and the app's tiny object graph. FileManagerApplication | - | | `MainActivity.kt` | 65 | MainActivity | - | ## benchmark/ The macrobenchmark test APK: four measurements over a fixture folder, run on a device | file | lines | what it holds | tests | |---|---|---|---| | `BrowserBenchmark.kt` | 249 | The four numbers docs/PERFORMANCE.md promises, measured on a benchmark build of the app. BrowserBenchmark, Fixture | - | ## Behaviour index Every test name in the suite, by file: the fastest way to see whether something is already covered, and what the app claims to do. ### `ProjectInvariantsTest.kt` (4) - core is pure Kotlin, with no Android imports - user-visible text comes from string resources, not from literals - the app asks for no permission that could reach the network - a name is one path segment, and that rule lives in one place ### `core/ArchivePathTest.kt` (18) - plain names are kept as they are - redundant segments are normalized away - absolute paths are rejected - windows drive letters are rejected - climbing out of the destination is rejected - backslashes are treated as separators - empty and dot-only names are rejected - a climb that stays inside the destination is allowed - an unsafe name never yields a path with a parent segment - detects the formats it can extract - detects 7z, which is readable without native code - recognises but refuses formats that would need native code - an ordinary file is not an archive - detection is case-insensitive and tgz wins over gz - single-file wrappers lose their own suffix - a compressed tar is recognised as a tar, not as a bare compressed file - every compressed tar names the tar inside it - only zip can be created ### `core/BatchRenameTest.kt` (16) - a second batch carries on where the first stopped - a file named exactly like the base counts as number zero - it continues from the highest number rather than from the first gap - the series is counted across extensions - names that merely resemble the pattern are ignored - a base that contains regex characters is matched literally - an empty folder starts at one - a plan keeps each file's own extension and numbers it in the order shown - a plan can continue the folder's series - a name that is already in the folder blocks the whole batch - a chain through a selected file is refused rather than half-done - files that already have the pattern's names are left alone - a file that already has its target name is counted and skipped, not renamed to itself - a start below one is treated as one, because there is no zeroth copy - a file with no extension gains none - a base has to be a name, not a path ### `core/FileCategoryTest.kt` (6) - the kinds that belong to no library are exactly the three that were decided - documents covers text as well as documents - every kind maps back to the library that claims it - an uncollected kind answers null - recent is the only library that is not about a kind - no kind is claimed by two libraries ### `core/FileEntryTest.kt` (8) - derives name and kind from the path - hidden follows the leading-dot convention - maps known extensions - falls back to the binary type - directories report the directory type - an intent takes the type its items share - a mixed batch falls back to the wildcard rather than hiding half the apps - an unknown extension is still shared as a file ### `core/FileKindTest.kt` (6) - a directory wins over its extension - classifies common extensions case-insensitively - only the last extension counts - apk is recognised separately from archives - unknown and hidden names are other - extensionOf ignores dotfiles and trailing dots ### `core/FileNamesTest.kt` (4) - an ordinary name is usable - a separator is not part of a name - a name has to say something - a null byte cannot be in a name ### `core/FileSizeTest.kt` (2) - bytes are shown without decimals - kilobytes and above use one decimal ### `core/NameMatcherTest.kt` (10) - an empty query matches everything - a plain query is a case-insensitive substring - a query with wildcards is treated as a glob - glob is case-insensitive - glob handles stars against empty and partial text - keeps the desired name when it is free - appends a counter before the extension - handles names without an extension - treats a dotfile as having no extension - preserves the base of multi-dot names ### `core/SortSpecTest.kt` (8) - directories come first in both directions - name sort is natural, so file2 precedes file10 - size sorts ascending and descending - date descending puts the newest first - ties break by name ascending - digit runs compare numerically - comparison ignores case - a prefix sorts before a longer name ### `core/SortedMergeTest.kt` (4) - a later batch slots into the order already on screen - the first batch into an empty list is sorted on its own - an empty batch changes nothing - an entry that ties on name does not jump ahead of one that arrived earlier ### `core/StoragePathTest.kt` (8) - normalizes separators and dot segments - adds a leading slash - the root is its own path - cannot escape above the root - exposes segments, name and parent - resolve handles relative and absolute input - ancestor and descendant are strict - is a value type ### `core/ThumbnailKeyTest.kt` (4) - the same file with the same size and timestamp is the same cache entry - a replaced file is a different cache entry even though its path is unchanged - a file edited in place is a different cache entry even though its size is unchanged - two files with the same size and timestamp do not share an entry ### `core/TransferPlannerTest.kt` (5) - no conflict copies straight in - conflicts follow the chosen policy - two identical names in one batch do not collide - a folder cannot be moved into itself or a descendant - a move into itself is skipped in the plan ### `data/AllFilesSourceTest.kt` (14) - reads a large folder in batches, covering every entry exactly once - lists the children of a directory - names lists a folder without reading metadata - names of a folder that cannot be read is null, not empty - names of a file is null too, because a file holds no names - listing a missing directory yields nothing - stat and exists agree - creates missing directories and empty files - delete removes a tree recursively - delete reports false for something that is not there - rename moves within the source and refuses to overwrite - writing then reading round-trips, and append works - free space comes from the injected probe - reports its backend name ### `data/SafDocumentIdsTest.kt` (6) - the root maps onto the tree document id - children append their relative path - every path round-trips - documents outside the tree are rejected - labels are derived from the tree id - trees are not confused with similarly prefixed ones ### `data/SafSourceTest.kt` (5) - lists the children of the tree root - stats a single document - a document the provider does not know does not exist - exposes a label and exactly one root - reports no free space, since the framework cannot ### `data/SqliteBookmarkStoreTest.kt` (9) - a starred folder reads back whole - the path comes back as the same value it was stored with - the newest favourite comes first - starring the same folder twice neither duplicates it nor moves it - removing a favourite takes only that one out - removing something that was never starred is not an error - an empty store reads back empty rather than throwing - a schema bump with no migration reports itself instead of emptying the list - an already current schema is left alone ### `data/SqliteSearchIndexTest.kt` (27) - a plain query is a substring of the name, case-insensitively - a query with a wildcard is a glob over the whole name - wildcards the user did not type stay literal - results are capped and ordered by name - a volume is replaced wholesale, and the stalest one decides the age - a half-finished pass leaves the old timestamp in place - forgetting a row takes it out of the results - clearing empties the rows and the timestamps - nothing indexed yet reports no age at all - a schema bump with no migration reports itself - children returns one folder's rows and nothing deeper - forgetting a folder takes its subtree and leaves its neighbours - forgetting a folder whose name contains a wildcard takes only that subtree - a catch-up keeps the time of the last full walk - the stamp remembers which hidden-files preference built it - a library query returns only the kinds it was asked for - a library query is newest first, so the cap keeps the newest - a library of two kinds holds both of them - a library that collects no kind answers nothing rather than everything - recent is ordered by date and leaves folders out - counts are grouped by kind - an index that has never been filled counts nothing - migrating from version 1 keeps the rows and adds the new columns - migrating from version 2 rebuilds the files table and forgets the stamps - duplicate candidates share a size, largest first - folders are never candidates, however their size falls out - the floor and the cap bound the candidate list ### `data/SqliteTrashIndexTest.kt` (7) - a recorded item reads back whole - recording the same staged path twice replaces instead of duplicating - records come back newest batch first - forgetting one batch leaves the others alone - an empty index reads back empty rather than throwing - a schema bump with no migration reports itself instead of emptying the index - an already current schema is left alone ### `data/StorageAccessPolicyTest.kt` (6) - android 11 and up key off the all-files special permission - android 10 and below key off the legacy write grant - from android 13 notifications are the only grant left to ask for - storage grants are asked for up to android 12 - already granted permissions are not requested again - the permission constants match the platform strings ### `data/VolumeRepositoryTest.kt` (6) - volumes are listed primary first - roots are built as directories in order - space is summed across volumes - an unknown volume reports no space - a volume without a space reading contributes nothing - used bytes never go negative ### `domain/ArchiveEngineTest.kt` (30) - a zip round-trips folders and files - createZip reports progress over the files it will write - a zip-slip entry cannot escape the destination - a single-file gzip is unwrapped to its inner name - a single-file xz is unwrapped, which needs the optional xz library - a plain tar round-trips its files - a gzip tar round-trips its files - a bzip2 tar round-trips its files - an xz tar round-trips its files - an unsupported format is refused rather than guessed at - a 7z round-trips its files - a 7z-slip entry cannot escape the destination - 7z is refused with a readable reason when the backend cannot seek - extracting something that is not there is reported - listing reads names, sizes and nesting without extracting anything - a listing is the same when the backend cannot seek - a 7z is listed from its index - a compressed file is listed as the one name inside it - an unsupported archive cannot be listed either - one entry can be extracted without unpacking the rest - one entry can be extracted when the backend cannot seek - one entry can be extracted from a 7z - extracting a single hostile entry is refused - an entry can be read without extracting anything - an entry can be read when the backend cannot seek - an entry can be read from a 7z, which has no stream per entry - an entry inside a compressed wrapper is read under the name its archive implies - reading refuses to hand out more than the limit - an entry can be written straight to a file outside the backend - reading an entry the archive does not hold is null ### `domain/DuplicateFinderTest.kt` (8) - files holding the same bytes are one group, and the group says what it would give back - three copies are one group of three - files of the same length but different contents are never a group - files that share a beginning but differ later are caught by the full pass - a file that cannot be read is counted rather than guessed at - groups are ordered by the space they would give back - a file whose length nothing else shares is never read - nothing to compare is nothing to report ### `domain/FileOperationsTest.kt` (19) - copy brings a file across with its content - copy recurses into folders - copy creates the target folder when it does not exist - copy reports progress that reaches the total - keep both renames the incoming copy - skip leaves the existing file untouched - replace overwrites the existing file - ask is surfaced to the caller and skipped by the engine - move renames within the backend - move falls back to copy and delete when rename is unavailable - move keeps the source when the copy failed - a failing file is reported and the batch continues - an item that vanished is reported - cancellation stops between entries and reports partial work - delete removes files and folders - delete reports an item that is already gone - rename falls back to a copy and delete - rename refuses to clobber an existing target - createDirectory makes the folder ### `domain/FileSearchTest.kt` (8) - finds a match however deep it is - a hidden folder is skipped whole, not just hidden from the results - hidden entries are searched when the preference asks for them - a matching folder is still descended into - matches stream out as they are found - the walk stops at the limit - globs work as they do everywhere else - a query that matches nothing finds nothing ### `domain/SearchIndexerTest.kt` (14) - a walk records every file under the volume - a hidden folder is skipped whole when the preference says so - rows are handed over in batches rather than one write per file - progress is reported before the first batch is written - a catch-up reads names and the metadata of nothing it already knows - a catch-up finds a file that arrived and leaves the rest alone - a catch-up drops a file that has gone - a catch-up takes a folder that has gone with its whole subtree - a folder that cannot be read is left exactly as it was - a catch-up records that it was a catch-up and not a walk - a catch-up with the other hidden-files preference forgets what it should not show - a walk and a catch-up over a real tree keep the index level with it - the timestamp is stamped from the clock when the pass finishes - a second pass replaces the first rather than doubling it ### `domain/TrashTest.kt` (19) - staging moves an item aside and records where it came from - restoring puts an item back exactly where it was - a folder comes back whole - two items sharing a name stage without one overwriting the other - restoring never overwrites a name that was taken again meanwhile - expiry drops only the batches that are old enough - staging nothing yields no undo handle - restoring a batch that is already gone is reported, not thrown - a batch folder is recognised by its name, and nothing else is - the trash folder is hidden, the way Android hides dotfiles - staging records where the item came from - a staged delete is still recoverable after the process forgets everything - the index is emptied once a restore has succeeded - a row whose file has gone is not offered back - purging forgets the rows it deletes - purging drops rows whose folder has gone, so the index cannot grow forever - what is offered back carries the name, the type, the size and where it came from - one item can be put back without disturbing the rest of its batch - a batch is forgotten once its last item is back ### `domain/UsageAnalysisTest.kt` (6) - folder sizes are recursive and sorted largest first - rows are reported as they are measured - the largest files are found anywhere below the folder - the total rolls up the whole tree - an empty folder measures zero - a folder that is gone measures nothing rather than blowing up ### `service/FileOpServiceTest.kt` (4) - the channel is created once and reused - an indeterminate operation says it is preparing - a determinate operation shows the counter - starting the service puts it in the foreground with that title ### `ui/browser/BrowserDialogsTest.kt` (5) - the batch dialog spells the names out before anything moves - a name the folder already holds is named, and the button is off - files that already hold the names are said to be done - a name with a slash is refused while it is typed - a plain name is accepted ### `ui/browser/BrowserRootsTest.kt` (4) - a starred folder shows its label, where it is, and how to remove it - a favourite that has gone says so instead of its path - a library the index knows nothing about shows no count - a library with an index behind it shows what it holds ### `ui/browser/BrowserTopBarTest.kt` (4) - inside a folder the star invites the folder to be starred - a starred folder says so instead - a library or the root list has nothing to star - the star gives way to the search field ### `ui/browser/BrowserViewModelTest.kt` (90) - starts on the root list - opening a folder lists it with directories first and natural order - hidden files are filtered out until asked for - search narrows the listing - sorting by size reorders the listing - a folder of six hundred files is still listed in natural order - navigating up goes to the parent then to the roots - selection survives toggling and clears on navigation - a file with no preview of its own is handed to the system - an image still opens in the built-in viewer, but can also be handed on - the SAF fallback will not hand out a file, and says why - a zip opens its contents instead of unpacking them - an archive that cannot be read closes again and says so - documents and media open in their own viewers - markup is drawn, and the text editor stays one action away - an apk is inspected rather than being handed to another app - a text entry inside an archive is previewed without writing anything - markup inside an archive is rendered, and not offered for editing - an image entry is copied into the scratch space, and the copy goes with the preview - a preview that would be too large is refused before anything is read - an entry with no viewer is offered an extraction instead - closing a preview of an entry returns to the archive it came from - searching everywhere answers from the index, across folders - switching scope re-answers the search rather than leaving the old results - the index is rebuilt when it has never been built - a fresh index is neither walked nor caught up - a stale index is caught up, and the answer includes what the catch-up found - a changed hidden-files preference asks for a walk rather than a catch-up - a search from the volume list is answered everywhere, with no folder open - starring the folder on screen adds it to the favourites - starring it again takes it back out - a favourite outlives the view model that added it - the newest favourite comes first - a favourite whose folder has gone is kept and marked - opening a favourite that has gone says so instead of showing it empty - a library cannot be starred - opening a library lists the index's files of that kind and no folders - a library brings the index up to date behind the answer - a library row whose file has gone is dropped and forgotten - returning to the app keeps the library open rather than dropping to the roots - the home counts come from the index, and there are none without one - finding duplicates compares the index's candidates and reports what matched - a scan with nothing to compare says so rather than reporting a clean device - deleting the ticked copies closes the scan rather than patching it - ticking a copy does not tick every copy - a search from inside a library is answered everywhere, because a library is not a folder - a preview that needs a real file is refused in the SAF fallback - createFolder makes a directory in the current folder - createFolder refuses a name that is a path - rename moves the entry to the new name - delete removes the selected entries directly when the platform has no dialog - delete prefers the platform dialog and leaves the files alone until it returns - copy then paste duplicates into the current folder - cut then paste moves and clears the clipboard - a pasted name that already exists asks before overwriting - without any grant the browser asks for a folder - a granted tree becomes the only root - focusing a folder in the master pane lists it for the detail pane - focusing a file keeps the entry and previews no children - the detail pane honours the hidden-files preference - navigating clears the focus so the pane cannot show a stale folder - a focused volume previews the contents of that volume - the browsed folder and view settings survive a restart - a search query is not restored, so the folder is not hidden on launch - a selection and a clipboard do not survive a restart - restarting on the roots screen stays on the roots screen - search reaches into a subfolder, not just the one being browsed - clearing the query brings the folder back - navigating abandons a search, because its results belonged to the old folder - the search honours the hidden-files preference - a delete only moves the item aside, so it can be taken back - undoing a delete puts the file back - batch rename numbers the files in the order they are listed - batch rename continues a series the folder already has - a batch rename is a single undo step - batch rename refuses a batch whose names are already taken - a filename cannot be a path - a batch rename cannot be given a path either - undoing a rename puts the old name back - undoing a cut-paste moves the item back - a copy is not undoable, because nothing was changed - the undo handle is cleared once it has been used - a shared file is saved into the folder the user chose - a shared file never overwrites one already in the folder - a file the sender cannot name still lands under a usable one - a share that cannot be read leaves nothing behind - cancelling a share leaves an ordinary browser - with no reader wired up, a share is not silently pretended - while providing, tapping a file chooses it instead of opening it - while providing, a folder still navigates so the file can actually be reached ### `ui/browser/SelectionBarTest.kt` (2) - several files are offered a rename and not the details - a single file keeps both ### Test files with no source file of the same name - `ProjectInvariantsTest.kt` (4 tests) - ProjectInvariants, ProjectInvariants --- Purpose taken from the first KDoc rather than from a declaration sharing the file's name (15 of 78): `MainActivity.kt`, `ArchiveFormats.kt`, `FileNames.kt`, `ApkInspector.kt`, `OperationModels.kt`, `TextRes.kt`, `BrowserComponents.kt`, `BrowserDetailPane.kt`, `BrowserDialogs.kt`, `BrowserRoots.kt`, `BrowserScreen.kt`, `EntryIntents.kt`, `Color.kt`, `Theme.kt`, `Type.kt`