b72847b822
With the install watcher answering MIUI's dialog, all four tests ran and three produced numbers: coldStart 574.17 ms P50 (553.95-730.19), opening a 1000-file folder 14 frames P50 (13-17), scrolling it with four swipes 98 frames P50 (93-100). The search benchmark still fails, on the app bar's search action not being in the accessibility tree after twenty seconds - the setup wait is raised from ten to twenty now that the folder render is known to be the slow step. The frame counts are labelled as frame counts and not as jank, because this phone reports no frame durations at all: the same results file contains frameCount and nothing else, so 14 frames says the interaction is short and says nothing about whether a frame was slow. The doc also keeps the measurements that explain the watcher's shape - the dialog is FLAG_SECURE, its lifetime varies between under a second and 8.5 s, and pm install from an app uid fails earlier with MIUI's XSpace SecurityException - along with the honest cost: MIUI keeps mCurrentFocus on the installer after the window is gone, so a coordinate tap can land on the home screen, which it did. The watcher no longer reuses a saved position at all and refuses to tap within two seconds of the previous tap.
671 lines
35 KiB
Markdown
671 lines
35 KiB
Markdown
# 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.
|
|
|
|
77 source files, 12707 lines - 33 test files, 386 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) |
|
|
| `StoragePath.kt` | 89 | A normalized, absolute, backend-agnostic storage path. StoragePath | `StoragePathTest` (8) |
|
|
| `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` | 125 | The "All files access" backend: plain java.io.File over absolute paths. AllFilesSource, File() | `AllFilesSourceTest` (13) |
|
|
| `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` | 96 | 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` | 301 | 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` | 2100 | Drives the browser screen. BrowserViewModel, ArchivePreview | `BrowserViewModelTest` (89) |
|
|
| `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` | 126 | 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` | 215 | 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/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/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` (13)
|
|
|
|
- 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` (89)
|
|
|
|
- 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
|
|
- 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 76): `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`
|