aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java
AgeCommit message (Collapse)AuthorFilesLines
54 min.fix(app): address the step 5 review and emulator findingscomposeDanilo M.6-10/+40
- A store a cancelled attempt already made for the chosen key (its .gpg-id names exactly that key) no longer counts as occupied: choosing the key again commits and moves on instead of refusing. - SSH key import reads at most 100 kB whatever the provider reports, like the PGP import, instead of reading the stream whole. - PGPKeyManager replaces a key through a temporary file moved over it atomically, so a failed write keeps the old key; listings only read .key files, so a stray temporary file is never taken for a key. - "View generated public SSH key" goes through the settings result launcher, so the preference visibility refreshes afterwards. - A plain start from LaunchActivity replaces the task: an open MainActivity (e.g. one whose store vanished) is no longer left below the new one, and one back leaves the app. - The PGP keys screen's import FAB carries its content description itself; M3 cleared the icon's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 min.refactor(app): remove the legacy setup screensDanilo M.13-1395/+0
The Compose welcome, git server, SSH key, key generation and PGP keys screens replace OnboardingActivity with its fragments, GitServerConfigActivity with BasicBottomSheet, SshKeyImportActivity, SshKeyGenActivity, ShowSshKeyFragment, PGPKeyImportActivity and PGPKeyListActivity with KeyList and PGPKeyListViewModel, their layouts and manifest entries, and ui:compose's APSAppBar. Resources only they used are removed too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 min.fix(app): localize the occupied store refusalDanilo M.2-5/+5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 min.fix(app): guard store creation and tidy the setup screensDanilo M.4-5/+9
- createStore refuses a non-empty store folder (only .git allowed), so a restored, cloned store keeps its .gpg-id - failed store creation cleans up with deleteWithoutFollowingLinks, which also removes a fresh .git - PGP keys screen: Later goes through the guarded leave path - Import SSH key row launches via the result launcher so the public key row refreshes - GitServerViewModel.changeUrl always refits the auth mode, also for an empty URL Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 min.feat(app): run setup in MainActivityDanilo M.9-49/+185
The welcome screen starts the app without a store, the list sends a vanished store back to it, and its clone prompt opens the git server screen. A clone asks for the store's PGP key when none matches it. Settings, the entry view, autofill decryption and git operations open the setup screens through MainActivity.setupIntent. The No SSH key dialog's Cancel and the unusable screen lock prompt no longer finish the calling activity, which is now the app's main screen. KeysMode is a route argument, so it is kept from minification (lint MissingKeepAnnotation). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 min.feat(app): add the Compose PGP keys screenDanilo M.1-0/+331
One screen for managing keys, choosing the key of a new local store and importing the key a store needs. Rows show user ID and key ID; the import keeps the old success, replace and failure dialogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 min.feat(app): add the Compose SSH key and key generation screensDanilo M.1-0/+379
The SSH key screen shows the key's state and, for a generated key, the public key with copy and share, and imports a key file after the old replace confirmation. The generation screen ports the old one; the key is generated in the ViewModel after the screen lock prompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
100 min.feat(app): add the Compose welcome and git server screensDanilo M.2-0/+448
The welcome screen merges the old welcome and repository type screens. The git server screen serves clone and edit: the old URL checks as dialogs, authentication mode buttons, the SSH key's state with Manage, the host key reset, and a clone that keeps the screen on failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
102 min.fix(app): harden the setup key importsDanilo M.5-48/+120
- PGP key import reads at most 1 MiB (bounded read, buffer zeroed) and refuses larger files with a new message. - SshKeyViewModel catches any Exception from import and generation (CancellationException rethrown) instead of a short list, so an unexpected provider or sshj error no longer crashes the app. - Pending PGP key bytes are zeroed in a finally block, also on cancellation during addKey after a replace, and an earlier pending pick is zeroed before a new one takes its place. - PGPKeyManager never replaces a stored secret key with a public-only key of the same ID, with or without replace (KeyDowngradeException); the setup ViewModel shows a localized message for it. Unit test added. - storeRecipients strips '#' comments and a trailing '!' as pass reads .gpg-id; tests added. - GitServerViewModel.clearStore returns the failure when the store folder cannot be emptied, so the clone does not start; the plan's Task 3 call site reports it. - readGpgId opens the file with NOFOLLOW_LINKS and reads at most 64 KiB + 1 bytes, refusing more. - SshKey.import writes the new private key to a temp file and moves it over atomically before dropping the old key's Keystore entry, public key and passphrase, so an I/O error keeps the old key. - KeyUtils.isSecretKey notes (ponytail) that GnuPG stub and smartcard keyrings count as secret. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 min.feat(app): add the setup ViewModelsDanilo M.3-0/+493
Git server (URL, mode, clone state, the after-clone key check), SSH key (import and generation off the main thread, outcome kept across recreation) and PGP keys (rows with key IDs, import with the replace question, delete by key ID, creation of a local store). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 min.feat(app): add the setup model and stricter key checksDanilo M.2-10/+127
URL checks and fixes, authentication modes per URL, the clone target, the store's recipients, a size- and symlink-checked .gpg-id read and the .gpg-id text for a new store (uppercase long key IDs), as pure functions for the Compose setup screens. The SSH key import now also requires a format sshj recognizes (an OpenPGP private key block matched the old marker check and was imported as the SSH key) and no longer crashes on an empty size cursor. KeyUtils tells secret keys apart and no longer throws on a key without user ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 hoursfix(git): get sshj's Ed25519 keys from Bouncy CastleDanilo M.1-0/+18
Recent Conscrypt (Android 14 mainline updates) registers a KeyFactory for Ed25519. With sshj leaving provider selection to the JCA, its Ed25519KeyFactory now gets Conscrypt's key class, whose algorithm name sshj does not map to ssh-ed25519: KeyType.fromKey returns UNKNOWN and every Ed25519 host key fails with "Don't know how to encode key", so clone and sync over SSH break. 0.1.4 is affected since the sshj 0.41.2 update. Register a provider at position 1 that serves only KeyFactory.Ed25519 from Bouncy Castle (kept by the existing R8 rule). Everything else, including signatures with Android Keystore keys, still goes through normal JCA provider selection. Verified on an Android 14 tablet: clone over SSH failed before and works with the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit ad3c63f665e6264ed1db42d6ae2dfc0947e33393)
6 hoursfix(app): pick editor recipients and folder from resolved pathsDanilo M.3-6/+5
- Look up the .gpg-id from the normalized target's folder, so a prefilled folder such as a/../b cannot pick a/'s recipients. - Derive the edited entry's folder from its canonical parent, so a store path spelled differently does not show as ../../data/...; the file name still comes from the link. - Drop the REPO_PATH shortcut extra nothing reads any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursrefactor(app): remove the legacy editor, generators and folder pickerDanilo M.11-1164/+2
The Compose editor, generator sheet, OTP dialog and folder picker replace PasswordCreationActivity, the generator and manual OTP dialog fragments, SelectFolderActivity with its fragment and adapter, their layouts and menus, and upstream's unused EditPasswordScreen with the PasswordField it used. flowbinding and swiperefreshlayout had no other users. BasePGPActivity keeps what autofill decryption still needs. Also drops two stale SshKey baseline entries, the snackbar FAB anchor whose view is gone, and resources only the deleted files used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfeat(app): host the Compose editor in the autofill save flowDanilo M.1-53/+71
AutofillSaveActivity shows the editor itself, prefilled from the form and FLAG_SECURE, instead of starting the legacy editor for a result. The autofill match and the generated-password fill work as before: the username and password are read back from the saved content, as the old editor did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfix(app): finish an editor save after recreationDanilo M.3-35/+65
A successful save left saving set while the destination committed in the activity's lifecycleScope. An activity recreated then (theme, locale, split-screen resize) cancelled that scope: onSaved never ran, and the surviving ViewModel kept Save disabled and back swallowed. The ViewModel now keeps the success in a memory-only saved state, set inside the NonCancellable write so a cancelled caller cannot lose it. A LaunchedEffect on it runs the commit and onSaved, so a recreated editor finishes the job; "Save and copy" and the stale-original notice run once only, and left() clears it before onSaved navigates. Editing a symlinked entry derived its folder from the canonical path but its name from the link, saving a new entry next to the target. The folder is now relative to the non-canonical repository directory; the save still checks the target is inside the store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfeat(app): open the Compose editor from the list and entry viewDanilo M.4-55/+359
EditRoute replaces the legacy editor for new entries and edits. An edit copies its plaintext from the entry view's ViewModel below it, never through the route or saved state. Saving returns to the list, closing returns to where the editor was opened. FLAG_SECURE is now counted in MainActivity, so moving between the entry view and the editor never clears it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfeat(app): add the Compose editor screen and generator sheetDanilo M.2-0/+511
Stateless editor: name, folder breadcrumbs with Change, password with reveal and generator, username, extra content and the OTP actions. The generator sheet has random and diceware tabs with the old options; the manual OTP dialog and the sheet are FLAG_SECURE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfix(app): harden the editor save path and the pickerDanilo M.4-8/+33
- Run the save's write, original delete, autofill and history updates under NonCancellable so a recreated activity cannot leave a half done rename. - fsync the temporary file before the atomic move. - Move dialog: refuse targets outside the store (symlinks) and moving a folder into itself or a descendant, with a toast. - Folder picker: do not navigate into a new folder whose mkdirs failed; show a toast (new string, en and it). - Normalize entryFile so "./" and "a/../b" give the same keys as the list; covered in EditorModelTest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfeat(app): add the editor ViewModelDanilo M.3-8/+259
Holds the form in memory only, generates passwords with the stored generator options, decodes QR images and saves off the main thread: nothing is written when the target would replace another entry or when encryption fails, the file is written through a temporary file, and a renamed or moved entry takes its autofill matches and recently-used time along. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfeat(app): add the Compose folder picker for movesDanilo M.2-29/+153
A dialog with breadcrumbs, subfolder rows and New folder replaces the legacy folder picker activity for the list's move action; the editor will share it. New folder reuses the list's name dialog and checks. A second tap on Select no longer moves twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 hoursfeat(app): add the editor model and the picker's folder listingDanilo M.2-0/+156
Pure functions for the Compose editor: the form and the pass file it writes, validation, the target file, the overwrite check, an atomic write, OTP URI building and the random generator's options. The folder picker lists subfolders with the list's hidden filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 hoursrefactor(app): remove the legacy decrypt screenDanilo M.5-589/+0
The Compose entry view replaces DecryptActivity, its layouts, menu and field adapter, and upstream's unused ViewPasswordScreen. BasePGPActivity keeps what the editor and autofill still need. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 hoursfix(app): harden the entry passphrase promptDanilo M.2-8/+18
Ignore a submit while no prompt is showing, so OK plus IME Done cannot start two decrypts. Key the passphrase dialog on the retry count so each attempt gets a fresh field and shows the wrong password error. Note the process death gap around key import and the editor. Record the TotpModule, text selection and file IO decisions in the plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 hoursfeat(app): show the Compose entry view in MainActivityDanilo M.3-33/+301
The list navigates to EntryRoute and shortcuts start on it, replacing the legacy decrypt screen. FLAG_SECURE is on while an entry is composed and off on the list; the route is checked with isFileInRepo again before decrypting. Saving in the editor returns to the list. Password and one-time code copies start the clipboard clear timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 hoursfeat(app): add the Compose entry screenDanilo M.1-0/+183
Header with avatar, name and folder; password, username and one-time code with a countdown bar in the first group; extra fields in the second. FieldRow can start revealed (the "Show the password" setting) and shows masked values in JetBrains Mono, as the old screen did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 hoursfeat(app): add the entry ViewModelDanilo M.2-1/+217
Ports the decrypt screen's unlock flow: passphrase cache after a biometric success, passphrase prompt with three tries, decryption off the main thread and the auto-close timer. The decrypted entry is held in memory only and dropped by the timer or when the destination is popped. A cached passphrase that fails is no longer retried in a loop. TotpModule is also installed in ViewModelComponent, so the entry factory's TotpFinder can be injected into the ViewModel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 hoursrefactor(app): make the PGP clipboard and .gpg-id helpers extensionsDanilo M.2-96/+96
The Compose entry view lives in MainActivity, which is not a BasePGPActivity. The helpers move unchanged to FragmentActivity extensions in the same file, so the existing subclasses keep calling them as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 hoursfeat(app): add the entry view model and the OTP periodDanilo M.1-0/+35
Totp now carries its period, so the entry view can draw how much of the code's lifetime is left. EntryFields groups a decrypted entry the way the spec lays it out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
17 hoursfix(app): stop stray search focus and expose list semanticsDanilo M.1-6/+30
Out of touch mode the view system gives its default focus to the first Compose focusable, the search field, which kept it and brought the keyboard back on every return to the list. Each return now clears focus once the window has focus (earlier, the view still holds a stale touch mode and clearing refocuses the field). Search on start runs on the first of those returns only, tracked in a saveable flag, so popping back from a folder or rotating no longer refocuses it. The extended FAB label sits under clearAndSetSemantics in Material 3, so the icon now carries the "New entry" description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursrefactor(app): remove the legacy PasswordStore listDanilo M.8-1259/+9
The Compose list replaces it. The autofill inline suggestion's attribution intent now opens LaunchActivity, behind the biometric gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursfix(app): keep list work alive across rotation and tidy search focusDanilo M.2-16/+16
- MainActivity handles orientation|screenSize itself so rotation no longer cancels store and git work running in lifecycleScope. - Search-on-start focus moved into SearchField (own FocusRequester), only at the store root, fixing "FocusRequester is not initialized". - refresh() leaves an open folder removed by a pull; used on resume and after git, move, rename and delete. - Zero-count delete uses deleteWithoutFollowingLinks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursfix(app): harden store delete, move and renameDanilo M.1-12/+28
- renameFolder stops when the move fails instead of recording and committing - delete runs its file work on the IO dispatcher - folder delete no longer follows directory symlinks (new test) - overwrite dialog is dismissed when its coroutine is cancelled Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursfix(app): compare whole path components in isInsideRepositoryDanilo M.1-2/+3
The check used a string contains on canonical paths, so a typed name such as ../store-evil/x resolved to a sibling directory whose path contains the store's and passed. New entries and folder renames could then land outside the store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursfeat(app): show the Compose password list in MainActivityDanilo M.2-13/+450
Folders are back stack entries (ListRoute(path)), so back goes up one level. MainActivity extends BaseGitActivity for pull to sync and the commits after move, rename and delete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursfeat(app): add the Compose password list screenDanilo M.1-0/+314
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursfeat(app): port the list's store operations out of PasswordStoreDanilo M.1-0/+159
A move now waits for the overwrite answer before it commits, and new folder names must stay inside the store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
18 hoursfeat(app): add the list rows and folder navigation modelDanilo M.2-0/+68
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
19 hoursfeat(app): route the launcher through MainActivityDanilo M.1-15/+5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
19 hoursfix(app): make LegacyScreen a top-level composableDanilo M.1-10/+13
Slack's ComposeUnstableReceiver lint rejects an instance composable on the activity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
19 hoursfeat(app): add the single-activity Compose hostDanilo M.1-0/+77
Its two routes hand off to the legacy list and decrypt activities until those screens are ported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
20 hoursrefactor(autofill): remove the fill-OTP-from-SMS pathsDanilo M.3-42/+0
Filling OTPs from SMS used Play Services' SMS User Consent API, removed with the nonFree flavor; since then AutofillSmsActivity was a stub that never offered the dataset. Delete it, its manifest entry, the dataset builders in both response builders, its drawable and string. AutofillAction.FillOtpFromSms stays in autofill-parser: it is public, metalava-checked API and harmless for a client that never fills it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
20 hoursfix(editor): write a space after "username:"Danilo M.2-27/+1
Entries created in the app said "username:dave". pass users and other clients write "username: dave"; the parser already trims either form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
20 hoursbuild(deps): update sshj to 0.41.2 and drop eddsaDanilo M.2-13/+20
sshj 0.40 replaced the unmaintained net.i2p.crypto:eddsa 0.3.0 (last release 2019) with Bouncy Castle's Ed25519. 0.41.2 still targets Java 8. The app used eddsa directly for wrapped Ed25519 SSH keys. Generate the 32-byte seed with SecureRandom and build the key pair with sshj's Ed25519KeyFactory and Bouncy Castle's public key derivation. The wrapped format is still the raw RFC 8032 seed, so keys generated by 0.1.3 keep working; Ed25519KeyPairTest pins this with the RFC 8032 test vector. sshj 0.40 also changed setSecurityProvider(null) to reset the registerBouncyCastle flag, which undid the earlier setRegisterBouncyCastle(false): sshj then forced every operation through Bouncy Castle, which cannot sign with Android Keystore keys ("no encoding for EC private key"), and hardware-backed SSH keys failed to authenticate. Call the two in the opposite order; SshjConfigTest checks that sshj leaves provider selection to the JCA. Verified with the R8 release build on the emulator against the e2e server: a hardware ECDSA key, a newly generated wrapped Ed25519 key and an imported OpenSSH Ed25519 key all authenticated and synced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 hoursfeat(crypto): replace androidx security-crypto with Keystore AES-GCMDanilo M.8-106/+283
security-crypto is deprecated in 1.1.0 and the app was stuck on 1.1.0-alpha06, since the stable release fails the warnings-as-errors build. It also pulled in Tink for what is a single AES-GCM operation. KeystorePreferences is a SharedPreferences whose values are AES-GCM sealed under an Android Keystore key (random IV, entry name as AAD so values cannot be swapped between entries); a value that fails to decrypt reads as absent. It backs the Git credentials (git_secrets), the proxy credentials (proxy_secrets) and the PGP passphrase cache, whose key needs the screen lock or a strong biometric within 60s and is recreated if a new biometric enrollment invalidates it. Wrapped Ed25519 SSH keys are sealed the same way into the private key file. Password generator settings are not secret and move to plain preferences. Nothing is migrated: removeSecurityCryptoData deletes the old encrypted preference files, Tink keysets, their Keystore master keys and a security-crypto wrapped Ed25519 key once on upgrade. Only 0.1.x installs from the last few hours have such data. Hardware SSH keys and imported keys are untouched. Tests: KeystorePreferencesTest (round trip, defaults, no plaintext, random IVs, AAD binding, tamper rejection, remove/clear) with a software AES key. Verified on the emulator by upgrading a 0.1.2 debug build that held a wrapped Ed25519 key, Tink keysets and a cached passphrase: the old data was deleted, a new Ed25519 key authenticated over SSH after a fingerprint unlock, and a passphrase cached on first decrypt was reused on the second with only the fingerprint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 hoursfix(crypto): refuse to encrypt when a .gpg-id recipient key is missingDanilo M.2-6/+24
CryptoRepository.encrypt looked up every .gpg-id recipient and dropped the ones it could not find, so an entry saved on a device that lacked one recipient's key was encrypted to the others only. With a store shared between a phone key and a smartcard key, entries created on the phone could no longer be decrypted with the smartcard alone. Fail with the lookup error instead (KeyNotFoundException names the key), so the user imports the missing public key and saves again. PasswordCreationActivity also ignored the Result of encrypt and wrote the output buffer regardless: on any encryption failure it wrote an empty .gpg file, overwriting the entry when editing, and committed it. Show the error and leave the file untouched. CryptoRepositoryTest covers both the missing-recipient failure and the all-present success path, with keys generated by PGPainless (added as an app test dependency). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
22 hoursfix(ssh): unlock screen-lock protected SSH keys with a fingerprintDanilo M.2-3/+17
A Keystore SSH key generated with "Protect with screen lock credential" was bound to AUTH_DEVICE_CREDENTIAL only, while the unlock prompt also accepted biometrics. A fingerprint passed the prompt, then signing the SSH auth request threw UserNotAuthenticatedException, sshj logged "User not authenticated" and the clone failed with "Socket closed". Bind new keys to AUTH_DEVICE_CREDENTIAL or AUTH_BIOMETRIC_STRONG, and allow only strong biometrics in the prompt on Android 11+, since weak ones never unlock Keystore keys (the androidx MasterKey keys already use credential or strong biometric). Android 9 and 10 cannot combine BIOMETRIC_STRONG with DEVICE_CREDENTIAL, so they keep the weak class. Existing protected keys still need the PIN until regenerated. Verified on the emulator (Class 3 fingerprint): a protected ECDSA key unlocked with a fingerprint authenticates and clones over SSH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
26 hoursbuild: drop the nonFree flavor, Sentry and Play ServicesDanilo M.2-11/+26
The fork targets GrapheneOS and ships only FOSS builds. The nonFree flavor added Sentry crash reporting, wired to upstream's Sentry project, and Play Services SMS one-time-code autofill, which needs Google's servers. Neither is wanted, so remove the flavor dimension entirely: - Delete app/src/nonFree, the SentryPlugin, the sentry-stub module, the Sentry and Play Services catalog entries, and the Sentry scope setup in Application. - Move the free AutofillSmsActivity stub into main; it keeps the SMS OTP feature off exactly as the free build did. A ponytail note marks the FillOtpFromSms paths for later removal. - SlimTests no longer needs to skip a nonFree variant. - Workflows and docs use :app:assembleDebug / :app:assembleRelease. A bare assembleRelease would also verify library modules in release mode, where ui:compose fails on a pre-existing AppCompat attribute reference the old flavored task never touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27 hoursbuild(deps): update JGit from 4.5.7 to 5.13.5Danilo M.4-2/+24
JGit 4.5.7 dates from 2019. 5.13 is the last line that needs only Java 8 APIs, so it works with Android's desugaring; it also fixes GHSA-3p86-9955-h393 (arbitrary file overwrite on checkout). GHSA-vrpq-qp53-qv56 (XXE in repo manifest parsing) has no 5.x fix but the app never parses repo manifests. API changes: - SshSessionFactory now requires getType(). - CheckoutCommand.setForce is deprecated; setForceRefUpdate is its documented replacement with the same meaning. JGit 5 references java.lang.management and javax.management, which Android lacks; R8 flagged them. Both paths are made unreachable rather than only silenced: - WindowCache registers JMX statistics by default; Application.onCreate now installs a WindowCacheConfig with exposeStatsViaJmx = false. - GC.gc() takes a PID lock that queries ManagementFactory and would throw NoClassDefFoundError on a device. GcOperation now runs the GC steps (packRefs, repack, prune) directly; the lock only guards against concurrent gc processes, which cannot happen in the app. Aggressive packing is dropped. The R8 -dontwarn rules and the lint baseline entries are added only for those two now-unreachable references. JGitBehaviourTest covers the operations' command chains plus the GC steps and passes on 5.13.5. It now ignores the developer's global git config, which JGit 5 reads (commit.gpgsign made test commits try to sign). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
27 hoursfix(git): keep the branch name when breaking out of a failed rebaseDanilo M.1-2/+15
When a pull with rebase stops on a conflict, HEAD is detached and getCurrentBranch() returned null. "Abort rebase and push new branch" then pushed the local commits to "conflicting-null-<time>" and failed to switch back with "Branch name <null> is not allowed", leaving the repository on the conflict branch. Fall back to the branch JGit records in rebase-merge/head-name (or rebase-apply/head-name) while a rebase is in progress. Found while driving the conflict flow on an emulator against the e2e test server; verified there: the edit is pushed to conflicting-master-<time> and the operation completes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>